Compare commits

..

38 Commits

Author SHA1 Message Date
Hayden
845b2c2c73 fix(docs): restore classic Material look under Zensical's modern theme
Zensical ships a redesigned "modern" theme: a flat header that uses the page
background color and the Inter typeface, rather than Material's colored app-bar
and Roboto, and it keeps links / the nav active-item pill / the announce banner
on its default indigo accent. The mealie palette and custom.css load fine, but
the modern theme no longer wires --md-primary-fg-color to the header, and the
indigo accent clashes with the brand orange.

- mkdocs.yml: set theme.font to Roboto / Roboto Mono (Zensical honors theme.font)
- custom.css:
  - restore the orange app-bar (.md-header/.md-tabs) from --md-primary-fg-color
  - brand the accent (links, nav active pill, announce banner) by overriding
    --md-typeset-a-color and --md-accent-fg-color[--transparent]; the
    [scheme][primary] selector matches the theme's own (0,2,0) rules so it wins
    in both light (mealie) and dark (slate) modes
  - make the announce banner text adaptive so it is readable in light mode
    (was hardcoded light-gray, invisible on the modern theme's light banner)
- ai-providers.md: fix a broken self-referential link surfaced by Zensical's
  build check ("backend configuration" now points to backend-config.md)

Verified with headless-Chrome screenshots in both light and dark mode:
`zensical build` is clean ("No issues found"); Roboto fonts, orange header/tabs,
orange links, and a readable announce banner in both schemes.
2026-05-31 10:23:37 -05:00
Hayden
15b3e59c55 chore(docs): migrate documentation from MkDocs to Zensical
Material for MkDocs is in maintenance mode; Zensical is its successor from
the same maintainers. Zensical reads the existing mkdocs.yml natively, so
this is a tooling swap rather than a config rewrite.

- pyproject.toml: replace mkdocs-material with zensical in docs/dev groups
- Taskfile/CI/netlify: swap `mkdocs build|serve` for `zensical build|serve`
- mkdocs.yml: move custom `demo_url` under `extra:` (Zensical doesn't expose
  custom top-level config keys to templates); move `custom_dir` out of the
  docs source tree so raw templates aren't published as static files
- home.html: use `config.extra.demo_url`; replace the non-functional
  `lang.t('source.link.title')` (a Python call MiniJinja can't run, already
  rendering as literal text) with a static tooltip

Verified: `zensical build` exits clean ("No issues found"), 46 pages, with
mermaid, admonitions, content tabs, task lists, nav tabs, code-copy, custom
homepage, and announce banner all rendering at parity with the prior build.
2026-05-31 10:10:54 -05:00
Arsène Reymond
0af9633193 fix: add missing dependencies in package.json (#7709)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2026-05-31 14:25:00 +00:00
mealie-actions[bot]
b5987f5a46 chore(l10n): Crowdin locale sync (#7713)
Co-authored-by: GitHub Action <action@github.com>
2026-05-31 03:13:02 +00:00
mealie-commit-bot[bot]
e24187fefb chore: bump version to v3.19.2 2026-05-29 04:11:50 +00:00
Michael Genson
396fcd5ee4 fix: Ensure secret key is not empty (#7701) 2026-05-28 23:09:31 -05:00
renovate[bot]
5a3d202879 chore(deps): lock file maintenance (#7697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-27 20:09:07 +00:00
renovate[bot]
62377ae7ad fix(deps): update dependency ingredient-parser-nlp to v2.7.0 (#7695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-27 19:34:54 +00:00
renovate[bot]
7498e22278 fix(deps): update dependency sqlalchemy to v2.0.50 (#7693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-27 19:34:00 +00:00
renovate[bot]
af6c9e074e fix(deps): update dependency uvicorn to v0.48.0 (#7696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-27 19:33:15 +00:00
renovate[bot]
71dba654b8 chore(deps): update dependency coverage to v7.14.1 (#7691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-27 19:31:38 +00:00
renovate[bot]
ba69fcf824 fix(deps): update dependency fastapi to v0.136.3 (#7692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-27 19:31:05 +00:00
Michael Genson
8219ac0168 dev: Set renovarte bot PRs to "immediate" (#7690) 2026-05-27 14:11:10 -05:00
mealie-commit-bot[bot]
47f66676e4 chore: bump version to v3.19.1 2026-05-27 16:49:42 +00:00
Hayden
31d9479d17 chore(l10n): New Crowdin updates (#7687) 2026-05-27 11:48:54 -05:00
Michael Genson
6a8eae7ce4 fix: Make most recipe action columns filterable (#7689) 2026-05-27 11:47:29 -05:00
Hayden
3bddfc21ce chore(l10n): New Crowdin updates (#7661)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-27 04:13:05 +00:00
renovate[bot]
975a16c74b chore(deps): update dependency ruff to v0.15.14 (#7678)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:38:12 +00:00
renovate[bot]
840da0e935 fix(deps): update dependency python-ldap to v3.4.7 (#7680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:38:02 +00:00
renovate[bot]
0e22f3f8fa fix(deps): update dependency orjson to v3.11.9 (#7672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:37:11 +00:00
renovate[bot]
ff67fb6a4f chore(deps): update dependency types-python-dateutil to v2.9.0.20260518 (#7669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:10:36 +00:00
renovate[bot]
97f37d0def chore(deps): update dependency types-requests to v2.33.0.20260518 (#7671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:10:24 +00:00
renovate[bot]
37171d174b fix(deps): update dependency openai to v2.38.0 (#7677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:09:45 +00:00
renovate[bot]
f010c13661 chore(deps): update node.js to 8530f76 (#7668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:06:37 +00:00
renovate[bot]
84622af5f8 fix(deps): update dependency pydantic to v2.13.4 (#7673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 23:02:10 +00:00
renovate[bot]
024dad6663 chore(deps): lock file maintenance (#7685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 22:49:52 +00:00
renovate[bot]
f1998121aa fix(deps): update dependency pyjwt to v2.13.0 (#7682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:57:36 +00:00
renovate[bot]
94ca311616 chore(deps): update dependency mypy to v2.1.0 (#7681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:57:32 +00:00
renovate[bot]
44c4bbb9ab chore(deps): update dependency coverage to v7.14.0 (#7676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:55:43 +00:00
renovate[bot]
0c263c98c9 fix(deps): update dependency requests to v2.34.2 (#7683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:55:22 +00:00
renovate[bot]
c235dc8d4d fix(deps): update dependency uvicorn to v0.47.0 (#7684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:51:54 +00:00
renovate[bot]
1b7eda0f2c fix(deps): update dependency python-multipart to v0.0.29 (#7675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:51:31 +00:00
renovate[bot]
f3725b7184 fix(deps): update dependency lxml to v6.1.1 (#7679)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:48:28 +00:00
renovate[bot]
00a4b51ec1 fix(deps): update dependency pydantic-settings to v2.14.1 (#7674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:46:54 +00:00
renovate[bot]
2cf042fce9 chore(deps): update dependency types-pyyaml to v6.0.12.20260518 (#7670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:44:36 +00:00
renovate[bot]
55a8fdfee5 chore(deps): update dependency nuxt to v4.4.6 [security] (#7667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 21:07:29 +00:00
Michael Genson
1ab5323f34 dev: Disable approvals for lockfile maintenance PR creation (#7666) 2026-05-26 16:11:03 -05:00
Hayden
fb4ba490af chore(l10n): New Crowdin updates (#7660) 2026-05-24 20:56:08 -05:00
32 changed files with 3392 additions and 3176 deletions

View File

@@ -63,7 +63,7 @@ task setup # Install all dependencies (Python + Node)
task dev:services # Start Postgres & Mailpit containers
task py # Start FastAPI backend (port 9000)
task ui # Start Nuxt frontend (port 3000)
task docs # Start MkDocs documentation server
task docs # Start Zensical documentation server
```
**Code generation (REQUIRED after schema changes):**

View File

@@ -30,7 +30,7 @@ jobs:
run: uv sync --only-group docs --no-install-project
- name: Build docs
run: uv run --no-project mkdocs build -d site
run: uv run --no-project zensical build
working-directory: docs
- name: Upload artifact

View File

@@ -29,7 +29,7 @@ tasks:
desc: runs the documentation server
dir: docs
cmds:
- uv run python -m mkdocs serve
- uv run zensical serve
setup:ui:
desc: setup frontend dependencies

View File

@@ -1,7 +1,7 @@
###############################################
# Frontend Build
###############################################
FROM node:24@sha256:050bf2bbe33c1d6754e060bec89378a79ed831f04a7bb1a53fe45e997df7b3bb \
FROM node:24@sha256:8530f76a96d88820d288761f022e318970dda93d01536919fbc16076b7983e63 \
AS frontend-builder
WORKDIR /frontend

View File

@@ -17,6 +17,31 @@
--md-default-accent-bg-color: #1f1e1e;
}
/*
* Zensical's "modern" theme ships a flat header that uses the page background
* color. Restore the classic Material colored app-bar (and matching nav tabs)
* driven by the brand orange, and brand the link color to match.
*/
.md-header,
.md-tabs {
background-color: var(--md-primary-fg-color);
color: var(--md-primary-bg-color);
}
/*
* Brand the accent colors. The modern theme leaves links, the nav active-item
* pill, and the announce banner on its default indigo accent (the latter two
* via --md-accent-fg-color--transparent), which clashes with the orange. The
* [scheme][primary] selector matches the theme's own (0,2,0) accent rules so
* the override wins in both light (mealie) and dark (slate) modes.
*/
[data-md-color-scheme="mealie"][data-md-color-primary="indigo"],
[data-md-color-scheme="slate"][data-md-color-primary="indigo"] {
--md-typeset-a-color: var(--md-primary-fg-color);
--md-accent-fg-color: #e58325;
--md-accent-fg-color--transparent: #e583251a;
}
/* frontpage elements */
.tx-hero h1 {
font-size: 2.41rem !important;
@@ -64,7 +89,7 @@ th {
}
.announce-left > a {
color: #e0e0e0;
color: var(--md-default-fg-color);
font-weight: bold;
}

View File

@@ -20,7 +20,7 @@ If you have another provider you'd like to use, such as Azure, you can configure
Note that some models are capable of handling multiple features (e.g. `gpt-5` can handle both normal chat requests and image recognition requests). You may configure one provider for multiple provider features.
While Mealie has prompts for each AI task, you can override these with your own prompts if you'd like. For more information, check out the [backend configuration](./installation/ai-providers.md).
While Mealie has prompts for each AI task, you can override these with your own prompts if you'd like. For more information, check out the [backend configuration](./backend-config.md).
## AI Features
- The OpenAI Ingredient Parser can be used as an alternative to the NLP and Brute Force parsers. Simply choose the OpenAI parser while parsing ingredients (:octicons-tag-24: v1.7.0)

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.19.0`
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.19.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.19.0 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.19.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.19.0 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.19.2 # (3)
container_name: mealie
restart: always
ports:

View File

@@ -1,5 +1,4 @@
site_name: Mealie
demo_url: https://demo.mealie.io
site_url: https://docs.mealie.io
use_directory_urls: true
theme:
@@ -16,7 +15,7 @@ theme:
toggle:
icon: material/weather-sunny
name: Switch to light mode
custom_dir: docs/overrides
custom_dir: overrides
features:
- content.code.annotate
- content.code.copy
@@ -28,6 +27,9 @@ theme:
- navigation.tabs.sticky
favicon: assets/img/favicon.png
name: material
font:
text: Roboto
code: Roboto Mono
icon:
logo: material/silverware-variant
@@ -50,6 +52,8 @@ markdown_extensions:
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.details
extra:
demo_url: https://demo.mealie.io
extra_css:
- assets/stylesheets/custom.css
extra_javascript:

View File

@@ -228,7 +228,7 @@
class="md-button md-button--primary">
Get started
</a>
<a href="{{ config.demo_url }}" title="{{ lang.t('source.link.title') }}" target="_blank" class="md-button">
<a href="{{ config.extra.demo_url }}" title="View the Mealie demo" target="_blank" class="md-button">
View the Demo
</a>
</div>

View File

@@ -38,7 +38,7 @@ export const LOCALES = [
{
name: "Svenska (Swedish)",
value: "sv-SE",
progress: 75,
progress: 76,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -52,7 +52,7 @@ export const LOCALES = [
{
name: "Slovenščina (Slovenian)",
value: "sl-SI",
progress: 56,
progress: 57,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -73,7 +73,7 @@ export const LOCALES = [
{
name: "Română (Romanian)",
value: "ro-RO",
progress: 59,
progress: 60,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -108,7 +108,7 @@ export const LOCALES = [
{
name: "Nederlands (Dutch)",
value: "nl-NL",
progress: 97,
progress: 98,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -150,7 +150,7 @@ export const LOCALES = [
{
name: "Íslenska (Icelandic)",
value: "is-IS",
progress: 56,
progress: 57,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -248,14 +248,14 @@ export const LOCALES = [
{
name: "Deutsch (German)",
value: "de-DE",
progress: 98,
progress: 99,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Dansk (Danish)",
value: "da-DK",
progress: 99,
progress: 100,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -276,7 +276,7 @@ export const LOCALES = [
{
name: "Български (Bulgarian)",
value: "bg-BG",
progress: 71,
progress: 72,
dir: "ltr",
pluralFoodHandling: "always",
},

View File

@@ -224,8 +224,8 @@
"add-field": "Tilføj felt",
"date-created": "Oprettet",
"date-updated": "Opdateret",
"key": "Key",
"value": "Value"
"key": "Nøgle",
"value": "Værdi"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på, du vil slette <b>{groupName}<b/>?",
@@ -287,37 +287,37 @@
"total-households": "Husstande i Alt",
"you-must-select-a-group-before-selecting-a-household": "Du skal vælge en gruppe, før du vælger en husstand",
"ai-provider-settings": {
"ai-provider-settings": "AI Provider Settings",
"ai-provider": "AI Provider",
"ai-providers": "AI Providers",
"ai-provider-settings-description": "Configure AI providers to enable AI-powered features, such as enhanced ingredient parsing, creating recipes from videos, and more!",
"providers": "Providers",
"create-provider": "Create Provider",
"edit-provider": "Edit Provider",
"default-provider": "Default Provider",
"default-provider-description": "Required to enable AI features",
"audio-provider": "Audio Provider",
"audio-provider-description": "Enables audio transcription features, such as creating recipes from videos",
"image-provider": "Image Provider",
"image-provider-description": "Enables image recognition features, such as creating recipes from images",
"provider-name": "Provider Name",
"api-key": "API Key",
"api-key-description-create": "Your provider's API key for authentication. If your service (e.g. Ollama) doesn't use an API key, you still have to put something here.",
"api-key-description-edit": "Leave this blank unless you want to change it.",
"base-url": "Base URL",
"base-url-description": "If you're using OpenAI leave this blank. Must be an OpenAI-compatible endpoint (e.g. \"http://localhost:11434/v1\").",
"ai-provider-settings": "AI-udbyderindstillinger",
"ai-provider": "AI-udbyder",
"ai-providers": "AI-udbydere",
"ai-provider-settings-description": "Konfigurér AI-udbydere for at slå AI-funktioner til, såsom forbedret ingredienshåndtering, at oprette opskrifter fra videoer med mere.",
"providers": "Udbydere",
"create-provider": "Opret udbyder",
"edit-provider": "Redigér udbyder",
"default-provider": "Standardudbyder",
"default-provider-description": "Påkrævet for at slå AI-funktioner til",
"audio-provider": "Lydudbyder",
"audio-provider-description": "Slå lydtranskriberingsfunktioner til, såsom at oprette opskrifter fra videoer",
"image-provider": "Billedudbyder",
"image-provider-description": "Slår billedgenkendelsesfunktioner til, såsom at oprette opskrifter fra billeder",
"provider-name": "Udbydernavn",
"api-key": "API-nøgle",
"api-key-description-create": "Din udbyders API-nøgle til godkendelse. Hvis din udbyder ikke benytter en API-nøgle (eks. Ollama), skal du stadig skrive ét eller andet,",
"api-key-description-edit": "Undlad at udfylde dette, medmindre du vil ændre det.",
"base-url": "Basis-URL",
"base-url-description": "Undlad at udfylde, hvis du benytter OpenAI. Skal være et OpenAI-kompatibelt endpoint (eks. \"http://localhost:11434/v1\").",
"model": "Model",
"model-description": "Which model your AI provider should use (e.g. \"gpt-5\").",
"request-timeout-seconds": "Request Timeout (seconds)",
"provider-created": "Provider created",
"provider-updated": "Provider updated",
"provider-deleted": "Provider deleted",
"provider-create-failed": "Failed to create provider",
"provider-update-failed": "Failed to update provider",
"provider-delete-failed": "Failed to delete provider",
"request-headers": "Request Headers",
"request-params": "Request Parameters",
"no-default-provider-warning": "You have not set a default provider, so AI features are disabled"
"model-description": "Hvilken model skal din udbyder benytte (eks. \"gpt-5\")?",
"request-timeout-seconds": "Forespørgsels-time-out",
"provider-created": "Udbyder oprettet",
"provider-updated": "Udbyder opdateret",
"provider-deleted": "Udbyder slettet",
"provider-create-failed": "Kunne ikke oprette udbyder",
"provider-update-failed": "Kunne ikke opdatere udbyder",
"provider-delete-failed": "Kunne ikke slette udbyder",
"request-headers": "Forespørgsels-headers",
"request-params": "Forespørgselsparametre",
"no-default-provider-warning": "Du har ikke sat en standardudbyder, så AI-funktioner er slået fra"
}
},
"household": {
@@ -1397,7 +1397,7 @@
"already-set-up-bring-to-homepage": "Jeg er allerede oprettet, bare bringe mig til startsiden",
"common-settings-for-new-sites": "Her er nogle almindelige indstillinger for nye sites",
"setup-complete": "Opsætning færdig!",
"ai-providers-description": "Optionally configure AI providers for your group. AI providers enable features like creating recipes from images, importing recipes from videos, and enhanced ingredient parsing. You can always configure this later from your group settings.",
"ai-providers-description": "Konfigurér valgfrit AI-udbydere for din gruppe. AI-udbydere muliggør handlinger, såsom at oprette opskrifter fra billeder, importere opskrifter fra videoer, og forbedret håndtering af ingredienser. Det er altid muligt at konfigurere dette senere under dine gruppeindstillinger.",
"here-are-a-few-things-to-help-you-get-started": "Her er et par ting, der kan hjælpe dig i gang med Mealie",
"restore-from-v1-backup": "Har du en sikkerhedskopi fra en tidligere udgave af Mealie v1? Du kan gendanne den her.",
"manage-profile-or-get-invite-link": "Administrer din egen profil, eller tag et invitationslink til at dele med andre."

View File

@@ -169,7 +169,7 @@
"token": "Token",
"tuesday": "Τρίτη",
"type": "Τύπος",
"undo": "Undo",
"undo": "Αναίρεση",
"update": "Ενημέρωση",
"updated": "Ενημερώθηκε",
"upload": "Ανέβασμα",
@@ -952,7 +952,7 @@
"quantity": "Ποσότητα: {0}",
"shopping-list": "Λίστα για ψώνια",
"shopping-lists": "Λίστες για ψώνια",
"add-item": "Add item",
"add-item": "Προσθήκη στοιχείου",
"food": "Τρόφιμο",
"note": "Σημείωση",
"label": "Ετικέτα",

View File

@@ -224,8 +224,8 @@
"add-field": "Veld toevoegen",
"date-created": "Datum aangemaakt",
"date-updated": "Datum bijgewerkt",
"key": "Key",
"value": "Value"
"key": "Sleutel",
"value": "Waarde"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Weet je zeker dat je <b>{groupName}<b/> wil verwijderen?",
@@ -287,37 +287,37 @@
"total-households": "Totaal aantal huishoudens",
"you-must-select-a-group-before-selecting-a-household": "Kies een groep voordat je een huishouden kiest",
"ai-provider-settings": {
"ai-provider-settings": "AI Provider Settings",
"ai-provider": "AI Provider",
"ai-providers": "AI Providers",
"ai-provider-settings-description": "Configure AI providers to enable AI-powered features, such as enhanced ingredient parsing, creating recipes from videos, and more!",
"providers": "Providers",
"create-provider": "Create Provider",
"edit-provider": "Edit Provider",
"default-provider": "Default Provider",
"default-provider-description": "Required to enable AI features",
"audio-provider": "Audio Provider",
"audio-provider-description": "Enables audio transcription features, such as creating recipes from videos",
"image-provider": "Image Provider",
"image-provider-description": "Enables image recognition features, such as creating recipes from images",
"provider-name": "Provider Name",
"api-key": "API Key",
"api-key-description-create": "Your provider's API key for authentication. If your service (e.g. Ollama) doesn't use an API key, you still have to put something here.",
"api-key-description-edit": "Leave this blank unless you want to change it.",
"base-url": "Base URL",
"base-url-description": "If you're using OpenAI leave this blank. Must be an OpenAI-compatible endpoint (e.g. \"http://localhost:11434/v1\").",
"ai-provider-settings": "AI-aanbieder instellingen",
"ai-provider": "AI-aanbieder",
"ai-providers": "AI-aanbieders",
"ai-provider-settings-description": "Configureer AI-aanbieders om krachtige AI-aangedreven functies, zoals verbeterde ingrediënt parsing, aanmaken van recepten op basis van video's en nog meer!",
"providers": "Aanbieders",
"create-provider": "Aanbieder aanmaken",
"edit-provider": "Aanbieder bewerken",
"default-provider": "Standaard aanbieder",
"default-provider-description": "Vereist om AI-functies in te schakelen",
"audio-provider": "Audio aanbieder",
"audio-provider-description": "Maakt audiotransscriptie functionaliteiten mogelijk, waarmee recepten op basis van video's gemaakt kunnen worden",
"image-provider": "Afbeelding aanbieder",
"image-provider-description": "Maakt beeldherkenning functionaliteiten mogelijk, waarmee recepten op basis van afbeeldingen gemaakt kunnen worden",
"provider-name": "Naam aanbieder",
"api-key": "API-sleutel",
"api-key-description-create": "Je aanbieder's API-sleutel voor authenticatie. Als je service (bijv. Ollama) geen API-sleutel gebruikt, moet je hier alsnog iets invoeren.",
"api-key-description-edit": "Laat dit leeg tenzij je het wilt wijzigen.",
"base-url": "Basis URL",
"base-url-description": "Als je OpenAI gebruikt laat je dit leeg. Moet een OpenAI-compatibel eindpunt zijn (bijv. \"http://localhost:11434/v1\").",
"model": "Model",
"model-description": "Which model your AI provider should use (e.g. \"gpt-5\").",
"request-timeout-seconds": "Request Timeout (seconds)",
"provider-created": "Provider created",
"provider-updated": "Provider updated",
"provider-deleted": "Provider deleted",
"provider-create-failed": "Failed to create provider",
"provider-update-failed": "Failed to update provider",
"provider-delete-failed": "Failed to delete provider",
"request-headers": "Request Headers",
"request-params": "Request Parameters",
"no-default-provider-warning": "You have not set a default provider, so AI features are disabled"
"model-description": "Welk model je AI-aanbieder moet gebruiken (bijv. \"gpt-5\").",
"request-timeout-seconds": "Verzoek time-out (seconden)",
"provider-created": "Aanbieder aangemaakt",
"provider-updated": "Aanbieder bijgewerkt",
"provider-deleted": "Aanbieder verwijderd",
"provider-create-failed": "Aanmaken van aanbieder mislukt",
"provider-update-failed": "Bijwerken van aanbieder mislukt",
"provider-delete-failed": "Verwijderen van aanbieder mislukt",
"request-headers": "Aanvraagheaders",
"request-params": "Aanvraag parameters",
"no-default-provider-warning": "Je hebt geen standaard aanbieder ingesteld, AI-functies zijn uitgeschakeld"
}
},
"household": {
@@ -663,7 +663,7 @@
"create-recipe-description": "Maak een nieuw recept.",
"create-recipes": "Recepten aanmaken",
"import-with-zip": "Importeer met .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Maak een recept op basis van een afbeelding",
"create-recipe-from-an-image-description": "Maak een recept door een afbeelding ervan te uploaden. Mealie probeert de tekst met behulp van AI uit de afbeelding te halen en er een recept uit te maken.",
"crop-and-rotate-the-image": "Snijd de afbeelding bij zodat alleen tekst zichtbaar is. En draai t plaatje zodat het leesbaar is.",
"create-from-images": "Maak recept van een afbeelding",
@@ -1397,7 +1397,7 @@
"already-set-up-bring-to-homepage": "Ik ben al ingesteld, breng me naar de startpagina",
"common-settings-for-new-sites": "Hier zijn enkele algemene instellingen voor nieuwe sites",
"setup-complete": "Installatie voltooid!",
"ai-providers-description": "Optionally configure AI providers for your group. AI providers enable features like creating recipes from images, importing recipes from videos, and enhanced ingredient parsing. You can always configure this later from your group settings.",
"ai-providers-description": "Optioneel kun je AI-aanbieders instellen voor je groep. AI-aanbieders maken functies, zoals het maken van recepten op basis van afbeeldingen, het malen van recepten op basis van video's, en verbeterde ingrediëntenparsing mogelijk. Je kunt dit later altijd instellen vanuit je groepsinstellingen.",
"here-are-a-few-things-to-help-you-get-started": "Hier zijn een aantal dingen om je op weg te helpen met Mealie",
"restore-from-v1-backup": "Heb je een back-up van een vorig exemplaar van Mealie v1? Deze kan je hier terugzetten.",
"manage-profile-or-get-invite-link": "Beheer je eigen profiel, of gebruik een uitnodigingslink om te delen met anderen."

View File

@@ -98,7 +98,7 @@
"dashboard": "Panou de control",
"delete": "Șterge",
"disabled": "Inactiv",
"done": "Done",
"done": "Gata",
"download": "Descarcă",
"duplicate": "Duplicat",
"edit": "Editează",
@@ -169,7 +169,7 @@
"token": "Token",
"tuesday": "Marţi",
"type": "Tip",
"undo": "Undo",
"undo": "Anulează acțiunea",
"update": "Actualizează",
"updated": "Actualizat",
"upload": "Încarcă",
@@ -368,8 +368,8 @@
"any-household": "Orice locuință",
"no-meal-plan-defined-yet": "Nici un plan de mese definit încă",
"no-meal-planned-for-today": "Nicio masă planificată pentru astăzi",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-hint": "Numărul de zile din trecut la încărcarea paginii",
"numberOfDaysPast-label": "Număr implicit de zile din trecut",
"numberOfDays-hint": "Număr de zile pe pagină încărcată",
"numberOfDays-label": "Zile implicite",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Numai rețetele cu aceste categorii vor fi utilizate în Planurile de mese",
@@ -675,8 +675,8 @@
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Creează o rețetă furnizând numele. Toate rețetele trebuie să aibă nume unice.",
"new-recipe-names-must-be-unique": "Numele rețetei trebuie să fie unic",
"scrape-recipe": "Importare rețetă",
"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-description": "Extrage o rețetă după url. Introdu url-ul site-ului din care vrei să extragi rețeta, iar Mealie va încerca să o importe și să o adauge în colecția ta.",
"scrape-recipe-description-transcription": "Poți introduce și URL-ul unui videoclip, iar Mealie va încerca să îl transcrie într-o rețetă.",
"scrape-recipe-have-a-lot-of-recipes": "Ai mai multe rețete pe care vrei să le imporți simultan?",
"scrape-recipe-suggest-bulk-importer": "Încearcă importatorul în bulk",
"scrape-recipe-have-raw-html-or-json-data": "Ai date de tip HTML sau JSON?",
@@ -815,7 +815,7 @@
"irreversible-acknowledgment": "Înțeleg că această acțiune este ireversibilă, distructivă și poate provoca pierderea datelor",
"restore-backup": "Restaurează backup"
},
"backup-and-exports": "Backups",
"backup-and-exports": "Copii de rezervă",
"change-password": "Schimbă parola",
"current": "Versiune:",
"custom-pages": "Pagini personalizate",
@@ -928,17 +928,17 @@
"server-side-base-url-error-text": "`BASE_URL` încă este setat la valoarea implicită pe serverul API. Acest lucru va cauza probleme cu link-urile de notificări generate pe server pentru e-mailuri, etc.",
"server-side-base-url-success-text": "Adresa URL a serverului nu se potrivește cu cea implicită",
"ldap-ready": "LDAP pregătit",
"ldap-not-ready": "LDAP Not Ready",
"ldap-not-ready": "LDAP nu este pregătit\"",
"ldap-ready-error-text": "Nu toate valorile LDAP sunt configurate. Acest lucru poate fi ignorat dacă nu utilizați autentificarea cu LDAP.",
"ldap-ready-success-text": "Variabilele LDAP necesare sunt setate.",
"build": "Compilare",
"recipe-scraper-version": "Versiune \"scraper\" de rețete",
"oidc-ready": "OIDC pregătit",
"oidc-not-ready": "OIDC Not Ready",
"oidc-not-ready": "OIDC nu este pregătit",
"oidc-ready-error-text": "Nu toate valorile OIDC sunt configurate. Acest lucru poate fi ignorat dacă nu folosiți autentificarea OIDC.",
"oidc-ready-success-text": "Variabilele OIDC necesare sunt setate.",
"openai-ready": "OpenAI pregătit",
"openai-not-ready": "OpenAI Not Ready",
"openai-not-ready": "OpenAI nu este pregătit",
"openai-ready-error-text": "Nu toate valorile OpenAI sunt configurate. Acest lucru poate fi ignorat dacă nu utilizaţi caracteristicile OpenAI.",
"openai-ready-success-text": "Variabilele necesare OpenAI sunt setate."
},
@@ -946,15 +946,15 @@
"all-lists": "Toate listele",
"create-shopping-list": "Creează listă de cumpărături",
"from-recipe": "Dintr-o rețetă",
"ingredient-of-recipe": "Ingredient of {recipe}",
"ingredient-of-recipe": "Ingredient din {recipe}",
"list-name": "Nume listă",
"new-list": "Listă nouă",
"quantity": "Cantitate: {0}",
"shopping-list": "Listă de cumpărături",
"shopping-lists": "Liste de cumpărături",
"add-item": "Add item",
"add-item": "Adaugă articol",
"food": "Aliment",
"note": "Note",
"note": "Notă",
"label": "Etichetă",
"save-label": "Salvează etichetă",
"linked-item-warning": "Acest element este legat de una sau mai multe rețete. Ajustarea unităților sau a alimentelor va produce rezultate neașteptate la adăugarea sau scoaterea rețetei din listă.",
@@ -978,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Sunteți sigur că doriți să debifați toate elementele?",
"are-you-sure-you-want-to-delete-checked-items": "Sunteți sigur că doriți să ștergeți toate elementele selectate?",
"no-shopping-lists-found": "Nu s-au găsit liste de cumpărături",
"item-checked-off": "Checked off {item}"
"item-checked-off": "{item} a fost bifat"
},
"sidebar": {
"all-recipes": "Toate reţetele",
@@ -1181,18 +1181,18 @@
"example-unit-plural": "ex: Linguri",
"example-unit-abbreviation-singular": "ex: Lg",
"example-unit-abbreviation-plural": "ex: Lg",
"standardization": "Standardization",
"standardization-description": "How this unit can be represented as a standard unit. This enables unit conversion features such as merging compatible units in shopping lists.",
"standard-unit": "Standard Unit",
"standard-quantity": "Standard Quantity",
"unit-conversion": "Unit Conversion",
"standardization": "Standardizare",
"standardization-description": "Modul în care această unitate poate fi reprezentată ca unitate standard. Activează funcții de conversie a unităților, cum ar fi combinarea unităților compatibile în listele de cumpărături.",
"standard-unit": "Unitate standard",
"standard-quantity": "Cantitate standard",
"unit-conversion": "Conversie unități",
"standard-unit-labels": {
"fluid-ounce": "fluid ounce",
"cup": "cup",
"ounce": "ounce",
"pound": "pound",
"milliliter": "milliliter",
"liter": "liter",
"fluid-ounce": "uncie fluidă",
"cup": "cană",
"ounce": "uncie",
"pound": "livră",
"milliliter": "mililitru",
"liter": "litru",
"gram": "gram",
"kilogram": "kilogram"
}
@@ -1514,10 +1514,10 @@
"max-length": "Trebuie să aibă cel mult {max} caracter | Trebuie să aibă cel mult {max} caractere"
},
"announcements": {
"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"
"announcements": "Anunțuri",
"all-announcements": "Toate anunțurile",
"mark-all-as-read": "Marchează toate ca citite",
"show-announcements-from-mealie": "Afișează anunțurile de la Mealie",
"show-announcements-setting-description": "Stabilește dacă utilizatorii pot vedea anunțuri de la Mealie. Când opțiunea este activată, utilizatorii pot alege în continuare să nu le vadă în setările personale"
}
}

View File

@@ -11,6 +11,7 @@ export default withNuxt({
"@stylistic/no-tabs": ["error"],
"@stylistic/no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"@typescript-eslint/no-explicit-any": "off",
"import/no-extraneous-dependencies": ["error"],
"vue/first-attribute-linebreak": "error",
"vue/html-closing-bracket-newline": "error",
"vue/max-attributes-per-line": [

View File

@@ -1,6 +1,6 @@
{
"name": "mealie",
"version": "3.19.0",
"version": "3.19.2",
"private": true,
"scripts": {
"dev": "nuxt dev",
@@ -21,18 +21,24 @@
"@mdi/js": "^7.4.47",
"@nuxt/fonts": "^0.11.4",
"@nuxtjs/i18n": "^9.2.1",
"@sphinxxxx/color-conversion": "^2.2.2",
"@vite-pwa/nuxt": "^0.10.6",
"@vueuse/core": "^12.7.0",
"@vueuse/shared": "^14.3.0",
"axios": "^1.8.1",
"date-fns": "^4.1.0",
"dompurify": "^3.4.7",
"fuse.js": "^7.1.0",
"isomorphic-dompurify": "^3.4.0",
"json-editor-vue": "^0.18.1",
"marked": "^15.0.12",
"nuxt": "^4.4.2",
"sse.js": "^2.8.0",
"ufo": "^1.6.4",
"vue": "^3.5.35",
"vue-advanced-cropper": "^2.8.9",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.4.4",
"vuetify": "^4.0.5",
"vuetify-nuxt-module": "^0.19.5"
},
@@ -41,6 +47,7 @@
"@stylistic/eslint-plugin": "^5.4.0",
"@types/node": "^25.5.2",
"@types/sortablejs": "^1.15.8",
"@vitejs/plugin-vue": "^6.0.7",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.0.2",
"eslint-plugin-format": "^1.0.1",

File diff suppressed because it is too large Load Diff

View File

@@ -50,13 +50,19 @@ def determine_secrets(data_dir: Path, secret: str, production: bool) -> str:
secrets_file = data_dir.joinpath(secret)
if secrets_file.is_file():
with open(secrets_file) as f:
return f.read()
else:
data_dir.mkdir(parents=True, exist_ok=True)
with open(secrets_file, "w") as f:
new_secret = secrets.token_hex(32)
f.write(new_secret)
return new_secret
existing_secret = f.read().strip()
if existing_secret:
return existing_secret
data_dir.mkdir(parents=True, exist_ok=True)
new_secret = secrets.token_hex(32)
tmp_file = secrets_file.with_suffix(".tmp")
with open(tmp_file, "w") as f:
f.write(new_secret)
f.flush()
os.fsync(f.fileno())
tmp_file.replace(secrets_file)
return new_secret
def get_secrets_dir() -> str | None:

View File

@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
@@ -14,14 +14,14 @@ if TYPE_CHECKING:
class GroupRecipeAction(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipe_actions"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), index=True)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("groups.id"), index=True)
group: Mapped["Group"] = relationship("Group", back_populates="recipe_actions", single_parent=True)
household_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("households.id"), index=True)
household_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("households.id"), index=True)
household: Mapped["Household"] = relationship("Household", back_populates="recipe_actions")
action_type: Mapped[str] = mapped_column(String, index=True)
title: Mapped[str] = mapped_column(String, index=True)
action_type: FilterableColumn[str] = mapped_column(String, index=True)
title: FilterableColumn[str] = mapped_column(String, index=True)
url: Mapped[str] = mapped_column(String)
@auto_init()

View File

@@ -18,17 +18,17 @@
"yield": "Recoltă",
"yields": "Producţii"
},
"and-amount": "and {amount}",
"or-ingredient": "or {ingredient}",
"and-amount": "și {amount}",
"or-ingredient": "sau {ingredient}",
"create-progress": {
"creating-recipe-with-ai": "Creating recipe with AI...",
"creating-recipe-from-transcript-with-ai": "Creating recipe from transcript with AI...",
"creating-recipe-from-webpage-data": "Creating recipe from webpage data...",
"downloading-image": "Downloading image...",
"downloading-video": "Downloading video...",
"extracting-recipe-data": "Extracting recipe data...",
"fetching-webpage": "Fetching webpage...",
"transcribing-audio-with-ai": "Transcribing audio with AI..."
"creating-recipe-with-ai": "Se creează rețeta cu AI...",
"creating-recipe-from-transcript-with-ai": "Se creează rețeta din transcriere cu AI...",
"creating-recipe-from-webpage-data": "Se creează rețeta din datele paginii web...",
"downloading-image": "Se descarcă imaginea...",
"downloading-video": "Se descarcă videoclipul...",
"extracting-recipe-data": "Se extrag datele rețetei...",
"fetching-webpage": "Se preia pagina web...",
"transcribing-audio-with-ai": "Se transcrie sunetul cu AI..."
}
},
"mealplan": {

View File

@@ -6427,8 +6427,8 @@
"sugar": {
"aliases": [],
"description": "",
"name": "sugar",
"plural_name": "sugar"
"name": "ζάχαρη",
"plural_name": "ζάχαρη"
},
"brown sugar": {
"aliases": [
@@ -6542,8 +6542,8 @@
"raw sugar": {
"aliases": [],
"description": "",
"name": "raw sugar",
"plural_name": "raw sugar"
"name": "ακατέργαστη ζάχαρη",
"plural_name": "ακατέργαστη ζάχαρη"
},
"golden syrup": {
"aliases": [],
@@ -6560,8 +6560,8 @@
"liquid stevia": {
"aliases": [],
"description": "",
"name": "liquid stevia",
"plural_name": "liquid stevia"
"name": "υγρή στέβια",
"plural_name": "υγρή στέβια"
},
"grenadine": {
"aliases": [],
@@ -16087,8 +16087,8 @@
"fish oil": {
"aliases": [],
"description": "",
"name": "fish oil",
"plural_name": "fish oil"
"name": "ιχθυέλαιο",
"plural_name": "ιχθυέλαιο"
},
"lime essential oil": {
"aliases": [],
@@ -16099,8 +16099,8 @@
"probiotic": {
"aliases": [],
"description": "",
"name": "probiotic",
"plural_name": "probiotics"
"name": "προβιοτικό",
"plural_name": "προβιοτικά"
},
"activated charcoal": {
"aliases": [],
@@ -16111,8 +16111,8 @@
"egg powder": {
"aliases": [],
"description": "",
"name": "egg powder",
"plural_name": "egg powder"
"name": "σκόνη αυγών",
"plural_name": "σκόνη αυγών"
},
"reishi mushroom": {
"aliases": [],
@@ -16267,8 +16267,8 @@
"banana powder": {
"aliases": [],
"description": "",
"name": "banana powder",
"plural_name": "banana powder"
"name": "σκόνη μπανάνας",
"plural_name": "σκόνη μπανάνας"
},
"chaga mushroom powder": {
"aliases": [],

View File

@@ -3930,7 +3930,7 @@
"aliases": [],
"description": "",
"name": "andouille",
"plural_name": "andouilles"
"plural_name": "andouillette"
},
"boneless lamb": {
"aliases": [],

View File

@@ -1856,7 +1856,7 @@
"aliases": [],
"description": "",
"name": "onion seed",
"plural_name": "onion seeds"
"plural_name": "Semințe de ceapă"
},
"watermelon seed": {
"aliases": [],
@@ -1873,8 +1873,8 @@
"melon seed": {
"aliases": [],
"description": "",
"name": "melon seed",
"plural_name": "melon seeds"
"name": "Sămânță de pepene",
"plural_name": "Semințe de pepene"
},
"lotus seed": {
"aliases": [],
@@ -2038,19 +2038,19 @@
"aliases": [],
"description": "",
"name": "brânză mozzarella",
"plural_name": "mozzarella cheese"
"plural_name": "brânză mozzarella"
},
"feta cheese": {
"aliases": [],
"description": "",
"name": "feta cheese",
"plural_name": "feta cheese"
"name": "brânză feta",
"plural_name": "brânză feta"
},
"ricotta cheese": {
"aliases": [],
"description": "",
"name": "ricotta cheese",
"plural_name": "ricotta cheese"
"name": "brânză ricotta",
"plural_name": "brânză ricotta"
},
"cheddar-jack cheese": {
"aliases": [],
@@ -2067,32 +2067,32 @@
"blue cheese": {
"aliases": [],
"description": "",
"name": "blue cheese",
"plural_name": "blue cheese"
"name": "brânză cu mucegai albastru",
"plural_name": "brânzeturi cu mucegai albastru"
},
"goat cheese": {
"aliases": [],
"description": "",
"name": "goat cheese",
"plural_name": "goat cheese"
"name": "brânză de capră",
"plural_name": "brânzeturi de capră"
},
"fresh mozzarella cheese": {
"aliases": [],
"description": "",
"name": "fresh mozzarella cheese",
"plural_name": "fresh mozzarella cheese"
"name": "mozzarella proaspătă",
"plural_name": "mozzarella proaspătă"
},
"swis cheese": {
"aliases": [],
"description": "",
"name": "swis cheese",
"plural_name": "swis cheese"
"name": "brânză elvețiană",
"plural_name": "brânzeturi elvețiene"
},
"pecorino cheese": {
"aliases": [],
"description": "",
"name": "pecorino cheese",
"plural_name": "pecorino cheese"
"name": "brânză pecorino",
"plural_name": "brânză pecorino"
},
"gruyere cheese": {
"aliases": [],
@@ -2109,8 +2109,8 @@
"cottage cheese": {
"aliases": [],
"description": "",
"name": "cottage cheese",
"plural_name": "cottage cheese"
"name": "brânză de vaci",
"plural_name": "brânză de vaci"
},
"american cheese": {
"aliases": [],
@@ -2121,8 +2121,8 @@
"provolone cheese": {
"aliases": [],
"description": "",
"name": "provolone cheese",
"plural_name": "provolone cheese"
"name": "brânză provolone",
"plural_name": "brânză provolone"
},
"mexican cheese blend": {
"aliases": [],
@@ -2169,7 +2169,7 @@
"gouda cheese": {
"aliases": [],
"description": "",
"name": "gouda cheese",
"name": "brânză gouda",
"plural_name": "gouda cheese"
},
"cotija cheese": {
@@ -2187,8 +2187,8 @@
"smoked cheese": {
"aliases": [],
"description": "",
"name": "smoked cheese",
"plural_name": "smoked cheese"
"name": "brânză afumată",
"plural_name": "brânză afumată"
},
"halloumi cheese": {
"aliases": [],
@@ -2229,7 +2229,7 @@
"burrata cheese": {
"aliases": [],
"description": "",
"name": "burrata cheese",
"name": "brânză burrata",
"plural_name": "burrata cheese"
},
"havarti cheese": {
@@ -2283,8 +2283,8 @@
"raclette cheese": {
"aliases": [],
"description": "",
"name": "raclette cheese",
"plural_name": "raclette cheese"
"name": "brânză raclette",
"plural_name": "brânză raclette"
},
"colby-jack cheese": {
"aliases": [],
@@ -2457,8 +2457,8 @@
"hard goat cheese": {
"aliases": [],
"description": "",
"name": "hard goat cheese",
"plural_name": "hard goat cheese"
"name": "brânză tare de capră",
"plural_name": "brânză tare de capră"
},
"kashkaval cheese": {
"aliases": [],
@@ -2469,8 +2469,8 @@
"sheep cheese": {
"aliases": [],
"description": "",
"name": "sheep cheese",
"plural_name": "sheep cheese"
"name": "brânză de oaie",
"plural_name": "brânză de oaie"
},
"amul cheese": {
"aliases": [],
@@ -2630,49 +2630,49 @@
"aliases": [],
"description": "",
"name": "lapte",
"plural_name": "milk"
"plural_name": "lapte"
},
"heavy cream": {
"aliases": [],
"description": "",
"name": "smântână pentru frișcă",
"plural_name": "heavy cream"
"plural_name": "smântână pentru frișcă"
},
"sour cream": {
"aliases": [],
"description": "",
"name": "smântână",
"plural_name": "sour cream"
"plural_name": "smântână"
},
"buttermilk": {
"aliases": [],
"description": "",
"name": "buttermilk",
"plural_name": "buttermilk"
"name": "lapte bătut",
"plural_name": "lapte bătut"
},
"yogurt": {
"aliases": [],
"description": "",
"name": "iaurt",
"plural_name": "yogurts"
"plural_name": "iaurt"
},
"greek yogurt": {
"aliases": [],
"description": "",
"name": "greek yogurt",
"plural_name": "greek yogurts"
"name": "iaurt grecesc",
"plural_name": "iaurturi grecești"
},
"cream": {
"aliases": [],
"description": "",
"name": "cream",
"plural_name": "cream"
"name": "smântână",
"plural_name": "smântână"
},
"whipped cream": {
"aliases": [],
"description": "",
"name": "whipped cream",
"plural_name": "whipped cream"
"name": "frișcă",
"plural_name": "frișcă"
},
"ghee": {
"aliases": [
@@ -2692,7 +2692,7 @@
"aliases": [],
"description": "",
"name": "lapte condensat",
"plural_name": "condensed milk"
"plural_name": "lapte condensat"
},
"half and half": {
"aliases": [],
@@ -2710,7 +2710,7 @@
"aliases": [],
"description": "",
"name": "înghețată",
"plural_name": "ice cream"
"plural_name": "înghețată"
},
"margarine": {
"aliases": [],
@@ -2734,7 +2734,7 @@
"aliases": [],
"description": "",
"name": "lapte praf",
"plural_name": "milk powder"
"plural_name": "lapte praf"
},
"curd": {
"aliases": [],
@@ -2788,7 +2788,7 @@
"aliases": [],
"description": "",
"name": "lapte cu ciocolată",
"plural_name": "chocolate milk"
"plural_name": "lapte cu ciocolată"
},
"liquid egg substitute": {
"aliases": [],
@@ -2872,7 +2872,7 @@
"aliases": [],
"description": "",
"name": "ganache",
"plural_name": "ganaches"
"plural_name": "ganache"
},
"cajeta": {
"aliases": [],
@@ -2883,8 +2883,8 @@
"duck egg": {
"aliases": [],
"description": "",
"name": "duck egg",
"plural_name": "duck eggs"
"name": "ou de rață",
"plural_name": "ouă de rață"
},
"salted egg": {
"aliases": [],
@@ -2908,7 +2908,7 @@
"aliases": [],
"description": "",
"name": "lapte crud",
"plural_name": "raw milk"
"plural_name": "lapte crud"
},
"lime curd": {
"aliases": [],
@@ -3027,7 +3027,7 @@
"chocolate milk powder": {
"aliases": [],
"description": "",
"name": "chocolate milk powder",
"name": "lapte praf de ciocolată",
"plural_name": "chocolate milk powder"
},
"liquid rennet": {
@@ -3337,7 +3337,7 @@
"smoked tofu": {
"aliases": [],
"description": "",
"name": "smoked tofu",
"name": "tofu afumat",
"plural_name": "smoked tofus"
},
"coconut powder": {

View File

@@ -9,13 +9,13 @@
"name": "lingură",
"plural_name": "linguri",
"description": "",
"abbreviation": "tbsp"
"abbreviation": "lg"
},
"cup": {
"name": "cană",
"plural_name": "căni",
"description": "",
"abbreviation": "c"
"abbreviation": "cană"
},
"fluid-ounce": {
"name": "uncie fluidă",
@@ -30,7 +30,7 @@
"abbreviation": "pt"
},
"quart": {
"name": "quart",
"name": "sfert de galon",
"plural_name": "sferturi de galon",
"description": "",
"abbreviation": "qt"
@@ -139,8 +139,8 @@
"abbreviation": ""
},
"sprig": {
"name": "sprig",
"plural_name": "sprigs",
"name": "crenguță",
"plural_name": "crenguțe",
"description": "",
"abbreviation": ""
}

View File

@@ -1,9 +1,9 @@
[build]
publish = "docs/site/"
command = """
pip3 install mkdocs-material &&
pip3 install zensical &&
cd docs &&
mkdocs build
zensical build
"""
[[headers]]

View File

@@ -1,6 +1,6 @@
[project]
name = "mealie"
version = "3.19.0"
version = "3.19.2"
description = "A Recipe Manager"
authors = [{ name = "Hayden", email = "hay-kot@pm.me" }]
license = "AGPL-3.0-only"
@@ -9,7 +9,7 @@ dependencies = [
"Jinja2==3.1.6",
"Pillow==12.2.0",
"PyYAML==6.0.3",
"SQLAlchemy==2.0.49",
"SQLAlchemy==2.0.50",
"aiofiles==25.1.0",
"alembic==1.18.4",
"aniso8601==10.0.1",
@@ -17,21 +17,21 @@ dependencies = [
"apprise==1.10.0",
"bcrypt==5.0.0",
"extruct==0.18.0",
"fastapi==0.136.1",
"fastapi==0.136.3",
"httpx==0.28.1",
"lxml==6.1.0",
"orjson==3.11.8",
"pydantic==2.13.3",
"lxml==6.1.1",
"orjson==3.11.9",
"pydantic==2.13.4",
"pyhumps==3.8.0",
"python-dateutil==2.9.0.post0",
"python-dotenv==1.2.2",
"python-ldap==3.4.5",
"python-multipart==0.0.27",
"python-ldap==3.4.7",
"python-multipart==0.0.29",
"python-slugify==8.0.4",
"recipe-scrapers==15.11.0",
"requests==2.33.1",
"requests==2.34.2",
"tzdata==2026.2",
"uvicorn[standard]==0.46.0",
"uvicorn[standard]==0.48.0",
"beautifulsoup4==4.14.3",
"isodate==0.7.2",
"text-unidecode==1.3",
@@ -39,14 +39,14 @@ dependencies = [
"authlib==1.7.2",
"html2text==2025.4.15",
"paho-mqtt==1.6.1",
"pydantic-settings==2.14.0",
"pydantic-settings==2.14.1",
"pillow-heif==1.3.0",
"pyjwt==2.12.1",
"openai==2.34.0",
"pyjwt==2.13.0",
"openai==2.38.0",
"typing-extensions==4.15.0",
"itsdangerous==2.2.0",
"yt-dlp==2026.3.17",
"ingredient-parser-nlp==2.6.0",
"ingredient-parser-nlp==2.7.0",
"pint==0.25.3",
"httpx-curl-cffi==0.1.5",
]
@@ -61,23 +61,23 @@ pgsql = [
[dependency-groups]
docs = [
"mkdocs-material==9.7.6",
"zensical==0.0.42",
]
dev = [
"coverage==7.13.5",
"coverage==7.14.1",
"coveragepy-lcov==0.1.2",
"mkdocs-material==9.7.6",
"mypy==2.0.0",
"zensical==0.0.42",
"mypy==2.1.0",
"pre-commit==4.6.0",
"pylint==4.0.5",
"pytest==9.0.3",
"pytest-asyncio==1.3.0",
"rich==15.0.0",
"ruff==0.15.12",
"types-PyYAML==6.0.12.20260408",
"types-python-dateutil==2.9.0.20260408",
"ruff==0.15.14",
"types-PyYAML==6.0.12.20260518",
"types-python-dateutil==2.9.0.20260518",
"types-python-slugify==8.0.2.20240310",
"types-requests==2.33.0.20260503",
"types-requests==2.33.0.20260518",
"types-urllib3==1.26.25.14",
"pydantic-to-typescript2==1.0.6",
"freezegun==1.5.5",

View File

@@ -1,5 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"prCreation": "immediate",
"lockFileMaintenance": {
"enabled": true
},
@@ -24,6 +25,13 @@
],
"dependencyDashboardApproval": true
},
{
"description": "Always create lockfile maintenance PRs without dashboard approval",
"matchUpdateTypes": [
"lockFileMaintenance"
],
"dependencyDashboardApproval": false
},
{
"matchManagers": [
"pep621"

View File

@@ -1,12 +1,13 @@
import json
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import pytest
from mealie.core.config import get_app_settings
from mealie.core.settings.settings import AppSettings
from mealie.core.settings.settings import AppSettings, determine_secrets
def test_non_default_settings(monkeypatch):
@@ -367,3 +368,42 @@ def test_sensitive_settings_mask(monkeypatch: pytest.MonkeyPatch):
for setting in sensitive_settings:
assert settings[setting] == "*****"
assert settings_json[setting] == "*****"
class DetermineSecretsTests:
def test_non_production_returns_fixed_key(self, tmp_path: Path):
result = determine_secrets(tmp_path, ".secret", production=False)
assert result == "shh-secret-test-key"
def test_generates_secret_when_file_missing(self, tmp_path: Path):
result = determine_secrets(tmp_path, ".secret", production=True)
assert result
assert (tmp_path / ".secret").read_text() == result
def test_reuses_existing_secret(self, tmp_path: Path):
(tmp_path / ".secret").write_text("existing-secret")
result = determine_secrets(tmp_path, ".secret", production=True)
assert result == "existing-secret"
def test_regenerates_when_file_is_empty(self, tmp_path: Path):
(tmp_path / ".secret").write_text("")
result = determine_secrets(tmp_path, ".secret", production=True)
assert result
assert (tmp_path / ".secret").read_text() == result
def test_regenerates_when_file_is_whitespace_only(self, tmp_path: Path):
(tmp_path / ".secret").write_text(" \n ")
result = determine_secrets(tmp_path, ".secret", production=True)
assert result
assert (tmp_path / ".secret").read_text() == result
def test_generates_unique_secrets(self, tmp_path: Path):
dir_a = tmp_path / "a"
dir_b = tmp_path / "b"
result_a = determine_secrets(dir_a, ".secret", production=True)
result_b = determine_secrets(dir_b, ".secret", production=True)
assert result_a != result_b
def test_no_tmp_file_left_after_write(self, tmp_path: Path):
determine_secrets(tmp_path, ".secret", production=True)
assert not (tmp_path / ".tmp").exists()

986
uv.lock generated

File diff suppressed because it is too large Load Diff