Compare commits

...

85 Commits

Author SHA1 Message Date
Hayden
e141343aa9 New translations en-us.json (French)
[ci skip]
2026-06-20 03:53:06 -05:00
Hayden
8c8ff6cfcb New translations en-us.json (Slovak)
[ci skip]
2026-06-19 15:02:42 -05:00
Hayden
919ab3aa1e New translations en-us.json (German)
[ci skip]
2026-06-19 02:58:00 -05:00
Hayden
04c9aca203 New translations en-us.json (Czech)
[ci skip]
2026-06-19 02:57:58 -05:00
Hayden
af6bdc6a4a New translations en-us.json (Czech)
[ci skip]
2026-06-19 02:57:57 -05:00
Hayden
4766dcf503 New translations en-us.json (Slovak)
[ci skip]
2026-06-18 14:55:26 -05:00
Hayden
76b12a6614 New translations en-us.json (Czech)
[ci skip]
2026-06-18 14:55:24 -05:00
Hayden
701822a45d New translations en-us.json (Slovak)
[ci skip]
2026-06-18 02:30:12 -05:00
Hayden
4df0072208 New translations en-us.json (Slovak)
[ci skip]
2026-06-18 02:30:10 -05:00
Hayden
09fe4f7511 New translations en-us.json (Spanish)
[ci skip]
2026-06-17 02:17:14 -05:00
renovate[bot]
a166f127a2 chore(deps): update dependency ruff to v0.15.17 (#7763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-16 19:15:21 +00:00
renovate[bot]
8556fb752c fix(deps): update dependency pillow-heif to v1.4.0 (#7762)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-16 02:19:26 +00:00
renovate[bot]
c2af718c29 fix(deps): update dependency openai to v2.41.1 (#7761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-16 02:18:43 +00:00
Hayden
9f7a968607 chore(l10n): New Crowdin updates (#7756)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-06-16 02:05:46 +00:00
cheebreezee
544071f3e7 fix: refactor cookie settings for Home Assistant i-frame login (#7741) 2026-06-15 14:08:35 +00:00
renovate[bot]
ecb6caf71b fix(deps): update dependency yt-dlp to v2026.6.9 (#7758)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-15 01:47:53 +00:00
renovate[bot]
f40bf3849e chore(deps): lock file maintenance (#7759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-15 01:38:12 +00:00
mealie-actions[bot]
629d41d38a chore(l10n): Crowdin locale sync (#7757)
Co-authored-by: GitHub Action <action@github.com>
2026-06-14 03:14:08 +00:00
Hayden
e4a6274810 chore(l10n): New Crowdin updates (#7749)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-06-13 11:55:20 +00:00
renovate[bot]
e1fed11bb0 fix(deps): update dependency beautifulsoup4 to v4.15.0 (#7753)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-12 18:50:31 +00:00
renovate[bot]
d125c88da6 chore(deps): update node.js to 40ad9f3 (#7752)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-12 01:16:18 +00:00
renovate[bot]
aa2192a78f chore(deps): update node.js to 9c1d881 (#7748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-11 14:56:51 +00:00
Hayden
8f48887267 chore(l10n): New Crowdin updates (#7747) 2026-06-10 23:24:50 -05:00
renovate[bot]
7cc50145ec fix(deps): update dependency openai to v2.41.0 (#7733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 22:03:39 +00:00
renovate[bot]
fe9fb77316 chore(deps): update dependency ruff to v0.15.16 (#7726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 22:03:35 +00:00
renovate[bot]
204bd2aeb3 fix(deps): update dependency uvicorn to v0.49.0 (#7744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 21:34:16 +00:00
renovate[bot]
b8d5375478 fix(deps): update dependency apprise to v1.11.0 (#7728)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 21:30:47 +00:00
renovate[bot]
4289860ffd fix(deps): update dependency python-multipart to v0.0.32 (#7729)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 21:29:03 +00:00
renovate[bot]
a1c724fac4 chore(deps): lock file maintenance (#7742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 21:23:25 +00:00
Michael Genson
ac0bb4fb2c fix: Upgrade pytest-asyncio (#7746) 2026-06-09 16:09:35 -05:00
mealie-actions[bot]
040ec56c18 chore(l10n): Crowdin locale sync (#7734)
Co-authored-by: GitHub Action <action@github.com>
2026-06-07 03:13:31 +00:00
Alexander Wenzel
f025bbce57 fix: default ingredient_references on RecipeInstruction init (#7732) 2026-06-06 22:54:41 +00:00
renovate[bot]
47c6d01617 chore(deps): update dependency vitest to v4 [security] (#7723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-01 21:38:46 +00:00
renovate[bot]
653be9a604 chore(deps): update dependency pytest-asyncio to v1.4.0 (#7694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-31 16:24:28 +00:00
Hayden
2d8b74282a fix: harden recipe content against stored XSS (chips, instructions, asset media) (#7719) 2026-05-31 16:14:16 +00:00
Hayden
48752bcd06 fix: support CSV/TXT upload and add validation for Plan to Eat import (#6360) (#7622) 2026-05-31 15:59:50 +00:00
Hayden
a46620d236 chore: drop unused python dependencies aniso8601 and appdirs (#7717) 2026-05-31 15:59:10 +00:00
Hayden
3bde6df958 chore: add 5-day dependency cooling period for supply-chain hardening (#7718) 2026-05-31 15:55:15 +00:00
Brian Choromanski
e1ddc06eff feat: Added version info to backup file (#7416)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2026-05-31 15:35:52 +00:00
Zachary Schaffter
262b531add feat: warn when deleting foods used in recipes (#7117)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2026-05-31 10:42:13 -05:00
miah
364af97060 dev: Improve support for front end unit tests (#7163) 2026-05-31 10:41:52 -05:00
Brian Choromanski
7b0d1fde64 feat: Enhanced PR Lint/Validation (#7329)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2026-05-31 10:41:43 -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
mealie-commit-bot[bot]
d70978cd8b chore: bump version to v3.19.0 2026-05-24 19:30:20 +00:00
Michael Genson
3b2bcca639 fix: Prevent swiping AND scrolling on shopping list (#7659) 2026-05-24 13:28:58 -05:00
Hayden
16163a9189 chore(l10n): New Crowdin updates (#7653)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-24 16:50:04 +00:00
mealie-actions[bot]
5ce448af7a chore(l10n): Crowdin locale sync (#7655)
Co-authored-by: GitHub Action <action@github.com>
2026-05-24 03:11:27 +00:00
Michael Genson
c3f87736d0 feat: In-app AI Provider Configuration (#7650) 2026-05-23 11:13:10 -05:00
Hayden
f6fe92b400 chore(l10n): New Crowdin updates (#7652) 2026-05-23 10:54:10 -05:00
Hayden
823b938a2c fix: enforce organize-group-data permission on food/tag/category mutations (#7651) 2026-05-23 05:32:57 +00:00
202 changed files with 11401 additions and 4811 deletions

View File

@@ -44,6 +44,7 @@
8000, // used by mkdocs
9000,
9091, // used by docker production
51204, // used for test coverage report
24678 // used by nuxt when hot-reloading using polling
],
// Use 'onCreateCommand' to run commands at the end of container creation.

View File

@@ -20,6 +20,10 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
env:
# Install from the committed lockfile; never re-resolve (see pyproject
# [tool.uv] exclude-newer cooling window).
UV_FROZEN: "1"
steps:
- uses: actions/checkout@v6

View File

@@ -14,6 +14,10 @@ permissions:
jobs:
sync-locales:
runs-on: ubuntu-latest
env:
# Install from the committed lockfile; never re-resolve (see pyproject
# [tool.uv] exclude-newer cooling window).
UV_FROZEN: "1"
steps:
- name: Generate GitHub App Token
id: app-token

View File

@@ -3,7 +3,7 @@ name: Pull Request Linter
on:
workflow_call:
pull_request:
types: [edited] # This captures the PR title changing
types: [edited, reopened] # This captures the PR title/body changing
branches:
- mealie-next
@@ -41,3 +41,50 @@ jobs:
ignoreLabels: |
bot
ignore-semantic-pull-request
validate-template:
name: Validate PR template
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Check required PR template sections
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
if (pr.user.type === "Bot") {
console.log("Skipping template check for bot");
return;
}
const response = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: ".github/pull_request_template.md",
});
const template = Buffer.from(response.data.content, "base64").toString("utf8");
const lines = template.split("\n");
const requiredHeadings = [];
let lastHeading = null;
for (const line of lines) {
if (line.startsWith("## ")) {
lastHeading = line.trim();
} else if (line.trim() === "_(REQUIRED)_" && lastHeading) {
requiredHeadings.push(lastHeading);
lastHeading = null;
}
}
const body = pr.body || "";
const missing = requiredHeadings.filter(h => !body.includes(h));
if (missing.length > 0) {
core.setFailed(`Missing headings:\n${missing.join("\n")}`);
} else {
console.log("All required headings present");
}

View File

@@ -18,6 +18,8 @@ jobs:
name: "Lint PR"
if: github.event_name == 'pull_request'
uses: ./.github/workflows/pull-request-lint.yml
permissions:
pull-requests: write
backend-tests:
name: "Backend Server Tests"

View File

@@ -13,6 +13,10 @@ jobs:
env:
PRODUCTION: false
# Install from the committed lockfile; never re-resolve. The rolling
# `exclude-newer` cooling window (pyproject [tool.uv]) would otherwise make
# every uv command re-resolve and fail on in-window pins.
UV_FROZEN: "1"
strategy:
fail-fast: true

30
.vscode/test-block.code-snippets vendored Normal file
View File

@@ -0,0 +1,30 @@
{
"Test Block": {
"prefix": "mtest",
"body": [
"import { mount } from \"@vue/test-utils\";",
"import { describe, expect, test, vi } from \"vitest\";",
"import { makeWrapper } from \"~/tests/utils\";",
"",
"const wrapper = () => makeWrapper(() => {",
" return ${1:composable}();",
"});",
"",
"describe(\"${TM_FILENAME_BASE/(.*)\\..+$/$1/}\", () => {",
" describe(\"${2:method}\", () => {",
" test(\"It does the thing\", () => {",
" const { ${2:method} } = wrapper();",
" const result = ${2:method}();",
" expect(result).toBe(EXPECTED);",
" });",
" });",
"});",
"",
],
"description": "Insert a test block",
"scope": "typescript",
"include": [
"**/*.test.{ts,tsx,vue}"
]
}
}

View File

@@ -7,6 +7,10 @@ env:
DEFAULT_GROUP: Home
DEFAULT_HOUSEHOLD: Family
PRODUCTION: false
# Install from the committed lockfile; never re-resolve. Required because the
# rolling `exclude-newer` cooling window (pyproject [tool.uv]) would otherwise
# make every `uv run`/`uv sync` re-resolve and fail on in-window pins.
UV_FROZEN: "1"
API_PORT: 9000
API_DOCS: True
TOKEN_TIME: 256 # hours

View File

@@ -1,7 +1,7 @@
###############################################
# Frontend Build
###############################################
FROM node:24@sha256:050bf2bbe33c1d6754e060bec89378a79ed831f04a7bb1a53fe45e997df7b3bb \
FROM node:24@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7 \
AS frontend-builder
WORKDIR /frontend
@@ -52,6 +52,11 @@ RUN apt-get update \
RUN pip install uv
# Install from the committed lockfile; never re-resolve. The rolling
# `exclude-newer` cooling window (pyproject [tool.uv]) would otherwise make
# `uv export` below re-resolve and fail on in-window pins.
ENV UV_FROZEN=1
WORKDIR /mealie
# copy project files here to ensure they will be cached.

View File

@@ -58,9 +58,6 @@ load_secrets() {
"OIDC_CONFIGURATION_URL"
"OIDC_CLIENT_ID"
"OIDC_CLIENT_SECRET"
"OPENAI_BASE_URL"
"OPENAI_API_KEY"
)
# If any secrets are set, prefer them over base environment variables.

View File

@@ -1,10 +1,10 @@
!!! info
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
An easy way to add recipes to Mealie from an Apple device is via an Apple Shortcut. This is a short guide to install an configure a shortcut able to add recipes via a link or image(s).
An easy way to add recipes to Mealie from an Apple device is via an Apple Shortcut. This is a short guide to install an configure a shortcut able to add recipes via a link or image(s).
!!! note
If adding via images make sure to enable [Mealie's OpenAI Integration](https://docs.mealie.io/documentation/getting-started/installation/open-ai/)
If adding via images make sure to enable [Mealie's AI Integration](https://docs.mealie.io/documentation/getting-started/installation/ai-providers)
## Javascript can only be run via Shortcuts on the Safari browser on MacOS and iOS. If you do not use Safari you may skip this section
Some sites have begun blocking AI scraping bots, inadvertently blocking the recipe scraping library Mealie uses as well. To circumvent this, the shortcut uses javascript to capture the raw html loaded in the browser and sends that to mealie when possible.
@@ -23,7 +23,7 @@ An API key is needed to authenticate with mealie. To create an api key for a use
The shortcut can be installed via **[This link](https://www.icloud.com/shortcuts/52834724050b42aebe0f2efd8d067360)**. Upon install, replace "MEALIE_API_KEY" with the API key generated previously and "MEALIE_URI" with the full URL used to access your mealie instance e.g. "http://10.0.0.5:9000" or "https://mealie.domain.com".
## Using the Shortcut
Once installed, the shortcut will automatically appear as an option when sharing an image or webpage. It can also be useful to add the shortcut to the home screen of your device. If selected from the home screen or shortcuts app, a menu will appear with prompts to import via **taking photo(s)**, **selecting photo(s)**, **scanning a URL**, or **pasting a URL**.
Once installed, the shortcut will automatically appear as an option when sharing an image or webpage. It can also be useful to add the shortcut to the home screen of your device. If selected from the home screen or shortcuts app, a menu will appear with prompts to import via **taking photo(s)**, **selecting photo(s)**, **scanning a URL**, or **pasting a URL**.
!!! note
Despite the Mealie API being able to accept multiple recipe images for import it is currently impossible to send multiple files in 1 web request via Shortcuts. Instead, the shortcut combines the images into a singular, vertically-concatenated image to send to mealie. This can result in slightly less-accurate text recognition.
Despite the Mealie API being able to accept multiple recipe images for import it is currently impossible to send multiple files in 1 web request via Shortcuts. Instead, the shortcut combines the images into a singular, vertically-concatenated image to send to mealie. This can result in slightly less-accurate text recognition.

View File

@@ -33,7 +33,7 @@
6. Click the Edit button/icon again
7. Scroll to the ingredients and you should see new fields for Amount, Unit, Food, and Note. The Note in particular will contain the original text of the Recipe.
8. Click `Parse` and you will be taken to the ingredient parsing page.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`, or the `OpenAI Parser` if you've [enabled OpenAI support](./installation/backend-config.md#openai).
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`, or the `OpenAI Parser` if you've [enabled AI support](./installation/ai-providers.md).
10. Click `Parse All`, and your ingredients should be separated out into Units and Foods based on your seeding in Step 1 above.
11. For ingredients where the Unit or Food was not found, you can click a button to accept an automatically suggested Food to add to the database. Or, manually enter the Unit/Food and hit `Enter` (or click `Create`) to add it to the database
12. When done, click `Save All` and you will be taken back to the recipe. Now the Unit and Food fields of the recipe should be filled out.

View File

@@ -11,7 +11,7 @@ Mealie offers several ways to create recipes:
- **Recipe HTML or JSON:** Copy/paste structured HTML or JSON and Mealie can import it.
- **Manual Editor:** Create recipes from scratch using the integrated editor.
Mealie's [AI integration](./installation/open-ai.md) greatly expands the ways you can create recipes:
Mealie's [AI integration](./installation/ai-providers.md) greatly expands the ways you can create recipes:
- **Image Import:** Upload an image of a written or typed recipe and Mealie will use OCR and AI to import it.
- **Video URL Import:** Provide a video URL (e.g., YouTube) and Mealie will transcribe the audio and turn it into a recipe.

View File

@@ -0,0 +1,29 @@
# AI Integration
:octicons-tag-24: v1.7.0
Mealie's AI integration enables several features and enhancements throughout the application. To enable AI features, you must have access to an AI provider (such as OpenAI). Mealie works with any OpenAI-compatible API.
## Configuration
To set up AI providers, visit your group settings.
[Group Settings Demo](https://demo.mealie.io/group){ .md-button .md-button--primary }
- To enable AI features at all, you *must* set a default provider (e.g. `gpt-5`)
- To enable image recognition features, such as creating a recipe from an image, configure a provider capable of image recognition (e.g. `gpt-5`)
- To enable audio transcription features, such as importing a recipe from a video, configure a provider capable of audio transcriptions (e.g. `whisper-1`)
For most users, choosing an OpenAI model (such as `gpt-5`) and supplying the OpenAI API key is all you need to do. Note that while OpenAI has a free tier, it's not sufficiently capable for Mealie (or most other production use cases). For more information, check out [OpenAI's rate limits](https://platform.openai.com/docs/guides/rate-limits). If you deposit $5 into your OpenAI account, you will be permanently bumped up to Tier 1, which is sufficient for Mealie. Cost per-request is dependant on many factors, but Mealie tries to keep token counts conservative.
If you have another provider you'd like to use, such as Azure, you can configure Mealie to use that instead as long as it has an OpenAI-compatible API. For instance, a common self-hosted alternative to OpenAI is [Ollama](https://ollama.com/). To use Ollama with Mealie, set your `base_url` to `http://localhost:11434/v1` (where `http://localhost:11434` is wherever you're hosting Ollama, and `/v1` enables the OpenAI-compatible endpoints). Note that you *must* provide an API key, even though it is ultimately ignored by Ollama.
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).
## 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)
- When importing a recipe via URL, if the default recipe scraper is unable to read the recipe data from a webpage, the webpage contents will be parsed by OpenAI (:octicons-tag-24: v1.9.0)
- You can import an image of a written recipe, which is sent to OpenAI and imported into Mealie. The recipe can be hand-written or typed, as long as the text is in the picture. You can also optionally have OpenAI translate the recipe into your own language (:octicons-tag-24: v1.12.0)
- You can import a recipe via a video URL (e.g., a YouTube link). The video is transcribed AI, and the transcription is parsed into a recipe (:octicons-tag-24: v3.13.0)

View File

@@ -29,6 +29,7 @@
| --------------------------- | :-----: | ----------------------------------------------------------------------------------- |
| SECURITY_MAX_LOGIN_ATTEMPTS | 5 | Maximum times a user can provide an invalid password before their account is locked |
| SECURITY_USER_LOCKOUT_TIME | 24 | Time in hours for how long a users account is locked |
| ALLOWED_IFRAME_HOSTS | `""` | Comma-separated extra hostnames allowed as `<iframe>` sources in recipe content. Extends the built-in list of trusted video providers (YouTube, Vimeo). Subdomains are included automatically. Only `https` sources are permitted. Adding hosts here opts into rendering embeds from those origins to all viewers, including the public, so add only origins you trust. |
### Database
@@ -120,22 +121,10 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md)
:octicons-tag-24: v1.7.0
Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md).
For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values as JSON encoded strings (e.g. `OPENAI_CUSTOM_PARAMS='{"k1": "v1", "k2": "v2"}'`)
Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./ai-providers.md).
| Variables | Default | Description |
|-------------------------------------------------------------------------|:-----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| OPENAI_BASE_URL<super>[&dagger;][secrets]</super> | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
| OPENAI_API_KEY<super>[&dagger;][secrets]</super> | None | Your OpenAI API Key. Enables OpenAI-related features |
| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
| OPENAI_AUDIO_MODEL <br/> :octicons-tag-24: v3.13.0 | whisper-1 | Which OpenAI model to use for audio transcriptions, if enabled. If you're not sure, leave this empty |
| OPENAI_CUSTOM_HEADERS <br/> :octicons-tag-24: v2.0.0 | None | Custom HTTP headers to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
| OPENAI_CUSTOM_PARAMS <br/> :octicons-tag-24: v2.0.0 | None | Custom HTTP query params to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
| OPENAI_ENABLE_IMAGE_SERVICES <br/> :octicons-tag-24: v1.12.0 | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
| OPENAI_ENABLE_TRANSCRIPTION_SERVICES <br/> :octicons-tag-24: v3.13.0 | True | Whether to enable OpenAI transcription services, such as creating recipes via video URL. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
| OPENAI_REQUEST_TIMEOUT | 300 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
| OPENAI_CUSTOM_PROMPT_DIR <br/> :octicons-tag-24: v3.10.0 | None. | Path to custom prompt files. Only existing files in your custom directory will override the defaults; any missing or empty custom files will automatically fall back to the system defaults. See https://github.com/mealie-recipes/mealie/tree/mealie-next/mealie/services/openai/prompts for expected file names. |
### Theming
@@ -315,7 +304,6 @@ at least these sensitive environment variables when working within shared enviro
- `POSTGRES_PASSWORD`
- `SMTP_PASSWORD`
- `LDAP_QUERY_PASSWORD`
- `OPENAI_API_KEY`
[docker-secrets]: https://docs.docker.com/compose/use-secrets/
[secrets]: #docker-secrets

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.18.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

@@ -1,24 +0,0 @@
# OpenAI Integration
:octicons-tag-24: v1.7.0
Mealie's OpenAI integration enables several features and enhancements throughout the application. To enable OpenAI features, you must have an account with OpenAI and configure Mealie to use the OpenAI API key (for more information, check out the [backend configuration](./backend-config.md#openai)).
## Configuration
For most users, supplying the OpenAI API key is all you need to do; you will use the regular OpenAI service with the default language model. Note that while OpenAI has a free tier, it's not sufficiently capable for Mealie (or most other production use cases). For more information, check out [OpenAI's rate limits](https://platform.openai.com/docs/guides/rate-limits). If you deposit $5 into your OpenAI account, you will be permanently bumped up to Tier 1, which is sufficient for Mealie. Cost per-request is dependant on many factors, but Mealie tries to keep token counts conservative.
Alternatively, if you have another service you'd like to use in-place of OpenAI, you can configure Mealie to use that instead, as long as it has an OpenAI-compatible API. For instance, a common self-hosted alternative to OpenAI is [Ollama](https://ollama.com/). To use Ollama with Mealie, change your `OPENAI_BASE_URL` to `http://localhost:11434/v1` (where `http://localhost:11434` is wherever you're hosting Ollama, and `/v1` enables the OpenAI-compatible endpoints). Note that you *must* provide an API key, even though it is ultimately ignored by Ollama.
If you wish to disable image recognition features (to save costs, or because your custom model doesn't support them) you can set `OPENAI_ENABLE_IMAGE_SERVICES` to `False`.
If you wish to disable transcription features (to save costs, or because your custom model doesn't support them) you can set `OPENAI_ENABLE_TRANSCRIPTION_SERVICES` to `False`.
For more information on what configuration options are available, check out the [backend configuration](./backend-config.md#openai).
## OpenAI 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)
- When importing a recipe via URL, if the default recipe scraper is unable to read the recipe data from a webpage, the webpage contents will be parsed by OpenAI (:octicons-tag-24: v1.9.0)
- You can import an image of a written recipe, which is sent to OpenAI and imported into Mealie. The recipe can be hand-written or typed, as long as the text is in the picture. You can also optionally have OpenAI translate the recipe into your own language (:octicons-tag-24: v1.12.0)
- You can import a recipe via a video URL (e.g., a YouTube link). The video is transcribed using OpenAI's Whisper model, and the transcription is parsed into a recipe (:octicons-tag-24: v3.13.0)

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

View File

@@ -76,7 +76,7 @@ nav:
- Backend Configuration: "documentation/getting-started/installation/backend-config.md"
- Security: "documentation/getting-started/installation/security.md"
- Logs: "documentation/getting-started/installation/logs.md"
- OpenAI: "documentation/getting-started/installation/open-ai.md"
- AI Providers: "documentation/getting-started/installation/ai-providers.md"
- Usage:
- Backup and Restoring: "documentation/getting-started/usage/backups-and-restoring.md"
- Permissions and Public Access: "documentation/getting-started/usage/permissions-and-public-access.md"

View File

@@ -0,0 +1,63 @@
<template>
<div>
<p>AI providers can now be configured directly in Mealie, without managing environment variables or secrets.</p>
<div class="mb-2">
AI providers enable features such as:
<ul class="ml-6">
<li>Creating recipes from images</li>
<li>Importing recipes from videos (YouTube, TikTok, etc.)</li>
<li>Enhanced ingredient parsing</li>
<li>And more!</li>
</ul>
</div>
<hr class="mt-2 mb-4">
<p>
<span v-if="group?.aiProviderSettings?.aiEnabled">
Your group already has AI providers configured.
</span>
<span v-else>
Your group does not currently have any AI providers configured.
</span>
<span v-if="user?.canManage">
You can manage them here:
<br>
<v-btn class="mt-2" color="primary" to="/group">
{{ $t("profile.group-settings") }}
</v-btn>
</span>
<span v-else-if="!group?.aiProviderSettings?.aiEnabled">
Contact a group manager or server admin to set up AI providers for your group.
</span>
</p>
<div v-if="user?.admin">
<br>
<p>
As an admin, you can configure AI providers for any group. Unlike the old environment variable approach, providers are configured per-group:
<br>
<v-btn class="mt-2" color="primary" to="/admin/manage/groups">
{{ $t("group.admin-group-management") }}
</v-btn>
</p>
</div>
</div>
</template>
<script setup lang="ts">
import { useGroupSelf } from "~/composables/use-groups";
import type { AnnouncementMeta } from "~/composables/use-announcements";
const { user } = useMealieAuth();
const { group } = useGroupSelf();
</script>
<script lang="ts">
export const meta: AnnouncementMeta = {
title: "Improved AI Provider Configuration",
};
</script>
<style scoped lang="css">
p {
padding-bottom: 8px;
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<div>
<p>
To harden Mealie against malicious content, <code>&lt;iframe&gt;</code> embeds in recipe
instructions, notes, and descriptions are now restricted to a trusted set of hosts.
</p>
<div class="mb-2">
By default, embeds are allowed only from well-known video providers:
<ul class="ml-6">
<li>YouTube</li>
<li>Vimeo</li>
</ul>
</div>
<p>
Existing recipes that embed content from <strong>other</strong> hosts will no longer render
those embeds. The rest of the recipe is unaffected.
</p>
<div v-if="user?.admin">
<hr class="mt-2 mb-4">
<p>
As an admin, you can allow additional hosts with the <code>ALLOWED_IFRAME_HOSTS</code>
environment variable (comma-separated). It extends the built-in defaults, and only
<code>https</code> sources are permitted. See the configuration docs for details:
<br>
<v-btn
class="mt-2"
color="primary"
href="https://docs.mealie.io/documentation/getting-started/installation/backend-config/"
target="_blank"
>
Backend Configuration
</v-btn>
</p>
</div>
</div>
</template>
<script setup lang="ts">
import type { AnnouncementMeta } from "~/composables/use-announcements";
const { user } = useMealieAuth();
</script>
<script lang="ts">
export const meta: AnnouncementMeta = {
title: "Recipe embeds restricted to trusted hosts",
};
</script>
<style scoped lang="css">
p {
padding-bottom: 8px;
}
</style>

View File

@@ -0,0 +1,204 @@
<template>
<BaseDialog
v-model="dialog"
:title="isEdit ? $t('group.ai-provider-settings.edit-provider') : $t('group.ai-provider-settings.create-provider')"
:icon="$globals.icons.robot"
:loading="loading"
can-submit
:submit-icon="isEdit ? $globals.icons.save : $globals.icons.createAlt"
:submit-text="isEdit ? $t('general.update') : $t('general.create')"
:submit-disabled="submitDisabled"
@submit="handleSubmit"
@close="resetForm"
>
<v-card-text v-if="init" style="max-height: 70vh; overflow-y: auto;">
<v-form ref="form" v-no-autofill>
<v-text-field
v-model="formData.name"
:label="$t('group.ai-provider-settings.provider-name')"
:rules="[validators.required]"
density="compact"
variant="outlined"
class="mb-4"
/>
<v-text-field
v-model="formData.model"
:label="$t('group.ai-provider-settings.model')"
:hint="$t('group.ai-provider-settings.model-description')"
:rules="[validators.required]"
density="compact"
variant="outlined"
class="mb-4"
/>
<v-text-field
v-model="formData.apiKey"
:label="$t('group.ai-provider-settings.api-key')"
:hint="$t(
isEdit
? 'group.ai-provider-settings.api-key-description-edit'
: 'group.ai-provider-settings.api-key-description-create',
)"
:persistent-hint="isEdit"
:rules="isEdit ? [] : [validators.required]"
density="compact"
variant="outlined"
type="password"
class="mb-4"
/>
<v-text-field
v-model="formData.baseUrl"
:label="$t('group.ai-provider-settings.base-url')"
:hint="$t('group.ai-provider-settings.base-url-description')"
density="compact"
variant="outlined"
class="mb-4"
/>
<v-number-input
v-model.number="formData.timeout"
:label="$t('group.ai-provider-settings.request-timeout-seconds')"
type="number"
:min="0"
hide-details
control-variant="stacked"
density="compact"
variant="outlined"
class="mb-4"
/>
<v-expansion-panels v-model="advancedPanel" variant="accordion">
<v-expansion-panel>
<v-expansion-panel-title class="text-subtitle-2" expand-icon="$expand" collapse-icon="$expand">
{{ $t('search.advanced') }}
</v-expansion-panel-title>
<v-expansion-panel-text class="px-0">
<div class="mb-2 text-subtitle-2">
{{ $t('group.ai-provider-settings.request-headers') }}
</div>
<BaseKeyValueEditor
v-model="formData.requestHeaders"
class="mb-4"
/>
<v-divider class="mb-4" />
<div class="mb-2 text-subtitle-2">
{{ $t('group.ai-provider-settings.request-params') }}
</div>
<BaseKeyValueEditor
v-model="formData.requestParams"
/>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-form>
</v-card-text>
<AppLoader v-else waiting-text="" />
</BaseDialog>
</template>
<script setup lang="ts">
import { useAIProviders } from "~/composables/use-ai-providers";
import { validators } from "~/composables/use-validators";
import type { AIProviderCreate, AIProviderUpdate } from "~/lib/api/types/group";
const props = withDefaults(defineProps<{
providerId?: string;
}>(), {
providerId: undefined,
});
const emit = defineEmits<{
(e: "create", data: AIProviderCreate): void;
(e: "update", id: string, data: AIProviderUpdate): void;
}>();
const dialog = defineModel<boolean>({ default: false });
const { $globals } = useNuxtApp();
const { loading, getOne } = useAIProviders();
const init = ref(false);
const form = ref();
const advancedPanel = ref<number | undefined>(undefined);
const isEdit = computed(() => !!props.providerId);
const defaultForm = () => ({
name: "",
model: "",
apiKey: "",
baseUrl: "",
timeout: 300,
requestHeaders: {} as Record<string, string>,
requestParams: {} as Record<string, string>,
});
const formData = reactive(defaultForm());
const submitDisabled = computed(() => {
return !formData.name?.trim() || !formData.model?.trim() || (!isEdit.value && !formData.apiKey?.trim());
});
// Fetch existing provider when editing; reset form for create mode
watch(
() => [dialog.value, props.providerId] as const,
async ([open, id]) => {
if (!open) return;
if (!id) {
// Create mode — just show the empty form
resetForm();
init.value = true;
return;
}
init.value = false;
const { data } = await getOne(id);
init.value = true;
if (data) {
formData.name = data.name;
formData.model = data.model;
formData.apiKey = "";
formData.baseUrl = data.baseUrl ?? "";
formData.timeout = data.timeout ?? 300;
formData.requestHeaders = { ...(data.requestHeaders ?? {}) };
formData.requestParams = { ...(data.requestParams ?? {}) };
}
},
{ immediate: true },
);
function handleSubmit() {
// Required field guard (button is also disabled, but keep as a safeguard)
if (!formData.name?.trim() || !formData.model?.trim()) return;
if (!isEdit.value && !formData.apiKey?.trim()) return;
if (isEdit.value && props.providerId) {
const payload: AIProviderUpdate & { apiKey?: string } = {
name: formData.name,
model: formData.model,
baseUrl: formData.baseUrl || null,
timeout: formData.timeout,
requestHeaders: Object.keys(formData.requestHeaders).length ? formData.requestHeaders : undefined,
requestParams: Object.keys(formData.requestParams).length ? formData.requestParams : undefined,
};
if (formData.apiKey) {
payload.apiKey = formData.apiKey;
}
emit("update", props.providerId, payload);
}
else {
const createPayload = {
name: formData.name,
model: formData.model,
apiKey: formData.apiKey,
baseUrl: formData.baseUrl || null,
timeout: formData.timeout,
requestHeaders: Object.keys(formData.requestHeaders).length ? formData.requestHeaders : undefined,
requestParams: Object.keys(formData.requestParams).length ? formData.requestParams : undefined,
};
emit("create", createPayload as AIProviderCreate);
}
}
function resetForm() {
Object.assign(formData, defaultForm());
form.value?.reset();
advancedPanel.value = undefined;
}
</script>

View File

@@ -0,0 +1,170 @@
<template>
<div v-if="providerSettings">
<BaseCardSectionTitle v-if="!hideHeader" :title="$t('group.ai-provider-settings.ai-provider-settings')">
<template v-if="noDefaultProviderWarning" #append-title>
<v-tooltip location="bottom" color="warning">
<template #activator="{ props: tooltipProps }">
<v-icon v-bind="tooltipProps" size="small" color="warning" class="ms-2">
{{ $globals.icons.alert }}
</v-icon>
</template>
<span>{{ $t('group.ai-provider-settings.no-default-provider-warning') }}</span>
</v-tooltip>
</template>
</BaseCardSectionTitle>
<v-card-text v-if="!hideHeader" class="pt-0 pb-10 px-0">
{{ $t("group.ai-provider-settings.ai-provider-settings-description") }}
</v-card-text>
<v-row class="mb-4">
<v-col cols="12">
<v-autocomplete
v-model="local.defaultProviderId"
:label="$t('group.ai-provider-settings.default-provider')"
:items="local.providers"
item-title="name"
item-value="id"
clearable
hide-details
density="compact"
variant="outlined"
/>
<v-card-subtitle class="mt-1">
{{ $t("group.ai-provider-settings.default-provider-description") }}
</v-card-subtitle>
</v-col>
<v-col cols="12">
<v-autocomplete
v-model="local.audioProviderId"
:label="$t('group.ai-provider-settings.audio-provider')"
:items="local.providers"
item-title="name"
item-value="id"
clearable
hide-details
density="compact"
variant="outlined"
/>
<v-card-subtitle class="mt-1">
{{ $t("group.ai-provider-settings.audio-provider-description") }}
</v-card-subtitle>
</v-col>
<v-col cols="12">
<v-autocomplete
v-model="local.imageProviderId"
:label="$t('group.ai-provider-settings.image-provider')"
:items="local.providers"
item-title="name"
item-value="id"
clearable
hide-details
density="compact"
variant="outlined"
/>
<v-card-subtitle class="mt-1">
{{ $t("group.ai-provider-settings.image-provider-description") }}
</v-card-subtitle>
</v-col>
</v-row>
<GroupAIProviderDialog
v-model="dialogOpen"
:provider-id="editingProviderId ?? undefined"
@create="(data) => $emit('create', data)"
@update="(id, data) => $emit('update', id, data)"
/>
<BaseCardSectionTitle
:title="$t('group.ai-provider-settings.providers')"
size="medium"
class="pt-2"
>
<template #append-title>
<BaseButton
:text="$t('group.ai-provider-settings.create-provider')"
class="ms-auto my-2"
create
small
@click="openCreate"
/>
</template>
</BaseCardSectionTitle>
<v-card
v-for="provider in local.providers"
:key="provider.id"
variant="tonal"
class="pa-0 mb-4"
>
<v-row no-gutters>
<v-col :cols="10">
<v-card-text>
{{ provider.name }}
</v-card-text>
</v-col>
<v-col :cols="2">
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.edit,
text: $t('general.edit'),
event: 'edit',
},
{
icon: $globals.icons.delete,
text: $t('general.delete'),
event: 'delete',
},
]"
@edit="openEdit(provider.id)"
@delete="$emit('delete', provider.id)"
/>
</v-col>
</v-row>
</v-card>
</div>
</template>
<script setup lang="ts">
import type { AIProviderCreate, AIProviderUpdate } from "~/lib/api/types/group";
import type { AIProviderSettingsOut } from "~/lib/api/types/user";
const providerSettings = defineModel<AIProviderSettingsOut>({ required: true });
const props = withDefaults(defineProps<{
hideHeader?: boolean;
}>(), {
hideHeader: false,
});
const { hideHeader } = toRefs(props);
const local = reactive({ ...providerSettings.value });
watch(local, (newVal) => { providerSettings.value = { ...newVal }; });
// Sync back when the parent refreshes after create/update/delete
watch(providerSettings, (newVal) => { if (newVal) Object.assign(local, newVal); });
const noDefaultProviderWarning = computed(
() => local.providers.length > 0 && !local.defaultProviderId,
);
defineEmits<{
(e: "create", data: AIProviderCreate): void;
(e: "update", id: string, data: AIProviderUpdate): void;
(e: "delete", id: string): void;
}>();
const dialogOpen = ref(false);
const editingProviderId = ref<string | null>(null);
function openCreate() {
editingProviderId.value = null;
dialogOpen.value = true;
}
function openEdit(id: string) {
editingProviderId.value = id;
dialogOpen.value = true;
}
</script>

View File

@@ -59,9 +59,10 @@
>
<v-card-text>
{{ $t("general.confirm-delete-generic") }}
<p v-if="deleteTarget" class="mt-4 ml-4">
<p v-if="deleteTarget" class="mt-4 mb-0 font-weight-bold">
{{ deleteTarget.name || deleteTarget.title || deleteTarget.id }}
</p>
<slot name="delete-dialog-bottom" />
</v-card-text>
</BaseDialog>
@@ -88,6 +89,7 @@
</template>
</v-virtual-scroll>
</v-card>
<slot name="delete-dialog-bottom" />
</v-card-text>
</BaseDialog>
@@ -151,7 +153,7 @@ const createDialog = defineModel("createDialog", { type: Boolean, default: false
const editForm = defineModel<{ items: AutoFormItems; data: Record<string, any> }>("editForm", { required: true });
const editDialog = defineModel("editDialog", { type: Boolean, default: false });
defineProps({
const props = defineProps({
icon: {
type: String,
required: true,
@@ -185,6 +187,10 @@ defineProps({
type: String,
default: "name",
},
onDeleteDialogOpen: {
type: Function as PropType<(items: any[]) => Promise<void>>,
default: null,
},
});
// ============================================================
@@ -212,8 +218,11 @@ const editEventHandler = (item: any) => {
const deleteTarget = ref<any>(null);
const deleteDialog = ref(false);
function deleteEventHandler(item: any) {
async function deleteEventHandler(item: any) {
deleteTarget.value = item;
if (props.onDeleteDialogOpen) {
await props.onDeleteDialogOpen([item]);
}
deleteDialog.value = true;
}
@@ -222,8 +231,11 @@ function deleteEventHandler(item: any) {
const bulkDeleteTarget = ref<Array<any>>([]);
const bulkDeleteDialog = ref(false);
function bulkDeleteEventHandler(items: Array<any>) {
async function bulkDeleteEventHandler(items: Array<any>) {
bulkDeleteTarget.value = items;
if (props.onDeleteDialogOpen) {
await props.onDeleteDialogOpen(items);
}
bulkDeleteDialog.value = true;
console.log("Bulk Delete Event Handler", items);
}

View File

@@ -25,6 +25,7 @@
<script setup lang="ts">
import type { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
import { truncateText as truncatePlainText } from "~/lib/sanitize/text";
export type UrlPrefixParam = "tags" | "categories" | "tools";
@@ -50,10 +51,7 @@ const props = withDefaults(defineProps<Props>(), {
defineEmits(["item-selected"]);
function truncateText(text: string, length = 20, clamp = "...") {
if (!props.truncate) return text;
const node = document.createElement("div");
node.innerHTML = text;
const content = node.textContent || "";
return content.length > length ? content.slice(0, length) + clamp : content;
return truncatePlainText(text, length, clamp);
}
</script>

View File

@@ -200,6 +200,7 @@ import { useUserApi } from "~/composables/api";
import { useIngredientTextParser } from "~/composables/recipes";
import { useFoodData, useFoodStore, useUnitData, useUnitStore } from "~/composables/store";
import { useGlobalI18n } from "~/composables/use-global-i18n";
import { useGroupSelf } from "~/composables/use-groups";
import { alert } from "~/composables/use-toast";
import { useParsingPreferences } from "~/composables/use-users/preferences";
@@ -215,7 +216,7 @@ const emit = defineEmits<{
(e: "save", value: NoUndefinedField<RecipeIngredient[]>): void;
}>();
const { $appInfo } = useNuxtApp();
const { group } = useGroupSelf();
const i18n = useGlobalI18n();
const api = useUserApi();
const drag = ref(false);
@@ -240,7 +241,7 @@ const availableParsers = computed(() => {
{
text: i18n.t("recipe.parser.openai-parser"),
value: "openai",
hide: !$appInfo.enableOpenai,
hide: !group.value?.aiProviderSettings?.aiEnabled,
},
];
});

View File

@@ -10,24 +10,9 @@
}"
>
<v-row
v-touch="{
move: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
swipeInfo.touchendX = screenX;
swipeInfo.touchendY = screenY;
},
start: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
swipeInfo.touchstartX = screenX;
swipeInfo.touchstartY = screenY;
},
end: () => {
if (swiping < SWIPE_THRESHOLD) {
swipeInfo = {};
return;
}
swipeInfo = {};
toggleChecked();
},
}"
ref="swipeRowRef"
v-touch="{ move: onSwipeMove, start: onSwipeStart, end: onSwipeEnd }"
style="touch-action: pan-y;"
no-gutters
class="flex-nowrap align-center"
>
@@ -214,9 +199,23 @@ const emit = defineEmits<{
}>();
const SWIPE_THRESHOLD = 50;
const SCROLL_THRESHOLD = 50;
const { isRtl } = useRtl();
const swipeRowRef = ref<InstanceType<typeof import("vuetify/components").VRow> | null>(null);
onMounted(() => {
const el = swipeRowRef.value?.$el as HTMLElement | undefined;
if (!el) return;
el.addEventListener(
"touchmove",
(e: TouchEvent) => {
if (swipeInfo.value.gesture === "swipe") {
e.preventDefault();
}
},
{ passive: false },
);
});
const i18n = useI18n();
const displayRecipeRefs = ref(false);
const itemLabelCols = computed<string>(() => (model.value?.checked ? "auto" : "6"));
@@ -267,22 +266,66 @@ function save() {
edit.value = false;
}
const swipeInfo: Ref<{ touchstartX?: number; touchendX?: number; touchstartY?: number; touchendY?: number }> = ref({});
const swiping = computed(() => {
const { touchstartX, touchendX, touchstartY, touchendY } = swipeInfo.value ?? {};
if (touchstartX === undefined || touchendX === undefined) {
return 0;
}
const deltaX = isRtl.value ? touchstartX - touchendX : touchendX - touchstartX;
type SwipeGesture = null | "scroll" | "swipe";
// If there's significant vertical movement, treat as a scroll gesture and ignore
if (touchstartY !== undefined && touchendY !== undefined) {
const deltaY = Math.abs(touchendY - touchstartY);
if (deltaY > SCROLL_THRESHOLD) {
return 0;
const swipeInfo = ref({
touchstartX: 0,
touchstartY: 0,
touchendX: 0,
touchendY: 0,
gesture: null as SwipeGesture,
});
function getSwipePoint(e: any) {
const touch = e?.touches?.[0] ?? e?.changedTouches?.[0] ?? e;
return { x: touch?.clientX ?? 0, y: touch?.clientY ?? 0 };
}
function resetSwipe() {
swipeInfo.value = { touchstartX: 0, touchstartY: 0, touchendX: 0, touchendY: 0, gesture: null };
}
function onSwipeStart(payload: any) {
const { x, y } = getSwipePoint(payload.originalEvent);
swipeInfo.value = { touchstartX: x, touchstartY: y, touchendX: x, touchendY: y, gesture: null };
}
function onSwipeMove(payload: any) {
const { x, y } = getSwipePoint(payload.originalEvent);
swipeInfo.value.touchendX = x;
swipeInfo.value.touchendY = y;
if (!swipeInfo.value.gesture) {
const deltaX = Math.abs(x - swipeInfo.value.touchstartX);
const deltaY = Math.abs(y - swipeInfo.value.touchstartY);
if (deltaY > 8 && deltaY > deltaX) {
swipeInfo.value.gesture = "scroll";
}
else if (deltaX > 8 && deltaX > deltaY) {
swipeInfo.value.gesture = "swipe";
}
else if (deltaX > 8 || deltaY > 8) {
// Diagonal / ambiguous — default to scroll
swipeInfo.value.gesture = "scroll";
}
}
return Math.min(Math.max(0, deltaX), 100);
}
function onSwipeEnd() {
if (swipeInfo.value.gesture === "swipe" && swiping.value >= SWIPE_THRESHOLD) {
toggleChecked();
}
resetSwipe();
}
const swiping = computed(() => {
if (swipeInfo.value.gesture !== "swipe") {
return 0;
}
const deltaX = isRtl.value
? swipeInfo.value.touchstartX - swipeInfo.value.touchendX
: swipeInfo.value.touchendX - swipeInfo.value.touchstartX;
return Math.max(0, Math.min(deltaX, 100));
});
const recipeList = computed<RecipeSummary[]>(() => {

View File

@@ -4,7 +4,7 @@
style="border-color: lightgrey;"
:to="link.to"
height="100%"
class="d-flex flex-column mt-4"
class="d-flex flex-column mt-4 pa-2"
>
<div
v-if="$vuetify.display.smAndDown"

View File

@@ -96,15 +96,17 @@
<script setup lang="ts">
import { useLoggedInState } from "~/composables/use-logged-in-state";
import type { SideBarLink } from "~/types/application-types";
import { useGroupSelf } from "~/composables/use-groups";
import { useCookbookPreferences } from "~/composables/use-users/preferences";
import { useCookbookStore, usePublicCookbookStore } from "~/composables/store/use-cookbook-store";
import type { ReadCookBook } from "~/lib/api/types/cookbook";
const i18n = useI18n();
const { $appInfo, $globals } = useNuxtApp();
const { $globals } = useNuxtApp();
const display = useDisplay();
const auth = useMealieAuth();
const { isOwnGroup } = useLoggedInState();
const { group } = useGroupSelf();
const route = useRoute();
const groupSlug = computed(() => route.params.groupSlug as string || auth.user.value?.groupSlug || "");
@@ -131,7 +133,7 @@ const cookbooks = computed(() => {
return [];
});
const showImageImport = computed(() => $appInfo.enableOpenaiImageServices);
const showImageImport = computed(() => group.value?.aiProviderSettings?.imageProviderEnabled);
const sidebar = ref<boolean>(false);
onMounted(() => {

View File

@@ -7,7 +7,8 @@
'mt-8': section,
}"
>
<v-card-title class="text-h5 pl-0 py-0" style="font-weight: normal;">
<v-card-title :class="`text-title-${size} pl-0 py-0 d-flex align-center`" style="font-weight: normal;">
<slot name="prepend-title" />
<v-icon
v-if="icon"
size="small"
@@ -16,6 +17,7 @@
{{ icon }}
</v-icon>
{{ title }}
<slot name="append-title" />
</v-card-title>
<v-card-text
v-if="$slots.default"
@@ -30,11 +32,17 @@
</template>
<script setup lang="ts">
type Size = "large" | "medium" | "small";
defineProps({
title: {
type: String,
required: true,
},
size: {
type: String as () => Size,
default: "large",
},
icon: {
type: String,
default: "",

View File

@@ -0,0 +1,127 @@
<template>
<div>
<div
v-for="(value, key) in (modelValue ?? {})"
:key="key"
class="d-flex align-center mb-2 gap-2"
>
<v-text-field
:model-value="key"
:label="resolvedKeyLabel"
density="compact"
variant="outlined"
hide-details
readonly
class="me-3 flex-grow-1"
/>
<v-text-field
:model-value="value"
:label="resolvedValueLabel"
density="compact"
variant="outlined"
hide-details
class="ms-3 flex-grow-1"
@update:model-value="updateValue(key, $event)"
/>
<v-btn
icon
variant="text"
color="error"
size="small"
@click="removeEntry(key)"
>
<v-icon>{{ $globals.icons.delete }}</v-icon>
</v-btn>
</div>
<div class="d-flex align-center mt-2 gap-2" @focusout="onNewEntryFocusOut">
<v-text-field
v-model="newKey"
:label="resolvedKeyLabel"
density="compact"
variant="outlined"
hide-details
class="me-3 flex-grow-1"
@keydown.enter.prevent="addEntry"
/>
<v-text-field
v-model="newValue"
:label="resolvedValueLabel"
density="compact"
variant="outlined"
hide-details
class="ms-3 flex-grow-1"
@keydown.enter.prevent="addEntry"
/>
<v-btn
icon
variant="text"
color="primary"
size="small"
:disabled="!newKey?.trim()"
@click="addEntry"
>
<v-icon>{{ $globals.icons.createAlt }}</v-icon>
</v-btn>
</div>
</div>
</template>
<script setup lang="ts">
import { useGlobalI18n } from "~/composables/use-global-i18n";
const i18n = useGlobalI18n();
const props = defineProps<{
modelValue?: Record<string, string> | null;
keyLabel?: string;
valueLabel?: string;
}>();
const emit = defineEmits<{
(e: "update:modelValue", value: Record<string, string>): void;
}>();
const { $globals } = useNuxtApp();
const resolvedKeyLabel = computed(() => props.keyLabel ?? i18n.t("general.key"));
const resolvedValueLabel = computed(() => props.valueLabel ?? i18n.t("general.value"));
const newKey = ref("");
const newValue = ref("");
function current(): Record<string, string> {
return { ...(props.modelValue ?? {}) };
}
function addEntry() {
const key = newKey.value?.trim();
if (!key) return;
const updated = current();
updated[key] = newValue.value;
emit("update:modelValue", updated);
newKey.value = "";
newValue.value = "";
}
function onNewEntryFocusOut(e: FocusEvent) {
const relatedTarget = e.relatedTarget as HTMLElement | null;
const currentTarget = e.currentTarget as HTMLElement;
if (!relatedTarget || !currentTarget.contains(relatedTarget)) {
addEntry();
}
}
function updateValue(key: string, value: string) {
const updated = current();
updated[key] = value;
emit("update:modelValue", updated);
}
function removeEntry(key: string) {
const updated = current();
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete updated[key];
emit("update:modelValue", updated);
}
</script>

View File

@@ -4,12 +4,8 @@
</template>
<script setup lang="ts">
import DOMPurify from "isomorphic-dompurify";
import { marked } from "marked";
enum DOMPurifyHook {
UponSanitizeAttribute = "uponSanitizeAttribute",
}
import { sanitizeMarkdownHtml } from "~/lib/sanitize/markdown";
const props = defineProps({
source: {
@@ -18,48 +14,11 @@ const props = defineProps({
},
});
const ALLOWED_STYLE_TAGS = [
"background-color", "color", "font-style", "font-weight", "text-decoration", "text-align",
];
function sanitizeMarkdown(rawHtml: string | null | undefined): string {
if (!rawHtml) {
return "";
}
DOMPurify.addHook(DOMPurifyHook.UponSanitizeAttribute, (node, data) => {
if (data.attrName === "style") {
const styles = data.attrValue.split(";").filter((style) => {
const [property] = style.split(":");
return ALLOWED_STYLE_TAGS.includes(property.trim().toLowerCase());
});
data.attrValue = styles.join(";");
}
});
const sanitized = DOMPurify.sanitize(rawHtml, {
ALLOWED_TAGS: [
"strong", "em", "b", "i", "u", "p", "code", "pre", "samp", "kbd", "var", "sub", "sup", "dfn", "cite",
"small", "address", "hr", "br", "id", "div", "span", "h1", "h2", "h3", "h4", "h5", "h6",
"ul", "ol", "li", "dl", "dt", "dd", "abbr", "a", "img", "blockquote", "iframe",
"del", "ins", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "colgroup",
],
ALLOWED_ATTR: [
"href", "src", "alt", "height", "width", "class", "allow", "title", "allowfullscreen", "frameborder",
"scrolling", "cite", "datetime", "name", "abbr", "target", "border", "start", "style",
],
});
Object.values(DOMPurifyHook).forEach((hook) => {
DOMPurify.removeHook(hook);
});
return sanitized;
}
const { $appInfo } = useNuxtApp();
const value = computed(() => {
const rawHtml = marked.parse(props.source || "", { async: false, breaks: true });
return sanitizeMarkdown(rawHtml);
return sanitizeMarkdownHtml(rawHtml, $appInfo?.allowedIframeHosts ?? []);
});
</script>

View File

@@ -0,0 +1,83 @@
import type { IngredientFood, RecipeSummary, ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/household";
export const MOCK_ITEM: ShoppingListItemOut = {
shoppingListId: "",
id: "",
groupId: "",
householdId: "",
display: "MOCK_ITEM",
updatedAt: "100",
position: 1,
checked: false,
createdAt: "100",
};
export const MOCK_RECIPE: RecipeSummary = {
id: "recipe-id",
name: "Recipe!",
};
export const MOCK_RECIPE2: RecipeSummary = {
...MOCK_RECIPE,
id: undefined,
name: "Recipe 2!",
};
export const MOCK_FOOD: IngredientFood = {
id: "1",
name: "food 1",
};
export const MOCK_FOOD2: IngredientFood = {
id: "2",
name: "food 2",
};
export const MOCK_LABEL: ShoppingListMultiPurposeLabelOut = {
shoppingListId: "",
labelId: "",
id: "",
label: {
name: "MOCK_LABEL",
groupId: "",
id: "",
},
};
export const MOCK_LABEL2: ShoppingListMultiPurposeLabelOut = {
shoppingListId: "",
labelId: "",
id: "",
label: {
name: "MOCK_LABEL2",
groupId: "",
id: "",
},
};
export const MOCK_SHOPPING_LIST: ShoppingListOut = {
groupId: "",
userId: "",
id: "",
householdId: "",
labelSettings: [
MOCK_LABEL,
MOCK_LABEL2,
],
listItems: [
MOCK_ITEM,
],
recipeReferences: [{
id: "",
shoppingListId: "",
recipeId: "",
recipeQuantity: 0,
recipe: MOCK_RECIPE,
}, {
id: "",
shoppingListId: "",
recipeId: "",
recipeQuantity: 0,
recipe: MOCK_RECIPE2,
}],
};

View File

@@ -0,0 +1,89 @@
import * as vueusecore from "@vueuse/core";
import { describe, expect, test, vi } from "vitest";
import type { ShoppingListItemOut } from "~/lib/api/types/household";
import { makeWrapper } from "~/tests/utils";
import { useShoppingListCopy } from "../use-shopping-list-copy";
import { MOCK_ITEM } from "./mocks";
vi.mock("@vueuse/core", { spy: true });
const mockCopy = vi.fn().mockImplementation(args => new Promise(resolve => resolve(args)));
vi.mocked(vueusecore.useClipboard).mockImplementation(() => {
return {
isSupported: computed(() => true),
copied: computed(() => true),
text: computed(() => ""),
copy: mockCopy,
};
});
const wrapper = () => makeWrapper(useShoppingListCopy);
const TEST_HEADER = "SPECIAL HEADER!";
const MOCK_LIST: { [key: string]: ShoppingListItemOut[] } = {
[TEST_HEADER]: [MOCK_ITEM],
[TEST_HEADER + "2"]: [MOCK_ITEM],
};
describe("Shopping list copy composable", () => {
describe("copyListItems", () => {
test("copies markdown lists correctly", () => {
const { copyListItems } = wrapper();
copyListItems(MOCK_LIST, "markdown");
const expected = [
"# SPECIAL HEADER!",
"- [ ] MOCK_ITEM",
"",
"# SPECIAL HEADER!2",
"- [ ] MOCK_ITEM",
].join("\n");
expect(mockCopy).toBeCalledWith(expected);
});
test("copies plain text lists correctly", () => {
const { copyListItems } = wrapper();
copyListItems(MOCK_LIST, "plain");
const expected = [
"[SPECIAL HEADER!]",
"MOCK_ITEM",
"",
"[SPECIAL HEADER!2]",
"MOCK_ITEM",
].join("\n");
expect(mockCopy).toBeCalledWith(expected);
});
});
describe("formatCopiedLabelHeading", () => {
test("copies markdown headers correctly", () => {
const { formatCopiedLabelHeading } = wrapper();
const header = formatCopiedLabelHeading("markdown", TEST_HEADER);
expect(header).toEqual(`# ${TEST_HEADER}`);
});
test("copies plain text headers correctly", () => {
const { formatCopiedLabelHeading } = wrapper();
const header = formatCopiedLabelHeading("plain", TEST_HEADER);
expect(header).toEqual(`[${TEST_HEADER}]`);
});
});
describe("formatCopiedListItem", () => {
test("copies markdown items correctly", () => {
const { formatCopiedListItem } = wrapper();
const header = formatCopiedListItem("markdown", MOCK_ITEM);
expect(header).toEqual(`- [ ] ${MOCK_ITEM.display}`);
});
test("copies plain text items correctly", () => {
const { formatCopiedListItem } = wrapper();
const header = formatCopiedListItem("plain", MOCK_ITEM);
expect(header).toEqual(MOCK_ITEM.display);
});
test("copies items without a display as empty", () => {
const { formatCopiedListItem } = wrapper();
const header = formatCopiedListItem("plain", { ...MOCK_ITEM, display: undefined });
expect(header).toEqual("");
});
});
});

View File

@@ -0,0 +1,175 @@
import { describe, expect, test } from "vitest";
import type { ShoppingListOut } from "~/lib/api/types/household";
import { makeWrapper } from "~/tests/utils";
import { useShoppingListSorting } from "../use-shopping-list-sorting";
import { MOCK_FOOD, MOCK_FOOD2, MOCK_ITEM, MOCK_LABEL, MOCK_LABEL2, MOCK_SHOPPING_LIST } from "./mocks";
const wrapper = () => makeWrapper(() => {
const { t } = useI18n();
return {
t,
...useShoppingListSorting(),
};
});
describe("use-shopping-list-sorting", () => {
describe("sortItems", () => {
const { sortItems } = wrapper();
test("sorts by position first", () => {
const result = sortItems(MOCK_ITEM, { ...MOCK_ITEM, position: 0 });
const result2 = sortItems({ ...MOCK_ITEM, position: 0 }, MOCK_ITEM);
expect(result).toBe(1);
expect(result2).toBe(-1);
});
test("sorts by createdAt next", () => {
const result = sortItems(MOCK_ITEM, { ...MOCK_ITEM, createdAt: "0" });
const result2 = sortItems({ ...MOCK_ITEM, createdAt: "0" }, MOCK_ITEM);
expect(result).toBe(1);
expect(result2).toBe(-1);
});
test("sorts similar items into the same spot", () => {
const result = sortItems(MOCK_ITEM, MOCK_ITEM);
expect(result).toBe(0);
});
test("handles nulls", () => {
const result = sortItems(MOCK_ITEM, { ...MOCK_ITEM, position: undefined });
const result2 = sortItems({ ...MOCK_ITEM, position: undefined }, MOCK_ITEM);
expect(result).toBe(1);
expect(result2).toBe(-1);
});
test("handles nulls", () => {
const result = sortItems(MOCK_ITEM, { ...MOCK_ITEM, createdAt: undefined });
const result2 = sortItems({ ...MOCK_ITEM, createdAt: undefined }, MOCK_ITEM);
expect(result).toBe(1);
expect(result2).toBe(-1);
});
});
describe("sortListItems", () => {
const { sortListItems } = wrapper();
test("sorts by position first", () => {
const sortedList = { ...MOCK_SHOPPING_LIST, listItems: [MOCK_ITEM, { ...MOCK_ITEM, position: 0 }, { ...MOCK_ITEM, createdAt: "0" }] };
sortListItems(sortedList);
expect(sortedList.listItems).toEqual([
{ ...MOCK_ITEM, position: 0 },
{ ...MOCK_ITEM, createdAt: "0" },
MOCK_ITEM,
]);
});
test("handles nulls", () => {
const sortedList = { ...MOCK_SHOPPING_LIST, listItems: undefined };
sortListItems(sortedList);
expect(sortedList.listItems).toEqual(undefined);
});
});
describe("updateItemsByLabel", () => {
const { updateItemsByLabel, t } = wrapper();
test("sorts by group", () => {
const sortedList = {
...MOCK_SHOPPING_LIST, listItems: [
MOCK_ITEM,
{ ...MOCK_ITEM, label: MOCK_LABEL2.label, labelId: "2" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
],
};
const result = updateItemsByLabel(sortedList);
expect(result).toEqual({
[t("shopping-list.no-label")]: [
MOCK_ITEM,
],
[MOCK_LABEL.label.name]: [
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
],
[MOCK_LABEL2.label.name]: [
{ ...MOCK_ITEM, label: MOCK_LABEL2.label, labelId: "2" },
],
});
});
test("ignores checked items", () => {
const sortedList = {
...MOCK_SHOPPING_LIST, listItems: [
MOCK_ITEM,
{ ...MOCK_ITEM, label: MOCK_LABEL2.label, labelId: "2" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1", checked: true },
],
};
const result = updateItemsByLabel(sortedList);
expect(result).toEqual({
[t("shopping-list.no-label")]: [
MOCK_ITEM,
],
[MOCK_LABEL.label.name]: [
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
],
[MOCK_LABEL2.label.name]: [
{ ...MOCK_ITEM, label: MOCK_LABEL2.label, labelId: "2" },
],
});
});
test("returns unordered labels if no ordering is specified", () => {
const sortedList = {
...MOCK_SHOPPING_LIST,
labelSettings: undefined,
listItems: [
MOCK_ITEM,
{ ...MOCK_ITEM, label: MOCK_LABEL2.label, labelId: "2" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1", checked: true },
],
};
const result = updateItemsByLabel(sortedList);
expect(result).toEqual({
[t("shopping-list.no-label")]: [
MOCK_ITEM,
],
[MOCK_LABEL2.label.name]: [
{ ...MOCK_ITEM, label: MOCK_LABEL2.label, labelId: "2" },
],
[MOCK_LABEL.label.name]: [
{ ...MOCK_ITEM, label: MOCK_LABEL.label, labelId: "1" },
],
});
});
});
describe("groupAndSortListItemsByFood", () => {
const { groupAndSortListItemsByFood } = wrapper();
test("sorts by group", () => {
const sortedList = { ...MOCK_SHOPPING_LIST };
groupAndSortListItemsByFood(sortedList);
expect(sortedList.listItems).toEqual(MOCK_SHOPPING_LIST.listItems);
});
test("groups checked items together", () => {
const sortedList: ShoppingListOut = {
...MOCK_SHOPPING_LIST, listItems: [
{ ...MOCK_ITEM, checked: true, food: MOCK_FOOD },
{ ...MOCK_ITEM, checked: true, food: MOCK_FOOD2 },
],
};
groupAndSortListItemsByFood(sortedList);
expect(sortedList.listItems).toEqual([
{ ...MOCK_ITEM, checked: true, food: MOCK_FOOD },
{ ...MOCK_ITEM, checked: true, food: MOCK_FOOD2, position: 1 },
]);
});
test("populates position and created at if not present", () => {
const sortedList: ShoppingListOut = {
...MOCK_SHOPPING_LIST, listItems: [
{ ...MOCK_ITEM, food: MOCK_FOOD, position: undefined },
{ ...MOCK_ITEM, food: MOCK_FOOD2, createdAt: undefined },
],
};
groupAndSortListItemsByFood(sortedList);
expect(sortedList.listItems).toEqual([
{ ...MOCK_ITEM, food: MOCK_FOOD2, createdAt: undefined },
{ ...MOCK_ITEM, food: MOCK_FOOD, position: 1 },
]);
});
test("handles nulls", () => {
const sortedList: ShoppingListOut = { ...MOCK_SHOPPING_LIST, listItems: undefined };
groupAndSortListItemsByFood(sortedList);
expect(sortedList.listItems).toEqual(undefined);
});
});
});

View File

@@ -0,0 +1,63 @@
import { describe, expect, test } from "vitest";
import type { ShoppingListOut } from "~/lib/api/types/household";
import { makeWrapper } from "~/tests/utils";
import { useShoppingListState } from "../use-shopping-list-state";
import { MOCK_ITEM, MOCK_RECIPE, MOCK_RECIPE2, MOCK_SHOPPING_LIST } from "./mocks";
const wrapper = (list: ShoppingListOut = MOCK_SHOPPING_LIST) => makeWrapper(() => {
const { shoppingList, ...state } = useShoppingListState();
shoppingList.value = list;
return {
shoppingList,
...state,
};
});
describe("use-shopping-list-state", () => {
describe("checked items are sorted", () => {
const { sortCheckedItems } = wrapper();
test("by timestamp", () => {
const sorted = sortCheckedItems(MOCK_ITEM, { ...MOCK_ITEM, updatedAt: "200" });
const sorted2 = sortCheckedItems(MOCK_ITEM, { ...MOCK_ITEM, updatedAt: "0" });
expect(sorted).toBe(1);
expect(sorted2).toBe(-1);
});
test("by position if timestamps match", () => {
const sorted = sortCheckedItems(MOCK_ITEM, { ...MOCK_ITEM, position: 2 });
const sorted2 = sortCheckedItems(MOCK_ITEM, { ...MOCK_ITEM, position: 0 });
const sorted3 = sortCheckedItems({ ...MOCK_ITEM, position: undefined }, { ...MOCK_ITEM, position: undefined });
expect(sorted).toBe(1);
expect(sorted2).toBe(-1);
expect(sorted3).toBe(1);
});
});
describe("recipeMap", () => {
test("Updates to match shopping list recipe references", () => {
const { recipeMap } = wrapper();
expect(recipeMap).toEqual(new Map([
[MOCK_RECIPE.id, MOCK_RECIPE],
["", MOCK_RECIPE2],
]));
});
test("handles nulls", () => {
const { recipeMap } = wrapper({ ...MOCK_SHOPPING_LIST, recipeReferences: undefined });
expect(recipeMap).toEqual(new Map([]));
});
});
describe("checked and unchecked items", () => {
test("update appropriately", () => {
const mockCheckedItem = { ...MOCK_ITEM, checked: true };
const { listItems: { checked, unchecked } } = wrapper({
...MOCK_SHOPPING_LIST, listItems: [
MOCK_ITEM,
mockCheckedItem,
],
});
expect(unchecked[0]).toEqual(MOCK_ITEM);
expect(checked[0]).toEqual(mockCheckedItem);
});
});
});

View File

@@ -0,0 +1,55 @@
import { useUserApi } from "~/composables/api";
import type { AIProviderCreate, AIProviderUpdate } from "~/lib/api/types/group";
export function useAIProviders() {
const api = useUserApi();
const loading = ref(false);
async function getOne(id: string) {
loading.value = true;
try {
return await api.aiProviders.getOne(id);
}
finally {
loading.value = false;
}
}
async function createOne(payload: AIProviderCreate) {
loading.value = true;
try {
return await api.aiProviders.createOne(payload);
}
finally {
loading.value = false;
}
}
async function updateOne(id: string, payload: AIProviderUpdate) {
loading.value = true;
try {
return await api.aiProviders.updateOne(id, payload);
}
finally {
loading.value = false;
}
}
async function deleteOne(id: string) {
loading.value = true;
try {
return await api.aiProviders.deleteOne(id);
}
finally {
loading.value = false;
}
}
return {
loading: readonly(loading),
getOne,
createOne,
updateOne,
deleteOne,
};
}

View File

@@ -22,7 +22,10 @@ const allAnnouncements: Announcement[] = Object.entries(_announcementsUnsorted)
.map(([path, mod]) => {
const key = path.split("/").at(-1)!.replace(".vue", "");
const parsed = new Date(key.split("_", 1)[0]!);
const dateParts = key.split("_", 1)[0]!.split("-").map(Number);
const parsed = dateParts.length === 3
? new Date(dateParts[0]!, dateParts[1]! - 1, dateParts[2]!)
: new Date(NaN);
const date = isNaN(parsed.getTime()) ? undefined : parsed;
return {

View File

@@ -1,6 +1,7 @@
import { ref, computed } from "vue";
import type { UserOut } from "~/lib/api/types/user";
import { clearAllStores } from "~/composables/store";
import { getTokenCookieOptions } from "~/composables/use-token-cookie";
interface AuthData {
value: UserOut | null;
@@ -30,10 +31,7 @@ export const useAuthBackend = function (): AuthState {
const runtimeConfig = useRuntimeConfig();
const tokenName = runtimeConfig.public.AUTH_TOKEN;
const tokenCookie = useCookie(tokenName, {
maxAge: $appInfo.tokenTime * 60 * 60,
secure: $appInfo.production && window?.location?.protocol === "https:",
});
const tokenCookie = useCookie(tokenName, getTokenCookieOptions());
function setToken(token: string | null) {
tokenCookie.value = token;

View File

@@ -42,6 +42,25 @@ export const useGroupSelf = function () {
return data || undefined;
},
async updateAIProviderSettings() {
if (!groupSelfRef.value) {
await refreshGroupSelf();
}
if (!groupSelfRef.value?.aiProviderSettings) {
return;
}
const { data } = await api.groups.setAIProviderSettings(groupSelfRef.value.aiProviderSettings);
if (data) {
groupSelfRef.value.aiProviderSettings = data;
}
return data || undefined;
},
async refresh() {
await refreshGroupSelf();
},
};
const group = actions.get();

View File

@@ -3,7 +3,7 @@ export const LOCALES = [
{
name: "繁體中文 (Chinese traditional)",
value: "zh-TW",
progress: 98,
progress: 97,
dir: "ltr",
pluralFoodHandling: "never",
},
@@ -24,21 +24,21 @@ export const LOCALES = [
{
name: "Українська (Ukrainian)",
value: "uk-UA",
progress: 86,
progress: 85,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Türkçe (Turkish)",
value: "tr-TR",
progress: 54,
progress: 53,
dir: "ltr",
pluralFoodHandling: "never",
},
{
name: "Svenska (Swedish)",
value: "sv-SE",
progress: 75,
progress: 76,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -59,14 +59,14 @@ export const LOCALES = [
{
name: "Slovenčina (Slovak)",
value: "sk-SK",
progress: 61,
progress: 62,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Pусский (Russian)",
value: "ru-RU",
progress: 59,
progress: 58,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -80,28 +80,28 @@ export const LOCALES = [
{
name: "Português (Portuguese)",
value: "pt-PT",
progress: 57,
progress: 56,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Português do Brasil (Brazilian Portuguese)",
value: "pt-BR",
progress: 99,
progress: 98,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Polski (Polish)",
value: "pl-PL",
progress: 99,
progress: 98,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Norsk (Norwegian)",
value: "no-NO",
progress: 60,
progress: 59,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -129,7 +129,7 @@ export const LOCALES = [
{
name: "한국어 (Korean)",
value: "ko-KR",
progress: 55,
progress: 54,
dir: "ltr",
pluralFoodHandling: "never",
},
@@ -157,7 +157,7 @@ export const LOCALES = [
{
name: "Magyar (Hungarian)",
value: "hu-HU",
progress: 61,
progress: 62,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -171,7 +171,7 @@ export const LOCALES = [
{
name: "עברית (Hebrew)",
value: "he-IL",
progress: 73,
progress: 72,
dir: "rtl",
pluralFoodHandling: "always",
},
@@ -199,14 +199,14 @@ export const LOCALES = [
{
name: "Belge (Belgian)",
value: "fr-BE",
progress: 72,
progress: 78,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Suomi (Finnish)",
value: "fi-FI",
progress: 99,
progress: 98,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -241,7 +241,7 @@ export const LOCALES = [
{
name: "Ελληνικά (Greek)",
value: "el-GR",
progress: 57,
progress: 58,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -269,28 +269,28 @@ export const LOCALES = [
{
name: "Català (Catalan)",
value: "ca-ES",
progress: 60,
progress: 59,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Български (Bulgarian)",
value: "bg-BG",
progress: 71,
progress: 72,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "العربية (Arabic)",
value: "ar-SA",
progress: 98,
progress: 97,
dir: "rtl",
pluralFoodHandling: "always",
},
{
name: "Afrikaans (Afrikaans)",
value: "af-ZA",
progress: 37,
progress: 36,
dir: "ltr",
pluralFoodHandling: "always",
},

View File

@@ -0,0 +1,9 @@
export function getTokenCookieOptions(): Parameters<typeof useCookie>[1] {
const isSecureConnection = useNuxtApp().$appInfo.production && window?.location?.protocol === "https:";
return {
maxAge: useNuxtApp().$appInfo.tokenTime * 60 * 60,
secure: isSecureConnection,
sameSite: isSecureConnection ? "none" : "lax",
partitioned: isSecureConnection,
};
}

View File

@@ -223,7 +223,9 @@
"show-advanced": "Wys uitgebreide",
"add-field": "Voeg veld by",
"date-created": "Datum Geskep",
"date-updated": "Datum Opgedateer"
"date-updated": "Datum Opgedateer",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Is jy seker jy wil <b>{groupName}<b/> uitvee?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Veranderinge aan hierdie groep sal onmiddellik weerspieël word.",
"group-id-value": "Groep-Id: {0}",
"total-households": "Total Households",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household",
"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\").",
"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"
}
},
"household": {
"household": "Household",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Die kombinasie van die geselekteerde kosse sal die oorspronklike kos en die nuwe kos kombineer. Die oorspronklike kos sal verwyder word en alle verwysings sal opgedateer word om na die nuwe kos te wys.",
"merge-food-example": "Voeg {food1} saam met {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."

View File

@@ -223,7 +223,9 @@
"show-advanced": "إظهار متقدمة",
"add-field": "إضافة حقل",
"date-created": "تاريخ الإنشاء",
"date-updated": "تاريخ التحديث"
"date-updated": "تاريخ التحديث",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "هل انت متأكد من رغبتك في حذف <b>{groupName}<b/>؟",
@@ -283,7 +285,40 @@
"admin-group-management-text": "التغييرات التي ستطرأ على هذه المجموعة ستنعكس على الفور.",
"group-id-value": "معرف المجموعة: {0}",
"total-households": "مجموع المنزل",
"you-must-select-a-group-before-selecting-a-household": "يجب عليك تحديد مجموعة قبل تحديد المنزل"
"you-must-select-a-group-before-selecting-a-household": "يجب عليك تحديد مجموعة قبل تحديد المنزل",
"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\").",
"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"
}
},
"household": {
"household": "المنزل",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "سيؤدي دمج الأطعمة المختارة إلى دمج الطعام الأصلي والطعام المستهدف في طعام واحد. سيتم حذف الطعام الأصلي، وسيتم تحديث جميع الإشارات إليه لتشير إلى الطعام المستهدف.",
"merge-food-example": "دمج {food1} مع {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "أنا بالفعل جاهز، فقط أحضر ني إلى الصفحة الرئيسية",
"common-settings-for-new-sites": "فيما يلي بعض الإعدادات الشائعة للمواقع الجديدة",
"setup-complete": "تمت الإعدادات!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "إليك بعض الأشياء لمساعدتك على البدء مع ميالي",
"restore-from-v1-backup": "لديك نسخة احتياطية من مثيل سابق لـ Mealie v1؟ يمكنك استعادتها هنا.",
"manage-profile-or-get-invite-link": "إدارة الملف الشخصي الخاص بك، أو التقط رابط دعوة للمشاركة مع الآخرين."

View File

@@ -98,7 +98,7 @@
"dashboard": "Табло",
"delete": "Изтриване",
"disabled": "Изключено",
"done": "Done",
"done": "Готово",
"download": "Изтегли",
"duplicate": "Дублиране",
"edit": "Редактирай",
@@ -169,11 +169,11 @@
"token": "Токен",
"tuesday": "Вторник",
"type": "Тип",
"undo": "Undo",
"undo": "Отмяна",
"update": "Актуализация",
"updated": "Последно обновени",
"upload": "Качи",
"url": "URL",
"url": "URL АДРЕС",
"view": "Преглед",
"wednesday": "Сряда",
"yes": "Да",
@@ -184,7 +184,7 @@
"start": "Начало",
"toggle-view": "Смяна на изгледа",
"date": "Дата",
"id": "Id",
"id": "Идентификатор",
"owner": "Собственик",
"change-owner": "Промени собственика",
"date-added": "Дата на добавяне",
@@ -223,7 +223,9 @@
"show-advanced": "Разширени настройки",
"add-field": "Добави поле",
"date-created": "Дата на създаване",
"date-updated": "Дата на актуализация"
"date-updated": "Дата на актуализация",
"key": "Ключ",
"value": "Стойност"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Промените по тази група ще бъдат отразени моментално.",
"group-id-value": "ID на Групата: {0}",
"total-households": "Общ брой домакинства",
"you-must-select-a-group-before-selecting-a-household": "Трябва да изберете група, преди да изберете домакинство"
"you-must-select-a-group-before-selecting-a-household": "Трябва да изберете група, преди да изберете домакинство",
"ai-provider-settings": {
"ai-provider-settings": "Настройки на доставчика на ИИ",
"ai-provider": "Доставчик на изкуствен интелект",
"ai-providers": "Доставчици на изкуствен интелект",
"ai-provider-settings-description": "Конфигурирайте доставчици на изкуствен интелект, за да активирате функции, задвижвани от изкуствен интелект, като например подобрен анализ на съставки, създаване на рецепти от видеоклипове и други!",
"providers": "Доставчици",
"create-provider": "Създаване на доставчик",
"edit-provider": "Редактиране на доставчик",
"default-provider": "Доставчик по подразбиране",
"default-provider-description": "Необходимо е за активиране на функции с изкуствен интелект",
"audio-provider": "Доставчик на аудио",
"audio-provider-description": "Активира функции за аудио транскрипция, като например създаване на рецепти от видеоклипове",
"image-provider": "Доставчик на изображения",
"image-provider-description": "Активира функции за разпознаване на изображения, като например създаване на рецепти от изображения",
"provider-name": "Име на доставчика",
"api-key": "API ключ",
"api-key-description-create": "API ключът на вашия доставчик за удостоверяване. Ако вашата услуга (напр. Ollama) не използва API ключ, пак трябва да въведете нещо тук.",
"api-key-description-edit": "Оставете полето празно, освен ако не искате да го промените.",
"base-url": "Основен линк",
"base-url-description": "Ако използвате OpenAI, оставете това празно. Трябва да е съвместима OpenAI крайна точка (напр. \"http://localhost:11434/v1\").",
"model": "Модел",
"model-description": "Кой модел трябва да използва вашият доставчик на изкуствен интелект (напр. „gpt-5“).",
"request-timeout-seconds": "Време за изчакване на заявката (секунди)",
"provider-created": "Доставчика е създаден",
"provider-updated": "Доставчикът е обновен",
"provider-deleted": "Доставчикът е изтрит",
"provider-create-failed": "Грешка при създаването на доставчик",
"provider-update-failed": "Грешка при обновяването на доставчик",
"provider-delete-failed": "Грешка при изтриването на доставчик",
"request-headers": "Хедъри на заявката",
"request-params": "Параметри на заявката",
"no-default-provider-warning": "Не сте задали доставчик по подразбиране, така че функциите на изкуствения интелект са деактивирани"
}
},
"household": {
"household": "Домакинство",
@@ -628,7 +663,7 @@
"create-recipe-description": "Създайте нова рецепта от чернова.",
"create-recipes": "Създайте рецепти",
"import-with-zip": "Импортирай от .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Създаване на рецепта от снимка",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Изрежете и завъртете изображението, така че да се вижда само текстът и той да е в правилната ориентация.",
"create-from-images": "Създаване от изображения",
@@ -859,9 +894,9 @@
"webhooks": {
"test-webhooks": "Тестови Webhooks",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "URL адресите, изброени по-долу, ще получат webhooks, съдържащи данните на рецептите от плана за хранене в планирания ден. В момента Webhooks ще се изпълняват на",
"webhook-url": "Webhook URL",
"webhooks-caps": "WEBHOOKS",
"webhooks": "Webhooks",
"webhook-url": "URL адрес на уебхук",
"webhooks-caps": "Уебхуук",
"webhooks": "Уебхуук",
"webhook-name": "Име на webhook",
"description": "Дефинираните по-долу webhooks ще бъдат изпълнени, когато е определено хранене за деня. В планираното време webhooks ще бъдат изпратени с данните от рецептата, която е планирана за деня. Имайте предвид, че изпълнението на webhook не е точно. Webhooks се изпълняват на интервал от 5 минути, така че ще бъдат изпълнени в рамките на +/- 5 минути от планираното."
},
@@ -917,7 +952,7 @@
"quantity": "Количество: {0}",
"shopping-list": "Списък за пазаруване",
"shopping-lists": "Списъци за пазаруване",
"add-item": "Add item",
"add-item": "Добавяне на елемент",
"food": "Продукт",
"note": "Бележка",
"label": "Етикет",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Сигурни ли сте, че искате да премахнете отметката от всички елементи?",
"are-you-sure-you-want-to-delete-checked-items": "Сигурни ли сте, че искате да изтриете всички отметнати елементи?",
"no-shopping-lists-found": "Не са намерени списъци за пазаруване",
"item-checked-off": "Checked off {item}"
"item-checked-off": "Неизбран елемент {item}"
},
"sidebar": {
"all-recipes": "Всички рецепти",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Комбинирането на избраните храни ще обедини изходната храна и целевата храна в една единствена храна. Изходната храна ще бъде изтрита и всички препратки към изходната храна ще бъдат актуализирани, за да сочат към целевата храна.",
"merge-food-example": "Обединяване на {food1} с {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Вече съм настроен, просто ме отведете до началната страница",
"common-settings-for-new-sites": "Ето някои общи настройки за нови сайтове",
"setup-complete": "Настройката е завършена!",
"ai-providers-description": "По желание конфигурирайте доставчици на изкуствен интелект за вашата група. Доставчиците на изкуствен интелект активират функции като създаване на рецепти от изображения, импортиране на рецепти от видеоклипове и подобрен анализ на съставките. Винаги можете да конфигурирате това по-късно от настройките на вашата група.",
"here-are-a-few-things-to-help-you-get-started": "Ето няколко неща, които ще Ви помогнат да започнете с Mealie",
"restore-from-v1-backup": "Имате резервно копие от предишна инстанция на Mealie v1? Можете да го възстановите тук.",
"manage-profile-or-get-invite-link": "Управлявайте собствения си профил или вземете връзка за покана, която да споделите с други."
@@ -1478,10 +1516,10 @@
"max-length": "Трябва да бъде най-много {max} символа|Трябва да бъде най-много {max} символа"
},
"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": "Съобщения",
"all-announcements": "Всички съобщения",
"mark-all-as-read": "Маркирай всички като прочетени",
"show-announcements-from-mealie": "Показване на съобщенията от Mealie",
"show-announcements-setting-description": "Искате ли да разрешите на потребителите да виждат съобщения от Mealie? Когато е активирано, потребителите все още могат да се откажат от виждането им в потребителските си настройки"
}
}

View File

@@ -223,7 +223,9 @@
"show-advanced": "Mostrar els paràmetres avançats",
"add-field": "Afegir camp",
"date-created": "Data de creació",
"date-updated": "Data dactualització"
"date-updated": "Data dactualització",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Els canvis en aquest grup s'actualitzaran immediatament.",
"group-id-value": "ID del grup: {0}",
"total-households": "Llars totals",
"you-must-select-a-group-before-selecting-a-household": "Heu de seleccionar un grup abans de seleccionar una llar"
"you-must-select-a-group-before-selecting-a-household": "Heu de seleccionar un grup abans de seleccionar una llar",
"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\").",
"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"
}
},
"household": {
"household": "Llar",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combinant els aliments seleccionats, es fusionaran els dos aliments en un. El primer aliment serà eliminat i totes les referències s'actualitzaran a l'aliment resultant.",
"merge-food-example": "Combinant {food1} i {food2}",
"seed-dialog-text": "Afegeix a la base de dades els noms dels aliments en el vostre idioma. Açò crearà més de 200 aliments comuns per a què pugueu organitzar la vostra base de dades. Els noms dels aliments han estat traduïts gràcies a l'esforç de la comunitat.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Ja ho tinc configurat, porta'm a la pàgina principal",
"common-settings-for-new-sites": "Aquí hi ha algunes configuracions comunes per noves pàgines",
"setup-complete": "Configuració completada!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Aquí hi ha unes quantes coses per ajudar-te a posar Mealie en marxa",
"restore-from-v1-backup": "Tens una còpia de seguretat d'una instància prèvia de Mealie v1? Pots restaurar-la aquí.",
"manage-profile-or-get-invite-link": "Gestiona el teu propi perfil, o agafa un enllaç d'invitació per compartir amb altres."

View File

@@ -169,7 +169,7 @@
"token": "Token",
"tuesday": "Úterý",
"type": "Typ",
"undo": "Undo",
"undo": "Zpět",
"update": "Aktualizace",
"updated": "Aktualizováno",
"upload": "Nahrát",
@@ -223,7 +223,9 @@
"show-advanced": "Zobrazit pokročilé",
"add-field": "Přidat pole",
"date-created": "Datum vytvoření",
"date-updated": "Datum aktualizace"
"date-updated": "Datum aktualizace",
"key": "Klíč",
"value": "Hodnota"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Jste si jisti, že chcete smazat <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Změny v této skupině budou okamžitě zohledněny.",
"group-id-value": "ID skupiny: {0}",
"total-households": "Celkem domácností",
"you-must-select-a-group-before-selecting-a-household": "Před výběrem domácnosti musíte vybrat skupinu"
"you-must-select-a-group-before-selecting-a-household": "Před výběrem domácnosti musíte vybrat skupinu",
"ai-provider-settings": {
"ai-provider-settings": "Nastavení AI providera",
"ai-provider": "AI provider",
"ai-providers": "AI provideři",
"ai-provider-settings-description": "Configure AI providers to enable AI-powered features, such as enhanced ingredient parsing, creating recipes from videos, and more!",
"providers": "Provideři",
"create-provider": "Vytvořit providera",
"edit-provider": "Upravit providera",
"default-provider": "Výchozí provider",
"default-provider-description": "Požadováno pro povolení AI funkcí",
"audio-provider": "Audio provider",
"audio-provider-description": "Povoluje funkce přepisu audia jako vytváření receptů z videí",
"image-provider": "Provider obrázků",
"image-provider-description": "Povolit funkce rozpoznávání obrázků jako vytváření receptů z obrázků",
"provider-name": "Název providera",
"api-key": "Klíč API",
"api-key-description-create": "API klíč vašeho providera pro ověření. Pokud vaše služba (např. Ollama) nepoužívá API klíč, stále je potřeba sem něco vložit.",
"api-key-description-edit": "Vyplňte pouze pokud chcete změnit.",
"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\").",
"model": "Model",
"model-description": "Jaký model by měl váš AI provider použít (např. \"gpt-5\").",
"request-timeout-seconds": "Platnost požadavku (sekundy)",
"provider-created": "Provider vytvořen",
"provider-updated": "Provider aktualizován",
"provider-deleted": "Provider odstraněn",
"provider-create-failed": "Vytvoření providera se nezdařilo",
"provider-update-failed": "Aktualizace providera se nezdařila",
"provider-delete-failed": "Odstranění providera se nezdařilo",
"request-headers": "Hlavičky požadavků",
"request-params": "Parametry požadavků",
"no-default-provider-warning": "Nezvolili jste výchozího providera, AI funkce jsou zakázané"
}
},
"household": {
"household": "Domácnost",
@@ -628,7 +663,7 @@
"create-recipe-description": "Vytvořit nový recept od nuly.",
"create-recipes": "Vytvořit recepty",
"import-with-zip": "Importovat pomocí .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Vytvořit recept z obrázků",
"create-recipe-from-an-image-description": "Vytvořte recept nahráním obrázku. Mealie se pokusí z obrázku extrahovat text pomocí AI a vytvořit z něj recept.",
"crop-and-rotate-the-image": "Oříznout a otočit obrázek tak, aby byl viditelný pouze text a aby byl ve správné orientaci.",
"create-from-images": "Vytvořit z obrázků",
@@ -917,7 +952,7 @@
"quantity": "Množství: {0}",
"shopping-list": "Nákupní seznam",
"shopping-lists": "Nákupní seznamy",
"add-item": "Add item",
"add-item": "Přidat položku",
"food": "Jídlo",
"note": "Poznámka",
"label": "Popisek",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Opravdu chcete zrušit výběr všech položek?",
"are-you-sure-you-want-to-delete-checked-items": "Opravdu chcete odstranit všechny vybrané položky?",
"no-shopping-lists-found": "Nebyly nalezeny žádné nákupní seznamy",
"item-checked-off": "Checked off {item}"
"item-checked-off": "Odškrtnuta položka {item}"
},
"sidebar": {
"all-recipes": "Všechny recepty",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Varování: toto jídlo je použito v {count} receptu(receptech). Odstranění zanechá v receptu(receptech) prázdnou ingrediencí.",
"delete-affects-recipes-more": "Zobrazit všechny {count} recepty",
"merge-dialog-text": "Zkombinování zvolených potravin způsobí smazání zdrojové potraviny a veškeré odkazy na ni budou přesměrovány do cílové potraviny.",
"merge-food-example": "Sloučení {food1} do {food2}",
"seed-dialog-text": "Naplňte databázi potravinami z vašeho jazyka. Tímto vytvoříte přes 200 běžných potravin, které můžete použít k organizaci vaší databáze. Potraviny jsou přeloženy skrze komunitní úsilí.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Již mám nastaveno, vezmi mě na domovskou stránku",
"common-settings-for-new-sites": "Zde jsou některá běžná nastavení pro nové stránky",
"setup-complete": "Nastavení dokončeno!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Zde je několik věcí, které vám pomohou začít s Mealie",
"restore-from-v1-backup": "Máte zálohu z předchozí instance Mealie v1? Můžete ji obnovit zde.",
"manage-profile-or-get-invite-link": "Spravujte svůj vlastní profil, nebo přidejte pozvánku ke sdílení s ostatními."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Vis avanceret",
"add-field": "Tilføj felt",
"date-created": "Oprettet",
"date-updated": "Opdateret"
"date-updated": "Opdateret",
"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/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Ændringer i denne gruppe vil træde i kraft øjeblikkeligt.",
"group-id-value": "Gruppe-ID: {0}",
"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"
"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-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": "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": {
"household": "Husstand",
@@ -628,7 +663,7 @@
"create-recipe-description": "Opret ny opskrift fra bunden.",
"create-recipes": "Opret opskrift",
"import-with-zip": "Importér fra ZIP-fil",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Opret opskrift fra billeder",
"create-recipe-from-an-image-description": "Opret en opskrift ved at overføre et billede af den. Mealie vil forsøge at udtrække teksten fra billedet med AI og oprette en opskrift fra det.",
"crop-and-rotate-the-image": "Beskær og roter billedet, så kun teksten er synlig, og det vises i den rigtige retning.",
"create-from-images": "Opret fra billede",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Advarsel: denne ingrediens er brugt i {count} opskrift(er). Hvis du sletter den, vil det efterlade en tom ingrediens i opskriften/opskrifterne.",
"delete-affects-recipes-more": "Se alle {count} opskrifter",
"merge-dialog-text": "Ved at kombinere de udvalgte fødevarer vil de to valgte fødevarer blive til en enkelt fødevare. Kildefødevaren vil blive slettet, og alle henvisninger til kildefødevaren vil blive opdateret til at pege på målfødevaren.",
"merge-food-example": "Samler {food1} med {food2}",
"seed-dialog-text": "Tilføj standardfødevarer på dansk i databasen. Dette vil oprette cirka 2700 af de mest anvendte fødevarer, der kan bruges til at organisere din database. Fødevarer er oversat via en fællesskabsindsats.",
@@ -1362,6 +1399,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": "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

@@ -51,7 +51,7 @@
"category": "Kategorie"
},
"events": {
"apprise-url": "Apprise URL",
"apprise-url": "Apprise-URL",
"database": "Datenbank",
"delete-event": "Ereignis löschen",
"event-delete-confirmation": "Bist du dir sicher, dass du dieses Ereignis löschen möchtest?",
@@ -98,7 +98,7 @@
"dashboard": "Übersicht",
"delete": "Löschen",
"disabled": "Deaktiviert",
"done": "Done",
"done": "Erledigt",
"download": "Herunterladen",
"duplicate": "Duplizieren",
"edit": "Bearbeiten",
@@ -223,7 +223,9 @@
"show-advanced": "Erweiterte Optionen anzeigen",
"add-field": "Feld Hinzufügen",
"date-created": "Erstellungsdatum",
"date-updated": "Aktualisiert am"
"date-updated": "Aktualisiert am",
"key": "Schlüssel",
"value": "Wert"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Bist du dir sicher, dass du die Gruppe <b>{groupName}<b/> löschen möchtest?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Änderungen an dieser Gruppe sind sofort wirksam.",
"group-id-value": "Gruppen ID: {0}",
"total-households": "Haushalte insgesamt",
"you-must-select-a-group-before-selecting-a-household": "Du musst eine Gruppe auswählen, bevor du einen Haushalt auswählst"
"you-must-select-a-group-before-selecting-a-household": "Du musst eine Gruppe auswählen, bevor du einen Haushalt auswählst",
"ai-provider-settings": {
"ai-provider-settings": "KI-Anbieter-Einstellungen",
"ai-provider": "KI-Anbieter",
"ai-providers": "KI-Anbieter",
"ai-provider-settings-description": "Konfiguriere KI-Anbieter, um KI-basierte Funktionen wie das verbesserte Parsen von Zutaten, das Erstellen von Rezepten aus Videos und vieles mehr zu aktivieren!",
"providers": "Anbieter",
"create-provider": "Anbieter erstellen",
"edit-provider": "Anbieter bearbeiten",
"default-provider": "Standard-Anbieter",
"default-provider-description": "Zum Aktivieren der KI-Funktionen erforderlich",
"audio-provider": "Audio-Anbieter",
"audio-provider-description": "Aktiviert Audio-Transkriptionsfunktionen, wie das Erstellen von Rezepten aus Videos",
"image-provider": "Bildanbieter",
"image-provider-description": "Aktiviert Bilderkennungsfunktionen, wie das Erstellen von Rezepten aus Bildern",
"provider-name": "Anbietername",
"api-key": "API-Key",
"api-key-description-create": "Der API-Schlüssel deines Anbieters zur Authentifizierung. Wenn der Dienst (z.B. Ollama) keinen API-Schlüssel verwendet, musst du hier trotzdem etwas eintragen.",
"api-key-description-edit": "Lassen Sie dieses Feld leer, wenn Sie es nicht ändern möchten.",
"base-url": "Basis-URL",
"base-url-description": "Wenn du OpenAI verwendest, lasse dies leer. Muss ein OpenAI-kompatibler Endpunkt sein (z.B. \"http://localhost:11434/v1\").",
"model": "Modell",
"model-description": "Welches Modell dein KI-Anbieter verwenden soll (z.B. \"gpt-5\").",
"request-timeout-seconds": "Anfrage-Timeout (Sekunden)",
"provider-created": "Anbieter erstellt",
"provider-updated": "Anbieter aktualisiert",
"provider-deleted": "Anbieter gelöscht",
"provider-create-failed": "Anbieter konnte nicht erstellt werden",
"provider-update-failed": "Anbieter konnte nicht aktualisiert werden",
"provider-delete-failed": "Anbieter konnte nicht gelöscht werden",
"request-headers": "Anfrage-Header",
"request-params": "Anfrageparameter",
"no-default-provider-warning": "Sie haben keinen Standard-Anbieter gesetzt, daher sind die KI-Funktionen deaktiviert"
}
},
"household": {
"household": "Haushalt",
@@ -333,7 +368,7 @@
"any-household": "Beliebiger Haushalt",
"no-meal-plan-defined-yet": "Noch kein Essensplan definiert",
"no-meal-planned-for-today": "Kein Essen für heute geplant",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-hint": "Anzahl der Tage in der Vergangenheit beim Laden der Seite",
"numberOfDaysPast-label": "Standardtage in der Vergangenheit",
"numberOfDays-hint": "Anzahl der Tage beim Laden der Seite",
"numberOfDays-label": "Anzuzeigende Tage",
@@ -482,7 +517,7 @@
"recipe": {
"add-key": "Schlüssel hinzufügen",
"add-to-favorites": "Zu Favoriten hinzufügen",
"api-extras": "API Extras",
"api-extras": "API-Extras",
"calories": "Kalorien",
"calories-suffix": "Kalorien",
"carbohydrate-content": "Kohlenhydrate",
@@ -628,7 +663,7 @@
"create-recipe-description": "Erstelle ein neues Rezept von Grund auf.",
"create-recipes": "Rezepte erstellen",
"import-with-zip": "Von .zip importieren",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Rezept von einem Bild erstellen",
"create-recipe-from-an-image-description": "Erstelle ein Rezept, indem du ein Bild hochlädst. Mealie wird versuchen, den Text aus dem Bild mit Hilfe von KI zu extrahieren und ein Rezept daraus zu erstellen.",
"crop-and-rotate-the-image": "Beschneide und drehe das Bild so, dass nur der Text zu sehen ist und die Ausrichtung stimmt.",
"create-from-images": "Aus Bildern erstellen",
@@ -636,7 +671,7 @@
"please-wait-image-procesing": "Warte bitte, das Bild wird gerade bearbeitet. Dies kann einige Zeit dauern.",
"please-wait-images-processing": "Bitte warten, die Bilder werden verarbeitet. Dies kann einige Zeit dauern.",
"bulk-url-import": "URL Massenimport",
"debug-scraper": "Debug Scraper",
"debug-scraper": "Debug-Scraper",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Erstelle ein Rezept, indem du den Namen angibst. Alle Rezepte müssen eindeutige Namen haben.",
"new-recipe-names-must-be-unique": "Neue Rezeptnamen müssen eindeutig sein",
"scrape-recipe": "Rezept einlesen",
@@ -657,7 +692,7 @@
"import-from-html-or-json": "Aus HTML oder JSON importieren",
"import-from-html-or-json-description": "Importiere eine einzelne Datei aus Roh-HTML oder JSON. Das ist nützlich, wenn du ein Rezept von einer Seite, die Mealie nicht scrapen kann, oder von einer externen Quelle hast.",
"json-import-format-description-colon": "Um mit JSON zu importieren, muss die Datei in einem gültigen Format vorliegen:",
"json-editor": "JSON Editor",
"json-editor": "JSON-Editor",
"zip-files-must-have-been-exported-from-mealie": ".zip Dateien müssen aus Mealie exportiert worden sein",
"create-a-recipe-by-uploading-a-scan": "Erstelle ein Rezept durch Hochladen eines Scans.",
"upload-a-png-image-from-a-recipe-book": "Lade ein PNG-Bild aus einem Kochbuch hoch",
@@ -689,8 +724,8 @@
"alerts-explainer": "Es werden Warnungen angezeigt, wenn ein passendes Lebensmittel oder eine Einheit gefunden wurde, aber in der Datenbank nicht vorhanden ist.",
"select-parser": "Parser auswählen",
"natural-language-processor": "Natürliche Sprachverarbeitung",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"brute-parser": "Brute-Parser",
"openai-parser": "OpenAI-Parser",
"parse-all": "Alles parsen",
"no-unit": "Keine Einheit",
"missing-unit": "Fehlende Einheit erstellen: {unit}",
@@ -869,7 +904,7 @@
"bug-report-information": "Füge diese Informationen deiner Fehlermeldung hinzu. Die Details zu deiner Installation sind für die Entwickler hilfreich, damit dein Anliegen schnell gelöst werden kann.",
"tracker": "Fehler melden",
"configuration": "Konfiguration",
"docker-volume": "Docker Volume",
"docker-volume": "Docker-Volume",
"docker-volume-help": "Mealie setzt voraus, dass sich der Frontend-Container und das Backend das gleiche Docker-Volume oder den gleichen Speicher teilen. Dadurch wird sichergestellt, dass der Frontend-Container auf die Bilder und Assets auf der Festplatte zugreifen kann.",
"volumes-are-misconfigured": "Volumes sind falsch konfiguriert.",
"volumes-are-configured-correctly": "Volumes sind korrekt konfiguriert.",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Bist du sicher, dass du die Auswahl aller Elemente aufheben möchtest?",
"are-you-sure-you-want-to-delete-checked-items": "Bist du sicher, dass du alle ausgewählten Elemente löschen möchtest?",
"no-shopping-lists-found": "Keine Einkaufslisten gefunden",
"item-checked-off": "Checked off {item}"
"item-checked-off": "{item} erledigt"
},
"sidebar": {
"all-recipes": "Alle Rezepte",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "Zeige alle {count} Rezepte",
"merge-dialog-text": "Zusammenführen der ausgewählten Lebensmittel führt diese zusammen in ein einzelnes Lebensmittel. Die Ausgangslebensmittel werden gelöscht und alle Verweise werden auf das zusammengeführte Lebensmittel angepasst.",
"merge-food-example": "{food1} wird zu {food2} zusammengeführt",
"seed-dialog-text": "Füllt die Datenbank mit Lebensmitteln basierend auf deiner Landessprache. Hierdurch werden mehr als 200 gängige Lebensmittel eingetragen, die verwendet werden können, um die Datenbank zu organisieren. Die Lebensmittel werden von der Community übersetzt.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Ich habe schon alles eingerichtet, bring mich zur Startseite",
"common-settings-for-new-sites": "Hier sind einige allgemeine Einstellungen für neue Seiten",
"setup-complete": "Einrichtung abgeschlossen!",
"ai-providers-description": "Optionale Konfiguration von KI-Anbietern für Ihre Gruppe. KI-Anbieter ermöglichen Funktionen wie das Erstellen von Rezepten aus Bildern, das Importieren von Rezepten aus Videos und das verbesserte Parsen von Zutaten. Du kannst dies später immer in Ihren Gruppeneinstellungen konfigurieren.",
"here-are-a-few-things-to-help-you-get-started": "Hier sind einige Funktionen, die dich beim Start mit Mealie unterstützen",
"restore-from-v1-backup": "Hast du ein Backup von einer früheren v1 Instanz von Mealie? Hier kannst du es wiederherstellen.",
"manage-profile-or-get-invite-link": "Verwalte dein eigenes Profil oder erstelle einen Einladungslink, den du an andere weitergeben kannst."

View File

@@ -169,7 +169,7 @@
"token": "Token",
"tuesday": "Τρίτη",
"type": "Τύπος",
"undo": "Undo",
"undo": "Αναίρεση",
"update": "Ενημέρωση",
"updated": "Ενημερώθηκε",
"upload": "Ανέβασμα",
@@ -223,7 +223,9 @@
"show-advanced": "Εμφάνιση προχωρημένων επιλογών",
"add-field": "Προσθήκη πεδίου",
"date-created": "Ημερομηνία δημιουργίας",
"date-updated": "Ημερομηνία ενημέρωσης"
"date-updated": "Ημερομηνία ενημέρωσης",
"key": "Κλειδί",
"value": "Τιμή"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Θέλετε σίγουρα να διαγράψετε αυτό τον ασφαλή σύνδεσμο <b>{groupName}<b/>;",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Οι αλλαγές σε αυτή την ομάδα θα αντικατοπτρίζονται αμέσως.",
"group-id-value": "ID ομάδας: {0}",
"total-households": "Σύνολο νοικοκυριών",
"you-must-select-a-group-before-selecting-a-household": "Πρέπει να επιλέξετε μια ομάδα πριν επιλέξετε ένα νοικοκυριό"
"you-must-select-a-group-before-selecting-a-household": "Πρέπει να επιλέξετε μια ομάδα πριν επιλέξετε ένα νοικοκυριό",
"ai-provider-settings": {
"ai-provider-settings": "Ρυθμίσεις παρόχου τεχνητής νοημοσύνης",
"ai-provider": "Πάροχος τεχνητής νοημοσύνης",
"ai-providers": "Πάροχοι τεχνητής νοημοσύνης",
"ai-provider-settings-description": "Ρυθμίστε τους παρόχους τεχνητής νοημοσύνης για να ενεργοποιήσετε τις λειτουργίες AI, όπως βελτιωμένη ανάλυση συστατικών, δημιουργία συνταγών από βίντεο και πολλά άλλα!",
"providers": "Πάροχοι",
"create-provider": "Δημιουργία παρόχου",
"edit-provider": "Επεξεργασία παρόχου",
"default-provider": "Προεπιλεγμένος πάροχος",
"default-provider-description": "Απαιτείται για την ενεργοποίηση των χαρακτηριστικών τεχνητής νοημοσύνης",
"audio-provider": "Πάροχος ήχου",
"audio-provider-description": "Ενεργοποιεί λειτουργίες μεταγραφής ήχου, όπως η δημιουργία συνταγών από βίντεο",
"image-provider": "Πάροχος εικόνων",
"image-provider-description": "Ενεργοποιεί δυνατότητες αναγνώρισης εικόνας, όπως η δημιουργία συνταγών από εικόνες",
"provider-name": "Ονομα παρόχου",
"api-key": "Κλειδί API",
"api-key-description-create": "Το κλειδί API του παρόχου σας για έλεγχο ταυτότητας. Αν η υπηρεσία σας (π.χ. Ollama) δεν χρησιμοποιεί ένα κλειδί API, πρέπει να βάλετε κάτι εδώ.",
"api-key-description-edit": "Αφήστε το κενό εκτός αν θέλετε να το αλλάξετε.",
"base-url": "Βασική διεύθυνση URL",
"base-url-description": "Αν χρησιμοποιείτε το OpenAI αφήστε αυτό το κενό. Πρέπει να είναι ένα OpenAI συμβατό endpoint (π.χ. \"http://localhost:11434/v1\").",
"model": "Μοντέλο",
"model-description": "Ποιο μοντέλο θα πρέπει να χρησιμοποιήσει ο πάροχος τεχνητής νοημοσύνης σας (π.χ. \"gpt-5\").",
"request-timeout-seconds": "Χρονικό όριο αιτήματος (δευτερόλεπτα)",
"provider-created": "Ο πάροχος δημιουργήθηκε",
"provider-updated": "Ο πάροχος ενημερώθηκε",
"provider-deleted": "Ο πάροχος διαγράφτηκε",
"provider-create-failed": "Αποτυχία δημιουργίας παρόχου",
"provider-update-failed": "Αποτυχία ενημέρωσης παρόχου",
"provider-delete-failed": "Αποτυχία διαγραφής παρόχου",
"request-headers": "Κεφαλίδες αιτήματος",
"request-params": "Παράμετροι αιτήματος",
"no-default-provider-warning": "Δεν έχετε ορίσει προεπιλεγμένο πάροχο, οπότε οι λειτουργίες τεχνητής νοημοσύνης είναι απενεργοποιημένες"
}
},
"household": {
"household": "Νοικοκυριό",
@@ -917,7 +952,7 @@
"quantity": "Ποσότητα: {0}",
"shopping-list": "Λίστα για ψώνια",
"shopping-lists": "Λίστες για ψώνια",
"add-item": "Add item",
"add-item": "Προσθήκη στοιχείου",
"food": "Τρόφιμο",
"note": "Σημείωση",
"label": "Ετικέτα",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Προειδοποίηση: αυτό το τρόφιμο χρησιμοποιείται σε {count} συνταγή(ες). Η διαγραφή του θα αφήσει ένα άδειο συστατικό στη συνταγή(ες).",
"delete-affects-recipes-more": "Προβολή όλων των {count} συνταγών",
"merge-dialog-text": "Ο συνδυασμός των επιλεγμένων τροφίμων θα συγχωνεύσει το αρχικό τρόφιμο και το τρόφιμο στόχος σε ένα μόνο τρόφιμο. Το αρχικό τρόφιμο θα διαγραφεί και όλες οι αναφορές σε αυτό θα ενημερωθούν ώστε να δείχνουν στο τρόφιμο-στόχο.",
"merge-food-example": "Συγχώνευση {food1} στο {food2}",
"seed-dialog-text": "Τροδοδοτήστε τη βάση δεδομένων με τρόφιμα που βασίζονται στην τοπική σας γλώσσα. Αυτό θα δημιουργήσει 200+ κοινά τρόφιμα που μπορούν να χρησιμοποιηθούν για την οργάνωση της βάσης δεδομένων σας. Τα τρόφιμα μεταφράζονται μέσω μιας προσπάθειας της κοινότητας.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Εχω ήδη κάνει εγκατάσταση, απλά πήγαινέ με στην αρχική σελίδα",
"common-settings-for-new-sites": "Εδώ είναι μερικές κοινές ρυθμίσεις για νέους ιστότοπους",
"setup-complete": "Η εγκατάσταση ολοκληρώθηκε!",
"ai-providers-description": "Προαιρετικά ρυθμίστε τους παρόχους τεχνητής νοημοσύνης για την ομάδα σας. Οι πάροχοι τεχνητής νοημοσύνης ενεργοποιούν λειτουργίες όπως η δημιουργία συνταγών από εικόνες, η εισαγωγή συνταγών από βίντεο και η βελτιωμένη ανάλυση συστατικών. Αυτό μπορείτε πάντα να το διαμορφώσετε αργότερα από τις ρυθμίσεις της ομάδας σας.",
"here-are-a-few-things-to-help-you-get-started": "Εδώ είναι μερικά πράγματα που θα σας βοηθήσουν να ξεκινήσετε με το Mealie",
"restore-from-v1-backup": "Εχετε ένα αντίγραφο ασφαλείας από μια προηγούμενη υπόσταση του Mealie v1; Μπορείτε να το επαναφέρετε εδώ.",
"manage-profile-or-get-invite-link": "Διαχειριστείτε το δικό σας προφίλ, ή λάβετε έναν σύνδεσμο πρόσκλησης για να μοιραστείτε με άλλους."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Show Advanced",
"add-field": "Add Field",
"date-created": "Date Created",
"date-updated": "Date Updated"
"date-updated": "Date Updated",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Changes to this group will be reflected immediately.",
"group-id-value": "Group ID: {0}",
"total-households": "Total Households",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household",
"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\").",
"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"
}
},
"household": {
"household": "Household",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
"merge-food-example": "Merging {food1} into {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Show Advanced",
"add-field": "Add Field",
"date-created": "Date Created",
"date-updated": "Date Updated"
"date-updated": "Date Updated",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Changes to this group will be reflected immediately.",
"group-id-value": "Group Id: {0}",
"total-households": "Total Households",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household",
"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\").",
"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"
}
},
"household": {
"household": "Household",
@@ -427,7 +462,7 @@
"mealie-text": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.",
"plantoeat": {
"title": "Plan to Eat",
"description-long": "Mealie can import recipies from Plan to Eat."
"description-long": "Mealie can import recipes from Plan to Eat. Upload a ZIP archive, CSV, or TXT file exported from Plan to Eat."
},
"myrecipebox": {
"title": "My Recipe Box",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
"merge-food-example": "Merging {food1} into {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create ~2700 common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,8 @@
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Mostrar Avanzado",
"add-field": "Añadir campo",
"date-created": "Fecha de creación",
"date-updated": "Fecha de actualización"
"date-updated": "Fecha de actualización",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar <b>{groupName}<b/>",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Los cambios en este grupo se reflejarán inmediatamente.",
"group-id-value": "Id del Grupo: {0}",
"total-households": "Total de Casas",
"you-must-select-a-group-before-selecting-a-household": "Debe seleccionar un grupo antes de seleccionar un hogar"
"you-must-select-a-group-before-selecting-a-household": "Debe seleccionar un grupo antes de seleccionar un hogar",
"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\").",
"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"
}
},
"household": {
"household": "Casa",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combinar los alimentos seleccionados fusionará el alimento origen y destinatario en un solo alimento. El alimento origen será eliminado y todas las referencias a él serán actualizadas para apuntar al nuevo alimento.",
"merge-food-example": "Fusionando {food1} con {food2}",
"seed-dialog-text": "Rellena la base de datos con alimentos basados en tu idioma local. Esto creará más de 200 alimentos comunes que se pueden usar para organizar tu base de datos. Los alimentos son traducidos a través de un esfuerzo comunitario.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Estoy bien, solo llévame al Inicio",
"common-settings-for-new-sites": "Aquí hay algunos ajustes comunes para sitios nuevos",
"setup-complete": "¡Configuración completada!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Aquí hay algunas cosas para ayudarte a empezar con Mealie",
"restore-from-v1-backup": "¿Tienes una copia de seguridad de Mealie v1? Puedes restaurarla aquí.",
"manage-profile-or-get-invite-link": "Gestiona tu perfil, o usa un enlace de invitación para compartir con otros."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Kuva täpsemad sätted",
"add-field": "Lisa väli",
"date-created": "Loomise kuupäev",
"date-updated": "Üleslaadimise kuupäev"
"date-updated": "Üleslaadimise kuupäev",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Kas oled kindel, et tahad kustutada <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Selle grupi muudatused on koheselt nähtavad",
"group-id-value": "Grupi ID: {0}",
"total-households": "Kokku leibkondi",
"you-must-select-a-group-before-selecting-a-household": "Sa pead valima grupi enne leibkonna valimist"
"you-must-select-a-group-before-selecting-a-household": "Sa pead valima grupi enne leibkonna valimist",
"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\").",
"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"
}
},
"household": {
"household": "Leibkond",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Valitud toitude kombineerimine ühendab koostisained ja keskendub ühele roale. Lähtetoidud eemaldatakse ja kõik viited lähtetoidule värskendatakse, et osutada sihttoidule.",
"merge-food-example": "{food1} liitmine {food2}-ga",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Ma olen juba vajalikud asjad seadistanud, vii mind pealehele",
"common-settings-for-new-sites": "Siin on mõned harilikud sätted uute lehekülgede jaoks",
"setup-complete": "Seadistus valmis!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Siin on mõned asjad mis aitavad sul teha algust Mealie-ga",
"restore-from-v1-backup": "Kas sul on tagavarakoopia varasemast Mealie v1 instantsist? Sa saad taastada selle siin.",
"manage-profile-or-get-invite-link": "Halda oma profiili, või haara kutselink teistega jagamiseks."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Näytä Lisäasetukset",
"add-field": "Lisää Kenttä",
"date-created": "Luontipäivä",
"date-updated": "Päivitetty"
"date-updated": "Päivitetty",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Haluatko varmasti poistaa ryhmän <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Muutokset tähän ryhmään tulevat näkymään välittömästi.",
"group-id-value": "Ryhmän tunniste: {0}",
"total-households": "Kotitaloudet Yhteensä",
"you-must-select-a-group-before-selecting-a-household": "Sinun tulee valita ryhmä ennen kuin valitset kotitalouden"
"you-must-select-a-group-before-selecting-a-household": "Sinun tulee valita ryhmä ennen kuin valitset kotitalouden",
"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\").",
"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"
}
},
"household": {
"household": "Kotitalous",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Valittujen elintarvikkeiden yhdistäminen yhdistää raaka-aineet ja kohdistaa ne yhteen ainoaan ruokaan. Lähde-elintarvikkeet poistetaan, ja kaikki viittaukset lähtöelintarvikkeeseen saatetaan ajan tasalle niin, että ne osoittavat kohteena olevan elintarvikkeen.",
"merge-food-example": "Yhdistä {food1} ja {food2} yhdeksi",
"seed-dialog-text": "Lisää tietokantaan paikallisen kielen mukaisia raaka-aineita. Tämä luo yli 200 yleistä raaka-ainetta, joita voidaan käyttää tietokannan järjestämiseen. Raaka-aineiden nimet ovat yhteisön kääntämiä.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Olen jo valmis, vie minut kotisivulle",
"common-settings-for-new-sites": "Tässä muutamia yleisiä asetuksia uusille sivustoille",
"setup-complete": "Asennus valmis.",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Näillä muutamilla asioilla pääset alkuun",
"restore-from-v1-backup": "Onko sinulla varmuuskopio aiemmasta Mealie v1 -instanssista? Palauta se tästä.",
"manage-profile-or-get-invite-link": "Hallitse profiiliasi tai hanki kutsulinkki muille."

View File

@@ -169,7 +169,7 @@
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
"undo": "Undo",
"undo": "Annuler",
"update": "Mettre à jour",
"updated": "Mis à jour",
"upload": "Importer",
@@ -223,7 +223,9 @@
"show-advanced": "Afficher les paramètres avancés",
"add-field": "Ajouter un champ",
"date-created": "Date de création",
"date-updated": "Date de mise à jour"
"date-updated": "Date de mise à jour",
"key": "Clé",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Voulez-vous vraiment supprimer <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Les modifications apportées à ce groupe seront immédiatement prises en compte.",
"group-id-value": "ID groupe: {0}",
"total-households": "Nombre de foyers",
"you-must-select-a-group-before-selecting-a-household": "Vous devez sélectionner un groupe avant de sélectionner un foyer"
"you-must-select-a-group-before-selecting-a-household": "Vous devez sélectionner un groupe avant de sélectionner un foyer",
"ai-provider-settings": {
"ai-provider-settings": "AI Provider Settings",
"ai-provider": "AI Provider",
"ai-providers": "AI Providers",
"ai-provider-settings-description": "Configurez les fournisseurs d'IA pour activer les fonctionnalités alimentées par l'AI, telles que l'analyse améliorée des ingrédients, la création de recettes à partir de vidéos, et plus encore !",
"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\").",
"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": "Vous n'avez pas défini de fournisseur par défaut, donc les fonctionnalités IA sont désactivées"
}
},
"household": {
"household": "Foyer",
@@ -628,7 +663,7 @@
"create-recipe-description": "Créer une nouvelle recette de zéro.",
"create-recipes": "Créer des recettes",
"import-with-zip": "Importer un .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Créer une recette depuis une image",
"create-recipe-from-an-image-description": "Créez une recette en téléchargeant une image de celle-ci. Mealie utilisera lIA pour tenter dextraire le texte et de créer une recette.",
"crop-and-rotate-the-image": "Rogner et pivoter limage pour que seul le texte soit visible, et quil soit dans la bonne orientation.",
"create-from-images": "Créer à partir dune image",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Voulez-vous vraiment désélectionner tous les éléments ?",
"are-you-sure-you-want-to-delete-checked-items": "Voulez-vous vraiment supprimer tous les éléments sélectionnés ?",
"no-shopping-lists-found": "Aucune liste de courses trouvée",
"item-checked-off": "Checked off {item}"
"item-checked-off": "{item} coché"
},
"sidebar": {
"all-recipes": "Recettes",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "La combinaison des aliments sélectionnés fusionnera laliment source et laliment cible en un seul aliment. Laliment source sera supprimé et toutes les références à laliment source seront mises à jour pour pointer vers laliment cible.",
"merge-food-example": "Fusion de {food1} dans {food2}",
"seed-dialog-text": "Initialisez la base de données avec des aliments basés sur votre langue locale. Cela permettra de créer plus de 200 aliments communs qui pourront être utilisés pour organiser votre base de données. Les aliments sont traduits grâce à un effort communautaire.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Jai déjà tout configuré, amenez moi à lécran daccueil",
"common-settings-for-new-sites": "Voici quelques paramètres courants pour les nouveaux sites",
"setup-complete": "Configuration terminée !",
"ai-providers-description": "Vous pouvez configurer un fournisseur d'IA pour votre groupe. Les fournisseurs d'IA permettent d'activer des fonctionnalités telles que la création de recettes depuis des images, l'importation de recettes depuis des vidéos ainsi qu'un traitement amélioré des ingrédients. Vous pourrez toujours configurer cela plus tard dans les paramètres de votre groupe.",
"here-are-a-few-things-to-help-you-get-started": "Voici quelques trucs pour vous aider à commencer avec Mealie",
"restore-from-v1-backup": "Vous avez une sauvegarde dune précédente instance de Mealie v1 ? Vous pouvez la restaurer ici.",
"manage-profile-or-get-invite-link": "Gérez votre propre profil, ou récupérez un lien dinvitation à partager avec dautres."

View File

@@ -169,7 +169,7 @@
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
"undo": "Undo",
"undo": "Annuler",
"update": "Mettre à jour",
"updated": "Mis à jour",
"upload": "Importer",
@@ -223,7 +223,9 @@
"show-advanced": "Afficher les paramètres avancés",
"add-field": "Ajouter un champ",
"date-created": "Date de création",
"date-updated": "Date de mise à jour"
"date-updated": "Date de mise à jour",
"key": "Clé",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Êtes-vous certain de vouloir supprimer <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Les modifications apportées à ce groupe seront immédiatement prises en compte.",
"group-id-value": "ID groupe: {0}",
"total-households": "Nombre de foyers",
"you-must-select-a-group-before-selecting-a-household": "Vous devez sélectionner un groupe avant de sélectionner un foyer"
"you-must-select-a-group-before-selecting-a-household": "Vous devez sélectionner un groupe avant de sélectionner un foyer",
"ai-provider-settings": {
"ai-provider-settings": "AI Provider Settings",
"ai-provider": "AI Provider",
"ai-providers": "AI Providers",
"ai-provider-settings-description": "Configurez les fournisseurs d'IA pour activer les fonctionnalités alimentées par l'AI, telles que l'analyse améliorée des ingrédients, la création de recettes à partir de vidéos, et plus encore !",
"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\").",
"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": "Vous n'avez pas défini de fournisseur par défaut, donc les fonctionnalités IA sont désactivées"
}
},
"household": {
"household": "Foyer",
@@ -392,7 +427,7 @@
"nextcloud": {
"description": "Importer des recettes depuis un livre de recettes Nextcloud existant",
"description-long": "Les recettes Nextcloud peuvent être importées depuis un fichier zip qui contient les données stockées dans Nextcloud. Consultez la structure de dossiers d'exemple ci-dessous pour vous assurer que vos recettes peuvent être importées.",
"title": "Nextcloud Cookbook"
"title": "Livre de recettes Nextcloud (Cookbook)"
},
"copymethat": {
"description-long": "Mealie peut importer des recettes à partir de Copy Me That. Exportez vos recettes au format HTML, puis téléverser-le .zip ci-dessous.",
@@ -628,7 +663,7 @@
"create-recipe-description": "Créer une nouvelle recette à partir de zéro.",
"create-recipes": "Créer des recettes",
"import-with-zip": "Importer un .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Créer une recette depuis une image",
"create-recipe-from-an-image-description": "Créez une recette en téléversant une image de celle-ci. Mealie utilisera lIA pour tenter dextraire le texte et de créer une recette.",
"crop-and-rotate-the-image": "Rogner et pivoter limage pour que seul le texte soit visible et quil soit dans la bonne orientation.",
"create-from-images": "Créer à partir dimages",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Voulez-vous vraiment désélectionner tous les éléments ?",
"are-you-sure-you-want-to-delete-checked-items": "Voulez-vous vraiment supprimer tous les éléments sélectionnés ?",
"no-shopping-lists-found": "Aucune liste de courses trouvée",
"item-checked-off": "Checked off {item}"
"item-checked-off": "{item} coché"
},
"sidebar": {
"all-recipes": "Les recettes",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "La combinaison des aliments sélectionnés fusionnera laliment source et laliment cible en un seul aliment. Laliment source sera supprimé et toutes les références à laliment source seront mises à jour pour pointer vers laliment cible.",
"merge-food-example": "Fusion de {food1} dans {food2}",
"seed-dialog-text": "Initialisez la base de données avec des aliments basés sur votre langue locale. Cela permettra de créer plus de 200 aliments communs qui pourront être utilisés pour organiser votre base de données. Les aliments sont traduits grâce à un effort communautaire.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Jai déjà tout configuré, amenez moi à lécran daccueil",
"common-settings-for-new-sites": "Voici quelques paramètres communs pour les nouveaux sites",
"setup-complete": "Configuration terminée!",
"ai-providers-description": "Vous pouvez configurer un fournisseur d'IA pour votre groupe. Les fournisseurs d'IA permettent d'activer des fonctionnalités telles que la création de recettes depuis des images, l'importation de recettes depuis des vidéos ainsi qu'un traitement amélioré des ingrédients. Vous pourrez toujours configurer cela plus tard dans les paramètres de votre groupe.",
"here-are-a-few-things-to-help-you-get-started": "Voici quelques trucs pour vous aider à commencer avec Mealie",
"restore-from-v1-backup": "Vous avez une sauvegarde dune précédente instance de Mealie v1 ? Vous pouvez la restaurer ici.",
"manage-profile-or-get-invite-link": "Gérez votre propre profil, ou récupérez un lien dinvitation à partager avec dautres."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Afficher les paramètres avancés",
"add-field": "Ajouter un champ",
"date-created": "Date de création",
"date-updated": "Date de mise à jour"
"date-updated": "Date de mise à jour",
"key": "Clé",
"value": "Valeur"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Voulez-vous vraiment supprimer <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Les modifications apportées à ce groupe seront immédiatement prises en compte.",
"group-id-value": "ID groupe: {0}",
"total-households": "Nombre de foyers",
"you-must-select-a-group-before-selecting-a-household": "Vous devez sélectionner un groupe avant de sélectionner un foyer"
"you-must-select-a-group-before-selecting-a-household": "Vous devez sélectionner un groupe avant de sélectionner un foyer",
"ai-provider-settings": {
"ai-provider-settings": "Paramètres du fournisseur d'IA",
"ai-provider": "Fournisseur d'IA",
"ai-providers": "Fournisseurs d'IA",
"ai-provider-settings-description": "Configurez les fournisseurs d'IA pour activer les fonctionnalités alimentées par l'AI, telles que l'analyse améliorée des ingrédients, la création de recettes à partir de vidéos, et plus encore !",
"providers": "Fournisseurs",
"create-provider": "Créer un fournisseur",
"edit-provider": "Éditer le fournisseur",
"default-provider": "Fournisseur par défaut",
"default-provider-description": "Requis pour activer les fonctionnalités IA",
"audio-provider": "Fournisseur audio",
"audio-provider-description": "Active les fonctionnalités de transcription audio, comme la création de recettes à partir de vidéos",
"image-provider": "Fournisseur d'images",
"image-provider-description": "Active les fonctionnalités de reconnaissance d'image, comme la création de recettes à partir d'images",
"provider-name": "Nom du fournisseur",
"api-key": "Clé API",
"api-key-description-create": "La clé API de votre fournisseur pour l'authentification. Si votre service (par exemple Ollama) n'utilise pas une clé API, vous devez malgré tout toujours mettre quelque chose ici.",
"api-key-description-edit": "Laissez ce champ vide à moins que vous vouliez le modifier.",
"base-url": "URL de base",
"base-url-description": "Si vous utilisez OpenAI laissez ce champ vide. Doit être un point de terminaison compatible OpenAI (par exemple \"http://localhost:11434/v1\").",
"model": "Modèle",
"model-description": "Quel modèle doit utiliser votre fournisseur d'IA (par exemple \"gpt-5\").",
"request-timeout-seconds": "Délai d'attente de la requête (secondes)",
"provider-created": "Fournisseur créé",
"provider-updated": "Fournisseur mis à jour",
"provider-deleted": "Fournisseur supprimé",
"provider-create-failed": "Échec de la création du fournisseur",
"provider-update-failed": "Échec de la mise à jour du fournisseur",
"provider-delete-failed": "Échec de la suppression du fournisseur",
"request-headers": "En-têtes de la requête",
"request-params": "Paramètres de la requête",
"no-default-provider-warning": "Vous n'avez pas défini de fournisseur par défaut, donc les fonctionnalités IA sont désactivées"
}
},
"household": {
"household": "Foyer",
@@ -392,7 +427,7 @@
"nextcloud": {
"description": "Importer des recettes depuis Nextcloud Cookbook",
"description-long": "Les recettes Nextcloud peuvent être importées depuis un fichier zip qui contient les données stockées dans Nextcloud. Consultez la structure de dossiers d'exemple ci-dessous pour vous assurer que vos recettes peuvent être importées.",
"title": "Nextcloud Cookbook"
"title": "Livre de recettes Nextcloud (Cookbook)"
},
"copymethat": {
"description-long": "Mealie peut importer des recettes à partir de Copy Me That. Exportez vos recettes au format HTML, puis téléchargez le .zip ci-dessous.",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Attention : cet aliment est utilisé dans {count} recette(s). Le supprimer laissera un ingrédient vide dans la ou les recette(s).",
"delete-affects-recipes-more": "Visualiser les {count} recettes",
"merge-dialog-text": "La combinaison des aliments sélectionnés fusionnera laliment source et laliment cible en un seul aliment. Laliment source sera supprimé et toutes les références à laliment source seront mises à jour pour pointer vers laliment cible.",
"merge-food-example": "Fusion de {food1} dans {food2}",
"seed-dialog-text": "Initialisez la base de données avec des aliments basés sur votre langue locale. Cela permettra de créer plus de 200 aliments communs qui pourront être utilisés pour organiser votre base de données. Les aliments sont traduits grâce à un effort communautaire.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Jai déjà tout configuré, amenez-moi à lécran daccueil",
"common-settings-for-new-sites": "Voici quelques paramètres courants pour les nouveaux sites",
"setup-complete": "Configuration terminée !",
"ai-providers-description": "Vous pouvez configurer un fournisseur d'IA pour votre groupe. Les fournisseurs d'IA permettent d'activer des fonctionnalités telles que la création de recettes depuis des images, l'importation de recettes depuis des vidéos ainsi qu'un traitement amélioré des ingrédients. Vous pourrez toujours configurer cela plus tard dans les paramètres de votre groupe.",
"here-are-a-few-things-to-help-you-get-started": "Voici quelques trucs pour vous aider à commencer avec Mealie",
"restore-from-v1-backup": "Vous avez une sauvegarde dune précédente instance de Mealie v1 ? Vous pouvez la restaurer ici.",
"manage-profile-or-get-invite-link": "Gérez votre propre profil, ou récupérez un lien dinvitation à partager avec dautres."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Mostrar Avanzadas",
"add-field": "Adicionar Campo",
"date-created": "Data de Creación",
"date-updated": "Data de Atualización"
"date-updated": "Data de Atualización",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Estás seguro de que queres eliminar <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Os cambios neste grupo reflectiranse inmediatamente.",
"group-id-value": "Id do grupo: {0}",
"total-households": "Casas Totais",
"you-must-select-a-group-before-selecting-a-household": "Tes que selecionar un grupo antes de selecionar unha casa"
"you-must-select-a-group-before-selecting-a-household": "Tes que selecionar un grupo antes de selecionar unha casa",
"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\").",
"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"
}
},
"household": {
"household": "Casa",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combinar os alimentos selecionados xuntará o alimento de orixen e o alimento de destino nun único alimento. O alimento de orixen será eliminado, e todas as referencias a este, serán atualizadas para apuntar para o alimento de destino.",
"merge-food-example": "A xuntar {food1} con {food2}",
"seed-dialog-text": "Semente a base de datos con alimentos baseados no seu idioma local. Isto creará mais de 200 alimentos comuns que poden ser utilizados para organizar a sua base de datos. Os alimentos son traducidos através dun esforzo comunitario.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Xa estou listo, levame para a páxina inicial",
"common-settings-for-new-sites": "Aqui están algunhas configuracións comuns para sites novos",
"setup-complete": "Configuración Concluída!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Aqui están algunhas cousas para axudar a comezar co Mealie",
"restore-from-v1-backup": "Ten unha copia de seguranza dunha instancia do Mealie v1? Pode restaurala aqui.",
"manage-profile-or-get-invite-link": "Xestione o seu proprio perfil ou obteña unha ligazón de convite para compartir con outras persoas."

View File

@@ -223,7 +223,9 @@
"show-advanced": "הצג הגדרות מתקדמות",
"add-field": "הוסף שדה",
"date-created": "תאריך יצירה",
"date-updated": "תאריך עדכון"
"date-updated": "תאריך עדכון",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "למחוק את <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "שינויים לקבוצה זו ישתקפו מיידית.",
"group-id-value": "מזהה קבוצה: {0}",
"total-households": "סך כל משקי בית",
"you-must-select-a-group-before-selecting-a-household": "חובה לבחור קבוצה לפני בחירת משק בית"
"you-must-select-a-group-before-selecting-a-household": "חובה לבחור קבוצה לפני בחירת משק בית",
"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\").",
"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"
}
},
"household": {
"household": "משק בית",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "צירוף של המאכלים שנבחרו יאחד את אוכל המקור ואוכל היעד לפריט אוכל אחד. אוכל המקור ימחק וכל ההפניות / הייחוסים אליו יעודכנו ויופנו לאוכל היעד.",
"merge-food-example": "ממזג את {food1} לתוך {food2}",
"seed-dialog-text": "אכלס את מסד הנתונים עם אוכל בהתבסס על השפה המקומית שלך. הפעולה תיצור +200 מאכלים נפוצים שיכולים לשמש לארגון מסד הנתונים. מאכלים מתורגמים על ידי מאמצי הקהילה.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "כבר הגדרתי הכל, תעבירו אותי לעמוד הבית",
"common-settings-for-new-sites": "הגדרות נפוצות לאתרים חדשים יופיעו כאן",
"setup-complete": "ההגדרה הושלמה!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "כמה דברים שיעזרו לך להתחיל להשתמש ב-Mealie",
"restore-from-v1-backup": "יש לך גיבוי משרת Mealie v1? ניתן לשחזר אותו כאן.",
"manage-profile-or-get-invite-link": "ניתן לנהל את הפרופיל שלך, או לשתף את לינק ההזמנה לאחרים."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Prikaži napredno",
"add-field": "Dodaj polje",
"date-created": "Datum kreiranja",
"date-updated": "Datum ažuriranja"
"date-updated": "Datum ažuriranja",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Jeste li sigurni da želite izbrisati <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Promjene u ovoj grupi će se odmah odraziti.",
"group-id-value": "Id grupe: {0}",
"total-households": "Ukupno kućanstava",
"you-must-select-a-group-before-selecting-a-household": "Prije odabira kućanstva morate odabrati grupu"
"you-must-select-a-group-before-selecting-a-household": "Prije odabira kućanstva morate odabrati grupu",
"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\").",
"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"
}
},
"household": {
"household": "Domaćinstvo",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Spajanje odabrane hrane će spojiti izvornu hranu i ciljanu hranu u jednu hranu. Izvorna hrana će biti izbrisana, a sve reference na izvornu hranu će biti ažurirane kako bi pokazivale na ciljanu hranu.",
"merge-food-example": "Spajanje {food1} u {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Haladó beállítások megjelenítése",
"add-field": "Mező hozzáadása",
"date-created": "Létrehozás dátuma",
"date-updated": "Frissítés dátuma"
"date-updated": "Frissítés dátuma",
"key": "Kulcs",
"value": "Érték"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Biztosan törölni szeretnéd ezt: <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "A csoporthoz tartozó változtatások azonnal megjelennek.",
"group-id-value": "Csoport azonosító: {0}",
"total-households": "Háztartások száma",
"you-must-select-a-group-before-selecting-a-household": "A háztartás kiválasztása előtt ki kell választania egy csoportot"
"you-must-select-a-group-before-selecting-a-household": "A háztartás kiválasztása előtt ki kell választania egy csoportot",
"ai-provider-settings": {
"ai-provider-settings": "AI szolgáltató beállítások",
"ai-provider": "AI szolgáltató",
"ai-providers": "AI szolgáltató",
"ai-provider-settings-description": "Állítsa be az AI-szolgáltatókat az AI-alapú funkciók, például a továbbfejlesztett összetevő-elemzés, a videókból történő receptkészítés és még sok más használatához!",
"providers": "Szolgáltatók",
"create-provider": "Szolgáltató létrehozása",
"edit-provider": "Szolgáltató szerkesztése",
"default-provider": "Alapértelmezett szolgáltató",
"default-provider-description": "Az AI-funkciók használatához szükséges",
"audio-provider": "Audió szolgáltató",
"audio-provider-description": "Engedélyezi az audió-átírási funkciókat, például a videókból származó receptek létrehozását",
"image-provider": "Kép szolgáltató",
"image-provider-description": "Engedélyezi a képfelismerési funkciókat, például a képek alapján történő receptkészítést",
"provider-name": "Szolgáltató neve",
"api-key": "API Kulcs",
"api-key-description-create": "A szolgáltató hitelesítési API-kulcsa. Ha a szolgáltatása (pl. Ollama) nem használ API-kulcsot, akkor is be kell írnia ide valamit.",
"api-key-description-edit": "Ha nem szeretné módosítani, hagyja üresen.",
"base-url": "Alap URL",
"base-url-description": "Ha az OpenAI-t használja, hagyja üresen ezt a mezőt. Az endpointnak OpenAI-kompatibilisnek kell lennie (pl. „http://localhost:11434/v1”).",
"model": "Modell",
"model-description": "Melyik modellt kell használnia az AI-szolgáltatónak (pl. „gpt-5”).",
"request-timeout-seconds": "A kérés időtúllépése (másodpercben)",
"provider-created": "Szolgáltató létrehozva",
"provider-updated": "Szolgáltató frissítve",
"provider-deleted": "Szolgáltató törölve",
"provider-create-failed": "A szolgáltató létrehozása sikertelen",
"provider-update-failed": "A szolgáltató frissítése sikertelen",
"provider-delete-failed": "A szolgáltató törlése sikertelen",
"request-headers": "Kérés fejléce",
"request-params": "Kérelem paraméterek",
"no-default-provider-warning": "Mivel nem állított be alapértelmezett szolgáltatót, az AI-funkciók le vannak tiltva"
}
},
"household": {
"household": "Háztartás",
@@ -427,7 +462,7 @@
"mealie-text": "Mealie képes a v1.0 előtti kiadású Mealie alkalmazásból származó receptek importálására. Exportálja a receptjeit a régi példányából, és töltse fel a zip fájlt alább. Vegye figyelembe, hogy az exportból csak receptek importálhatók.",
"plantoeat": {
"title": "Plan to Eat",
"description-long": "Mealie képes recepteket importálni a Plan to Eat alkalmazásból."
"description-long": "Mealie képes recepteket importálni a Plan to Eat alkalmazásból. ZIP, CSV vagy TXT fájlként feltölthetők a Plan to Eat-ből exportálásuk után."
},
"myrecipebox": {
"title": "Az én receptes dobozom",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Figyelem: ez az alapanyag {count} receptben használt. Törlése után üres hozzávalóként jelenik meg a receptekben.",
"delete-affects-recipes-more": "Az összes {count} recept megtekintése",
"merge-dialog-text": "A kiválasztott alapanyagok egyesítésével a forrásalapanyag és a célalapanyag egyetlen alapanyag lesz. A forrásalapanyag törlésre kerül, és a forrásalapanyagra vonatkozó összes hivatkozás frissül, hogy a célalapanyagra mutasson.",
"merge-food-example": "{food1} összevonása {food2}-ba/be",
"seed-dialog-text": "Töltse be az adatbázist a helyi nyelvű alapanyagokkal. Ezáltal több mint 200 közös alapanyagot hoz létre, amelyek segítségével rendszerezheti az adatbázisát. Az alapanyagok fordítása közösségi összefogással történik.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Már kész is vagyok, vigyél az új kezdőoldalamra",
"common-settings-for-new-sites": "Itt van egy pár alap beállítás az új oldaladhoz",
"setup-complete": "Beállítás kész!",
"ai-providers-description": "Opcionálisan konfigurálhatja az AI-szolgáltatókat a csoportjához. Az AI-szolgáltatók olyan funkciókat tesznek lehetővé, mint a képekből történő receptkészítés, a videókból történő receptimportálás és a továbbfejlesztett hozzávalóelemzés. Ezt később bármikor beállíthatja a csoportbeállítások menüpontban.",
"here-are-a-few-things-to-help-you-get-started": "Itt van egy pár dolog ami segíthet a kezdésben a Mealie-vel",
"restore-from-v1-backup": "Van egy korábbi Mealie v1 biztonsági másolatod? Itt visszaállíthatod.",
"manage-profile-or-get-invite-link": "Alakítsa a profilját vagy szerezze meg a meghívó link-jét hogy megoszthassa másokkal."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Ítarlegar stillingar",
"add-field": "Bæta við dálk",
"date-created": "Búið til",
"date-updated": "Dagsetning uppfærð"
"date-updated": "Dagsetning uppfærð",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Ertu viss um að þú viljir eyða <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Breytingar á þessum hóp koma strax fram.",
"group-id-value": "Hóp ID: {0}",
"total-households": "Fjöldi heimila",
"you-must-select-a-group-before-selecting-a-household": "Þú verður að velja hóp áður en þú velur heimili"
"you-must-select-a-group-before-selecting-a-household": "Þú verður að velja hóp áður en þú velur heimili",
"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\").",
"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"
}
},
"household": {
"household": "Heimili",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Við að sameina valdar matvörur munu matvörurnar sem þú hefur valið sameinast í eina matvöru. Upprunalegu matvörunni verður eytt og allar tengingar við matvöruna mun breytast og vísa á sameinuðu matvöruna.",
"merge-food-example": "Sameina {food1} við {food2}",
"seed-dialog-text": "Bæta matvörum á þínu tungumáli við gagnagrunninn. Það munu bætast við um 2700 matvörur við gagnagrunninn sem hafa verið þýddar af notendum Mealie.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Allt er þegar klárt hjá mér, sendu mig á heimasíðuna",
"common-settings-for-new-sites": "Hér eru nokkrar algengar stillingar fyrir nýjar síður",
"setup-complete": "Uppsetningu lokið",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Hér eru nokkur atriði til að hjálpa þér að byrja á því að nota Mealie",
"restore-from-v1-backup": "Ertu með öryggisafrit af eldri útgáfur af Mealie útgáfu 1? Þú getur endurheimt hana hér.",
"manage-profile-or-get-invite-link": "Stilltu eigin prófil, eða sæktu boðs-tengil til að deila með öðrum."

View File

@@ -169,7 +169,7 @@
"token": "Token",
"tuesday": "Martedì",
"type": "Tipo",
"undo": "Undo",
"undo": "Annulla",
"update": "Aggiorna",
"updated": "Aggiornato",
"upload": "Carica",
@@ -223,7 +223,9 @@
"show-advanced": "Mostra Avanzate",
"add-field": "Aggiungi campo",
"date-created": "Data di Creazione",
"date-updated": "Data di aggiornamento"
"date-updated": "Data di aggiornamento",
"key": "Chiave",
"value": "Valore"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Sei sicuro di volerlo eliminare <b>{groupName}<b/>'?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Le modifiche a questo gruppo si rifletteranno immediatamente.",
"group-id-value": "Id Gruppo: {0}",
"total-households": "Famiglie Totali",
"you-must-select-a-group-before-selecting-a-household": "Devi selezionare un gruppo prima di selezionare una famiglia"
"you-must-select-a-group-before-selecting-a-household": "Devi selezionare un gruppo prima di selezionare una famiglia",
"ai-provider-settings": {
"ai-provider-settings": "Impostazioni Provider AI",
"ai-provider": "Provider AI",
"ai-providers": "Provider AI",
"ai-provider-settings-description": "Configura i provider AI per abilitare le funzionalità basate su AI, come l'analisi migliorata degli ingredienti, la creazione di ricette da video e altro ancora!",
"providers": "Provider",
"create-provider": "Crea Provider",
"edit-provider": "Modifica provider",
"default-provider": "Provider Predefinito",
"default-provider-description": "Necessario per abilitare le funzionalità AI",
"audio-provider": "Provider Audio",
"audio-provider-description": "Abilita le funzioni di trascrizione audio, come la creazione di ricette da video",
"image-provider": "Provider Immagini",
"image-provider-description": "Abilita le funzionalità di riconoscimento delle immagini, come la creazione di ricette dalle immagini",
"provider-name": "Nome provider",
"api-key": "Chiave API",
"api-key-description-create": "La chiave API del tuo provider per l'autenticazione. Se il tuo servizio (ad esempio Ollama) non utilizza una chiave API, devi comunque mettere qualcosa qui.",
"api-key-description-edit": "Lascia questo vuoto a meno che tu non voglia cambiarlo.",
"base-url": "URL Base",
"base-url-description": "Se stai usando OpenAI lascia questo vuoto. Deve essere un endpoint compatibile con OpenAI (es. \"http://localhost:11434/v1\").",
"model": "Modello",
"model-description": "Quale modello dovrebbe usare il tuo fornitore di AI (ad esempio \"gpt-5\").",
"request-timeout-seconds": "Timeout Richiesta (secondi)",
"provider-created": "Provider creato",
"provider-updated": "Provider aggiornato",
"provider-deleted": "Provider eliminato",
"provider-create-failed": "Errore nella creazione del provider",
"provider-update-failed": "Errore nell'aggiornamento del provider",
"provider-delete-failed": "Errore nella rimozione del provider",
"request-headers": "Header della Richiesta",
"request-params": "Parametri Della Richiesta",
"no-default-provider-warning": "Non hai impostato un provider predefinito, quindi le funzioni AI sono disabilitate"
}
},
"household": {
"household": "Famiglia",
@@ -628,7 +663,7 @@
"create-recipe-description": "Crea una nuova ricetta da zero.",
"create-recipes": "Crea Ricette",
"import-with-zip": "Importa da .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Crea Ricetta da Immagini",
"create-recipe-from-an-image-description": "Crea una ricetta caricando un'immagine di essa. Mealie tenterà di estrarre il testo dall'immagine usando l'IA e creare una ricetta da esso.",
"crop-and-rotate-the-image": "Ritaglia e ruota l'immagine in modo che solo il testo sia visibile e che sia orientato correttamente.",
"create-from-images": "Crea da immagini",
@@ -917,7 +952,7 @@
"quantity": "Quantità: {0}",
"shopping-list": "Lista della Spesa",
"shopping-lists": "Liste della Spesa",
"add-item": "Add item",
"add-item": "Aggiungi articolo",
"food": "Alimenti",
"note": "Nota",
"label": "Etichetta",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Sei sicuro di voler deselezionare tutti gli elementi?",
"are-you-sure-you-want-to-delete-checked-items": "Sei sicuro di voler rimuovere tutti gli articoli selezionati?",
"no-shopping-lists-found": "Nessuna lista della spesa trovata",
"item-checked-off": "Checked off {item}"
"item-checked-off": "{item} disattivato"
},
"sidebar": {
"all-recipes": "Ricette",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combinando gli alimenti selezionati, l'alimento di origine e l'alimento di destinazione verranno uniti in un unico alimento. L'alimento di origine verrà eliminato e tutti i riferimenti all'alimento di origine verranno aggiornati per puntare all'alimento di destinazione.",
"merge-food-example": "Unione di {food1} in {food2}",
"seed-dialog-text": "Inizializzare il database con alimenti della propria lingua locale. Ciò creerà oltre 200 alimenti comuni che potranno essere usati per organizzare il proprio database. Gli alimenti vengono tradotti grazie al contributo della comunità degli utenti.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Ho già configurato tutto, portami alla pagina iniziale",
"common-settings-for-new-sites": "Ecco alcune impostazioni comuni per i nuovi siti",
"setup-complete": "Configurazione completata!",
"ai-providers-description": "Configurare opzionalmente i provider AI per il tuo gruppo. I provider di AI abilitano funzionalità come la creazione di ricette da immagini, l'importazione di ricette da video e l'analisi migliorata degli ingredienti. Puoi sempre configurare questo dopo dalle impostazioni di gruppo.",
"here-are-a-few-things-to-help-you-get-started": "Qui ci sono alcune cose per aiutarvi a iniziare con Mealie",
"restore-from-v1-backup": "Hai un backup da un'istanza precedente di Mealie v1? Puoi ripristinarlo qui.",
"manage-profile-or-get-invite-link": "Gestisci il tuo profilo, o parti da un link di invito per condividere con gli altri."

View File

@@ -223,7 +223,9 @@
"show-advanced": "詳細を表示",
"add-field": "フィールドを追加",
"date-created": "作成日",
"date-updated": "更新日"
"date-updated": "更新日",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/> を削除しますか?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "このグループへの変更はすぐに反映されます。",
"group-id-value": "グループID: {0}",
"total-households": "世帯数",
"you-must-select-a-group-before-selecting-a-household": "世帯を選択する前にグループを選択する必要があります"
"you-must-select-a-group-before-selecting-a-household": "世帯を選択する前にグループを選択する必要があります",
"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\").",
"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"
}
},
"household": {
"household": "世帯",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "選択した食品を組み合わせると、ソース食品とターゲット食品が1つの食品に統合されます。 ソース食品は削除され、ソース食品への参照のすべてがターゲット食品を指し示すように更新されます。",
"merge-food-example": "{food1} を {food2} に統合",
"seed-dialog-text": "あなたの地元の言語に基づいた食品をデータベースにシードします。これにより、データベースの整理に使用できる200以上の一般的な食品が作成されます。食品はコミュニティの取り組みによって翻訳されます。",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "すでに設定は完了しています。ホームページにアクセスしてください",
"common-settings-for-new-sites": "新しいサイトの一般的な設定は次のとおりです",
"setup-complete": "セットアップ完了!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Mealieを始めるのに役立つことがいくつかあります",
"restore-from-v1-backup": "Mealie v1以前のインスタンスからのバックアップはありますかここで復元できます。",
"manage-profile-or-get-invite-link": "自分のプロフィールを管理するか、招待リンクを取得して他の人と共有します。"

View File

@@ -223,7 +223,9 @@
"show-advanced": "고급 표시",
"add-field": "필드 추가",
"date-created": "작성 날짜",
"date-updated": "업데이트 일자"
"date-updated": "업데이트 일자",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/>을(를) 삭제하시겠습니까?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "이 그룹에 대한 변경 사항은 즉시 반영됩니다.",
"group-id-value": "그룹 Id: {0}",
"total-households": "전체 가구 수",
"you-must-select-a-group-before-selecting-a-household": "가구를 선택하기 전에 반드시 그룹을 선택해야 합니다."
"you-must-select-a-group-before-selecting-a-household": "가구를 선택하기 전에 반드시 그룹을 선택해야 합니다.",
"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\").",
"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"
}
},
"household": {
"household": "가구",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "선택한 식품을 병합하면 원본 식품과 대상 식품이 하나의 식품으로 합쳐집니다. 원본 식품은 삭제되며, 원본 식품에 대한 모든 참조는 대상 식품을 가리키도록 업데이트됩니다.",
"merge-food-example": "{food1}을 {food2}에 병합",
"seed-dialog-text": "당신이 사용하는 언어에 맞춰 데이터베이스에 음식 정보를 추가하세요. 이렇게 하면 데이터베이스를 구성하는 데 사용할 수 있는 약 2700가지의 일반적인 음식 정보가 생성됩니다. 음식 정보는 커뮤니티 참여를 통해 번역됩니다.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "설정은 이미 완료되었으니, 그냥 홈페이지로 이동해 주세요",
"common-settings-for-new-sites": "새 사이트에 대한 일반적인 설정은 다음과 같습니다",
"setup-complete": "설정 완료",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Mealie를 시작하는 데 도움이 될 몇 가지 사항입니다.",
"restore-from-v1-backup": "이전 버전의 Mealie v1 백업본이 있으신가요? 여기서 복원할 수 있습니다.",
"manage-profile-or-get-invite-link": "자신의 프로필을 관리하거나, 다른 사람들과 공유할 초대 링크를 가져가세요."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Rodyti plačiau",
"add-field": "Pridėti lauką",
"date-created": "Sukūrimo data",
"date-updated": "Atnaujinimo data"
"date-updated": "Atnaujinimo data",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Ar tikrai norite ištrinti <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Pakeitimai šiai grupei pritaikomi iš karto.",
"group-id-value": "Grupės ID: {0}",
"total-households": "Iš viso namų ūkių",
"you-must-select-a-group-before-selecting-a-household": "Prieš pasirinkdami namų ūkį, turite pasirinkti grupę"
"you-must-select-a-group-before-selecting-a-household": "Prieš pasirinkdami namų ūkį, turite pasirinkti grupę",
"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\").",
"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"
}
},
"household": {
"household": "Namų ūkis",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Pasirinktų produktų sujungimas apibendrins pirminį ir antrinį produktus į vieną produktą. Pirminis produktas bus ištrintas, o visi jo panaudojimai bus pakeisti antriniu produktu.",
"merge-food-example": "Sujungiama {food1} su {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Rādīt papildu",
"add-field": "Pievienot lauku",
"date-created": "Izveidošanas datums",
"date-updated": "Atjaunināšanas datums"
"date-updated": "Atjaunināšanas datums",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Vai tiešām vēlaties dzēst <b>{groupName} <b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Izmaiņas šajā grupā tiks atspoguļotas nekavējoties.",
"group-id-value": "Grupas ID: {0}",
"total-households": "Kopējais mājsaimniecību skaits",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household",
"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\").",
"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"
}
},
"household": {
"household": "Mājsaimniecība",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Izvēlēto pārtikas produktu apvienošana apvienos avota pārtiku un mērķa pārtiku vienā ēdienā. Avota barība tiks svītrota, un visas atsauces uz avota pārtiku tiks atjauninātas, lai norādītu uz mērķa pārtiku.",
"merge-food-example": "Apvienošanās {food1} {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Es jau esmu iestatīts, vienkārši nogādājiet mani mājaslapā",
"common-settings-for-new-sites": "Šeit ir daži kopīgi iestatījumi jaunām vietnēm",
"setup-complete": "Iestatīšana pabeigta!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Šeit ir dažas lietas, kas palīdzēs jums sākt darbu ar Mealie",
"restore-from-v1-backup": "Vai jums ir dublējums no iepriekšējā Mealie v1 gadījuma? Jūs varat to atjaunot šeit.",
"manage-profile-or-get-invite-link": "Pārvaldiet savu profilu vai paņemiet uzaicinājuma saiti, lai kopīgotu ar citiem."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Laat geavanceerde keuzes zien",
"add-field": "Veld toevoegen",
"date-created": "Datum aangemaakt",
"date-updated": "Datum bijgewerkt"
"date-updated": "Datum bijgewerkt",
"key": "Sleutel",
"value": "Waarde"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Weet je zeker dat je <b>{groupName}<b/> wil verwijderen?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Wijzigingen in deze groep zijn meteen zichtbaar.",
"group-id-value": "Groeps-id: {0}",
"total-households": "Totaal aantal huishoudens",
"you-must-select-a-group-before-selecting-a-household": "Kies een groep voordat je een huishouden kiest"
"you-must-select-a-group-before-selecting-a-household": "Kies een groep voordat je een huishouden kiest",
"ai-provider-settings": {
"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": "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": {
"household": "Huishouden",
@@ -628,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",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Het combineren van de geselecteerde levensmiddelen zal het oorspronkelijke levensmiddel en het nieuwe levensmiddel samenvoegen. Het oorspronkelijke levensmiddel zal worden verwijderd en alle referenties worden aangepast, zodat ze naar het nieuwe levensmiddel verwijzen.",
"merge-food-example": "{food1} samenvoegen met {food2}",
"seed-dialog-text": "Vul de database met levensmiddelen in jouw taal. Dit maakt meer dan 200 veelvoorkomende levensmiddelen aan die je in jouw database kan gebruiken. De vertalingen zijn verzorgd door een gemeenschap.",
@@ -1362,6 +1399,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": "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

@@ -223,7 +223,9 @@
"show-advanced": "Vis avansert",
"add-field": "Legg til felt",
"date-created": "Opprettet dato",
"date-updated": "Dato oppdatert"
"date-updated": "Dato oppdatert",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på at du vil slette <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Endringer i denne gruppen vil gjenspeiles umiddelbart.",
"group-id-value": "Gruppe-ID: {0}",
"total-households": "Husholdninger totalt",
"you-must-select-a-group-before-selecting-a-household": "Du må velge en gruppe før du kan velge en husholdning"
"you-must-select-a-group-before-selecting-a-household": "Du må velge en gruppe før du kan velge en husholdning",
"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\").",
"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"
}
},
"household": {
"household": "Husholdning",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Ved å kombinere de valgte matvarene vil de bli slått sammen til én matvare. Den første matvaren vil bli slettet, og alle referanser til denne vil bli oppdatert til å peke til den nye matvaren.",
"merge-food-example": "Slår sammen {food1} til {food2}",
"seed-dialog-text": "Sett inn matvarer basert på ditt lokale språk i databasen. Dette vil lage 200+ vanlige matvarer som kan brukes til å organisere databasen din. Mat blir oversatt via fellesinnsats.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Jeg er allerede satt opp, bare ta meg med til hjemmesiden",
"common-settings-for-new-sites": "Her er noen vanlige innstillinger for nye sider",
"setup-complete": "Oppsett fullført!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Her er noen ting som kan hjelpe deg å komme i gang med Mealie",
"restore-from-v1-backup": "Har du en sikkerhetskopi fra en tidligere forekomst av Mealie v1? Du kan gjenopprette den her.",
"manage-profile-or-get-invite-link": "Administrer din egen profil, eller hent en invitasjonslenke for å dele med andre."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Pokaż zaawansowane",
"add-field": "Dodaj pole",
"date-created": "Data utworzenia",
"date-updated": "Data aktualizacji"
"date-updated": "Data aktualizacji",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Czy na pewno chcesz usunąć <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Zmiany w tej grupie zostaną natychmiast odzwierciedlone.",
"group-id-value": "Id grupy: {0}",
"total-households": "Gospodarstwa domowe razem",
"you-must-select-a-group-before-selecting-a-household": "Musisz wybrać grupę przed wybraniem gospodarstwa domowego"
"you-must-select-a-group-before-selecting-a-household": "Musisz wybrać grupę przed wybraniem gospodarstwa domowego",
"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\").",
"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"
}
},
"household": {
"household": "Gospodarstwo domowe",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Połączenie wybranych produktów spożywczych scali produkt źródłowy i produkt docelowy w jeden produkt. Produkt źródłowy zostanie usunięty, a wszystkie odniesienia do produktu źródłowego zostaną zaktualizowane tak, aby wskazywały na produkt docelowy.",
"merge-food-example": "Scalanie {food1} do {food2}",
"seed-dialog-text": "Wypełnij bazę danych produktami spożywczymi na podstawie Twojego lokalnego języka. Spowoduje to utworzenie ok. 2700 popularnych produktów spożywczych, które można wykorzystać do zorganizowania bazy danych. Produkty spożywcze są tłumaczone dzięki wysiłkom społeczności.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Już mam skonfigurowane, po prostu przenieś mnie na stronę główną",
"common-settings-for-new-sites": "Oto kilka typowych ustawień dla nowych witryn",
"setup-complete": "Konfiguracja zakończona!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Oto kilka rzeczy, które pomogą Ci zacząć z Mealie",
"restore-from-v1-backup": "Masz kopię zapasową z poprzedniej instancji Mealie v1? Możesz ją tutaj przywrócić.",
"manage-profile-or-get-invite-link": "Zarządzaj własnym profilem lub pobierz link z zaproszeniem, aby udostępnić go innym."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Mostrar Avançado",
"add-field": "Adicionar Campo",
"date-created": "Data de Criação",
"date-updated": "Data de Atualização"
"date-updated": "Data de Atualização",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Tem certeza que deseja excluir o grupo <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "As alterações a este grupo serão refletidas imediatamente.",
"group-id-value": "ID do grupo: {0}",
"total-households": "Total de domicílios",
"you-must-select-a-group-before-selecting-a-household": "Você deve selecionar um grupo antes de selecionar um domicílio"
"you-must-select-a-group-before-selecting-a-household": "Você deve selecionar um grupo antes de selecionar um domicílio",
"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\").",
"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"
}
},
"household": {
"household": "Domicílio",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "A combinação dos alimentos seleccionados irá fundir a fonte de alimentos e almejar os alimentos num único alimento. O alimento de origem será eliminado e todas as referências ao alimento de origem serão actualizadas de modo a indicar os alimentos visados.",
"merge-food-example": "Mesclando {food1} em {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Já estou pronto, apenas me leve à página inicial",
"common-settings-for-new-sites": "Estas são algumas configurações comuns para novos sites",
"setup-complete": "Configuração Concluída!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Aqui há algumas coisas para ajudá-lo a começar com o Mealie",
"restore-from-v1-backup": "Tem uma cópia de segurança de uma instância anterior do Mealie v1? Você pode restaurá-la aqui.",
"manage-profile-or-get-invite-link": "Gerencie seu próprio perfil, ou pegue um link de convite para compartilhar."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Mostrar Avançadas",
"add-field": "Adicionar Campo",
"date-created": "Data de Criação",
"date-updated": "Data de Atualização"
"date-updated": "Data de Atualização",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Tem a certeza que quer eliminar <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.",
"group-id-value": "ID do Grupo: {0}",
"total-households": "Total de Lares",
"you-must-select-a-group-before-selecting-a-household": "Tens de selecionar um grupo antes de selecionar uma casa"
"you-must-select-a-group-before-selecting-a-household": "Tens de selecionar um grupo antes de selecionar uma casa",
"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\").",
"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"
}
},
"household": {
"household": "Casa",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combinar os alimentos selecionados irá juntar o alimento de origem e o alimento alvo num alimento único. O alimento de origem será eliminado e todas as referências a este serão atualizadas para apontar para o alimento alvo.",
"merge-food-example": "A juntar {food1} com {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Já estou pronto, leva-me para a página inicial",
"common-settings-for-new-sites": "Aqui estão algumas configurações comuns para sites novos",
"setup-complete": "Configuração Concluída!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Aqui estão algumas coisas para ajudar a começar com o Mealie",
"restore-from-v1-backup": "Tem uma cópia de segurança de uma instância do Mealie v1? Pode restaurá-la aqui.",
"manage-profile-or-get-invite-link": "Gira o seu próprio perfil ou pegue num convite para partilhar com outros."

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ă",
@@ -223,7 +223,9 @@
"show-advanced": "Arată avansate",
"add-field": "Adaugă câmp",
"date-created": "Data creării",
"date-updated": "Data actualizată"
"date-updated": "Data actualizată",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Sunteți sigur că doriți să ștergeți <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Modificările la acest grup se vor reflecta imediat.",
"group-id-value": "ID grup: {0}",
"total-households": "Total locuințe",
"you-must-select-a-group-before-selecting-a-household": "Trebuie să selectaţi un grup înainte de a selecta o gospodărie"
"you-must-select-a-group-before-selecting-a-household": "Trebuie să selectaţi un grup înainte de a selecta o gospodărie",
"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\").",
"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"
}
},
"household": {
"household": "Locuință",
@@ -333,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",
@@ -640,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?",
@@ -780,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",
@@ -893,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."
},
@@ -911,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ă.",
@@ -943,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",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combinarea alimentelor selectate va fuziona alimentul sursă și alimentul țintă într-un singur aliment. Alimentul sursă va fi șters, iar toate referințele către alimentul sursă vor fi actualizate pentru a indica alimentul țintă.",
"merge-food-example": "Combinarea {food1} în {food2}",
"seed-dialog-text": "Populează baza de date cu alimente în funcție de limba ta locală. Aceasta va crea peste 200 de alimente comune care pot fi utilizate pentru a organiza baza de date. Alimentele sunt traduse printr-un efort comunitar.",
@@ -1146,18 +1183,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"
}
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Sunt deja configurat, du-mă direct la pagina principală",
"common-settings-for-new-sites": "Aici sunt câteva setări comune pentru noile site-uri",
"setup-complete": "Configurare finalizată!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Aici sunt câteva lucruri care te ajută să începi cu Mealie",
"restore-from-v1-backup": "Aveţi o copie de rezervă dintr-o instanţă anterioară a lui Mealie v1? O puteţi restaura aici.",
"manage-profile-or-get-invite-link": "Gestionează-ți propriul profil, sau apasă un link de invitație pentru a distribui cu alții."
@@ -1478,10 +1516,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

@@ -223,7 +223,9 @@
"show-advanced": "Показать расширенные",
"add-field": "Добавить поле",
"date-created": "Дата создания",
"date-updated": "Дата обновления"
"date-updated": "Дата обновления",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Вы действительно хотите удалить <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Изменения в этой группе будут отражены немедленно.",
"group-id-value": "Id группы: {0}",
"total-households": "Всего домохозяйств",
"you-must-select-a-group-before-selecting-a-household": "Вы должны выбрать группу, прежде чем выбирать домохозяйство"
"you-must-select-a-group-before-selecting-a-household": "Вы должны выбрать группу, прежде чем выбирать домохозяйство",
"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\").",
"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"
}
},
"household": {
"household": "Домохозяйство",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Сочетание выбранных продуктов позволит объединить первый и второй продукт в один. Первый продукт будет удалён, и все ссылки на него будут указывать на второй продукт.",
"merge-food-example": "Объединение {food1} в {food2}",
"seed-dialog-text": "Заполняет базу данных с продуктами на основе локального языка. Это добавит ~2700 типичных продуктов, которые могут быть использованы для организации вашей базы данных. Продукты переводятся с помощью усилий сообщества.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Я уже готов, просто открой домашнюю страницу",
"common-settings-for-new-sites": "Ниже приведены общие настройки для новых сайтов",
"setup-complete": "Настройка завершена!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Вот несколько вещей, которые помогут Вам начать работу с Mealie",
"restore-from-v1-backup": "У вас есть резервная копия предыдущего экземпляра Mealie v1? Вы можете восстановить ее здесь.",
"manage-profile-or-get-invite-link": "Управляйте своим профилем или получите ссылку-приглашение, чтобы поделиться ею с другими."

View File

@@ -169,7 +169,7 @@
"token": "Token",
"tuesday": "Utorok",
"type": "Typ",
"undo": "Undo",
"undo": "Vrátiť späť",
"update": "Aktualizovať",
"updated": "Aktualizované",
"upload": "Nahrať",
@@ -223,7 +223,9 @@
"show-advanced": "Zobraziť pokročilé",
"add-field": "Pridať pole",
"date-created": "Dátum vytvorenia",
"date-updated": "Dátum aktualizácie"
"date-updated": "Dátum aktualizácie",
"key": "Kľúč",
"value": "Hodnota"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Naozaj chcete odstrániť <b>{groupName}<b/>?",
@@ -276,14 +278,47 @@
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Skryje pole Potravina, Jednotka a Množstvo surovín a suroviny považuje za textové polia",
"general-preferences": "Všeobecné nastavenia",
"group-recipe-preferences": "Nastavenia receptu v rámci skupiny",
"report": "Report",
"report": "Nahlásiť",
"report-with-id": "ID reportu: {id}",
"group-management": "Spravovanie skupiny",
"admin-group-management": "Spravovanie administrátorskej skupiny",
"admin-group-management-text": "Zmeny týkajúce sa tejto skupiny budú vykonané okamžite.",
"group-id-value": "Id skupiny: {0}",
"total-households": "Domácností celkom",
"you-must-select-a-group-before-selecting-a-household": "Pred výberom domácnosti musíte vybrať skupinu"
"you-must-select-a-group-before-selecting-a-household": "Pred výberom domácnosti musíte vybrať skupinu",
"ai-provider-settings": {
"ai-provider-settings": "Nastavenie poskytovateľa umelej inteligencie (AI)",
"ai-provider": "Poskytovateľ AI",
"ai-providers": "Poskytovatelia AI",
"ai-provider-settings-description": "Nakonfigurujte poskytovateľov AI, aby ste povolili funkcie využívajúce umelú inteligenciu, ako je pokročilé rozpoznávanie ingrediencií, vytváranie receptov z videí a ďalšie!",
"providers": "Poskytovatelia",
"create-provider": "Vytvoriť poskytovateľa AI",
"edit-provider": "Upraviť AI poskytovateľa AI",
"default-provider": "Predvolený poskytovateľ AI",
"default-provider-description": "Vyžaduje sa na povolenie funkcií AI.",
"audio-provider": "Poskytovateľ AI pre zvuk",
"audio-provider-description": "Povoľuje funkcie prepisu zvuku, ako je napríklad vytváranie receptov z videí",
"image-provider": "Poskytovateľ AI pre video",
"image-provider-description": "Povoľuje funkcie rozpoznávania obrázkov, ako je napríklad vytváranie receptov z obrázkov",
"provider-name": "Názov poskytovateľa AI",
"api-key": "API kľúč",
"api-key-description-create": "API kľúč vášho poskytovateľa AI na overenie. Ak vaša služba (napr. Ollama) nepoužíva API kľúč, aj tak sem musíte niečo zadať.",
"api-key-description-edit": "Nechajte prázdne, pokiaľ ho nechcete zmeniť.",
"base-url": "Základná URL adresa",
"base-url-description": "Ak používate OpenAI, nechajte toto pole prázdne. Musí ísť o koncový bod kompatibilný s OpenAI (napr. \"http://localhost:11434/v1\").",
"model": "Model AI",
"model-description": "Ktorý model má váš poskytovateľ AI používať (napr. \"gpt-5\").",
"request-timeout-seconds": "Časový limit požiadavky (sekundy)",
"provider-created": "Poskytovateľ AI bol vytvorený",
"provider-updated": "Poskytovateľ AI bol aktualizovaný",
"provider-deleted": "Poskytovateľ AI bol odstránený",
"provider-create-failed": "Nepodarilo sa vytvoriť poskytovateľa AI",
"provider-update-failed": "Nepodarilo sa aktualizovať poskytovateľa AI",
"provider-delete-failed": "Nepodarilo sa odstrániť poskytovateľa AI",
"request-headers": "Hlavičky požiadavky",
"request-params": "Parametre požiadavky",
"no-default-provider-warning": "Nemáte nastaveného predvoleného poskytovateľa AI, preto sú funkcie AI neaktívne"
}
},
"household": {
"household": "Domácnosť",
@@ -333,8 +368,8 @@
"any-household": "Ľubovoľná domácnosť",
"no-meal-plan-defined-yet": "Nebol vytvorený žiadny jedálniček",
"no-meal-planned-for-today": "Pre dnešok žiadne jedlo v jedálničku",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-hint": "Počet dní do minulosti pri načítaní stránky",
"numberOfDaysPast-label": "Predvolený počet dní do minulosti",
"numberOfDays-hint": "Počet dní na stránke",
"numberOfDays-label": "Predvolený počet dní",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "V jedálničkoch budú použité iba recepty z nasledovných kategórií",
@@ -353,7 +388,7 @@
"dessert": "Dezert",
"type-any": "Ľubovoľný",
"day-any": "Ľubovoľný",
"editor": "Editor",
"editor": "Editovanie",
"meal-recipe": "Recept",
"meal-title": "Názov jedla",
"meal-note": "Poznámka",
@@ -374,8 +409,8 @@
"applies-to-all-days": "Platí pre všetky dni",
"applies-on-days": "Platí v {0}",
"meal-plan-settings": "Nastavenia jedálnička",
"add-all-to-list": "Add All to List",
"add-day-to-list": "Add Day to List"
"add-all-to-list": "Pridať všetko do zoznamu",
"add-day-to-list": "Pridať deň do zoznamu"
},
"migration": {
"migration-data-removed": "Prenesené dáta odstránené",
@@ -392,7 +427,7 @@
"nextcloud": {
"description": "Preniesť dáta z Nextcloud Cookbook",
"description-long": "Recepty z Nextcloud-u je možné importovať priamo zo zip-súboru, ktorý obsahuje dáta uložené na Nextcloud-e. Pozrite sa nižšie na príklad adresárovej štruktúry, aby ste sa uistili, že je možné vaše recepty importovať.",
"title": "Nextcloud Cookbook"
"title": "Kuchárska kniha Nextcloud"
},
"copymethat": {
"description-long": "Mealie dokáže importovať recepty z Copy Me That. Exportujte recepty vo formáte HTML a nižšie nahrajte zip-súbor.",
@@ -628,7 +663,7 @@
"create-recipe-description": "Vytvoriť nový recept od začiatku.",
"create-recipes": "Vytvoriť recept",
"import-with-zip": "Importovať .zip súbor",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Vytvoriť recept z obrázka",
"create-recipe-from-an-image-description": "Vytvoriť recept nahraním fotografie jedla. Mealie sa pokúsi previesť obrázok na text pomocou AI a vytvorí k nemu recept.",
"crop-and-rotate-the-image": "Orežte a otočte obrázok tak, aby bol viditeľný iba text a aby mal správnu orientáciu.",
"create-from-images": "Vytvoriť z obrázka",
@@ -640,8 +675,8 @@
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Vytvoriť recept zadaním názvu. Všetky recepty musia mať jedinečné názvy.",
"new-recipe-names-must-be-unique": "Názvy nových receptov musia byť jedinečné",
"scrape-recipe": "Scrapovať recept",
"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": "Stiahnuť recept zo zadanej adresy URL. Zadajte URL adresu stránky, z ktorej chcete stiahnuť recept. Mealie sa pokúsi recept stiahnuť a vložiť do vašej zbierky.",
"scrape-recipe-description-transcription": "Môžete tiež zadať URL adresu videa a Mealie sa pokúsi prepísať ho do podoby receptu.",
"scrape-recipe-have-a-lot-of-recipes": "Máte množstvo receptov, ktoré by ste chceli naraz zoscrapovať?",
"scrape-recipe-suggest-bulk-importer": "Vyskúšajte hromadný importér",
"scrape-recipe-have-raw-html-or-json-data": "Máte surové údaje HTML alebo JSON?",
@@ -867,7 +902,7 @@
},
"bug-report": "Nahlásiť chybu",
"bug-report-information": "Použite nasledujúcu informáciu na nahlásenie chyby. Poskytnutie detailov o vašej inštalácii vývojárom je najlepší spôsob, ako vyriešiť vaše problémy rýchlo.",
"tracker": "Tracker",
"tracker": "Sledovač",
"configuration": "Nastavenie",
"docker-volume": "Docker objem",
"docker-volume-help": "Mealie vyžaduje, aby frontend a backend zdieľali rovnaký dockerový objem alebo úložisko. Toto umožňuje, aby frontend mohol správne pristupovať k obrázkom a ďalším doplnkom uloženým na disku.",
@@ -893,17 +928,17 @@
"server-side-base-url-error-text": "`BASE_URL` v API servera má stále predvolenú hodnotu. Toto spôsobuje problémy pri vytváraní odkazov na upozornenia prostredníctvom e-mailov a pod.",
"server-side-base-url-success-text": "Súčasná URL servera nesúhlasí s predvolenou hodnotou",
"ldap-ready": "LDAP nastavené",
"ldap-not-ready": "LDAP Not Ready",
"ldap-not-ready": "LDAP nie je pripravený",
"ldap-ready-error-text": "Niektoré z LDAP hodnôt nie sú nakonfigurované. Toto môžete ignorovať, ak nepoužívate LDAP autentifikáciu.",
"ldap-ready-success-text": "Všetky potrebné LDAP premenné sú nastavené.",
"build": "Zostavenie",
"recipe-scraper-version": "Verzia scrapera receptov",
"oidc-ready": "OIDC pripravené",
"oidc-not-ready": "OIDC Not Ready",
"oidc-not-ready": "OIDC nie je pripravený",
"oidc-ready-error-text": "Niektoré z OIDC hodnôt nie sú nakonfigurované. Toto varovanie môžete ignorovať, ak nepoužívate OIDC autentifikáciu.",
"oidc-ready-success-text": "Všetky potrebné OIDC premenné sú nastavené.",
"openai-ready": "OpenAI pripravené",
"openai-not-ready": "OpenAI Not Ready",
"openai-not-ready": "OpenAI nie je pripravený",
"openai-ready-error-text": "Nie všetky hodnoty pre OpenAI sú nakonfigurované. Toto môžete ignorovať, ak nepoužívate funkcie OpenAI.",
"openai-ready-success-text": "Všetky požadované premenné pre OpenAI sú nastavené."
},
@@ -911,13 +946,13 @@
"all-lists": "Všetky zoznamy",
"create-shopping-list": "Vytvoriť nákupný zoznam",
"from-recipe": "Podľa receptu",
"ingredient-of-recipe": "Ingredient of {recipe}",
"ingredient-of-recipe": "Ingrediencie {recipe}",
"list-name": "Názov zoznamu",
"new-list": "Nový zoznam",
"quantity": "Množstvo: {0}",
"shopping-list": "Nákupný zoznam",
"shopping-lists": "Nákupné zoznamy",
"add-item": "Add item",
"add-item": "Pridať položku",
"food": "Jedlo",
"note": "Poznámka",
"label": "Štítok",
@@ -943,7 +978,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Naozaj chcete zrušiť označenie všetkých položiek?",
"are-you-sure-you-want-to-delete-checked-items": "Naozaj chcete odstrániť všetky označené položky?",
"no-shopping-lists-found": "Nenašli sa žiadne nákupné zoznamy",
"item-checked-off": "Checked off {item}"
"item-checked-off": "Označené ako vybavené: {item}"
},
"sidebar": {
"all-recipes": "Všetky recepty",
@@ -962,7 +997,7 @@
"language": "Jazyk",
"maintenance": "Údržba",
"background-tasks": "Úlohy na pozadí",
"parser": "Parser",
"parser": "Analyzátor prísad",
"developer": "Vývojár",
"cookbook": "Kuchárka",
"create-cookbook": "Vytvoriť novú kuchárku"
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Upozornenie: táto potravina sa používa v {count} receptoch (receptoch). Jej vymazaním zostane v recepte (receptoch) prázdna zložka.",
"delete-affects-recipes-more": "Zobraziť všetkých {count} receptov",
"merge-dialog-text": "Kombináciou vybraných potravín sa zdrojové jedlo a cieľové jedlo zlúčia do jedného jedla. Zdrojové jedlo bude vymazané a všetky odkazy na zdrojové jedlo budú aktualizované tak, aby ukazovali na cieľové jedlo.",
"merge-food-example": "Zlučujem {food1} do {food2}",
"seed-dialog-text": "Naplňte databázu potravinami vo vašom jazyku. Tým vytvoríte viac ako 2700 bežných potravín, ktoré môžete použiť na organizovanie vašej databáze. Potraviny sú preložené vďaka úsiliu komunity.",
@@ -1146,17 +1183,17 @@
"example-unit-plural": "napr.: polievkové lyžice",
"example-unit-abbreviation-singular": "napr.: PL",
"example-unit-abbreviation-plural": "napr.: PL",
"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": "Štandardizácia",
"standardization-description": "Ako môže byť táto jednotka vyjadrená ako štandardná jednotka. To umožňuje funkcie prevodu jednotiek ako je napríklad spájanie kompatibilných jednotiek v nákupných zoznamoch.",
"standard-unit": "Štandardná jednotka",
"standard-quantity": "Štandardná jednotka množstva",
"unit-conversion": "Premena jednotiek",
"standard-unit-labels": {
"fluid-ounce": "fluid ounce",
"cup": "cup",
"ounce": "ounce",
"pound": "pound",
"milliliter": "milliliter",
"fluid-ounce": "tekutá unca",
"cup": "šálka",
"ounce": "unca",
"pound": "libra",
"milliliter": "mililiter",
"liter": "liter",
"gram": "gram",
"kilogram": "kilogram"
@@ -1196,8 +1233,8 @@
"edit-recipe-action": "Upraviť akciu receptu",
"action-type": "Typ akcie",
"action-types": {
"link": "Link",
"post": "Post"
"link": "Odkaz",
"post": "Publikovať"
}
},
"create-alias": "Vytvoriť alias",
@@ -1351,7 +1388,7 @@
"ingredient-text": "Text prísady",
"average-confident": "{0} Spoľahlivý",
"try-an-example": "Skúste príklad",
"parser": "Parser",
"parser": "Analyzátor prísad",
"background-tasks": "Úlohy na pozadí",
"background-tasks-description": "Tu môžete vidieť všetky úlohy bežiace na pozadí aj s ich aktuálnym stavom",
"no-logs-found": "Neboli nájdené žiadne záznamy",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Som pripravený začať, vezmi ma na hlavnú stránku",
"common-settings-for-new-sites": "Toto sú bežné nastavenia nových inštalácií",
"setup-complete": "Nastavenie je dokončené!",
"ai-providers-description": "Voliteľne môžete nakonfigurovať poskytovateľov AI pre vašu skupinu. Poskytovatelia AI umožňujú funkcie, ako je vytváranie receptov z obrázkov, importovanie receptov z videí a pokročilé rozpoznávanie ingrediencií. Tieto nastavenia môžete kedykoľvek neskôr upraviť v nastaveniach vašej skupiny.",
"here-are-a-few-things-to-help-you-get-started": "Tu je niekoľko bodov, ktoré vám pomôžu na začiatku v Mealie",
"restore-from-v1-backup": "Máte zálohu z predchádzajúcej inštalácie Mealie v1? Tu ju môžete obnoviť.",
"manage-profile-or-get-invite-link": "Spravujte svoj profil ako súkromný alebo ho s pomocou pozývacieho linku zdieľajte s ostatnými."
@@ -1453,8 +1491,8 @@
"is-greater-than-or-equal-to": "je väčšie alebo rovné",
"is-less-than": "je menšie ako",
"is-less-than-or-equal-to": "je menšie alebo rovné",
"is-older-than": "is older than",
"is-newer-than": "is newer than"
"is-older-than": "je starší ako",
"is-newer-than": "je novší ako"
},
"relational-keywords": {
"is": "je",
@@ -1466,7 +1504,7 @@
"is-not-like": "nie je ako"
},
"dates": {
"days-ago": "days ago|day ago|days ago"
"days-ago": "dní dozadu"
}
},
"validators": {
@@ -1475,13 +1513,13 @@
"invalid-url": "Musí byť platná URL",
"no-whitespace": "Prázdne znaky nepovolené",
"min-length": "Musí mať aspoň {min} znakov",
"max-length": "Must Be At Most {max} Character|Must Be At Most {max} Characters"
"max-length": "Musí byť najviac {max} znakov|Musí byť najviac {max} znakov"
},
"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": "Oznámenia",
"all-announcements": "Všetky oznámenia",
"mark-all-as-read": "Označiť všetko ako prečítané",
"show-announcements-from-mealie": "Zobraziť oznámenia od Mealie",
"show-announcements-setting-description": "Povoliť používateľom zobrazovanie oznámení od Mealie. Ak je táto možnosť zapnutá, používatelia sa môžu aj naďalej odhlásiť z ich zobrazovania vo svojich používateľských nastaveniach."
}
}

View File

@@ -223,7 +223,9 @@
"show-advanced": "Prikaži napredno",
"add-field": "Dodaj polje",
"date-created": "Datum nastanka",
"date-updated": "Datum posodobitve"
"date-updated": "Datum posodobitve",
"key": "Ključ",
"value": "Vrednost"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Ste prepričani, da želite izbrisati <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Spremembe v tej skupini se poznajo takoj.",
"group-id-value": "ID skupine: {0}",
"total-households": "Skupaj gospodinjstev",
"you-must-select-a-group-before-selecting-a-household": "Preden izberete gospodinjstvo, morate izbrati skupino"
"you-must-select-a-group-before-selecting-a-household": "Preden izberete gospodinjstvo, morate izbrati skupino",
"ai-provider-settings": {
"ai-provider-settings": "Nastavitve ponudnika umetne inteligence",
"ai-provider": "Ponudnik umetne inteligence",
"ai-providers": "Ponudniki umetne inteligence",
"ai-provider-settings-description": "Konfigurirajte ponudnike umetne inteligence, da omogočite funkcije, ki jih poganja umetna inteligenca, kot so izboljšano razčlenjevanje sestavin, ustvarjanje receptov iz videoposnetkov in še več!",
"providers": "Ponudniki",
"create-provider": "Ustvari ponudnika",
"edit-provider": "Uredi ponudnika",
"default-provider": "Privzeti ponudnik",
"default-provider-description": "Potrebno za omogočanje funkcij umetne inteligence",
"audio-provider": "Ponudnik zvoka",
"audio-provider-description": "Omogoča funkcije prepisovanja zvoka, kot je ustvarjanje receptov iz videoposnetkov",
"image-provider": "Ponudnik slik",
"image-provider-description": "Omogoča funkcije prepoznavanja slik, kot je ustvarjanje receptov iz slik",
"provider-name": "Ime ponudnika",
"api-key": "API Ključ",
"api-key-description-create": "Ključ API vašega ponudnika za preverjanje pristnosti. Če vaša storitev (npr. Ollama) ne uporablja ključa API, morate vseeno nekaj vnesti tukaj.",
"api-key-description-edit": "Pustite to prazno, razen če želite to spremeniti.",
"base-url": "Osnovni URL",
"base-url-description": "Če uporabljate OpenAI, pustite to polje prazno. Mora biti končna točka, združljiva z OpenAI (npr. \"http://localhost:11434/v1\").",
"model": "Model",
"model-description": "Kateri model naj uporablja vaš ponudnik umetne inteligence (npr. \"gpt-5\").",
"request-timeout-seconds": "Časovna omejitev zahteve (sekunde)",
"provider-created": "Ponudnik ustvarjen",
"provider-updated": "Ponudnik posodobljen",
"provider-deleted": "Ponudnik izbrisan",
"provider-create-failed": "Ustvarjanje ponudnika ni uspelo",
"provider-update-failed": "Ponudnika ni bilo mogoče posodobiti",
"provider-delete-failed": "Brisanje ponudnika ni uspelo",
"request-headers": "Glave zahtev",
"request-params": "Parametri zahteve",
"no-default-provider-warning": "Niste nastavili privzetega ponudnika, zato so funkcije umetne inteligence onemogočene"
}
},
"household": {
"household": "Gospodinjstvo",
@@ -628,7 +663,7 @@
"create-recipe-description": "Ustvari nov recept.",
"create-recipes": "Ustvari recepte",
"import-with-zip": "Uvozi z .zip",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-images": "Ustvari recept iz slik",
"create-recipe-from-an-image-description": "Ustvarite recept tako, da naložite njegovo sliko. Mealie bo s pomočjo umetne inteligence poskušal izluščiti besedilo iz slike in iz njega ustvariti recept.",
"crop-and-rotate-the-image": "Obrežite in zasukajte sliko, tako da bo vidno samo besedilo in da bo v pravilnem položaju.",
"create-from-images": "Ustvari iz slik",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Opozorilo: ta jed se uporablja v {count} receptih. Če jo izbrišete, bo v receptu(-ih) ostala prazna sestavina.",
"delete-affects-recipes-more": "Oglejte si vse recepte ({count})",
"merge-dialog-text": "Združitev izbranih živil bo združila izvorno živilo in ciljno živilo v eno samo živilo. Izvorno živilo bo izbrisano in vse povezave na izvorno živilo, bodo po novem kazale na ciljno živilo.",
"merge-food-example": "Združujem {food1} v {food2}",
"seed-dialog-text": "Napolnite zbirko podatkov z živili, ki temeljijo na vašem lokalnem jeziku. Tako boste ustvarili več kot 200 običajnih živil, ki jih lahko uporabite za organizacijo vaše zbirke podatkov. Živila so prevedena s prizadevanjem skupnosti.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Vse imam že nastavljeno, odpri glavno stran",
"common-settings-for-new-sites": "Tule je par pogostih nastavitev za nove spletne strani",
"setup-complete": "Namestitev je zaključena!",
"ai-providers-description": "Po želji lahko konfigurirate ponudnike umetne inteligence za svojo skupino. Ponudniki umetne inteligence omogočajo funkcije, kot so ustvarjanje receptov iz slik, uvoz receptov iz videoposnetkov in izboljšano razčlenjevanje sestavin. To lahko vedno konfigurirate pozneje v nastavitvah skupine.",
"here-are-a-few-things-to-help-you-get-started": "Par stvari, ki to bodo pomagale začeti z Mealie",
"restore-from-v1-backup": "Imaš varnostno kopijo iz predhodnje instance Mealie v1? Obnoviš jo lahko tule.",
"manage-profile-or-get-invite-link": "Upravljaj s svojim profilom ali skopiraj povezavo z vabilom in jo deli z drugimi."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Прикажи напредно",
"add-field": "Додај поље",
"date-created": "Датум креирања",
"date-updated": "Датум измене"
"date-updated": "Датум измене",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Да ли сте сигурни да желите да обришете <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Промене у овој групи биће одмах видљиве.",
"group-id-value": "ИД групе: {0}",
"total-households": "Укупно домаћинстава",
"you-must-select-a-group-before-selecting-a-household": "Морате селектовати групу пре селектовања домаћинства"
"you-must-select-a-group-before-selecting-a-household": "Морате селектовати групу пре селектовања домаћинства",
"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\").",
"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"
}
},
"household": {
"household": "Домаћинство",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Комбиновање изабраних намирница спојиће изворну и циљну храну у једну храну. Изворна храна ће бити избрисана и све референце на изворну храну биће ажуриране да показују на циљну храну.",
"merge-food-example": "Спајање {food1} у {food2}",
"seed-dialog-text": "Попуните базу података храном на основу вашег локалног језика. Ово ће креирати преко 200 уобичајених намирница које се могу користити за организовање ваше базе података. Намирнице се преводе залагањем заједнице.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Већ сам подешен, само ме одведи на почетну страницу",
"common-settings-for-new-sites": "Ево неколико уобичајених подешавања за нове сајтове",
"setup-complete": "Подешавање је завршено!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Ево неколико ствари које ће вам помоћи да започнете са Mealieјем",
"restore-from-v1-backup": "Имате резервну копију из претходне инстанце Mealie v1? Можете је вратити овде.",
"manage-profile-or-get-invite-link": "Управљајте сопственим профилом или узмите линк за позивницу да бисте га поделили са другима."

View File

@@ -98,7 +98,7 @@
"dashboard": "Startsida",
"delete": "Ta bort",
"disabled": "Inaktiverad",
"done": "Done",
"done": "Färdig",
"download": "Ladda ner",
"duplicate": "Duplicera",
"edit": "Redigera",
@@ -223,7 +223,9 @@
"show-advanced": "Visa avancerat",
"add-field": "Lägg till fält",
"date-created": "Datum skapad",
"date-updated": "Datum uppdaterat"
"date-updated": "Datum uppdaterat",
"key": "Nyckel",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Är du säker på att du vill radera <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Ändringar i denna grupp kommer att återspeglas omedelbart.",
"group-id-value": "Grupp-Id: {0}",
"total-households": "Totalt antal hushåll",
"you-must-select-a-group-before-selecting-a-household": "Du måste välja en grupp innan du kan välja ett hushåll"
"you-must-select-a-group-before-selecting-a-household": "Du måste välja en grupp innan du kan välja ett hushåll",
"ai-provider-settings": {
"ai-provider-settings": "AI Provider Settings",
"ai-provider": "AI-leverantör",
"ai-providers": "AI-leverantörer",
"ai-provider-settings-description": "Konfigurera AI-leverantörer för att aktivera AI-drivna funktioner, såsom förbättrad ingrediensanalysering, skapa recept från videor och mycket mer!",
"providers": "Leverantörer",
"create-provider": "Skapa leverantör",
"edit-provider": "Ändra leverantör",
"default-provider": "Standard leverantör",
"default-provider-description": "Krävs för att aktivera AI-funktioner",
"audio-provider": "Ljudleverantör",
"audio-provider-description": "Möjliggör funktioner för ljudtranskription, till exempel att skapa recept från videor",
"image-provider": "Bildleverantör",
"image-provider-description": "Enables image recognition features, such as creating recipes from images",
"provider-name": "Leverantörsnamn",
"api-key": "API-nyckel",
"api-key-description-create": "Din leverantörs API-nyckel för autentisering. Om din tjänst (t.ex. Ollama) inte använder en API-nyckel, måste du fortfarande sätta något här.",
"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\").",
"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"
}
},
"household": {
"household": "Hushåll",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Kombinera valda livsmedel kommer att slå samman de valda livsmedlen till ett livsmedel. Ursprungslivsmedlet kommer att raderas och alla hänvisningar till detta kommer att uppdateras för att peka på det kombinerade livsmedlet.",
"merge-food-example": "Slå ihop {food1} till {food2}",
"seed-dialog-text": "Fyll databasen med livsmedel baserat på ditt språk. Detta kommer att skapa över 200 vanliga livsmedel som kan användas för att organisera din databas. Alla livsmedel är översatta med genom en kollektiv insats.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Jag har redan gjort inställningarna, ta mig bara till hemsidan",
"common-settings-for-new-sites": "Här är några vanliga inställningar för nya webbplatser",
"setup-complete": "Konfigurationen slutförd!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Här är några saker som hjälper dig att komma igång med Mealie",
"restore-from-v1-backup": "Har du en säkerhetskopia från en tidigare instans av Mealie v1? Du kan återställa den här.",
"manage-profile-or-get-invite-link": "Hantera din egen profil eller hämta en inbjudningslänk för att dela med andra."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Gelişmiş Göster",
"add-field": "Alan Ekle",
"date-created": "Oluşturma Tarihi",
"date-updated": "Güncellenme tarihi"
"date-updated": "Güncellenme tarihi",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/>'i silmek istediğine emin misin?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Bu gruptaki değişiklikler hemen yansıtılacaktır.",
"group-id-value": "Grup Kimliği: {0}",
"total-households": "Toplam Hane Halkı",
"you-must-select-a-group-before-selecting-a-household": " Bir hane seçmeden önce bir grup seçmelisiniz"
"you-must-select-a-group-before-selecting-a-household": " Bir hane seçmeden önce bir grup seçmelisiniz",
"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\").",
"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"
}
},
"household": {
"household": "Ev Halkı",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Seçilen gıdaların birleştirilmesi, kaynak gıdayı ve hedef gıdayı tek bir gıdada birleştirecektir. Kaynak gıda silinecek ve kaynak gıdaya yapılan tüm referanslar, hedef gıdayı işaret edecek şekilde güncellenecektir.",
"merge-food-example": "{food1}, {food2} ile birleştiriliyor",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Kurulumu zaten yaptım, beni yalnızca ana sayfaya götür",
"common-settings-for-new-sites": "Yeni siteler için bazı ortak ayarlar şunlardır",
"setup-complete": "Kurulum tamamlandı!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Mealie'yi kullanmaya başlamanıza yardımcı olacak birkaç şey",
"restore-from-v1-backup": "Mealie v1'in önceki örneğinden bir yedeğiniz mi var? Buradan geri yükleyebilirsiniz.",
"manage-profile-or-get-invite-link": "Kendi profilinizi yönetin veya başkalarıyla paylaşmak için bir davet bağlantısı alın."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Показати розширені",
"add-field": "Додати поле",
"date-created": "Дата Створення",
"date-updated": "Дата Оновлення"
"date-updated": "Дата Оновлення",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Ви дійсно бажаєте видалити <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Зміни до цієї групи будуть відображені негайно.",
"group-id-value": "Id групи: {0}",
"total-households": "Всього сімей",
"you-must-select-a-group-before-selecting-a-household": "Ви маєте вибрати групу перед тим, як вибирати сім'ю"
"you-must-select-a-group-before-selecting-a-household": "Ви маєте вибрати групу перед тим, як вибирати сім'ю",
"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\").",
"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"
}
},
"household": {
"household": "Сімʼя",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Об’єднання вибраних продуктів створить єдину позицію. При цьому вихідний продукт буде видалено, а всі посилання на нього автоматично буде оновлено на цільовий продукт.",
"merge-food-example": "Об'єднання {food1} в {food2}",
"seed-dialog-text": "Заповніть базу даних продуктами вашою мовою. Це дозволить створити близько 2700 поширених продуктів, які можна використовувати для організації вашої бази даних. Продукти перекладаються спільними зусиллями спільноти.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "Вже все налаштовано, просто перенеси мене на домашню сторінку",
"common-settings-for-new-sites": "Ось деякі загальні налаштування для нових сайтів",
"setup-complete": "Налаштування завершене!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Ось кілька речей, які допоможуть вам почати з Mealie",
"restore-from-v1-backup": "Маєте резервну копію з попереднього екземпляра Меаліе v1? Ви можете використати його тут.",
"manage-profile-or-get-invite-link": "Керуйте вашим власним профілем, або отримайте посилання-запрошення щоб поділитися з іншими."

View File

@@ -223,7 +223,9 @@
"show-advanced": "Show Advanced",
"add-field": "Add Field",
"date-created": "Date Created",
"date-updated": "Date Updated"
"date-updated": "Date Updated",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "Changes to this group will be reflected immediately.",
"group-id-value": "Group Id: {0}",
"total-households": "Total Households",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household",
"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\").",
"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"
}
},
"household": {
"household": "Household",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
"merge-food-example": "Merging {food1} into {food2}",
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."

View File

@@ -223,7 +223,9 @@
"show-advanced": "显示进阶设置",
"add-field": "添加项目",
"date-created": "创建日期",
"date-updated": "修改日期"
"date-updated": "修改日期",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "您确定要删除<b>{groupName}<b/>吗?",
@@ -283,7 +285,40 @@
"admin-group-management-text": "对本群组的更改将被立即应用。",
"group-id-value": "群组ID{0}",
"total-households": "总共家庭",
"you-must-select-a-group-before-selecting-a-household": "你必须先选择一个组才能选择一个家庭"
"you-must-select-a-group-before-selecting-a-household": "你必须先选择一个组才能选择一个家庭",
"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\").",
"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"
}
},
"household": {
"household": "家庭",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "此操作将把“待合并食物”和“目标食物”合并为一个食物(即“目标食物”)。 “待合并食物”将会被删除 ,之前所有对“待合并食物”的引用都会指向“目标食物”。",
"merge-food-example": "正在将{food1}合并到{food2} ",
"seed-dialog-text": "根据你当地的语言在数据库中输入食物。这将创建大约2700种常见的食物可以用来组织你的数据库。食物是通过社区努力翻译的。",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "我已经配置好了,直接跳转到主页",
"common-settings-for-new-sites": "这有一些新站点的常见设置",
"setup-complete": "配置完成!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "以下这些可以帮助你开始使用Mealie",
"restore-from-v1-backup": "有之前Mealie v1实例的备份数据你可以在这里恢复它们。",
"manage-profile-or-get-invite-link": "管理你自己的个人资料,或者获取邀请链接分享给其他人。"

View File

@@ -223,7 +223,9 @@
"show-advanced": "顯示進階選項",
"add-field": "新增欄位",
"date-created": "建立日期",
"date-updated": "更新日期"
"date-updated": "更新日期",
"key": "Key",
"value": "Value"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "確定要刪除 <b>{groupName}</b>",
@@ -283,7 +285,40 @@
"admin-group-management-text": "對此群組的變更將立即生效。",
"group-id-value": "群組 ID{0}",
"total-households": "家庭群組總數",
"you-must-select-a-group-before-selecting-a-household": "請先選擇群組,再選擇家庭群組"
"you-must-select-a-group-before-selecting-a-household": "請先選擇群組,再選擇家庭群組",
"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\").",
"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"
}
},
"household": {
"household": "家庭群組",
@@ -1109,6 +1144,8 @@
},
"data-pages": {
"foods": {
"delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).",
"delete-affects-recipes-more": "View all {count} recipes",
"merge-dialog-text": "合併所選食材將把來源食材與目標食材合併為單一食材。來源食材將被刪除,所有對來源食材的參照將更新為指向目標食材。",
"merge-food-example": "將 {food1} 合併至 {food2}",
"seed-dialog-text": "根據您的本地語言,將常見食材植入資料庫。這將建立 200 種以上可用於整理資料庫的常見食材。食材翻譯由社群共同維護。",
@@ -1362,6 +1399,7 @@
"already-set-up-bring-to-homepage": "我已完成設定,直接帶我到首頁",
"common-settings-for-new-sites": "以下是新站台的常用設定",
"setup-complete": "設定完成!",
"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.",
"here-are-a-few-things-to-help-you-get-started": "以下幾項可幫助您開始使用 Mealie",
"restore-from-v1-backup": "有舊版 Mealie v1 的備份?您可以在這裡還原。",
"manage-profile-or-get-invite-link": "管理您的個人資料,或取得邀請連結與他人分享。"

View File

@@ -0,0 +1,27 @@
import { BaseAPI } from "../base/base-clients";
import type { AIProviderCreate, AIProviderOut, AIProviderUpdate } from "~/lib/api/types/group";
const prefix = "/api/admin";
const routes = {
providers: (groupId: string) => `${prefix}/groups/${groupId}/ai-providers/providers`,
providersId: (groupId: string, providerId: string) => `${prefix}/groups/${groupId}/ai-providers/providers/${providerId}`,
};
export class AdminAIProvidersApi extends BaseAPI {
async createProvider(groupId: string, payload: AIProviderCreate) {
return await this.requests.post<AIProviderOut>(routes.providers(groupId), payload);
}
async getProvider(groupId: string, providerId: string) {
return await this.requests.get<AIProviderOut>(routes.providersId(groupId, providerId));
}
async updateProvider(groupId: string, providerId: string, payload: AIProviderUpdate) {
return await this.requests.put<AIProviderOut>(routes.providersId(groupId, providerId), payload);
}
async deleteProvider(groupId: string, providerId: string) {
return await this.requests.delete<AIProviderOut>(routes.providersId(groupId, providerId));
}
}

View File

@@ -4,11 +4,11 @@ import type { DebugResponse } from "~/lib/api/types/admin";
const prefix = "/api";
const routes = {
openai: `${prefix}/admin/debug/openai`,
openai: providerId => `${prefix}/admin/debug/openai/${providerId}`,
};
export class AdminDebugAPI extends BaseAPI {
async debugOpenAI(fileObject: Blob | File | undefined = undefined, fileName = "") {
async debugOpenAI(providerId: string, fileObject: Blob | File | undefined = undefined, fileName = "") {
let formData: FormData | null = null;
if (fileObject) {
formData = new FormData();
@@ -16,6 +16,6 @@ export class AdminDebugAPI extends BaseAPI {
formData.append("extension", fileName.split(".").pop() ?? "");
}
return await this.requests.post<DebugResponse>(routes.openai, formData);
return await this.requests.post<DebugResponse>(routes.openai(providerId), formData);
}
}

View File

@@ -6,6 +6,7 @@ import { AdminBackupsApi } from "./admin/admin-backups";
import { AdminMaintenanceApi } from "./admin/admin-maintenance";
import { AdminAnalyticsApi } from "./admin/admin-analytics";
import { AdminDebugAPI } from "./admin/admin-debug";
import { AdminAIProvidersApi } from "./admin/admin-ai-providers";
import type { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class AdminAPI {
@@ -17,6 +18,7 @@ export class AdminAPI {
public maintenance: AdminMaintenanceApi;
public analytics: AdminAnalyticsApi;
public debug: AdminDebugAPI;
public aiProviders: AdminAIProvidersApi;
constructor(requests: ApiRequestInstance) {
this.about = new AdminAboutAPI(requests);
@@ -27,6 +29,7 @@ export class AdminAPI {
this.maintenance = new AdminMaintenanceApi(requests);
this.analytics = new AdminAnalyticsApi(requests);
this.debug = new AdminDebugAPI(requests);
this.aiProviders = new AdminAIProvidersApi(requests);
Object.freeze(this);
}

View File

@@ -24,6 +24,7 @@ import { MultiPurposeLabelsApi } from "./user/group-multiple-purpose-labels";
import { GroupEventNotifierApi } from "./user/group-event-notifier";
import { MealPlanRulesApi } from "./user/group-mealplan-rules";
import { GroupDataSeederApi } from "./user/group-seeder";
import { AIProvidersAPI } from "./user/group-ai-providers";
import type { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class UserApiClient {
@@ -53,6 +54,7 @@ export class UserApiClient {
public groupEventNotifier: GroupEventNotifierApi;
public upload: UploadFile;
public seeders: GroupDataSeederApi;
public aiProviders: AIProvidersAPI;
constructor(requests: ApiRequestInstance) {
// Recipes
@@ -80,6 +82,7 @@ export class UserApiClient {
this.shopping = new ShoppingApi(requests);
this.multiPurposeLabels = new MultiPurposeLabelsApi(requests);
this.seeders = new GroupDataSeederApi(requests);
this.aiProviders = new AIProvidersAPI(requests);
// Admin
this.backups = new BackupAPI(requests);

View File

@@ -16,9 +16,6 @@ export interface AdminAboutInfo {
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
enableOpenai: boolean;
enableOpenaiImageServices: boolean;
enableOpenaiTranscriptionServices: boolean;
tokenTime: number;
versionLatest: string;
apiPort: number;
@@ -50,10 +47,8 @@ export interface AppInfo {
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
enableOpenai: boolean;
enableOpenaiImageServices: boolean;
enableOpenaiTranscriptionServices: boolean;
tokenTime: number;
allowedIframeHosts?: string[];
}
export interface AppStartupInfo {
isFirstLogin: boolean;
@@ -95,7 +90,6 @@ export interface CheckAppConfig {
emailReady: boolean;
ldapReady: boolean;
oidcReady: boolean;
enableOpenai: boolean;
baseUrlSet: boolean;
isUpToDate: boolean;
}

View File

@@ -17,6 +17,77 @@ export type SupportedMigrations =
| "recipekeeper"
| "cookn";
export interface AIProviderCreate {
name: string;
baseUrl?: string | null;
model: string;
timeout?: number;
requestHeaders?: {
[k: string]: string;
};
requestParams?: {
[k: string]: string;
};
}
export interface AIProviderOut {
name: string;
baseUrl?: string | null;
model: string;
timeout?: number;
requestHeaders?: {
[k: string]: string;
};
requestParams?: {
[k: string]: string;
};
id: string;
}
export interface AIProviderSave {
name: string;
baseUrl?: string | null;
model: string;
timeout?: number;
requestHeaders?: {
[k: string]: string;
};
requestParams?: {
[k: string]: string;
};
settingsId: string;
}
export interface AIProviderSettingsCreate {
groupId: string;
}
export interface AIProviderSettingsOut {
defaultProviderId: string | null;
audioProviderId: string | null;
imageProviderId: string | null;
providers: AIProviderSummary[];
aiEnabled: boolean;
audioProviderEnabled: boolean;
imageProviderEnabled: boolean;
}
export interface AIProviderSummary {
id: string;
name: string;
}
export interface AIProviderSettingsUpdate {
defaultProviderId: string | null;
audioProviderId: string | null;
imageProviderId: string | null;
}
export interface AIProviderUpdate {
name: string;
baseUrl?: string | null;
model: string;
timeout?: number;
requestHeaders?: {
[k: string]: string;
};
requestParams?: {
[k: string]: string;
};
}
export interface CreateGroupPreferences {
privateGroup?: boolean;
showAnnouncements?: boolean;
@@ -29,6 +100,7 @@ export interface GroupAdminUpdate {
id: string;
name: string;
preferences?: UpdateGroupPreferences | null;
aiProviderSettings?: AIProviderSettingsUpdate | null;
}
export interface UpdateGroupPreferences {
privateGroup?: boolean;

View File

@@ -59,6 +59,7 @@ export interface GroupInDB {
households?: GroupHouseholdSummary[] | null;
users?: UserSummary[] | null;
preferences?: ReadGroupPreferences | null;
aiProviderSettings?: AIProviderSettingsOut | null;
}
export interface CategoryBase {
name: string;
@@ -89,11 +90,25 @@ export interface ReadGroupPreferences {
groupId: string;
id: string;
}
export interface AIProviderSettingsOut {
defaultProviderId: string | null;
audioProviderId: string | null;
imageProviderId: string | null;
providers: AIProviderSummary[];
aiEnabled: boolean;
audioProviderEnabled: boolean;
imageProviderEnabled: boolean;
}
export interface AIProviderSummary {
id: string;
name: string;
}
export interface GroupSummary {
name: string;
id: string;
slug: string;
preferences?: ReadGroupPreferences | null;
aiProviderSettings?: AIProviderSettingsOut | null;
}
export interface LongLiveTokenCreateResponse {
name: string;

View File

@@ -0,0 +1,27 @@
import { BaseAPI } from "../base/base-clients";
import type { AIProviderCreate, AIProviderOut, AIProviderUpdate } from "~/lib/api/types/group";
const prefix = "/api/groups/ai-providers";
const routes = {
providers: `${prefix}/providers`,
providersId: (id: string) => `${prefix}/providers/${id}`,
};
export class AIProvidersAPI extends BaseAPI {
async getOne(id: string) {
return await this.requests.get<AIProviderOut>(routes.providersId(id));
}
async createOne(payload: AIProviderCreate) {
return await this.requests.post<AIProviderOut>(routes.providers, payload);
}
async updateOne(id: string, payload: AIProviderUpdate) {
return await this.requests.put<AIProviderOut, AIProviderUpdate>(routes.providersId(id), payload);
}
async deleteOne(id: string) {
return await this.requests.delete<AIProviderOut>(routes.providersId(id));
}
}

View File

@@ -3,6 +3,8 @@ import type { PaginationData } from "../types/non-generated";
import type { QueryValue } from "../base/route";
import type { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user";
import type {
AIProviderSettingsUpdate,
AIProviderSettingsOut,
GroupAdminUpdate,
GroupStorage,
ReadGroupPreferences,
@@ -15,6 +17,7 @@ const routes = {
groups: `${prefix}/admin/groups`,
groupsSelf: `${prefix}/groups/self`,
preferences: `${prefix}/groups/preferences`,
aiProviderSettings: `${prefix}/groups/ai-providers/settings`,
storage: `${prefix}/groups/storage`,
members: `${prefix}/groups/members`,
groupsId: (id: string | number) => `${prefix}/admin/groups/${id}`,
@@ -29,15 +32,15 @@ export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate
return await this.requests.get<GroupSummary>(routes.groupsSelf);
}
async getPreferences() {
return await this.requests.get<ReadGroupPreferences>(routes.preferences);
}
async setPreferences(payload: UpdateGroupPreferences) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<ReadGroupPreferences, UpdateGroupPreferences>(routes.preferences, payload);
}
async setAIProviderSettings(payload: AIProviderSettingsUpdate) {
return await this.requests.put<AIProviderSettingsOut, AIProviderSettingsUpdate>(routes.aiProviderSettings, payload);
}
async fetchMembers(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
return await this.requests.get<PaginationData<UserSummary>>(routes.members, { page, perPage, ...params });
}

View File

@@ -43,10 +43,6 @@ export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> {
return await this.requests.get<HouseholdRecipeSummary>(routes.householdsSelfRecipesSlug(recipeSlug));
}
async getPreferences() {
return await this.requests.get<ReadHouseholdPreferences>(routes.preferences);
}
async setPreferences(payload: UpdateHouseholdPreferences) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<ReadHouseholdPreferences, UpdateHouseholdPreferences>(routes.preferences, payload);

View File

@@ -0,0 +1,74 @@
import { describe, expect, test } from "vitest";
import { sanitizeMarkdownHtml } from "./markdown";
describe("sanitizeMarkdownHtml", () => {
test("returns empty string for nullish input", () => {
expect(sanitizeMarkdownHtml(null)).toEqual("");
expect(sanitizeMarkdownHtml(undefined)).toEqual("");
expect(sanitizeMarkdownHtml("")).toEqual("");
});
test("keeps allowed formatting tags", () => {
const html = sanitizeMarkdownHtml("<p>Mix <strong>flour</strong> and <em>water</em></p>");
expect(html).toContain("<strong>flour</strong>");
expect(html).toContain("<em>water</em>");
});
test("strips script tags and event handlers", () => {
const html = sanitizeMarkdownHtml("<p onclick=\"alert(1)\">hi</p><script>alert(1)</script>");
expect(html).not.toContain("script");
expect(html).not.toContain("onclick");
expect(html).not.toContain("alert");
});
test("strips img onerror payloads", () => {
const html = sanitizeMarkdownHtml("<img src=x onerror=alert(1)>");
expect(html).not.toContain("onerror");
});
// Form controls must never render in user content.
test("strips form, input, and button elements", () => {
const html = sanitizeMarkdownHtml("<form action=/x><input name=p><button>go</button></form>");
expect(html).not.toContain("<form");
expect(html).not.toContain("<input");
expect(html).not.toContain("<button");
});
test("strips iframes when no allowed hosts are configured", () => {
const html = sanitizeMarkdownHtml("<iframe src=\"https://evil.example/x\"></iframe>", []);
expect(html).not.toContain("<iframe");
});
test("strips iframes whose src host is not allowlisted", () => {
const html = sanitizeMarkdownHtml(
"<iframe src=\"https://evil.example/x\"></iframe>",
["youtube.com"],
);
expect(html).not.toContain("<iframe");
});
test("strips non-https iframes even for an allowlisted host", () => {
const html = sanitizeMarkdownHtml(
"<iframe src=\"http://www.youtube.com/embed/abc\"></iframe>",
["youtube.com"],
);
expect(html).not.toContain("<iframe");
});
test("keeps iframes from an allowlisted host (incl. subdomains)", () => {
const html = sanitizeMarkdownHtml(
"<iframe src=\"https://www.youtube.com/embed/abc\"></iframe>",
["youtube.com"],
);
expect(html).toContain("<iframe");
expect(html).toContain("https://www.youtube.com/embed/abc");
});
test("does not allow a lookalike host to pass the suffix check", () => {
const html = sanitizeMarkdownHtml(
"<iframe src=\"https://notyoutube.com/embed/abc\"></iframe>",
["youtube.com"],
);
expect(html).not.toContain("<iframe");
});
});

View File

@@ -0,0 +1,98 @@
import DOMPurify from "isomorphic-dompurify";
enum DOMPurifyHook {
UponSanitizeAttribute = "uponSanitizeAttribute",
AfterSanitizeAttributes = "afterSanitizeAttributes",
}
const ALLOWED_STYLE_PROPERTIES = [
"background-color", "color", "font-style", "font-weight", "text-decoration", "text-align",
];
const BASE_ALLOWED_TAGS = [
"strong", "em", "b", "i", "u", "p", "code", "pre", "samp", "kbd", "var", "sub", "sup", "dfn", "cite",
"small", "address", "hr", "br", "id", "div", "span", "h1", "h2", "h3", "h4", "h5", "h6",
"ul", "ol", "li", "dl", "dt", "dd", "abbr", "a", "img", "blockquote",
"del", "ins", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "colgroup",
];
const BASE_ALLOWED_ATTR = [
"href", "src", "alt", "height", "width", "class", "title",
"cite", "datetime", "name", "abbr", "target", "border", "start", "style",
];
// Attributes only meaningful on an <iframe>; added to the allowlist solely when iframe embeds
// are enabled via a configured host allowlist.
const IFRAME_ALLOWED_ATTR = ["allow", "allowfullscreen", "frameborder", "scrolling"];
/**
* Returns true if an iframe `src` points at one of the allowed hosts. Only https URLs are
* accepted, and a configured host matches the URL's hostname exactly or as a parent domain
* (e.g. "youtube.com" matches "www.youtube.com").
*/
function isAllowedIframeSrc(src: string, allowedHosts: string[]): boolean {
let url: URL;
try {
url = new URL(src);
}
catch {
return false;
}
if (url.protocol !== "https:") {
return false;
}
const hostname = url.hostname.toLowerCase();
return allowedHosts.some((host) => {
const allowed = host.toLowerCase();
return hostname === allowed || hostname.endsWith(`.${allowed}`);
});
}
/**
* Sanitizes pre-rendered HTML (from markdown) for display in user content such as recipe
* instructions, notes, and descriptions.
*
* Only the tags in `BASE_ALLOWED_TAGS` and attributes in `BASE_ALLOWED_ATTR` survive; everything
* else (scripts, event handlers, form controls, ...) is dropped. `style` attributes are filtered
* down to the properties in `ALLOWED_STYLE_PROPERTIES`. `<iframe>` is only kept when
* `allowedIframeHosts` is non-empty, and even then any iframe whose `src` is not an https URL on
* the host allowlist is removed.
*/
export function sanitizeMarkdownHtml(rawHtml: string | null | undefined, allowedIframeHosts: string[] = []): string {
if (!rawHtml) {
return "";
}
const allowIframe = allowedIframeHosts.length > 0;
DOMPurify.addHook(DOMPurifyHook.UponSanitizeAttribute, (_node, data) => {
if (data.attrName === "style") {
const styles = data.attrValue.split(";").filter((style) => {
const [property] = style.split(":");
return ALLOWED_STYLE_PROPERTIES.includes(property.trim().toLowerCase());
});
data.attrValue = styles.join(";");
}
});
if (allowIframe) {
DOMPurify.addHook(DOMPurifyHook.AfterSanitizeAttributes, (node) => {
if (node.nodeName === "IFRAME" && !isAllowedIframeSrc(node.getAttribute("src") || "", allowedIframeHosts)) {
node.parentNode?.removeChild(node);
}
});
}
const sanitized = DOMPurify.sanitize(rawHtml, {
ALLOWED_TAGS: allowIframe ? [...BASE_ALLOWED_TAGS, "iframe"] : BASE_ALLOWED_TAGS,
ALLOWED_ATTR: allowIframe ? [...BASE_ALLOWED_ATTR, ...IFRAME_ALLOWED_ATTR] : BASE_ALLOWED_ATTR,
});
Object.values(DOMPurifyHook).forEach((hook) => {
DOMPurify.removeHook(hook);
});
return sanitized;
}

View File

@@ -0,0 +1,35 @@
import { describe, expect, test, vi } from "vitest";
import { truncateText } from "./text";
describe("truncateText", () => {
test("returns short text unchanged", () => {
expect(truncateText("Dinner")).toEqual("Dinner");
});
test("truncates long text with clamp", () => {
expect(truncateText("a".repeat(25))).toEqual(`${"a".repeat(20)}...`);
});
test("respects custom length and clamp", () => {
expect(truncateText("abcdef", 3, "~")).toEqual("abc~");
});
test("does not clamp text exactly at the length boundary", () => {
expect(truncateText("abcde", 5)).toEqual("abcde");
expect(truncateText("abcdef", 5)).toEqual("abcde...");
});
// Markup in the input must be treated as plain text and never parsed into the live document.
test("does not parse or execute HTML payloads", () => {
const createElement = vi.spyOn(document, "createElement");
const payload = "<img src=x onerror=alert(1)>";
const result = truncateText(payload);
// The payload is returned verbatim (truncated only by length), proving it is treated as text.
expect(result).toEqual(`${payload.slice(0, 20)}...`);
// No DOM element is constructed, so no <img> can fire its onerror handler.
expect(createElement).not.toHaveBeenCalled();
createElement.mockRestore();
});
});

View File

@@ -0,0 +1,9 @@
/**
* Truncates plain text to `length` characters, appending `clamp` when truncated.
*
* The input is treated strictly as text and is never parsed as HTML, so markup in the input is
* returned verbatim rather than interpreted.
*/
export function truncateText(text: string, length = 20, clamp = "..."): string {
return text.length > length ? text.slice(0, length) + clamp : text;
}

View File

@@ -6,7 +6,7 @@
<br>
<DocLink
class="mt-2"
link="/documentation/getting-started/installation/open-ai"
link="/documentation/getting-started/installation/ai-providers"
/>
</BaseCardSectionTitle>
</v-container>
@@ -17,6 +17,36 @@
<div>
<v-card-text>
<v-container class="pa-0">
<v-row>
<v-col cols="12" md="6">
<v-select
v-if="groups"
v-model="selectedGroupId"
:items="groups"
item-title="name"
item-value="id"
:label="$t('group.group')"
density="compact"
variant="outlined"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="selectedProviderId"
:items="groupProviders"
item-title="name"
item-value="id"
:label="$t('group.ai-provider-settings.ai-provider')"
density="compact"
variant="outlined"
clearable
hide-details
:disabled="!selectedGroupId"
/>
</v-col>
</v-row>
<v-row>
<v-col
cols="auto"
@@ -61,6 +91,7 @@
<v-card-actions>
<BaseButton
type="submit"
:disabled="!selectedProviderId"
:text="$t('admin.run-test')"
:icon="$globals.icons.check"
:loading="loading"
@@ -85,7 +116,9 @@
<script setup lang="ts">
import { useAdminApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { alert } from "~/composables/use-toast";
import type { AIProviderSummary } from "~/lib/api/types/group";
definePageMeta({
layout: "admin",
@@ -106,10 +139,24 @@ const uploadedImage = ref<Blob | File>();
const uploadedImageName = ref<string>("");
const uploadedImagePreviewUrl = ref<string>();
function uploadImage(fileObject: File) {
uploadedImage.value = fileObject;
uploadedImageName.value = fileObject.name;
uploadedImagePreviewUrl.value = URL.createObjectURL(fileObject);
// Group + provider selection
const { groups } = useGroups();
const selectedGroupId = ref<string | null>(null);
const groupProviders = ref<AIProviderSummary[]>([]);
const selectedProviderId = ref<string | null>(null);
watch(selectedGroupId, (id) => {
groupProviders.value = [];
selectedProviderId.value = null;
if (!id) return;
const group = groups.value?.find(g => g.id === id);
groupProviders.value = group?.aiProviderSettings?.providers ?? [];
});
function uploadImage(fileObject: unknown) {
uploadedImage.value = fileObject as File;
uploadedImageName.value = (fileObject as File).name;
uploadedImagePreviewUrl.value = URL.createObjectURL(fileObject as File);
}
function clearImage() {
@@ -119,10 +166,15 @@ function clearImage() {
}
async function testOpenAI() {
if (!selectedProviderId.value) {
alert.error("Please select a provider");
return;
}
response.value = "";
loading.value = true;
const { data } = await api.debug.debugOpenAI(uploadedImage.value);
const { data } = await api.debug.debugOpenAI(selectedProviderId.value, uploadedImage.value);
loading.value = false;
if (!data) {

Some files were not shown because too many files have changed in this diff Show More