mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-27 02:03:13 -05:00
Compare commits
340 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
418a8ec72b | ||
|
|
770630bf73 | ||
|
|
89ee7475a6 | ||
|
|
bca5dd8282 | ||
|
|
dabd93c919 | ||
|
|
6991dff3e6 | ||
|
|
b0eece789d | ||
|
|
9fad4a9dce | ||
|
|
22d8c4d5dc | ||
|
|
7be24d3479 | ||
|
|
fbceb61b9a | ||
|
|
1be5bfaef1 | ||
|
|
fac1df31d3 | ||
|
|
6957e2fa74 | ||
|
|
4f02fae284 | ||
|
|
f2615c97e9 | ||
|
|
6b4c9a400d | ||
|
|
cca11b5a12 | ||
|
|
f697a7ee34 | ||
|
|
0d73338e12 | ||
|
|
2f4c6bd500 | ||
|
|
3807778e2f | ||
|
|
ee87a14401 | ||
|
|
ec458a0a08 | ||
|
|
2ff37c86d6 | ||
|
|
b7da3c0f73 | ||
|
|
d799136f0d | ||
|
|
d1d5754c6d | ||
|
|
52662fdce2 | ||
|
|
8df6033c19 | ||
|
|
c23660007e | ||
|
|
786aa2279c | ||
|
|
ab8c3be367 | ||
|
|
8bf8dfd3ed | ||
|
|
b3aa7aeb1a | ||
|
|
0f2b1d8d3a | ||
|
|
4de6391684 | ||
|
|
c3e68b7d8a | ||
|
|
7557d2e818 | ||
|
|
c22a2fc4a8 | ||
|
|
ad94a4f42f | ||
|
|
e6bf3b3acd | ||
|
|
711dd93851 | ||
|
|
2b6d7811ca | ||
|
|
3373abf787 | ||
|
|
741d37f59e | ||
|
|
b38c19ce71 | ||
|
|
1a385e941c | ||
|
|
c6f5b62ad0 | ||
|
|
84dad84326 | ||
|
|
f369c8fd6e | ||
|
|
467cf46c6d | ||
|
|
360b8e21d9 | ||
|
|
0b851e79ec | ||
|
|
faf716cb7e | ||
|
|
46f1ad7941 | ||
|
|
6e1112c73e | ||
|
|
6bd5a82b92 | ||
|
|
cba076b6a4 | ||
|
|
2ff1135b00 | ||
|
|
467b9c6d65 | ||
|
|
176e471276 | ||
|
|
193888fb30 | ||
|
|
13edefbf41 | ||
|
|
fd33468fda | ||
|
|
2a541f081a | ||
|
|
8ab09cf03b | ||
|
|
9e6ae2e514 | ||
|
|
94678fe6e0 | ||
|
|
ed533c8fad | ||
|
|
93f7d15917 | ||
|
|
53aa4dab51 | ||
|
|
92659c64eb | ||
|
|
6f871c6bdb | ||
|
|
f4f511aad6 | ||
|
|
03d384f3a5 | ||
|
|
0c2917a112 | ||
|
|
606a8f03a3 | ||
|
|
2d31c0abf2 | ||
|
|
15c752d428 | ||
|
|
b254cf3833 | ||
|
|
0bd023d8a8 | ||
|
|
aad50f2267 | ||
|
|
fcbc57b392 | ||
|
|
a62299e6ef | ||
|
|
82563fa948 | ||
|
|
7583c56b35 | ||
|
|
b9cc2dc257 | ||
|
|
0dcf81e764 | ||
|
|
3d3763d4b9 | ||
|
|
517727a4b6 | ||
|
|
1c26dff1e9 | ||
|
|
ed1834d945 | ||
|
|
bf8bc88ffb | ||
|
|
6c48eba5f7 | ||
|
|
dc7df0d4aa | ||
|
|
45d5194f19 | ||
|
|
8ad1a15bf1 | ||
|
|
57aeb401b8 | ||
|
|
e15a2f35e2 | ||
|
|
b28e135ceb | ||
|
|
148aca5e85 | ||
|
|
1ac7f90c28 | ||
|
|
413a8a82fc | ||
|
|
72c414bf94 | ||
|
|
b67263e63f | ||
|
|
1673eedff7 | ||
|
|
d3ee5f34f8 | ||
|
|
683f1ac69e | ||
|
|
d6d0f7de71 | ||
|
|
dd0eaac45f | ||
|
|
f8e672c7ac | ||
|
|
2aa9d84d6c | ||
|
|
2c13c4760e | ||
|
|
62bf733548 | ||
|
|
2c72ea17a2 | ||
|
|
06406c86f5 | ||
|
|
b7f7712011 | ||
|
|
4b13686261 | ||
|
|
9fade36014 | ||
|
|
eb1d569e95 | ||
|
|
1099e30a1d | ||
|
|
fa9a2d64f7 | ||
|
|
de142c47df | ||
|
|
c990420a87 | ||
|
|
d772e3bb4f | ||
|
|
bb8080475c | ||
|
|
c1e05f57db | ||
|
|
9ad68542e0 | ||
|
|
83997dbb47 | ||
|
|
b5f3c5bef7 | ||
|
|
ddd97cce10 | ||
|
|
0ecd57a50b | ||
|
|
e4efcee0df | ||
|
|
a6920f057e | ||
|
|
dbb212ceda | ||
|
|
5d5805459a | ||
|
|
3e68920e69 | ||
|
|
91c978a309 | ||
|
|
e7c101c96b | ||
|
|
004f3552c0 | ||
|
|
cd56149371 | ||
|
|
8edea0a7e0 | ||
|
|
df15e97026 | ||
|
|
760462e12f | ||
|
|
60793bb560 | ||
|
|
7c84d3dea5 | ||
|
|
eee1c5733d | ||
|
|
cf0a7ae9c9 | ||
|
|
be80d3e74c | ||
|
|
db1fabf5c8 | ||
|
|
e7e73772e0 | ||
|
|
e5cab0e4d0 | ||
|
|
6a14d5b7db | ||
|
|
57106c4cce | ||
|
|
61c6a991f3 | ||
|
|
6824b3c269 | ||
|
|
6b13166880 | ||
|
|
b2747d77e1 | ||
|
|
fc4d1b88d0 | ||
|
|
8798bd6e55 | ||
|
|
cef61ae29f | ||
|
|
e304d48e84 | ||
|
|
4f1a7c55b9 | ||
|
|
bae7acbc3b | ||
|
|
c0cf6a9aca | ||
|
|
f4570faf1a | ||
|
|
9548a7eb70 | ||
|
|
d5e3a1dacb | ||
|
|
1ce760ec7e | ||
|
|
9e23ed1a07 | ||
|
|
b3885cc3f8 | ||
|
|
5da990abd4 | ||
|
|
f709d11952 | ||
|
|
2a3463b746 | ||
|
|
cb7302d2d9 | ||
|
|
a30084a199 | ||
|
|
c0654a5d95 | ||
|
|
737a370874 | ||
|
|
2a2b6f312b | ||
|
|
96d220acbd | ||
|
|
b9a9b8695d | ||
|
|
e80c8a50e6 | ||
|
|
41795799e6 | ||
|
|
9980e49eef | ||
|
|
945810c47a | ||
|
|
a283828461 | ||
|
|
7c365b7c03 | ||
|
|
16da55f58b | ||
|
|
35f6b0e80e | ||
|
|
ab37c2e8c0 | ||
|
|
d1f82df936 | ||
|
|
6c7cb7e795 | ||
|
|
ecf80b8e9c | ||
|
|
e280734e33 | ||
|
|
24d8854723 | ||
|
|
2f9b711973 | ||
|
|
2b09495e87 | ||
|
|
ae5a1a9af2 | ||
|
|
a312c4dbf3 | ||
|
|
79fb1fb299 | ||
|
|
b7c1cdfd46 | ||
|
|
09873d4814 | ||
|
|
7f596c653b | ||
|
|
bb06b0414b | ||
|
|
6b720bafd6 | ||
|
|
6449591143 | ||
|
|
8fb43246b2 | ||
|
|
95d3fd4958 | ||
|
|
dfbc890f2c | ||
|
|
21886ab4b8 | ||
|
|
4b0df3ace8 | ||
|
|
7505b5cf65 | ||
|
|
45e71da402 | ||
|
|
3ded63dfdf | ||
|
|
735c3e3146 | ||
|
|
83861cfcb8 | ||
|
|
c22ff8ccad | ||
|
|
9a17a484f3 | ||
|
|
bc6734399f | ||
|
|
8e6f2a3d61 | ||
|
|
20fa3a25f2 | ||
|
|
1a9f5470ca | ||
|
|
2254d114be | ||
|
|
ef22d29ef1 | ||
|
|
ef165cd276 | ||
|
|
e6477920ce | ||
|
|
c49584d027 | ||
|
|
e56eabf1e2 | ||
|
|
a35f2ae56a | ||
|
|
2a4a195dcb | ||
|
|
6318e8d1c6 | ||
|
|
b7a4899302 | ||
|
|
7fe6ef4da5 | ||
|
|
0391763d18 | ||
|
|
3250384862 | ||
|
|
1bf496751c | ||
|
|
580700458c | ||
|
|
6c6276cb79 | ||
|
|
787f9293fb | ||
|
|
aa0547ae69 | ||
|
|
1c6b0f1122 | ||
|
|
014721b6f7 | ||
|
|
692d8a5681 | ||
|
|
120d4cfc5a | ||
|
|
e3b3f70621 | ||
|
|
c5e55a2207 | ||
|
|
f6d508af92 | ||
|
|
516a732b12 | ||
|
|
dfd4943304 | ||
|
|
511e08e7e9 | ||
|
|
ed50dfc145 | ||
|
|
ee542255a5 | ||
|
|
32fa9d4439 | ||
|
|
f2be3383ac | ||
|
|
fd6874e8dd | ||
|
|
09e514fa84 | ||
|
|
f4678f99ed | ||
|
|
4e9670acf6 | ||
|
|
fb097ca095 | ||
|
|
8f40d13f20 | ||
|
|
2aaef9ae54 | ||
|
|
a6d31638e2 | ||
|
|
f73aefce4e | ||
|
|
ff5131018b | ||
|
|
1c6c5042ae | ||
|
|
d770009e0d | ||
|
|
4a0b211f27 | ||
|
|
7dcf2ca33d | ||
|
|
95c15504d0 | ||
|
|
4e38625bde | ||
|
|
4a411f0483 | ||
|
|
95b6d901bf | ||
|
|
c9d74e25ac | ||
|
|
54aef24caf | ||
|
|
6050b1e25a | ||
|
|
b54cef2702 | ||
|
|
abb2c8110c | ||
|
|
f1e8d633fc | ||
|
|
c4f60942b5 | ||
|
|
abbaf12e9e | ||
|
|
04e6601d5b | ||
|
|
1ebc7d27d4 | ||
|
|
59d53a02b3 | ||
|
|
370da5aee3 | ||
|
|
af2992eee9 | ||
|
|
e17b8b813c | ||
|
|
9b4e0dd0d9 | ||
|
|
d7bc1c75da | ||
|
|
b963b99a4c | ||
|
|
9270e22f19 | ||
|
|
7df34890d4 | ||
|
|
36b0661e1d | ||
|
|
6da50b576f | ||
|
|
517491e507 | ||
|
|
82aca1f77f | ||
|
|
8c4f657aa7 | ||
|
|
4352762e93 | ||
|
|
ad0ea09be9 | ||
|
|
52c6fe34b2 | ||
|
|
b6ccb9fbdb | ||
|
|
f83ab6ecc1 | ||
|
|
1af0f426ae | ||
|
|
d960947258 | ||
|
|
f2735ba22b | ||
|
|
aa4c02ef30 | ||
|
|
f1fbf0d120 | ||
|
|
63a362a48a | ||
|
|
e0d7341139 | ||
|
|
5f5b06683a | ||
|
|
42523bbfc9 | ||
|
|
0a344731c8 | ||
|
|
e83fa89ec4 | ||
|
|
13cd7a1c0f | ||
|
|
0e23a41bdb | ||
|
|
e17b320dc8 | ||
|
|
403038a5b2 | ||
|
|
430e1d7d4e | ||
|
|
c44cd7ffab | ||
|
|
73dfb523ec | ||
|
|
21d57735c9 | ||
|
|
05e13e6078 | ||
|
|
5f6844eceb | ||
|
|
bea1a592d7 | ||
|
|
b54cdf6425 | ||
|
|
02da2114f9 | ||
|
|
a67533a778 | ||
|
|
59ad834c12 | ||
|
|
315d5b370e | ||
|
|
130813ffe4 | ||
|
|
65ddb7c9e2 | ||
|
|
dbe29e15ae | ||
|
|
980b3c634b | ||
|
|
457d8c93ce | ||
|
|
23aad6358c | ||
|
|
7c896361f2 | ||
|
|
5b7f5738e3 | ||
|
|
5bfcb80c98 | ||
|
|
b1278b45e2 | ||
|
|
e7ae76ea48 |
@@ -30,7 +30,6 @@
|
|||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"matangover.mypy",
|
"matangover.mypy",
|
||||||
"ms-python.black-formatter",
|
"ms-python.black-formatter",
|
||||||
"ms-python.isort",
|
|
||||||
"ms-python.pylint",
|
"ms-python.pylint",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
@@ -42,6 +41,7 @@
|
|||||||
"forwardPorts": [
|
"forwardPorts": [
|
||||||
3000,
|
3000,
|
||||||
9000,
|
9000,
|
||||||
|
9091, // used by docker production
|
||||||
24678 // used by nuxt when hot-reloading using polling
|
24678 // used by nuxt when hot-reloading using polling
|
||||||
],
|
],
|
||||||
// Use 'onCreateCommand' to run commands at the end of container creation.
|
// Use 'onCreateCommand' to run commands at the end of container creation.
|
||||||
|
|||||||
9
.github/DISCUSSION_TEMPLATE/oauth-provider-example.yaml
vendored
Normal file
9
.github/DISCUSSION_TEMPLATE/oauth-provider-example.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: OAuth setup with <PROVIDER>
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Configuration Example
|
||||||
|
description: Add your example configuration. You can provide code blocks, screenshots, and links.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
12
.github/ISSUE_TEMPLATE/task.yaml
vendored
12
.github/ISSUE_TEMPLATE/task.yaml
vendored
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: v1.0.0b Task
|
name: Task
|
||||||
description: "CONTRIBUTORS ONLY: Submit a Task that needs to be completed"
|
description: "CONTRIBUTORS ONLY: Submit a Task that needs to be completed"
|
||||||
title: "[v1.0.0b] [Task] - TASK DESCRIPTION"
|
title: "[Task] - TASK DESCRIPTION"
|
||||||
labels:
|
labels:
|
||||||
- task
|
- task
|
||||||
- v1
|
- v1
|
||||||
@@ -11,17 +11,17 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
Thanks for your interest in Mealie! 🚀
|
Thanks for your interest in Mealie! 🚀
|
||||||
|
|
||||||
This is a place for Mealie contributors to find tasks that need to get done around the repository. Tasks are different than issues as they are generally related to providing a new feature or improve an existing feature. They are _generally_ not related to an issue.
|
This is a place for Mealie contributors to find tasks that need to get done around the repository. Tasks are different than issues as they are generally related to providing a new feature or improving an existing feature. They are _generally_ not related to an issue.
|
||||||
|
|
||||||
**DO NOT** create a task unless
|
**DO NOT** create a task unless
|
||||||
- You are a contributors who has prior approval via discord/discussions
|
- You are a contributor who has prior approval via discord/discussions
|
||||||
- You have otherwise been given approval to post the tasks
|
- You have otherwise been given approval to post the tasks
|
||||||
|
|
||||||
Otherwise, your post will be closed/deleted.
|
Otherwise, your post will be closed/deleted.
|
||||||
|
|
||||||
**Interested in Taking This?**
|
**Interested in Taking This?**
|
||||||
|
|
||||||
If you're interested in completing this tasks and it hasn't already been taken, comment below and to let others know you're working on it. As you work through the task, I ask that you submit a draft pull request as soon as possible, and tag this issue so we can all collaborate as best as possible.
|
If you're interested in completing this task and it hasn't already been taken, comment below and to let others know you're working on it. As you work through the task, I ask that you submit a draft pull request as soon as possible, and tag this issue so we can all collaborate as best as possible.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: problem
|
id: problem
|
||||||
attributes:
|
attributes:
|
||||||
@@ -33,6 +33,6 @@ body:
|
|||||||
id: solution
|
id: solution
|
||||||
attributes:
|
attributes:
|
||||||
label: Proposed/Possible Solution(s)?
|
label: Proposed/Possible Solution(s)?
|
||||||
placeholder: Provide as much context around the idea as possible with potential files and roadblocks that may come up
|
placeholder: Provide as much context around the idea as possible with potential files and roadblocks that may come up.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
46
.github/workflows/e2e.yml
vendored
Normal file
46
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: E2E Tests
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- mealie-next
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./tests/e2e
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./tests/e2e/yarn.lock
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build Image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
file: ./docker/Dockerfile
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: mealie:e2e
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
- name: Deploy E2E Test Environment
|
||||||
|
run: docker compose up -d
|
||||||
|
working-directory: ./tests/e2e/docker
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install -g yarn && yarn
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: yarn playwright install --with-deps
|
||||||
|
- name: Check test environment
|
||||||
|
run: docker ps
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: yarn playwright test
|
||||||
|
- name: Destroy Test Environment
|
||||||
|
if: always()
|
||||||
|
run: docker compose down --volumes
|
||||||
|
working-directory: ./tests/e2e/docker
|
||||||
6
.github/workflows/partial-backend.yml
vendored
6
.github/workflows/partial-backend.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
|||||||
id: cache-validate
|
id: cache-validate
|
||||||
if: steps.cached-poetry-dependencies.outputs.cache-hit == 'true'
|
if: steps.cached-poetry-dependencies.outputs.cache-hit == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "import black;print('venv good?')" > test.py && poetry run python test.py && echo "cache-hit-success=true" >> $GITHUB_OUTPUT
|
echo "import fastapi;print('venv good?')" > test.py && poetry run python test.py && echo "cache-hit-success=true" >> $GITHUB_OUTPUT
|
||||||
rm test.py
|
rm test.py
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
@@ -78,9 +78,9 @@ jobs:
|
|||||||
poetry add "psycopg2-binary==2.9.9"
|
poetry add "psycopg2-binary==2.9.9"
|
||||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-hit-success != 'true'
|
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' || steps.cache-validate.outputs.cache-hit-success != 'true'
|
||||||
|
|
||||||
- name: Formatting (Black)
|
- name: Formatting (Ruff)
|
||||||
run: |
|
run: |
|
||||||
poetry run black . --check
|
poetry run ruff format . --check
|
||||||
|
|
||||||
- name: Lint (Ruff)
|
- name: Lint (Ruff)
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -68,6 +68,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
# This doesn't currently work for us because it creates the PR but the workflows don't run.
|
||||||
|
# TODO: Provide a personal access token as a parameter here, that solves that problem.
|
||||||
|
# https://github.com/peter-evans/create-pull-request
|
||||||
with:
|
with:
|
||||||
commit-message: "Update image tag, for release ${{ github.event.release.tag_name }}"
|
commit-message: "Update image tag, for release ${{ github.event.release.tag_name }}"
|
||||||
branch: "docs/newrelease-update-version-${{ github.event.release.tag_name }}"
|
branch: "docs/newrelease-update-version-${{ github.event.release.tag_name }}"
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ repos:
|
|||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude: ^tests/data/
|
exclude: ^tests/data/
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: 24.1.0
|
# Ruff version.
|
||||||
|
rev: v0.4.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: ruff-format
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -60,8 +60,5 @@
|
|||||||
},
|
},
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": false
|
||||||
},
|
}
|
||||||
"[python]": {
|
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
.vscode/tasks.json
vendored
10
.vscode/tasks.json
vendored
@@ -24,16 +24,6 @@
|
|||||||
},
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Init Database",
|
|
||||||
"command": "poetry run python mealie/db/init_db.py",
|
|
||||||
"type": "shell",
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "always",
|
|
||||||
"group": "groupA"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Dev: Start Frontend",
|
"label": "Dev: Start Frontend",
|
||||||
"command": "task ui",
|
"command": "task ui",
|
||||||
|
|||||||
13
Taskfile.yml
13
Taskfile.yml
@@ -74,6 +74,7 @@ tasks:
|
|||||||
desc: run code generators
|
desc: run code generators
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python dev/code-generation/main.py
|
- poetry run python dev/code-generation/main.py
|
||||||
|
- task: py:format
|
||||||
|
|
||||||
dev:services:
|
dev:services:
|
||||||
desc: starts postgres and mailpit containers
|
desc: starts postgres and mailpit containers
|
||||||
@@ -105,12 +106,12 @@ tasks:
|
|||||||
py:format:
|
py:format:
|
||||||
desc: runs python code formatter
|
desc: runs python code formatter
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run black mealie
|
- poetry run ruff format .
|
||||||
|
|
||||||
py:lint:
|
py:lint:
|
||||||
desc: runs python linter
|
desc: runs python linter
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run ruff mealie
|
- poetry run ruff check mealie
|
||||||
|
|
||||||
py:check:
|
py:check:
|
||||||
desc: runs all linters, type checkers, and formatters
|
desc: runs all linters, type checkers, and formatters
|
||||||
@@ -132,7 +133,6 @@ tasks:
|
|||||||
py:
|
py:
|
||||||
desc: runs the backend server
|
desc: runs the backend server
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python mealie/db/init_db.py
|
|
||||||
- poetry run python mealie/app.py
|
- poetry run python mealie/app.py
|
||||||
|
|
||||||
py:postgres:
|
py:postgres:
|
||||||
@@ -145,9 +145,14 @@ tasks:
|
|||||||
POSTGRES_PORT: 5432
|
POSTGRES_PORT: 5432
|
||||||
POSTGRES_DB: mealie
|
POSTGRES_DB: mealie
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python mealie/db/init_db.py
|
|
||||||
- poetry run python mealie/app.py
|
- poetry run python mealie/app.py
|
||||||
|
|
||||||
|
py:migrate:
|
||||||
|
desc: generates a new migration file e.g. task py:migrate:generate "add new column"
|
||||||
|
cmds:
|
||||||
|
- poetry run alembic revision --autogenerate -m "{{ .CLI_ARGS }}"
|
||||||
|
- task: py:format
|
||||||
|
|
||||||
ui:build:
|
ui:build:
|
||||||
desc: builds the frontend in frontend/dist
|
desc: builds the frontend in frontend/dist
|
||||||
dir: frontend
|
dir: frontend
|
||||||
|
|||||||
12
alembic.ini
12
alembic.ini
@@ -58,15 +58,3 @@ sqlalchemy.url =
|
|||||||
# post_write_hooks defines scripts or Python functions that are run
|
# post_write_hooks defines scripts or Python functions that are run
|
||||||
# on newly generated revision scripts. See the documentation for further
|
# on newly generated revision scripts. See the documentation for further
|
||||||
# detail and examples
|
# detail and examples
|
||||||
|
|
||||||
hooks = isort, black
|
|
||||||
|
|
||||||
# format using "isort" - use the console_scripts runner, against the "isort" entrypoint
|
|
||||||
isort.type = console_scripts
|
|
||||||
isort.entrypoint = isort
|
|
||||||
isort.options = REVISION_SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
|
||||||
black.type = console_scripts
|
|
||||||
black.entrypoint = black
|
|
||||||
black.options = REVISION_SCRIPT_FILENAME
|
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ target_metadata = SqlAlchemyBase.metadata
|
|||||||
|
|
||||||
# Set DB url from config
|
# Set DB url from config
|
||||||
settings = get_app_settings()
|
settings = get_app_settings()
|
||||||
config.set_main_option("sqlalchemy.url", settings.DB_URL)
|
|
||||||
|
if not settings.DB_URL:
|
||||||
|
raise Exception("DB URL not set in config")
|
||||||
|
|
||||||
|
config.set_main_option("sqlalchemy.url", settings.DB_URL.replace("%", "%%"))
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline():
|
def run_migrations_offline():
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ from sqlalchemy import orm
|
|||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
from mealie.core.root_logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "2298bb460ffd"
|
revision = "2298bb460ffd"
|
||||||
@@ -37,13 +40,20 @@ def find_user_id_for_group(group_id: UUID):
|
|||||||
with session:
|
with session:
|
||||||
try:
|
try:
|
||||||
# try to find an admin user
|
# try to find an admin user
|
||||||
user_id = session.execute(sa.text(stmt).bindparams(group_id=group_id)).scalar_one()
|
return session.execute(sa.text(stmt).bindparams(group_id=group_id)).scalar_one()
|
||||||
except orm.exc.NoResultFound:
|
except orm.exc.NoResultFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
# fallback to any user
|
# fallback to any user
|
||||||
user_id = session.execute(
|
return session.execute(
|
||||||
sa.text("SELECT id FROM users WHERE group_id=:group_id LIMIT 1").bindparams(group_id=group_id)
|
sa.text("SELECT id FROM users WHERE group_id=:group_id LIMIT 1").bindparams(group_id=group_id)
|
||||||
).scalar_one()
|
).scalar_one()
|
||||||
return user_id
|
except orm.exc.NoResultFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# no user could be found
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def populate_shopping_list_users():
|
def populate_shopping_list_users():
|
||||||
@@ -54,11 +64,17 @@ def populate_shopping_list_users():
|
|||||||
list_ids_and_group_ids = session.execute(sa.text("SELECT id, group_id FROM shopping_lists")).all()
|
list_ids_and_group_ids = session.execute(sa.text("SELECT id, group_id FROM shopping_lists")).all()
|
||||||
for list_id, group_id in list_ids_and_group_ids:
|
for list_id, group_id in list_ids_and_group_ids:
|
||||||
user_id = find_user_id_for_group(group_id)
|
user_id = find_user_id_for_group(group_id)
|
||||||
session.execute(
|
if user_id:
|
||||||
sa.text(f"UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams(
|
session.execute(
|
||||||
user_id=user_id, id=list_id
|
sa.text(f"UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams(
|
||||||
|
user_id=user_id, id=list_id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"No user found for shopping list {list_id} with group {group_id}; deleting shopping list"
|
||||||
|
)
|
||||||
|
session.execute(sa.text(f"DELETE FROM shopping_lists WHERE id=:id").bindparams(id=list_id))
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""add OIDC auth method
|
||||||
|
|
||||||
|
Revision ID: 09aba125b57a
|
||||||
|
Revises: 2298bb460ffd
|
||||||
|
Create Date: 2024-03-10 05:08:32.397027
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
import mealie.db.migration_types
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "09aba125b57a"
|
||||||
|
down_revision = "2298bb460ffd"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_postgres():
|
||||||
|
return op.get_context().dialect.name == "postgresql"
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
if is_postgres():
|
||||||
|
op.execute("ALTER TYPE authmethod ADD VALUE 'OIDC'")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
"""migrate favorites and ratings to user_ratings
|
||||||
|
|
||||||
|
Revision ID: d7c6efd2de42
|
||||||
|
Revises: 09aba125b57a
|
||||||
|
Create Date: 2024-03-18 02:28:15.896959
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from textwrap import dedent
|
||||||
|
from typing import Any
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
import mealie.db.migration_types
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "d7c6efd2de42"
|
||||||
|
down_revision = "09aba125b57a"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_postgres():
|
||||||
|
return op.get_context().dialect.name == "postgresql"
|
||||||
|
|
||||||
|
|
||||||
|
def new_user_rating(user_id: Any, recipe_id: Any, rating: float | None = None, is_favorite: bool = False):
|
||||||
|
if is_postgres():
|
||||||
|
id = str(uuid4())
|
||||||
|
else:
|
||||||
|
id = "%.32x" % uuid4().int
|
||||||
|
|
||||||
|
now = datetime.now().isoformat()
|
||||||
|
return {
|
||||||
|
"id": id,
|
||||||
|
"user_id": user_id,
|
||||||
|
"recipe_id": recipe_id,
|
||||||
|
"rating": rating,
|
||||||
|
"is_favorite": is_favorite,
|
||||||
|
"created_at": now,
|
||||||
|
"update_at": now,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_user_favorites_to_user_ratings():
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
|
with session:
|
||||||
|
user_ids_and_recipe_ids = session.execute(sa.text("SELECT user_id, recipe_id FROM users_to_favorites")).all()
|
||||||
|
rows = [
|
||||||
|
new_user_rating(user_id, recipe_id, is_favorite=True)
|
||||||
|
for user_id, recipe_id in user_ids_and_recipe_ids
|
||||||
|
if user_id and recipe_id
|
||||||
|
]
|
||||||
|
|
||||||
|
if is_postgres():
|
||||||
|
query = dedent(
|
||||||
|
"""
|
||||||
|
INSERT INTO users_to_recipes (id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
|
||||||
|
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at)
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
query = dedent(
|
||||||
|
"""
|
||||||
|
INSERT OR IGNORE INTO users_to_recipes
|
||||||
|
(id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
|
||||||
|
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
session.execute(sa.text(query), row)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_group_to_user_ratings(group_id: Any):
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
|
with session:
|
||||||
|
user_ids = (
|
||||||
|
session.execute(sa.text("SELECT id FROM users WHERE group_id=:group_id").bindparams(group_id=group_id))
|
||||||
|
.scalars()
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
recipe_ids_ratings = session.execute(
|
||||||
|
sa.text(
|
||||||
|
"SELECT id, rating FROM recipes WHERE group_id=:group_id AND rating > 0 AND rating IS NOT NULL"
|
||||||
|
).bindparams(group_id=group_id)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
# Convert recipe ratings to user ratings. Since we don't know who
|
||||||
|
# rated the recipe initially, we copy the rating to all users.
|
||||||
|
rows: list[dict] = []
|
||||||
|
for recipe_id, rating in recipe_ids_ratings:
|
||||||
|
for user_id in user_ids:
|
||||||
|
rows.append(new_user_rating(user_id, recipe_id, rating, is_favorite=False))
|
||||||
|
|
||||||
|
if is_postgres():
|
||||||
|
insert_query = dedent(
|
||||||
|
"""
|
||||||
|
INSERT INTO users_to_recipes (id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
|
||||||
|
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at)
|
||||||
|
ON CONFLICT (user_id, recipe_id) DO NOTHING;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
insert_query = dedent(
|
||||||
|
"""
|
||||||
|
INSERT OR IGNORE INTO users_to_recipes
|
||||||
|
(id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
|
||||||
|
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
update_query = dedent(
|
||||||
|
"""
|
||||||
|
UPDATE users_to_recipes
|
||||||
|
SET rating = :rating, update_at = :update_at
|
||||||
|
WHERE user_id = :user_id AND recipe_id = :recipe_id;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create new user ratings with is_favorite set to False
|
||||||
|
for row in rows:
|
||||||
|
session.execute(sa.text(insert_query), row)
|
||||||
|
|
||||||
|
# Update existing user ratings with the correct rating
|
||||||
|
for row in rows:
|
||||||
|
session.execute(sa.text(update_query), row)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_to_user_ratings():
|
||||||
|
migrate_user_favorites_to_user_ratings()
|
||||||
|
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
|
with session:
|
||||||
|
group_ids = session.execute(sa.text("SELECT id FROM groups")).scalars().all()
|
||||||
|
|
||||||
|
for group_id in group_ids:
|
||||||
|
migrate_group_to_user_ratings(group_id)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"users_to_recipes",
|
||||||
|
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("rating", sa.Float(), nullable=True),
|
||||||
|
sa.Column("is_favorite", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("update_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["recipe_id"],
|
||||||
|
["recipes.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["users.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("user_id", "recipe_id", "id"),
|
||||||
|
sa.UniqueConstraint("user_id", "recipe_id", name="user_id_recipe_id_rating_key"),
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_users_to_recipes_created_at"), "users_to_recipes", ["created_at"], unique=False)
|
||||||
|
op.create_index(op.f("ix_users_to_recipes_is_favorite"), "users_to_recipes", ["is_favorite"], unique=False)
|
||||||
|
op.create_index(op.f("ix_users_to_recipes_rating"), "users_to_recipes", ["rating"], unique=False)
|
||||||
|
op.create_index(op.f("ix_users_to_recipes_recipe_id"), "users_to_recipes", ["recipe_id"], unique=False)
|
||||||
|
op.create_index(op.f("ix_users_to_recipes_user_id"), "users_to_recipes", ["user_id"], unique=False)
|
||||||
|
|
||||||
|
migrate_to_user_ratings()
|
||||||
|
|
||||||
|
if is_postgres():
|
||||||
|
op.drop_index("ix_users_to_favorites_recipe_id", table_name="users_to_favorites")
|
||||||
|
op.drop_index("ix_users_to_favorites_user_id", table_name="users_to_favorites")
|
||||||
|
op.alter_column("recipes", "rating", existing_type=sa.INTEGER(), type_=sa.Float(), existing_nullable=True)
|
||||||
|
else:
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_users_to_favorites_recipe_id")
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_users_to_favorites_user_id")
|
||||||
|
with op.batch_alter_table("recipes") as batch_op:
|
||||||
|
batch_op.alter_column("rating", existing_type=sa.INTEGER(), type_=sa.Float(), existing_nullable=True)
|
||||||
|
|
||||||
|
op.drop_table("users_to_favorites")
|
||||||
|
op.create_index(op.f("ix_recipes_rating"), "recipes", ["rating"], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"recipes_ingredients", "quantity", existing_type=sa.Float(), type_=sa.INTEGER(), existing_nullable=True
|
||||||
|
)
|
||||||
|
op.drop_index(op.f("ix_recipes_rating"), table_name="recipes")
|
||||||
|
op.alter_column("recipes", "rating", existing_type=sa.Float(), type_=sa.INTEGER(), existing_nullable=True)
|
||||||
|
op.create_unique_constraint("ingredient_units_name_group_id_key", "ingredient_units", ["name", "group_id"])
|
||||||
|
op.create_unique_constraint("ingredient_foods_name_group_id_key", "ingredient_foods", ["name", "group_id"])
|
||||||
|
op.create_table(
|
||||||
|
"users_to_favorites",
|
||||||
|
sa.Column("user_id", sa.CHAR(length=32), nullable=True),
|
||||||
|
sa.Column("recipe_id", sa.CHAR(length=32), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["recipe_id"],
|
||||||
|
["recipes.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["users.id"],
|
||||||
|
),
|
||||||
|
sa.UniqueConstraint("user_id", "recipe_id", name="user_id_recipe_id_key"),
|
||||||
|
)
|
||||||
|
op.create_index("ix_users_to_favorites_user_id", "users_to_favorites", ["user_id"], unique=False)
|
||||||
|
op.create_index("ix_users_to_favorites_recipe_id", "users_to_favorites", ["recipe_id"], unique=False)
|
||||||
|
op.drop_index(op.f("ix_users_to_recipes_user_id"), table_name="users_to_recipes")
|
||||||
|
op.drop_index(op.f("ix_users_to_recipes_recipe_id"), table_name="users_to_recipes")
|
||||||
|
op.drop_index(op.f("ix_users_to_recipes_rating"), table_name="users_to_recipes")
|
||||||
|
op.drop_index(op.f("ix_users_to_recipes_is_favorite"), table_name="users_to_recipes")
|
||||||
|
op.drop_index(op.f("ix_users_to_recipes_created_at"), table_name="users_to_recipes")
|
||||||
|
op.drop_table("users_to_recipes")
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""add group recipe actions
|
||||||
|
|
||||||
|
Revision ID: 7788478a0338
|
||||||
|
Revises: d7c6efd2de42
|
||||||
|
Create Date: 2024-04-07 01:05:20.816270
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
import mealie.db.migration_types
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "7788478a0338"
|
||||||
|
down_revision = "d7c6efd2de42"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"recipe_actions",
|
||||||
|
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("action_type", sa.String(), nullable=False),
|
||||||
|
sa.Column("title", sa.String(), nullable=False),
|
||||||
|
sa.Column("url", sa.String(), nullable=False),
|
||||||
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("update_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["group_id"],
|
||||||
|
["groups.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_recipe_actions_action_type"), "recipe_actions", ["action_type"], unique=False)
|
||||||
|
op.create_index(op.f("ix_recipe_actions_created_at"), "recipe_actions", ["created_at"], unique=False)
|
||||||
|
op.create_index(op.f("ix_recipe_actions_group_id"), "recipe_actions", ["group_id"], unique=False)
|
||||||
|
op.create_index(op.f("ix_recipe_actions_title"), "recipe_actions", ["title"], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f("ix_recipe_actions_title"), table_name="recipe_actions")
|
||||||
|
op.drop_index(op.f("ix_recipe_actions_group_id"), table_name="recipe_actions")
|
||||||
|
op.drop_index(op.f("ix_recipe_actions_created_at"), table_name="recipe_actions")
|
||||||
|
op.drop_index(op.f("ix_recipe_actions_action_type"), table_name="recipe_actions")
|
||||||
|
op.drop_table("recipe_actions")
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -3,8 +3,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, ConfigDict
|
||||||
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject
|
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject, RequestType
|
||||||
|
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
|
|
||||||
@@ -12,23 +12,25 @@ OUTFILE = PROJECT_DIR / "tests" / "utils" / "api_routes" / "__init__.py"
|
|||||||
|
|
||||||
|
|
||||||
class PathObject(BaseModel):
|
class PathObject(BaseModel):
|
||||||
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||||
route_object: RouteObject
|
route_object: RouteObject
|
||||||
http_verbs: list[HTTPRequest]
|
http_verbs: list[HTTPRequest]
|
||||||
|
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
|
|
||||||
def get_path_objects(app: FastAPI):
|
def get_path_objects(app: FastAPI):
|
||||||
paths = []
|
paths = []
|
||||||
|
|
||||||
for key, value in app.openapi().items():
|
for key, value in app.openapi().items():
|
||||||
if key == "paths":
|
if key == "paths":
|
||||||
for key, value in value.items():
|
for key, value2 in value.items():
|
||||||
|
verbs = []
|
||||||
|
for k, v in value2.items():
|
||||||
|
verbs.append(HTTPRequest(request_type=k, **v))
|
||||||
|
|
||||||
paths.append(
|
paths.append(
|
||||||
PathObject(
|
PathObject(
|
||||||
route_object=RouteObject(key),
|
route_object=RouteObject(key),
|
||||||
http_verbs=[HTTPRequest(request_type=k, **v) for k, v in value.items()],
|
http_verbs=verbs,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
import dotenv
|
import dotenv
|
||||||
import requests
|
import requests
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from pydantic import Extra
|
from pydantic import ConfigDict
|
||||||
from requests import Response
|
from requests import Response
|
||||||
from utils import CodeDest, CodeKeys, inject_inline, log
|
from utils import CodeDest, CodeKeys, inject_inline, log
|
||||||
|
|
||||||
@@ -35,18 +35,24 @@ LOCALE_DATA: dict[str, LocaleData] = {
|
|||||||
"es-ES": LocaleData(name="Español (Spanish)"),
|
"es-ES": LocaleData(name="Español (Spanish)"),
|
||||||
"fi-FI": LocaleData(name="Suomi (Finnish)"),
|
"fi-FI": LocaleData(name="Suomi (Finnish)"),
|
||||||
"fr-FR": LocaleData(name="Français (French)"),
|
"fr-FR": LocaleData(name="Français (French)"),
|
||||||
|
"gl-ES": LocaleData(name="Galego (Galician)"),
|
||||||
"he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
|
"he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
|
||||||
|
"hr-HR": LocaleData(name="Hrvatski (Croatian)"),
|
||||||
"hu-HU": LocaleData(name="Magyar (Hungarian)"),
|
"hu-HU": LocaleData(name="Magyar (Hungarian)"),
|
||||||
|
"is-IS": LocaleData(name="Íslenska (Icelandic)"),
|
||||||
"it-IT": LocaleData(name="Italiano (Italian)"),
|
"it-IT": LocaleData(name="Italiano (Italian)"),
|
||||||
"ja-JP": LocaleData(name="日本語 (Japanese)"),
|
"ja-JP": LocaleData(name="日本語 (Japanese)"),
|
||||||
"ko-KR": LocaleData(name="한국어 (Korean)"),
|
"ko-KR": LocaleData(name="한국어 (Korean)"),
|
||||||
"no-NO": LocaleData(name="Norsk (Norwegian)"),
|
"lt-LT": LocaleData(name="Lietuvių (Lithuanian)"),
|
||||||
|
"lv-LV": LocaleData(name="Latviešu (Latvian)"),
|
||||||
"nl-NL": LocaleData(name="Nederlands (Dutch)"),
|
"nl-NL": LocaleData(name="Nederlands (Dutch)"),
|
||||||
|
"no-NO": LocaleData(name="Norsk (Norwegian)"),
|
||||||
"pl-PL": LocaleData(name="Polski (Polish)"),
|
"pl-PL": LocaleData(name="Polski (Polish)"),
|
||||||
"pt-BR": LocaleData(name="Português do Brasil (Brazilian Portuguese)"),
|
"pt-BR": LocaleData(name="Português do Brasil (Brazilian Portuguese)"),
|
||||||
"pt-PT": LocaleData(name="Português (Portuguese)"),
|
"pt-PT": LocaleData(name="Português (Portuguese)"),
|
||||||
"ro-RO": LocaleData(name="Română (Romanian)"),
|
"ro-RO": LocaleData(name="Română (Romanian)"),
|
||||||
"ru-RU": LocaleData(name="Pусский (Russian)"),
|
"ru-RU": LocaleData(name="Pусский (Russian)"),
|
||||||
|
"sl-SI": LocaleData(name="Slovenščina (Slovenian)"),
|
||||||
"sr-SP": LocaleData(name="српски (Serbian)"),
|
"sr-SP": LocaleData(name="српски (Serbian)"),
|
||||||
"sv-SE": LocaleData(name="Svenska (Swedish)"),
|
"sv-SE": LocaleData(name="Svenska (Swedish)"),
|
||||||
"tr-TR": LocaleData(name="Türkçe (Turkish)"),
|
"tr-TR": LocaleData(name="Türkçe (Turkish)"),
|
||||||
@@ -56,7 +62,7 @@ LOCALE_DATA: dict[str, LocaleData] = {
|
|||||||
"zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"),
|
"zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCALE_TEMPLATE = """// This Code is auto generated by gen_global_components.py
|
LOCALE_TEMPLATE = """// This Code is auto generated by gen_ts_locales.py
|
||||||
export const LOCALES = [{% for locale in locales %}
|
export const LOCALES = [{% for locale in locales %}
|
||||||
{
|
{
|
||||||
name: "{{ locale.name }}",
|
name: "{{ locale.name }}",
|
||||||
@@ -70,6 +76,8 @@ export const LOCALES = [{% for locale in locales %}
|
|||||||
|
|
||||||
|
|
||||||
class TargetLanguage(MealieModel):
|
class TargetLanguage(MealieModel):
|
||||||
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
||||||
|
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
locale: str
|
locale: str
|
||||||
@@ -78,10 +86,6 @@ class TargetLanguage(MealieModel):
|
|||||||
twoLettersCode: str
|
twoLettersCode: str
|
||||||
progress: float = 0.0
|
progress: float = 0.0
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = Extra.allow
|
|
||||||
allow_population_by_field_name = True
|
|
||||||
|
|
||||||
|
|
||||||
class CrowdinApi:
|
class CrowdinApi:
|
||||||
project_name = "Mealie"
|
project_name = "Mealie"
|
||||||
@@ -152,6 +156,7 @@ PROJECT_DIR = Path(__file__).parent.parent.parent
|
|||||||
datetime_dir = PROJECT_DIR / "frontend" / "lang" / "dateTimeFormats"
|
datetime_dir = PROJECT_DIR / "frontend" / "lang" / "dateTimeFormats"
|
||||||
locales_dir = PROJECT_DIR / "frontend" / "lang" / "messages"
|
locales_dir = PROJECT_DIR / "frontend" / "lang" / "messages"
|
||||||
nuxt_config = PROJECT_DIR / "frontend" / "nuxt.config.js"
|
nuxt_config = PROJECT_DIR / "frontend" / "nuxt.config.js"
|
||||||
|
reg_valid = PROJECT_DIR / "mealie" / "schema" / "_mealie" / "validators.py"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This snippet walks the message and dat locales directories and generates the import information
|
This snippet walks the message and dat locales directories and generates the import information
|
||||||
@@ -175,6 +180,19 @@ def inject_nuxt_values():
|
|||||||
inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales)
|
inject_inline(nuxt_config, CodeKeys.nuxt_local_dates, all_date_locales)
|
||||||
|
|
||||||
|
|
||||||
|
def inject_registration_validation_values():
|
||||||
|
all_langs = []
|
||||||
|
for match in locales_dir.glob("*.json"):
|
||||||
|
lang_string = f'"{match.stem}",'
|
||||||
|
all_langs.append(lang_string)
|
||||||
|
|
||||||
|
# sort
|
||||||
|
all_langs.sort()
|
||||||
|
|
||||||
|
log.debug(f"injecting locales into user registration validation -> {reg_valid}")
|
||||||
|
inject_inline(reg_valid, CodeKeys.nuxt_local_messages, all_langs)
|
||||||
|
|
||||||
|
|
||||||
def generate_locales_ts_file():
|
def generate_locales_ts_file():
|
||||||
api = CrowdinApi("")
|
api = CrowdinApi("")
|
||||||
models = api.get_languages()
|
models = api.get_languages()
|
||||||
@@ -193,6 +211,7 @@ def main():
|
|||||||
|
|
||||||
generate_locales_ts_file()
|
generate_locales_ts_file()
|
||||||
inject_nuxt_values()
|
inject_nuxt_values()
|
||||||
|
inject_registration_validation_values()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from utils import log
|
|||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
template = """// This Code is auto generated by gen_global_components.py
|
template = """// This Code is auto generated by gen_ts_types.py
|
||||||
{% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue";
|
{% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue";
|
||||||
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
|
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from humps import camelize
|
from humps import camelize
|
||||||
from pydantic import BaseModel, Extra, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
|
|
||||||
|
|
||||||
@@ -34,33 +33,30 @@ class ParameterIn(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class RouterParameter(BaseModel):
|
class RouterParameter(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|
||||||
required: bool = False
|
required: bool = False
|
||||||
name: str
|
name: str
|
||||||
location: ParameterIn = Field(..., alias="in")
|
location: ParameterIn = Field(..., alias="in")
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
|
|
||||||
class RequestBody(BaseModel):
|
class RequestBody(BaseModel):
|
||||||
required: bool = False
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|
||||||
class Config:
|
required: bool = False
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPRequest(BaseModel):
|
class HTTPRequest(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||||
|
|
||||||
request_type: RequestType
|
request_type: RequestType
|
||||||
description: str = ""
|
description: str = ""
|
||||||
summary: str
|
summary: str
|
||||||
requestBody: Optional[RequestBody]
|
request_body: RequestBody | None = None
|
||||||
|
|
||||||
parameters: list[RouterParameter] = []
|
parameters: list[RouterParameter] = []
|
||||||
tags: list[str] | None = []
|
tags: list[str] | None = []
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
def list_as_js_object_string(self, parameters, braces=True):
|
def list_as_js_object_string(self, parameters, braces=True):
|
||||||
if len(parameters) == 0:
|
if len(parameters) == 0:
|
||||||
return ""
|
return ""
|
||||||
@@ -71,11 +67,11 @@ class HTTPRequest(BaseModel):
|
|||||||
return ", ".join(parameters)
|
return ", ".join(parameters)
|
||||||
|
|
||||||
def payload(self):
|
def payload(self):
|
||||||
return "payload" if self.requestBody else ""
|
return "payload" if self.request_body else ""
|
||||||
|
|
||||||
def function_args(self):
|
def function_args(self):
|
||||||
all_params = [p.name for p in self.parameters]
|
all_params = [p.name for p in self.parameters]
|
||||||
if self.requestBody:
|
if self.request_body:
|
||||||
all_params.append("payload")
|
all_params.append("payload")
|
||||||
return self.list_as_js_object_string(all_params)
|
return self.list_as_js_object_string(all_params)
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import re
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import black
|
|
||||||
import isort
|
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
|
|
||||||
@@ -23,10 +21,7 @@ def render_python_template(template_file: Path | str, dest: Path, data: dict):
|
|||||||
|
|
||||||
text = tplt.render(data=data)
|
text = tplt.render(data=data)
|
||||||
|
|
||||||
text = black.format_str(text, mode=black.FileMode())
|
|
||||||
|
|
||||||
dest.write_text(text)
|
dest.write_text(text)
|
||||||
isort.file(dest)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -50,7 +45,7 @@ class CodeSlicer:
|
|||||||
self._next_line += 1
|
self._next_line += 1
|
||||||
|
|
||||||
|
|
||||||
def get_indentation_of_string(line: str, comment_char: str = "//") -> str:
|
def get_indentation_of_string(line: str, comment_char: str = "//|#") -> str:
|
||||||
return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n")
|
return re.sub(rf"{comment_char}.*", "", line).removesuffix("\n")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
- 9091:9000
|
- 9091:9000
|
||||||
environment:
|
environment:
|
||||||
ALLOW_SIGNUP: "false"
|
ALLOW_SIGNUP: "false"
|
||||||
|
LOG_LEVEL: "DEBUG"
|
||||||
|
|
||||||
DB_ENGINE: sqlite # Optional: 'sqlite', 'postgres'
|
DB_ENGINE: sqlite # Optional: 'sqlite', 'postgres'
|
||||||
# =====================================
|
# =====================================
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ init() {
|
|||||||
|
|
||||||
# Activate our virtual environment here
|
# Activate our virtual environment here
|
||||||
. /opt/pysetup/.venv/bin/activate
|
. /opt/pysetup/.venv/bin/activate
|
||||||
|
|
||||||
# Initialize Database Prerun
|
|
||||||
poetry run python /app/mealie/db/init_db.py
|
|
||||||
}
|
}
|
||||||
|
|
||||||
change_user
|
change_user
|
||||||
@@ -43,10 +40,11 @@ init
|
|||||||
GUNICORN_PORT=${API_PORT:-9000}
|
GUNICORN_PORT=${API_PORT:-9000}
|
||||||
|
|
||||||
# Start API
|
# Start API
|
||||||
hostip=`/sbin/ip route|awk '/default/ { print $3 }'`
|
HOST_IP=`/sbin/ip route|awk '/default/ { print $3 }'`
|
||||||
|
|
||||||
if [ "$WEB_GUNICORN" = 'true' ]; then
|
if [ "$WEB_GUNICORN" = 'true' ]; then
|
||||||
echo "Starting Gunicorn"
|
echo "Starting Gunicorn"
|
||||||
exec gunicorn mealie.app:app -b 0.0.0.0:$GUNICORN_PORT --forwarded-allow-ips=$hostip -k uvicorn.workers.UvicornWorker -c /app/gunicorn_conf.py --preload
|
exec gunicorn mealie.app:app -b 0.0.0.0:$GUNICORN_PORT --forwarded-allow-ips=$HOST_IP -k uvicorn.workers.UvicornWorker -c /app/gunicorn_conf.py --preload
|
||||||
else
|
else
|
||||||
exec uvicorn mealie.app:app --host 0.0.0.0 --forwarded-allow-ips=$hostip --port $GUNICORN_PORT
|
exec python /app/mealie/main.py
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This is the start of the maintainers guide for Mealie developers. Those who have
|
|||||||
|
|
||||||
If you are working on issues, it can be helpful to understand the workflow for our repository. When an issue comes in it is tagged with the `bug` and `triage` flags. This is to indicate that they need to be reviewed by a maintainer to determine validity.
|
If you are working on issues, it can be helpful to understand the workflow for our repository. When an issue comes in it is tagged with the `bug` and `triage` flags. This is to indicate that they need to be reviewed by a maintainer to determine validity.
|
||||||
|
|
||||||
After you've reviered an issue it will generally move into one of two states:
|
After you've reviewed an issue it will generally move into one of two states:
|
||||||
|
|
||||||
`bug:confirmed`
|
`bug:confirmed`
|
||||||
: Your were able to verify the issue and we determined we need to fix it
|
: Your were able to verify the issue and we determined we need to fix it
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# OpenID Connect (OIDC) Authentication
|
||||||
|
|
||||||
|
:octicons-tag-24: v1.4.0
|
||||||
|
|
||||||
|
Mealie supports 3rd party authentication via [OpenID Connect (OIDC)](https://openid.net/connect/), an identity layer built on top of OAuth2. OIDC is supported by many Identity Providers (IdP), including:
|
||||||
|
|
||||||
|
- [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect)
|
||||||
|
- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/)
|
||||||
|
- [Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_oidc)
|
||||||
|
- [Okta](https://www.okta.com/openid-connect/)
|
||||||
|
|
||||||
|
## Account Linking
|
||||||
|
|
||||||
|
Signing in with OAuth will automatically find your account in Mealie and link to it. If a user does not exist in Mealie, then one will be created (if enabled), but will be unable to log in with any other authentication method. An admin can configure another authentication method for such a user.
|
||||||
|
|
||||||
|
## Provider Setup
|
||||||
|
|
||||||
|
Before you can start using OIDC Authentication, you must first configure a new client application in your identity provider. Your identity provider must support the OAuth **Authorization Code flow with PKCE**. The steps will vary by provider, but generally, the steps are as follows.
|
||||||
|
|
||||||
|
1. Create a new client application
|
||||||
|
- The Provider type should be OIDC or OAuth2
|
||||||
|
- The Grant type should be `Authorization Code`
|
||||||
|
- The Application type should be `Web` or `SPA`
|
||||||
|
- The Client type should be `public`
|
||||||
|
|
||||||
|
2. Configure redirect URI
|
||||||
|
|
||||||
|
The redirect URI(s) that are needed:
|
||||||
|
|
||||||
|
1. `http(s)://DOMAIN:PORT/login`
|
||||||
|
2. `https(s)://DOMAIN:PORT/login?direct=1`
|
||||||
|
1. This URI is only required if your IdP supports [RP-Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) such as Keycloak. You may also be able to combine this into the previous URI by using a wildcard: `http(s)://DOMAIN:PORT/login*`
|
||||||
|
|
||||||
|
The redirect URI(s) should include any URL that Mealie is accessible from. Some examples include
|
||||||
|
|
||||||
|
http://localhost:9091/login
|
||||||
|
https://mealie.example.com/login
|
||||||
|
|
||||||
|
3. Configure origins
|
||||||
|
|
||||||
|
If your identity provider enforces CORS on any endpoints, you will need to specify your Mealie URL as an Allowed Origin.
|
||||||
|
|
||||||
|
4. Configure allowed scopes
|
||||||
|
|
||||||
|
The scopes required are `openid profile email`
|
||||||
|
|
||||||
|
If you plan to use the [groups](#groups) to configure access within Mealie, you will need to also add the scope defined by the `OIDC_GROUPS_CLAIM` environment variable. The default claim is `groups`
|
||||||
|
|
||||||
|
## Mealie Setup
|
||||||
|
|
||||||
|
Take the client id and your discovery URL and update your environment variables to include the required OIDC variables described in [Installation - Backend Configuration](../installation/backend-config.md#openid-connect-oidc).
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
There are two (optional) [environment variables](../installation/backend-config.md#openid-connect-oidc) that can control which of the users in your IdP can log in to Mealie and what permissions they will have. Keep in mind that these groups **do not necessarily correspond to groups in Mealie**. The groups claim is configurable via the `OIDC_GROUPS_CLAIM` environment variable. The groups should be **defined in your IdP** and be returned in the configured claim value.
|
||||||
|
|
||||||
|
`OIDC_USER_GROUP`: Users must be a part of this group (within your IdP) to be able to log in.
|
||||||
|
|
||||||
|
`OIDC_ADMIN_GROUP`: Users that are in this group (within your IdP) will be made an **admin** in Mealie.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Example configurations for several Identity Providers have been provided by the Community in the [GitHub Discussions](https://github.com/mealie-recipes/mealie/discussions/categories/oauth-provider-example).
|
||||||
|
|
||||||
|
If you don't see your provider and have successfully set it up, please consider [creating your own example](https://github.com/mealie-recipes/mealie/discussions/new?category=oauth-provider-example) so that others can have a smoother setup.
|
||||||
@@ -94,6 +94,10 @@ docker exec -it mealie-next bash
|
|||||||
python /app/mealie/scripts/change_password.py
|
python /app/mealie/scripts/change_password.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## I can't log in with external auth. How can I change my authentication method?
|
||||||
|
|
||||||
|
Follow the [steps above](#how-can-i-change-my-password) for changing your password. You will be prompted if you would like to switch your authentication method back to local auth so you can log in again.
|
||||||
|
|
||||||
## How do private groups and recipes work?
|
## How do private groups and recipes work?
|
||||||
|
|
||||||
Managing private groups and recipes can be confusing. The following diagram and notes should help explain how they work to determine if a recipe can be shared publicly.
|
Managing private groups and recipes can be confusing. The following diagram and notes should help explain how they work to determine if a recipe can be shared publicly.
|
||||||
|
|||||||
@@ -81,12 +81,63 @@ The meal planner has the concept of plan rules. These offer a flexible way to us
|
|||||||
|
|
||||||
The shopping lists feature is a great way to keep track of what you need to buy for your next meal. You can add items directly to the shopping list or link a recipe and all of it's ingredients to track meals during the week.
|
The shopping lists feature is a great way to keep track of what you need to buy for your next meal. You can add items directly to the shopping list or link a recipe and all of it's ingredients to track meals during the week.
|
||||||
|
|
||||||
!!! warning
|
|
||||||
At this time there isn't a tight integration between meal-plans and shopping lists; however, it's something we have planned for the future.
|
|
||||||
|
|
||||||
|
|
||||||
[Shopping List Demo](https://demo.mealie.io/shopping-lists){ .md-button .md-button--primary }
|
[Shopping List Demo](https://demo.mealie.io/shopping-lists){ .md-button .md-button--primary }
|
||||||
|
|
||||||
|
## Integrations
|
||||||
|
|
||||||
|
Mealie is designed to integrate with many different external services. There are several ways you can integrate with Mealie to achieve custom IoT automations, data synchronization, and anything else you can think of. [You can work directly with Mealie through the API](./api-usage.md), or leverage other services to make seamless integrations.
|
||||||
|
|
||||||
|
### Notifiers
|
||||||
|
|
||||||
|
Notifiers are event-driven notifications sent when specific actions are performed within Mealie. Some actions include:
|
||||||
|
- creating a recipe
|
||||||
|
- adding items to a shopping list
|
||||||
|
- creating a new mealplan
|
||||||
|
|
||||||
|
Notifiers use the [Apprise library](https://github.com/caronc/apprise/wiki), which integrates with a large number of notification services. In addition, certain custom notifiers send basic event data to the consumer (e.g. the `id` of the resource). These include:
|
||||||
|
|
||||||
|
- `form` and `forms`
|
||||||
|
- `json` and `jsons`
|
||||||
|
- `xml` and `xmls`
|
||||||
|
|
||||||
|
[Notifiers Demo](https://demo.mealie.io/group/notifiers){ .md-button .md-button--primary }
|
||||||
|
|
||||||
|
### Webhooks
|
||||||
|
|
||||||
|
Unlike notifiers, which are event-driven notifications, Webhooks allow you to send scheduled notifications to your desired endpoint. Webhooks are sent on the day of a scheduled mealplan, at the specified time, and contain the mealplan data in the request.
|
||||||
|
|
||||||
|
[Webhooks Demo](https://demo.mealie.io/group/webhooks){ .md-button .md-button--primary }
|
||||||
|
|
||||||
|
### Recipe Actions
|
||||||
|
|
||||||
|
Recipe Actions are custom actions you can add to all recipes in Mealie. This is a great way to add custom integrations that are fired manually. There are two types of recipe actions:
|
||||||
|
|
||||||
|
1. link - these actions will take you directly to an external page
|
||||||
|
2. post - these actions will send a `POST` request to the specified URL, with the recipe JSON in the request body. These can be used, for instance, to manually trigger a webhook in Home Assistant
|
||||||
|
|
||||||
|
Recipe Action URLs can include merge fields to inject the current recipe's data. For instance, you can use the following URL to include a Google search with the recipe's slug:
|
||||||
|
```
|
||||||
|
https://www.google.com/search?q=${slug}
|
||||||
|
```
|
||||||
|
|
||||||
|
When the action is clicked on, the `${slug}` field is replaced with the recipe's slug value. So, for example, it might take you to this URL on one of your recipes:
|
||||||
|
```
|
||||||
|
https://www.google.com/search?q=pasta-fagioli
|
||||||
|
```
|
||||||
|
|
||||||
|
A common use case for "link" recipe actions is to integrate with the Bring! shopping list. Simply add a Recipe Action with the following URL:
|
||||||
|
```
|
||||||
|
https://api.getbring.com/rest/bringrecipes/deeplink?url=${url}&source=web
|
||||||
|
```
|
||||||
|
|
||||||
|
Below is a list of all valid merge fields:
|
||||||
|
|
||||||
|
- ${id}
|
||||||
|
- ${slug}
|
||||||
|
- ${url}
|
||||||
|
|
||||||
|
To add, modify, or delete Recipe Actions, visit the Data Management page (more on that below).
|
||||||
|
|
||||||
## Data Management
|
## Data Management
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,21 @@
|
|||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
| ------------- | :-------------------: | ----------------------------------------------------------------------------------- |
|
| ----------------------------- | :-------------------: | ----------------------------------------------------------------------------------- |
|
||||||
| PUID | 911 | UserID permissions between host OS and container |
|
| PUID | 911 | UserID permissions between host OS and container |
|
||||||
| PGID | 911 | GroupID permissions between host OS and container |
|
| PGID | 911 | GroupID permissions between host OS and container |
|
||||||
| DEFAULT_GROUP | Home | The default group for users |
|
| DEFAULT_GROUP | Home | The default group for users |
|
||||||
| BASE_URL | http://localhost:8080 | Used for Notifications |
|
| BASE_URL | http://localhost:8080 | Used for Notifications |
|
||||||
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
|
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
|
||||||
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
|
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
|
||||||
| API_DOCS | True | Turns on/off access to the API documentation locally. |
|
| API_DOCS | True | Turns on/off access to the API documentation locally. |
|
||||||
| TZ | UTC | Must be set to get correct date/time on the server |
|
| TZ | UTC | Must be set to get correct date/time on the server |
|
||||||
| ALLOW_SIGNUP | true | Allow user sign-up without token |
|
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
|
||||||
|
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
|
||||||
|
| LOG_LEVEL | info | logging level configured |
|
||||||
|
|
||||||
|
<super>\*</super> Starting in v1.4.0 this was changed to default to `false` as apart of a security review of the application.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
@@ -25,14 +29,15 @@
|
|||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
| ----------------- | :------: | -------------------------------- |
|
| --------------------- | :------: | ----------------------------------------------------------------------- |
|
||||||
| DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
|
| DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
|
||||||
| POSTGRES_USER | mealie | Postgres database user |
|
| POSTGRES_USER | mealie | Postgres database user |
|
||||||
| POSTGRES_PASSWORD | mealie | Postgres database password |
|
| POSTGRES_PASSWORD | mealie | Postgres database password |
|
||||||
| POSTGRES_SERVER | postgres | Postgres database server address |
|
| POSTGRES_SERVER | postgres | Postgres database server address |
|
||||||
| POSTGRES_PORT | 5432 | Postgres database port |
|
| POSTGRES_PORT | 5432 | Postgres database port |
|
||||||
| POSTGRES_DB | mealie | Postgres database name |
|
| POSTGRES_DB | mealie | Postgres database name |
|
||||||
|
| POSTGRES_URL_OVERRIDE | None | Optional Postgres URL override to use instead of POSTGRES\_\* variables |
|
||||||
|
|
||||||
### Email
|
### Email
|
||||||
|
|
||||||
@@ -75,6 +80,28 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
|
|||||||
| LDAP_NAME_ATTRIBUTE | name | The LDAP attribute that maps to the user's name |
|
| LDAP_NAME_ATTRIBUTE | name | The LDAP attribute that maps to the user's name |
|
||||||
| LDAP_MAIL_ATTRIBUTE | mail | The LDAP attribute that maps to the user's email |
|
| LDAP_MAIL_ATTRIBUTE | mail | The LDAP attribute that maps to the user's email |
|
||||||
|
|
||||||
|
### OpenID Connect (OIDC)
|
||||||
|
|
||||||
|
:octicons-tag-24: v1.4.0
|
||||||
|
|
||||||
|
For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
|
||||||
|
|
||||||
|
| Variables | Default | Description |
|
||||||
|
| ---------------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| OIDC_AUTH_ENABLED | False | Enables authentication via OpenID Connect |
|
||||||
|
| OIDC_SIGNUP_ENABLED | True | Enables new users to be created when signing in for the first time with OIDC |
|
||||||
|
| OIDC_CONFIGURATION_URL | None | The URL to the OIDC configuration of your provider. This is usually something like https://auth.example.com/.well-known/openid-configuration |
|
||||||
|
| OIDC_CLIENT_ID | None | The client id of your configured client in your provider |
|
||||||
|
| OIDC_USER_GROUP | None | If specified, only users belonging to this group will be able to successfully authenticate, regardless of the `OIDC_ADMIN_GROUP`. For more information see [this page](../authentication/oidc.md#groups) |
|
||||||
|
| OIDC_ADMIN_GROUP | None | If specified, users belonging to this group will be made an admin. For more information see [this page](../authentication/oidc.md#groups) |
|
||||||
|
| OIDC_AUTO_REDIRECT | False | If `True`, then the login page will be bypassed an you will be sent directly to your Identity Provider. You can still get to the login page by adding `?direct=1` to the login URL |
|
||||||
|
| OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with <OIDC_PROVIDER_NAME\>" |
|
||||||
|
| OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked |
|
||||||
|
| OIDC_SIGNING_ALGORITHM | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
|
||||||
|
| OIDC_USER_CLAIM | email | This is the claim which Mealie will use to look up an existing user by (e.g. "email", "preferred_username") |
|
||||||
|
| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim**|
|
||||||
|
| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
|
||||||
|
|
||||||
### Themeing
|
### Themeing
|
||||||
|
|
||||||
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
|
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
|
||||||
@@ -96,7 +123,6 @@ Setting the following environmental variables will change the theme of the front
|
|||||||
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
|
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
|
||||||
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
|
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
|
||||||
|
|
||||||
|
|
||||||
[workers_per_core]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#workers_per_core
|
[workers_per_core]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#workers_per_core
|
||||||
[max_workers]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#max_workers
|
[max_workers]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#max_workers
|
||||||
[web_concurrency]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#web_concurrency
|
[web_concurrency]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#web_concurrency
|
||||||
|
|||||||
16
docs/docs/documentation/getting-started/installation/logs.md
Normal file
16
docs/docs/documentation/getting-started/installation/logs.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Logs
|
||||||
|
|
||||||
|
:octicons-tag-24: v1.5.0
|
||||||
|
|
||||||
|
## Highlighs
|
||||||
|
|
||||||
|
- Logs are written to `/app/data/mealie.log` by default in the container.
|
||||||
|
- Logs are also written to stdout and stderr.
|
||||||
|
- You can adjust the log level using the `LOG_LEVEL` environment variable.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Starting in v1.5.0 logging is now highly configurable. Using the `LOG_CONFIG_OVERRIDE` you can provide the application with a custom configuration to log however you'd like. This configuration file is based off the [Python Logging Config](https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig). It can be difficult to understand the configuration at first, so here are some resources to help get started.
|
||||||
|
|
||||||
|
- This [YouTube Video](https://www.youtube.com/watch?v=9L77QExPmI0) for a great walkthrough on the logging file format.
|
||||||
|
- Our [Logging Config](https://github.com/mealie-recipes/mealie/blob/mealie-next/mealie/core/logger/logconf.prod.json)
|
||||||
@@ -5,40 +5,39 @@ PostgreSQL might be considered if you need to support many concurrent users. In
|
|||||||
**For Environment Variable Configuration, see** [Backend Configuration](./backend-config.md)
|
**For Environment Variable Configuration, see** [Backend Configuration](./backend-config.md)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
|
||||||
version: "3.7"
|
|
||||||
services:
|
services:
|
||||||
mealie:
|
mealie:
|
||||||
image: ghcr.io/mealie-recipes/mealie:v1.1.0 # (3)
|
image: ghcr.io/mealie-recipes/mealie:v1.5.1 # (3)
|
||||||
container_name: mealie
|
container_name: mealie
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "9925:9000" # (1)
|
- "9925:9000" # (1)
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1000M # (2)
|
memory: 1000M # (2)
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
volumes:
|
volumes:
|
||||||
- mealie-data:/app/data/
|
- mealie-data:/app/data/
|
||||||
environment:
|
environment:
|
||||||
# Set Backend ENV Variables Here
|
# Set Backend ENV Variables Here
|
||||||
- ALLOW_SIGNUP=true
|
ALLOW_SIGNUP: true
|
||||||
- PUID=1000
|
PUID: 1000
|
||||||
- PGID=1000
|
PGID: 1000
|
||||||
- TZ=America/Anchorage
|
TZ: America/Anchorage
|
||||||
- MAX_WORKERS=1
|
MAX_WORKERS: 1
|
||||||
- WEB_CONCURRENCY=1
|
WEB_CONCURRENCY: 1
|
||||||
- BASE_URL=https://mealie.yourdomain.com
|
BASE_URL: https://mealie.yourdomain.com
|
||||||
|
# Database Settings
|
||||||
|
DB_ENGINE: postgres
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_SERVER: postgres
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_DB: mealie
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
# Database Settings
|
|
||||||
- DB_ENGINE=postgres
|
|
||||||
- POSTGRES_USER=mealie
|
|
||||||
- POSTGRES_PASSWORD=mealie
|
|
||||||
- POSTGRES_SERVER=postgres
|
|
||||||
- POSTGRES_PORT=5432
|
|
||||||
- POSTGRES_DB=mealie
|
|
||||||
restart: always
|
|
||||||
postgres:
|
postgres:
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
image: postgres:15
|
image: postgres:15
|
||||||
@@ -48,12 +47,15 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: mealie
|
POSTGRES_PASSWORD: mealie
|
||||||
POSTGRES_USER: mealie
|
POSTGRES_USER: mealie
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mealie-data:
|
mealie-data:
|
||||||
driver: local
|
|
||||||
mealie-pgdata:
|
mealie-pgdata:
|
||||||
driver: local
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- Updating This? Be Sure to also update the SQLite Annotations -->
|
<!-- Updating This? Be Sure to also update the SQLite Annotations -->
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Security
|
||||||
|
---
|
||||||
|
|
||||||
|
# Security Considerations
|
||||||
|
|
||||||
|
This page is a collection of security considerations for Mealie. It mostly deals with reported issues and how it's possible to mitigate them. Note that this page is for you to use as a guide for how secure you want to make your deployment. It's important to note that most of these will not apply to you, if you:
|
||||||
|
|
||||||
|
1. Run behind a VPN
|
||||||
|
2. Use a strong password
|
||||||
|
3. Disable Sign-Ups
|
||||||
|
4. Don't host for malicious users
|
||||||
|
|
||||||
|
Use your best judgement when deciding what to do.
|
||||||
|
|
||||||
|
## Denial of Service
|
||||||
|
|
||||||
|
By default, the API is **not** rate limited. This leaves Mealie open to a potential **Denial of Service Attack**. While it's possible to perform a **Denial of Service Attack** on any endpoint, there are a few key endpoints that are more vulnerable than others.
|
||||||
|
|
||||||
|
- `/api/recipes/create-url`
|
||||||
|
- `/api/recipes/{id}/image`
|
||||||
|
|
||||||
|
These endpoints are used to scrape data based off a user provided URL. It is possible for a malicious user to issue multiple requests to download an arbitrarily large external file (e.g a Debian ISO) and sufficiently saturate a CPU assigned to the container. While we do implement some protections against this by chunking the response, and using a timeout strategy, it's still possible to overload the CPU if an attacker issues multiple requests concurrently.
|
||||||
|
|
||||||
|
### Mitigation
|
||||||
|
|
||||||
|
If you'd like to mitigate this risk, we suggest that you rate limit the API in general, and apply strict rate limits to these endpoints. You can do this by utilizing a reverse proxy. See the following links to get started:
|
||||||
|
|
||||||
|
- [Traefik](https://doc.traefik.io/traefik/middlewares/http/ratelimit/)
|
||||||
|
- [Nginx](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html)
|
||||||
|
- [Caddy](https://caddyserver.com/docs/modules/http.handlers.rate_limit)
|
||||||
|
|
||||||
|
## Server Side Request Forgery
|
||||||
|
|
||||||
|
- `/api/recipes/create-url`
|
||||||
|
- `/api/recipes/{id}/image`
|
||||||
|
|
||||||
|
Given the nature of these APIs it's possible to perform a **Server Side Request Forgery** attack. This is where a malicious user can issue a request to an internal network resource, and potentially exfiltrate data. We _do_ perform some checks to mitigate access to resources within your network but at the end of the day, users of Mealie are allowed to trigger HTTP requests on **your server**.
|
||||||
|
|
||||||
|
### Mitigation
|
||||||
|
|
||||||
|
If you'd like to mitigate this risk, we suggest that you isolate the container that Mealie is running in to ensure that it's access to internal resources is limited only to what is required. _Note that Mealie does require access to the internet for recipe imports._ You might consider isolating Mealie from your home network entirely and only allowing access to the external internet.
|
||||||
@@ -9,12 +9,11 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
|
|||||||
**For Environment Variable Configuration, see** [Backend Configuration](./backend-config.md)
|
**For Environment Variable Configuration, see** [Backend Configuration](./backend-config.md)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
|
||||||
version: "3.7"
|
|
||||||
services:
|
services:
|
||||||
mealie:
|
mealie:
|
||||||
image: ghcr.io/mealie-recipes/mealie:v1.1.0 # (3)
|
image: ghcr.io/mealie-recipes/mealie:v1.5.1 # (3)
|
||||||
container_name: mealie
|
container_name: mealie
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "9925:9000" # (1)
|
- "9925:9000" # (1)
|
||||||
deploy:
|
deploy:
|
||||||
@@ -24,19 +23,17 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- mealie-data:/app/data/
|
- mealie-data:/app/data/
|
||||||
environment:
|
environment:
|
||||||
# Set Backend ENV Variables Here
|
# Set Backend ENV Variables Here
|
||||||
- ALLOW_SIGNUP=true
|
ALLOW_SIGNUP: true
|
||||||
- PUID=1000
|
PUID: 1000
|
||||||
- PGID=1000
|
PGID: 1000
|
||||||
- TZ=America/Anchorage
|
TZ: America/Anchorage
|
||||||
- MAX_WORKERS=1
|
MAX_WORKERS: 1
|
||||||
- WEB_CONCURRENCY=1
|
WEB_CONCURRENCY: 1
|
||||||
- BASE_URL=https://mealie.yourdomain.com
|
BASE_URL: https://mealie.yourdomain.com
|
||||||
restart: always
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mealie-data:
|
mealie-data:
|
||||||
driver: local
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- Updating This? Be Sure to also update the Postgres Annotations -->
|
<!-- Updating This? Be Sure to also update the Postgres Annotations -->
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Permissions and Public Access
|
# Permissions and Public Access
|
||||||
|
|
||||||
Mealie provides various levels of user access and permissions. This includes:
|
Mealie provides various levels of user access and permissions. This includes:
|
||||||
- Authentication and registration ([check out the LDAP guide](./ldap.md) for how to configure access using LDAP)
|
- Authentication and registration ([LDAP](../authentication/ldap.md) and [OpenID Connect](../authentication/oidc.md) are both supported)
|
||||||
- Customizable user permissions
|
- Customizable user permissions
|
||||||
- Fine-tuned public access for non-users
|
- Fine-tuned public access for non-users
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -41,7 +41,8 @@ markdown_extensions:
|
|||||||
custom_checkbox: true
|
custom_checkbox: true
|
||||||
- admonition
|
- admonition
|
||||||
- attr_list
|
- attr_list
|
||||||
- pymdownx.tabbed
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
- pymdownx.superfences:
|
- pymdownx.superfences:
|
||||||
custom_fences:
|
custom_fences:
|
||||||
- name: mermaid
|
- name: mermaid
|
||||||
@@ -71,9 +72,15 @@ nav:
|
|||||||
- SQLite (Recommended): "documentation/getting-started/installation/sqlite.md"
|
- SQLite (Recommended): "documentation/getting-started/installation/sqlite.md"
|
||||||
- PostgreSQL: "documentation/getting-started/installation/postgres.md"
|
- PostgreSQL: "documentation/getting-started/installation/postgres.md"
|
||||||
- Backend Configuration: "documentation/getting-started/installation/backend-config.md"
|
- Backend Configuration: "documentation/getting-started/installation/backend-config.md"
|
||||||
|
- Security: "documentation/getting-started/installation/security.md"
|
||||||
|
- Logs: "documentation/getting-started/installation/logs.md"
|
||||||
- Usage:
|
- Usage:
|
||||||
- Backup and Restoring: "documentation/getting-started/usage/backups-and-restoring.md"
|
- Backup and Restoring: "documentation/getting-started/usage/backups-and-restoring.md"
|
||||||
- LDAP Authentication: "documentation/getting-started/usage/ldap.md"
|
- Permissions and Public Access: "documentation/getting-started/usage/permissions-and-public-access.md"
|
||||||
|
|
||||||
|
- Authentication:
|
||||||
|
- LDAP: "documentation/getting-started/authentication/ldap.md"
|
||||||
|
- OpenID Connect: "documentation/getting-started/authentication/oidc.md"
|
||||||
|
|
||||||
- Community Guides:
|
- Community Guides:
|
||||||
- iOS Shortcuts: "documentation/community-guide/ios.md"
|
- iOS Shortcuts: "documentation/community-guide/ios.md"
|
||||||
|
|||||||
@@ -1,8 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- Edit Dialog -->
|
||||||
|
<BaseDialog
|
||||||
|
v-if="editTarget"
|
||||||
|
v-model="dialogStates.edit"
|
||||||
|
:width="650"
|
||||||
|
:icon="$globals.icons.pages"
|
||||||
|
:title="$t('general.edit')"
|
||||||
|
:submit-icon="$globals.icons.save"
|
||||||
|
:submit-text="$tc('general.save')"
|
||||||
|
@submit="editCookbook"
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<CookbookEditor :cookbook="editTarget" :actions="actions" />
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
|
|
||||||
|
<!-- Page -->
|
||||||
<v-container v-if="book" fluid>
|
<v-container v-if="book" fluid>
|
||||||
<v-app-bar color="transparent" flat class="mt-n1 rounded">
|
<v-app-bar color="transparent" flat class="mt-n1">
|
||||||
<v-icon large left> {{ $globals.icons.pages }} </v-icon>
|
<v-icon large left> {{ $globals.icons.pages }} </v-icon>
|
||||||
<v-toolbar-title class="headline"> {{ book.name }} </v-toolbar-title>
|
<v-toolbar-title class="headline"> {{ book.name }} </v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<BaseButton
|
||||||
|
v-if="isOwnGroup"
|
||||||
|
class="mx-1"
|
||||||
|
:edit="true"
|
||||||
|
@click="handleEditCookbook"
|
||||||
|
/>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-card flat>
|
<v-card flat>
|
||||||
<v-card-text class="py-0">
|
<v-card-text class="py-0">
|
||||||
@@ -22,17 +47,20 @@
|
|||||||
/>
|
/>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useRoute, ref, useContext, useMeta } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useRoute, ref, useContext, useMeta, reactive, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { useLazyRecipes } from "~/composables/recipes";
|
import { useLazyRecipes } from "~/composables/recipes";
|
||||||
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { useCookbook } from "~/composables/use-group-cookbooks";
|
import { useCookbook, useCookbooks } from "~/composables/use-group-cookbooks";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
import { RecipeCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeCardSection },
|
components: { RecipeCardSection, CookbookEditor },
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
@@ -43,10 +71,36 @@
|
|||||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
const slug = route.value.params.slug;
|
const slug = route.value.params.slug;
|
||||||
const { getOne } = useCookbook(isOwnGroup.value ? null : groupSlug.value);
|
const { getOne } = useCookbook(isOwnGroup.value ? null : groupSlug.value);
|
||||||
|
const { actions } = useCookbooks();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const tab = ref(null);
|
const tab = ref(null);
|
||||||
const book = getOne(slug);
|
const book = getOne(slug);
|
||||||
|
|
||||||
|
const dialogStates = reactive({
|
||||||
|
edit: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const editTarget = ref<RecipeCookBook | null>(null);
|
||||||
|
function handleEditCookbook() {
|
||||||
|
dialogStates.edit = true;
|
||||||
|
editTarget.value = book.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editCookbook() {
|
||||||
|
if (!editTarget.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await actions.updateOne(editTarget.value);
|
||||||
|
|
||||||
|
// if name changed, redirect to new slug
|
||||||
|
if (response?.slug && book.value?.slug !== response?.slug) {
|
||||||
|
router.push(`/g/${route.value.params.groupSlug}/cookbooks/${response?.slug}`);
|
||||||
|
}
|
||||||
|
dialogStates.edit = false;
|
||||||
|
editTarget.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
useMeta(() => {
|
useMeta(() => {
|
||||||
return {
|
return {
|
||||||
title: book?.value?.name || "Cookbook",
|
title: book?.value?.name || "Cookbook",
|
||||||
@@ -62,6 +116,12 @@
|
|||||||
recipes,
|
recipes,
|
||||||
removeRecipe,
|
removeRecipe,
|
||||||
replaceRecipes,
|
replaceRecipes,
|
||||||
|
isOwnGroup,
|
||||||
|
dialogStates,
|
||||||
|
editTarget,
|
||||||
|
handleEditCookbook,
|
||||||
|
editCookbook,
|
||||||
|
actions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
head: {}, // Must include for useMeta
|
head: {}, // Must include for useMeta
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
|
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
|
||||||
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
||||||
import { RecipeTag, RecipeCategory } from "~/lib/api/types/group";
|
import { RecipeTag, RecipeCategory } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<div v-if="!open" class="custom-btn-group ma-1">
|
<div v-if="!open" class="custom-btn-group ma-1">
|
||||||
<RecipeFavoriteBadge v-if="loggedIn" class="mx-1" color="info" button-style :slug="recipe.slug" show-always />
|
<RecipeFavoriteBadge v-if="loggedIn" class="mx-1" color="info" button-style :recipe-id="recipe.id" show-always />
|
||||||
<RecipeTimelineBadge v-if="loggedIn" button-style :slug="recipe.slug" :recipe-name="recipe.name" />
|
<RecipeTimelineBadge v-if="loggedIn" button-style :slug="recipe.slug" :recipe-name="recipe.name" />
|
||||||
<div v-if="loggedIn">
|
<div v-if="loggedIn">
|
||||||
<v-tooltip v-if="!locked" bottom color="info">
|
<v-tooltip v-if="!locked" bottom color="info">
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
print: true,
|
print: true,
|
||||||
printPreferences: true,
|
printPreferences: true,
|
||||||
share: loggedIn,
|
share: loggedIn,
|
||||||
|
recipeActions: true,
|
||||||
}"
|
}"
|
||||||
@print="$emit('print')"
|
@print="$emit('print')"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
<v-hover v-slot="{ hover }" :open-delay="50">
|
<v-hover v-slot="{ hover }" :open-delay="50">
|
||||||
<v-card
|
<v-card
|
||||||
:class="{ 'on-hover': hover }"
|
:class="{ 'on-hover': hover }"
|
||||||
|
:style="{ cursor }"
|
||||||
:elevation="hover ? 12 : 2"
|
:elevation="hover ? 12 : 2"
|
||||||
:to="route ? recipeRoute : ''"
|
:to="recipeRoute"
|
||||||
:min-height="imageHeight + 75"
|
:min-height="imageHeight + 75"
|
||||||
@click="$emit('click')"
|
@click="$emit('click')"
|
||||||
>
|
>
|
||||||
@@ -33,10 +34,10 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
<v-card-actions class="px-1">
|
<v-card-actions v-if="showRecipeContent" class="px-1">
|
||||||
<RecipeFavoriteBadge v-if="isOwnGroup" class="absolute" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="isOwnGroup" class="absolute" :recipe-id="recipeId" show-always />
|
||||||
|
|
||||||
<RecipeRating class="pb-1" :value="rating" :name="name" :slug="slug" :small="true" />
|
<RecipeRating class="pb-1" :value="rating" :recipe-id="recipeId" :slug="slug" :small="true" />
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" />
|
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" />
|
||||||
|
|
||||||
@@ -96,15 +97,15 @@ export default defineComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
ratingColor: {
|
||||||
|
type: String,
|
||||||
|
default: "secondary",
|
||||||
|
},
|
||||||
image: {
|
image: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: "abc123",
|
default: "abc123",
|
||||||
},
|
},
|
||||||
route: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
tags: {
|
tags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -123,14 +124,18 @@ export default defineComponent({
|
|||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
const showRecipeContent = computed(() => props.recipeId && props.slug);
|
||||||
const recipeRoute = computed<string>(() => {
|
const recipeRoute = computed<string>(() => {
|
||||||
return `/g/${groupSlug.value}/r/${props.slug}`;
|
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
||||||
});
|
});
|
||||||
|
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOwnGroup,
|
isOwnGroup,
|
||||||
recipeRoute,
|
recipeRoute,
|
||||||
|
showRecipeContent,
|
||||||
|
cursor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<v-card
|
<v-card
|
||||||
:ripple="false"
|
:ripple="false"
|
||||||
:class="isFlat ? 'mx-auto flat' : 'mx-auto'"
|
:class="isFlat ? 'mx-auto flat' : 'mx-auto'"
|
||||||
|
:style="{ cursor }"
|
||||||
hover
|
hover
|
||||||
:to="$listeners.selected ? undefined : recipeRoute"
|
:to="$listeners.selected ? undefined : recipeRoute"
|
||||||
@click="$emit('selected')"
|
@click="$emit('selected')"
|
||||||
@@ -37,22 +38,20 @@
|
|||||||
</v-list-item-subtitle>
|
</v-list-item-subtitle>
|
||||||
<div class="d-flex flex-wrap justify-end align-center">
|
<div class="d-flex flex-wrap justify-end align-center">
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
<RecipeFavoriteBadge v-if="isOwnGroup" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="isOwnGroup && showRecipeContent" :recipe-id="recipeId" show-always />
|
||||||
<v-rating
|
<RecipeRating
|
||||||
color="secondary"
|
|
||||||
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
|
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
|
||||||
background-color="secondary lighten-3"
|
|
||||||
dense
|
|
||||||
length="5"
|
|
||||||
size="15"
|
|
||||||
:value="rating"
|
:value="rating"
|
||||||
></v-rating>
|
:recipe-id="recipeId"
|
||||||
|
:slug="slug"
|
||||||
|
:small="true"
|
||||||
|
/>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
||||||
<!-- We also add padding to the v-rating above to compensate -->
|
<!-- We also add padding to the v-rating above to compensate -->
|
||||||
<RecipeContextMenu
|
<RecipeContextMenu
|
||||||
v-if="isOwnGroup"
|
v-if="isOwnGroup && showRecipeContent"
|
||||||
:slug="slug"
|
:slug="slug"
|
||||||
:menu-icon="$globals.icons.dotsHorizontal"
|
:menu-icon="$globals.icons.dotsHorizontal"
|
||||||
:name="name"
|
:name="name"
|
||||||
@@ -83,12 +82,14 @@ import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composi
|
|||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeCardImage from "./RecipeCardImage.vue";
|
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||||
|
import RecipeRating from "./RecipeRating.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipeFavoriteBadge,
|
RecipeFavoriteBadge,
|
||||||
RecipeContextMenu,
|
RecipeContextMenu,
|
||||||
|
RecipeRating,
|
||||||
RecipeCardImage,
|
RecipeCardImage,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -113,10 +114,6 @@ export default defineComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: "abc123",
|
default: "abc123",
|
||||||
},
|
},
|
||||||
route: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
recipeId: {
|
recipeId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -135,14 +132,19 @@ export default defineComponent({
|
|||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
const showRecipeContent = computed(() => props.recipeId && props.slug);
|
||||||
const recipeRoute = computed<string>(() => {
|
const recipeRoute = computed<string>(() => {
|
||||||
return `/g/${groupSlug.value}/r/${props.slug}`;
|
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
||||||
});
|
});
|
||||||
|
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOwnGroup,
|
isOwnGroup,
|
||||||
recipeRoute,
|
recipeRoute,
|
||||||
|
showRecipeContent,
|
||||||
|
cursor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
color="accent"
|
color="accent"
|
||||||
:small="small"
|
:small="small"
|
||||||
dark
|
dark
|
||||||
:to="isOwnGroup ? `${baseRecipeRoute}?${urlPrefix}=${category.id}` : undefined"
|
:to="`${baseRecipeRoute}?${urlPrefix}=${category.id}`"
|
||||||
>
|
>
|
||||||
{{ truncateText(category.name) }}
|
{{ truncateText(category.name) }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
@@ -18,8 +18,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/user";
|
|
||||||
|
|
||||||
export type UrlPrefixParam = "tags" | "categories" | "tools";
|
export type UrlPrefixParam = "tags" | "categories" | "tools";
|
||||||
|
|
||||||
@@ -56,7 +55,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const { isOwnGroup } = useLoggedInState();
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
||||||
@@ -74,7 +72,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
baseRecipeRoute,
|
baseRecipeRoute,
|
||||||
isOwnGroup,
|
|
||||||
truncateText,
|
truncateText,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,7 +59,13 @@
|
|||||||
v-on="on"
|
v-on="on"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-date-picker v-model="newMealdate" no-title @input="pickerMenu = false"></v-date-picker>
|
<v-date-picker
|
||||||
|
v-model="newMealdate"
|
||||||
|
no-title
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:local="$i18n.locale"
|
||||||
|
@input="pickerMenu = false"
|
||||||
|
/>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
<v-select
|
<v-select
|
||||||
v-model="newMealType"
|
v-model="newMealType"
|
||||||
@@ -99,6 +105,26 @@
|
|||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<div v-if="useItems.recipeActions && recipeActions && recipeActions.length">
|
||||||
|
<v-divider />
|
||||||
|
<v-list-group @click.stop>
|
||||||
|
<template #activator>
|
||||||
|
<v-list-item-title>{{ $tc("recipe.recipe-actions") }}</v-list-item-title>
|
||||||
|
</template>
|
||||||
|
<v-list dense class="ma-0 pa-0">
|
||||||
|
<v-list-item
|
||||||
|
v-for="(action, index) in recipeActions"
|
||||||
|
:key="index"
|
||||||
|
class="pl-6"
|
||||||
|
@click="executeRecipeAction(action)"
|
||||||
|
>
|
||||||
|
<v-list-item-title>
|
||||||
|
{{ action.title }}
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-list-group>
|
||||||
|
</div>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,10 +137,12 @@ import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
|||||||
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions";
|
||||||
|
import { useGroupSelf } from "~/composables/use-groups";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
|
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
import { ShoppingListSummary } from "~/lib/api/types/group";
|
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/group";
|
||||||
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
||||||
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
||||||
|
|
||||||
@@ -127,6 +155,7 @@ export interface ContextMenuIncludes {
|
|||||||
print: boolean;
|
print: boolean;
|
||||||
printPreferences: boolean;
|
printPreferences: boolean;
|
||||||
share: boolean;
|
share: boolean;
|
||||||
|
recipeActions: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContextMenuItem {
|
export interface ContextMenuItem {
|
||||||
@@ -156,6 +185,7 @@ export default defineComponent({
|
|||||||
print: true,
|
print: true,
|
||||||
printPreferences: true,
|
printPreferences: true,
|
||||||
share: true,
|
share: true,
|
||||||
|
recipeActions: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// Append items are added at the end of the useItems list
|
// Append items are added at the end of the useItems list
|
||||||
@@ -224,11 +254,16 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { i18n, $auth, $globals } = useContext();
|
const { i18n, $auth, $globals } = useContext();
|
||||||
|
const { group } = useGroupSelf();
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
|
const firstDayOfWeek = computed(() => {
|
||||||
|
return group.value?.preferences?.firstDayOfWeek || 0;
|
||||||
|
});
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Context Menu Setup
|
// Context Menu Setup
|
||||||
|
|
||||||
@@ -335,6 +370,19 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const groupRecipeActionsStore = useGroupRecipeActions();
|
||||||
|
|
||||||
|
async function executeRecipeAction(action: GroupRecipeActionOut) {
|
||||||
|
const response = await groupRecipeActionsStore.execute(action, props.recipe);
|
||||||
|
|
||||||
|
if (action.actionType === "post") {
|
||||||
|
if (!response || (response.status >= 200 && response.status < 300)) {
|
||||||
|
alert.success(i18n.tc("events.message-sent"));
|
||||||
|
} else {
|
||||||
|
alert.error(i18n.tc("events.something-went-wrong"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteRecipe() {
|
async function deleteRecipe() {
|
||||||
await api.recipes.deleteOne(props.slug);
|
await api.recipes.deleteOne(props.slug);
|
||||||
@@ -425,6 +473,8 @@ export default defineComponent({
|
|||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
recipeRef,
|
recipeRef,
|
||||||
recipeRefWithScale,
|
recipeRefWithScale,
|
||||||
|
executeRecipeAction,
|
||||||
|
recipeActions: groupRecipeActionsStore.recipeActions,
|
||||||
shoppingLists,
|
shoppingLists,
|
||||||
duplicateRecipe,
|
duplicateRecipe,
|
||||||
contextMenuEventHandler,
|
contextMenuEventHandler,
|
||||||
@@ -432,6 +482,7 @@ export default defineComponent({
|
|||||||
addRecipeToPlan,
|
addRecipeToPlan,
|
||||||
icon,
|
icon,
|
||||||
planTypeOptions,
|
planTypeOptions,
|
||||||
|
firstDayOfWeek,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
:rating="recipe.rating"
|
:rating="recipe.rating"
|
||||||
:image="recipe.image"
|
:image="recipe.image"
|
||||||
:recipe-id="recipe.id"
|
:recipe-id="recipe.id"
|
||||||
:route="true"
|
|
||||||
v-on="$listeners.selected ? { selected: () => handleSelect(recipe) } : {}"
|
v-on="$listeners.selected ? { selected: () => handleSelect(recipe) } : {}"
|
||||||
/>
|
/>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -22,7 +22,13 @@
|
|||||||
v-on="on"
|
v-on="on"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-date-picker v-model="expirationDate" no-title @input="datePickerMenu = false"></v-date-picker>
|
<v-date-picker
|
||||||
|
v-model="expirationDate"
|
||||||
|
no-title
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:local="$i18n.locale"
|
||||||
|
@input="datePickerMenu = false"
|
||||||
|
/>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions class="justify-end">
|
<v-card-actions class="justify-end">
|
||||||
@@ -60,6 +66,7 @@ import { defineComponent, computed, toRefs, reactive, useContext, useRoute } fro
|
|||||||
import { useClipboard, useShare, whenever } from "@vueuse/core";
|
import { useClipboard, useShare, whenever } from "@vueuse/core";
|
||||||
import { RecipeShareToken } from "~/lib/api/types/recipe";
|
import { RecipeShareToken } from "~/lib/api/types/recipe";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { useGroupSelf } from "~/composables/use-groups";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -106,9 +113,14 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
|
const { group } = useGroupSelf();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
|
const firstDayOfWeek = computed(() => {
|
||||||
|
return group.value?.preferences?.firstDayOfWeek || 0;
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Token Actions
|
// Token Actions
|
||||||
|
|
||||||
@@ -185,6 +197,7 @@ export default defineComponent({
|
|||||||
dialog,
|
dialog,
|
||||||
createNewToken,
|
createNewToken,
|
||||||
deleteToken,
|
deleteToken,
|
||||||
|
firstDayOfWeek,
|
||||||
shareRecipe,
|
shareRecipe,
|
||||||
copyTokenLink,
|
copyTokenLink,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,12 +67,16 @@
|
|||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item @click="toggleOrderDirection()">
|
<v-list-item @click="toggleOrderDirection()">
|
||||||
<v-icon left>
|
<v-icon left>
|
||||||
{{ $globals.icons.sort }}
|
{{
|
||||||
|
state.orderDirection === "asc" ?
|
||||||
|
$globals.icons.sortDescending : $globals.icons.sortAscending
|
||||||
|
}}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
{{ state.orderDirection === "asc" ? "Sort Descending" : "Sort Ascending" }}
|
{{ state.orderDirection === "asc" ? $tc("general.sort-descending") : $tc("general.sort-ascending") }}
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-divider />
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="v in sortable"
|
v-for="v in sortable"
|
||||||
:key="v.name"
|
:key="v.name"
|
||||||
@@ -120,11 +124,12 @@
|
|||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-container class="mt-6 px-md-6">
|
<v-container class="mt-6 px-md-6">
|
||||||
<RecipeCardSection
|
<RecipeCardSection
|
||||||
|
v-if="state.ready"
|
||||||
class="mt-n5"
|
class="mt-n5"
|
||||||
:icon="$globals.icons.search"
|
:icon="$globals.icons.search"
|
||||||
:title="$tc('search.results')"
|
:title="$tc('search.results')"
|
||||||
:recipes="recipes"
|
:recipes="recipes"
|
||||||
:query="passedQuery"
|
:query="passedQueryWithSeed"
|
||||||
@replaceRecipes="replaceRecipes"
|
@replaceRecipes="replaceRecipes"
|
||||||
@appendRecipes="appendRecipes"
|
@appendRecipes="appendRecipes"
|
||||||
/>
|
/>
|
||||||
@@ -133,11 +138,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, defineComponent, useRouter, onMounted, useContext, computed, Ref, useRoute } from "@nuxtjs/composition-api";
|
import { ref, defineComponent, useRouter, onMounted, useContext, computed, Ref, useRoute, watch } from "@nuxtjs/composition-api";
|
||||||
import { watchDebounced } from "@vueuse/shared";
|
import { watchDebounced } from "@vueuse/shared";
|
||||||
import SearchFilter from "~/components/Domain/SearchFilter.vue";
|
import SearchFilter from "~/components/Domain/SearchFilter.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
|
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
|
||||||
|
import { useUserSearchQuerySession } from "~/composables/use-users/preferences";
|
||||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
@@ -157,6 +163,7 @@ export default defineComponent({
|
|||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
const state = ref({
|
const state = ref({
|
||||||
auto: true,
|
auto: true,
|
||||||
|
ready: false,
|
||||||
search: "",
|
search: "",
|
||||||
orderBy: "created_at",
|
orderBy: "created_at",
|
||||||
orderDirection: "desc" as "asc" | "desc",
|
orderDirection: "desc" as "asc" | "desc",
|
||||||
@@ -170,6 +177,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
const searchQuerySession = useUserSearchQuerySession();
|
||||||
|
|
||||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
|
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
|
||||||
@@ -184,65 +192,11 @@ export default defineComponent({
|
|||||||
const tools = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
|
const tools = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
|
||||||
const selectedTools = ref<NoUndefinedField<RecipeTool>[]>([]);
|
const selectedTools = ref<NoUndefinedField<RecipeTool>[]>([]);
|
||||||
|
|
||||||
const passedQuery = ref<RecipeSearchQuery | null>(null);
|
function calcPassedQuery(): RecipeSearchQuery {
|
||||||
|
return {
|
||||||
function reset() {
|
// the search clear button sets search to null, which still renders the query param for a moment,
|
||||||
state.value.search = "";
|
// whereas an empty string is not rendered
|
||||||
state.value.orderBy = "created_at";
|
search: state.value.search ? state.value.search : "",
|
||||||
state.value.orderDirection = "desc";
|
|
||||||
state.value.requireAllCategories = false;
|
|
||||||
state.value.requireAllTags = false;
|
|
||||||
state.value.requireAllTools = false;
|
|
||||||
state.value.requireAllFoods = false;
|
|
||||||
selectedCategories.value = [];
|
|
||||||
selectedFoods.value = [];
|
|
||||||
selectedTags.value = [];
|
|
||||||
selectedTools.value = [];
|
|
||||||
|
|
||||||
router.push({
|
|
||||||
query: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
search();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleOrderDirection() {
|
|
||||||
state.value.orderDirection = state.value.orderDirection === "asc" ? "desc" : "asc";
|
|
||||||
}
|
|
||||||
|
|
||||||
function toIDArray(array: { id: string }[]) {
|
|
||||||
return array.map((item) => item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideKeyboard() {
|
|
||||||
input.value.blur()
|
|
||||||
}
|
|
||||||
|
|
||||||
const input: Ref<any> = ref(null);
|
|
||||||
|
|
||||||
async function search() {
|
|
||||||
await router.push({
|
|
||||||
query: {
|
|
||||||
categories: toIDArray(selectedCategories.value),
|
|
||||||
foods: toIDArray(selectedFoods.value),
|
|
||||||
tags: toIDArray(selectedTags.value),
|
|
||||||
tools: toIDArray(selectedTools.value),
|
|
||||||
// Only add the query param if it's or not default
|
|
||||||
...{
|
|
||||||
auto: state.value.auto ? undefined : "false",
|
|
||||||
search: state.value.search === "" ? undefined : state.value.search,
|
|
||||||
orderBy: state.value.orderBy === "createdAt" ? undefined : state.value.orderBy,
|
|
||||||
orderDirection: state.value.orderDirection === "desc" ? undefined : state.value.orderDirection,
|
|
||||||
requireAllCategories: state.value.requireAllCategories ? "true" : undefined,
|
|
||||||
requireAllTags: state.value.requireAllTags ? "true" : undefined,
|
|
||||||
requireAllTools: state.value.requireAllTools ? "true" : undefined,
|
|
||||||
requireAllFoods: state.value.requireAllFoods ? "true" : undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
passedQuery.value = {
|
|
||||||
search: state.value.search,
|
|
||||||
categories: toIDArray(selectedCategories.value),
|
categories: toIDArray(selectedCategories.value),
|
||||||
foods: toIDArray(selectedFoods.value),
|
foods: toIDArray(selectedFoods.value),
|
||||||
tags: toIDArray(selectedTags.value),
|
tags: toIDArray(selectedTags.value),
|
||||||
@@ -253,8 +207,87 @@ export default defineComponent({
|
|||||||
requireAllFoods: state.value.requireAllFoods,
|
requireAllFoods: state.value.requireAllFoods,
|
||||||
orderBy: state.value.orderBy,
|
orderBy: state.value.orderBy,
|
||||||
orderDirection: state.value.orderDirection,
|
orderDirection: state.value.orderDirection,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const passedQuery = ref<RecipeSearchQuery>(calcPassedQuery());
|
||||||
|
|
||||||
|
// we calculate this separately because otherwise we can't check for query changes
|
||||||
|
const passedQueryWithSeed = computed(() => {
|
||||||
|
return {
|
||||||
|
...passedQuery.value,
|
||||||
_searchSeed: Date.now().toString()
|
_searchSeed: Date.now().toString()
|
||||||
};
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
const queryDefaults = {
|
||||||
|
search: "",
|
||||||
|
orderBy: "created_at",
|
||||||
|
orderDirection: "desc" as "asc" | "desc",
|
||||||
|
requireAllCategories: false,
|
||||||
|
requireAllTags: false,
|
||||||
|
requireAllTools: false,
|
||||||
|
requireAllFoods: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
state.value.search = queryDefaults.search;
|
||||||
|
state.value.orderBy = queryDefaults.orderBy;
|
||||||
|
state.value.orderDirection = queryDefaults.orderDirection;
|
||||||
|
state.value.requireAllCategories = queryDefaults.requireAllCategories;
|
||||||
|
state.value.requireAllTags = queryDefaults.requireAllTags;
|
||||||
|
state.value.requireAllTools = queryDefaults.requireAllTools;
|
||||||
|
state.value.requireAllFoods = queryDefaults.requireAllFoods;
|
||||||
|
selectedCategories.value = [];
|
||||||
|
selectedFoods.value = [];
|
||||||
|
selectedTags.value = [];
|
||||||
|
selectedTools.value = [];
|
||||||
|
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOrderDirection() {
|
||||||
|
state.value.orderDirection = state.value.orderDirection === "asc" ? "desc" : "asc";
|
||||||
|
}
|
||||||
|
|
||||||
|
function toIDArray(array: { id: string }[]) {
|
||||||
|
// we sort the array to make sure the query is always the same
|
||||||
|
return array.map((item) => item.id).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideKeyboard() {
|
||||||
|
input.value.blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
const input: Ref<any> = ref(null);
|
||||||
|
|
||||||
|
async function search() {
|
||||||
|
const oldQueryValueString = JSON.stringify(passedQuery.value);
|
||||||
|
const newQueryValue = calcPassedQuery();
|
||||||
|
const newQueryValueString = JSON.stringify(newQueryValue);
|
||||||
|
if (oldQueryValueString === newQueryValueString) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
passedQuery.value = newQueryValue;
|
||||||
|
const query = {
|
||||||
|
categories: passedQuery.value.categories,
|
||||||
|
foods: passedQuery.value.foods,
|
||||||
|
tags: passedQuery.value.tags,
|
||||||
|
tools: passedQuery.value.tools,
|
||||||
|
// Only add the query param if it's not the default value
|
||||||
|
...{
|
||||||
|
auto: state.value.auto ? undefined : "false",
|
||||||
|
search: passedQuery.value.search === queryDefaults.search ? undefined : passedQuery.value.search,
|
||||||
|
orderBy: passedQuery.value.orderBy === queryDefaults.orderBy ? undefined : passedQuery.value.orderBy,
|
||||||
|
orderDirection: passedQuery.value.orderDirection === queryDefaults.orderDirection ? undefined : passedQuery.value.orderDirection,
|
||||||
|
requireAllCategories: passedQuery.value.requireAllCategories ? "true" : undefined,
|
||||||
|
requireAllTags: passedQuery.value.requireAllTags ? "true" : undefined,
|
||||||
|
requireAllTools: passedQuery.value.requireAllTools ? "true" : undefined,
|
||||||
|
requireAllFoods: passedQuery.value.requireAllFoods ? "true" : undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await router.push({ query });
|
||||||
|
searchQuerySession.value.recipe = JSON.stringify(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitUntilAndExecute(
|
function waitUntilAndExecute(
|
||||||
@@ -325,32 +358,69 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
onMounted(() => {
|
watch(
|
||||||
// Hydrate Search
|
() => route.value.query,
|
||||||
// wait for stores to be hydrated
|
() => {
|
||||||
|
if (state.value.ready) {
|
||||||
|
hydrateSearch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// read query params
|
async function hydrateSearch() {
|
||||||
const query = router.currentRoute.query;
|
const query = router.currentRoute.query;
|
||||||
|
if (query.auto?.length) {
|
||||||
if (query.auto) {
|
|
||||||
state.value.auto = query.auto === "true";
|
state.value.auto = query.auto === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.search) {
|
if (query.search?.length) {
|
||||||
state.value.search = query.search as string;
|
state.value.search = query.search as string;
|
||||||
|
} else {
|
||||||
|
state.value.search = queryDefaults.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.orderBy) {
|
if (query.orderBy?.length) {
|
||||||
state.value.orderBy = query.orderBy as string;
|
state.value.orderBy = query.orderBy as string;
|
||||||
|
} else {
|
||||||
|
state.value.orderBy = queryDefaults.orderBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.orderDirection) {
|
if (query.orderDirection?.length) {
|
||||||
state.value.orderDirection = query.orderDirection as "asc" | "desc";
|
state.value.orderDirection = query.orderDirection as "asc" | "desc";
|
||||||
|
} else {
|
||||||
|
state.value.orderDirection = queryDefaults.orderDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.requireAllCategories?.length) {
|
||||||
|
state.value.requireAllCategories = query.requireAllCategories === "true";
|
||||||
|
} else {
|
||||||
|
state.value.requireAllCategories = queryDefaults.requireAllCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.requireAllTags?.length) {
|
||||||
|
state.value.requireAllTags = query.requireAllTags === "true";
|
||||||
|
} else {
|
||||||
|
state.value.requireAllTags = queryDefaults.requireAllTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.requireAllTools?.length) {
|
||||||
|
state.value.requireAllTools = query.requireAllTools === "true";
|
||||||
|
} else {
|
||||||
|
state.value.requireAllTools = queryDefaults.requireAllTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.requireAllFoods?.length) {
|
||||||
|
state.value.requireAllFoods = query.requireAllFoods === "true";
|
||||||
|
} else {
|
||||||
|
state.value.requireAllFoods = queryDefaults.requireAllFoods;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
if (query.categories) {
|
if (query.categories?.length) {
|
||||||
promises.push(
|
promises.push(
|
||||||
waitUntilAndExecute(
|
waitUntilAndExecute(
|
||||||
() => categories.items.value.length > 0,
|
() => categories.items.value.length > 0,
|
||||||
@@ -363,9 +433,39 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
selectedCategories.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.foods) {
|
if (query.tags?.length) {
|
||||||
|
promises.push(
|
||||||
|
waitUntilAndExecute(
|
||||||
|
() => tags.items.value.length > 0,
|
||||||
|
() => {
|
||||||
|
const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string));
|
||||||
|
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
selectedTags.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.tools?.length) {
|
||||||
|
promises.push(
|
||||||
|
waitUntilAndExecute(
|
||||||
|
() => tools.items.value.length > 0,
|
||||||
|
() => {
|
||||||
|
const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id));
|
||||||
|
selectedTools.value = result as NoUndefinedField<RecipeTool>[];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
selectedTools.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.foods?.length) {
|
||||||
promises.push(
|
promises.push(
|
||||||
waitUntilAndExecute(
|
waitUntilAndExecute(
|
||||||
() => {
|
() => {
|
||||||
@@ -380,35 +480,28 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
selectedFoods.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.tags) {
|
await Promise.allSettled(promises);
|
||||||
promises.push(
|
};
|
||||||
waitUntilAndExecute(
|
|
||||||
() => tags.items.value.length > 0,
|
onMounted(async () => {
|
||||||
() => {
|
// restore the user's last search query
|
||||||
const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string));
|
if (searchQuerySession.value.recipe && !(Object.keys(route.value.query).length > 0)) {
|
||||||
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
|
try {
|
||||||
}
|
const query = JSON.parse(searchQuerySession.value.recipe);
|
||||||
)
|
await router.replace({ query });
|
||||||
);
|
} catch (error) {
|
||||||
|
searchQuerySession.value.recipe = "";
|
||||||
|
router.replace({ query: {} });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.tools) {
|
await hydrateSearch();
|
||||||
promises.push(
|
await search();
|
||||||
waitUntilAndExecute(
|
state.value.ready = true;
|
||||||
() => tools.items.value.length > 0,
|
|
||||||
() => {
|
|
||||||
const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id));
|
|
||||||
selectedTools.value = result as NoUndefinedField<RecipeTool>[];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.allSettled(promises).then(() => {
|
|
||||||
search();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watchDebounced(
|
watchDebounced(
|
||||||
@@ -426,7 +519,7 @@ export default defineComponent({
|
|||||||
selectedTools,
|
selectedTools,
|
||||||
],
|
],
|
||||||
async () => {
|
async () => {
|
||||||
if (state.value.auto) {
|
if (state.value.ready && state.value.auto) {
|
||||||
await search();
|
await search();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -459,7 +552,7 @@ export default defineComponent({
|
|||||||
recipes,
|
recipes,
|
||||||
removeRecipe,
|
removeRecipe,
|
||||||
replaceRecipes,
|
replaceRecipes,
|
||||||
passedQuery,
|
passedQueryWithSeed,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
head: {},
|
head: {},
|
||||||
|
|||||||
@@ -22,11 +22,12 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { useUserSelfRatings } from "~/composables/use-users";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { UserOut } from "~/lib/api/types/user";
|
import { UserOut } from "~/lib/api/types/user";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
slug: {
|
recipeId: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
@@ -42,19 +43,23 @@ export default defineComponent({
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
|
const { userRatings, refreshUserRatings } = useUserSelfRatings();
|
||||||
|
|
||||||
// TODO Setup the correct type for $auth.user
|
// TODO Setup the correct type for $auth.user
|
||||||
// See https://github.com/nuxt-community/auth-module/issues/1097
|
// See https://github.com/nuxt-community/auth-module/issues/1097
|
||||||
const user = computed(() => $auth.user as unknown as UserOut);
|
const user = computed(() => $auth.user as unknown as UserOut);
|
||||||
const isFavorite = computed(() => user.value?.favoriteRecipes?.includes(props.slug));
|
const isFavorite = computed(() => {
|
||||||
|
const rating = userRatings.value.find((r) => r.recipeId === props.recipeId);
|
||||||
|
return rating?.isFavorite || false;
|
||||||
|
});
|
||||||
|
|
||||||
async function toggleFavorite() {
|
async function toggleFavorite() {
|
||||||
if (!isFavorite.value) {
|
if (!isFavorite.value) {
|
||||||
await api.users.addFavorite(user.value?.id, props.slug);
|
await api.users.addFavorite(user.value?.id, props.recipeId);
|
||||||
} else {
|
} else {
|
||||||
await api.users.removeFavorite(user.value?.id, props.slug);
|
await api.users.removeFavorite(user.value?.id, props.recipeId);
|
||||||
}
|
}
|
||||||
$auth.fetchUser();
|
await refreshUserRatings();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isFavorite, toggleFavorite };
|
return { isFavorite, toggleFavorite };
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<v-date-picker
|
<v-date-picker
|
||||||
v-model="newTimelineEventTimestamp"
|
v-model="newTimelineEventTimestamp"
|
||||||
no-title
|
no-title
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
:local="$i18n.locale"
|
:local="$i18n.locale"
|
||||||
@input="datePickerMenu = false"
|
@input="datePickerMenu = false"
|
||||||
/>
|
/>
|
||||||
@@ -109,10 +110,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||||
import { whenever } from "@vueuse/core";
|
import { whenever } from "@vueuse/core";
|
||||||
import { VForm } from "~/types/vuetify";
|
import { VForm } from "~/types/vuetify";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { useGroupSelf } from "~/composables/use-groups";
|
||||||
import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe";
|
import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -129,6 +131,7 @@ export default defineComponent({
|
|||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const madeThisDialog = ref(false);
|
const madeThisDialog = ref(false);
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
|
const { group } = useGroupSelf();
|
||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
const domMadeThisForm = ref<VForm>();
|
const domMadeThisForm = ref<VForm>();
|
||||||
const newTimelineEvent = ref<RecipeTimelineEventIn>({
|
const newTimelineEvent = ref<RecipeTimelineEventIn>({
|
||||||
@@ -153,6 +156,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const firstDayOfWeek = computed(() => {
|
||||||
|
return group.value?.preferences?.firstDayOfWeek || 0;
|
||||||
|
});
|
||||||
|
|
||||||
function clearImage() {
|
function clearImage() {
|
||||||
newTimelineEventImage.value = undefined;
|
newTimelineEventImage.value = undefined;
|
||||||
newTimelineEventImageName.value = "";
|
newTimelineEventImageName.value = "";
|
||||||
@@ -226,6 +233,7 @@ export default defineComponent({
|
|||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
domMadeThisForm,
|
domMadeThisForm,
|
||||||
madeThisDialog,
|
madeThisDialog,
|
||||||
|
firstDayOfWeek,
|
||||||
newTimelineEvent,
|
newTimelineEvent,
|
||||||
newTimelineEventImage,
|
newTimelineEventImage,
|
||||||
newTimelineEventImagePreviewUrl,
|
newTimelineEventImagePreviewUrl,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="edit" class="d-flex justify-end">
|
<div v-if="edit" class="d-flex justify-end">
|
||||||
<BaseButton class="ml-auto my-2" @click="addNote"> {{ $t("general.new") }}</BaseButton>
|
<BaseButton class="ml-auto my-2" @click="addNote"> {{ $t("general.add") }}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
$globals.icons.tags"
|
$globals.icons.tags"
|
||||||
return-object
|
return-object
|
||||||
v-bind="inputAttrs"
|
v-bind="inputAttrs"
|
||||||
|
auto-select-first
|
||||||
|
:search-input.sync="searchInput"
|
||||||
|
@change="resetSearchInput"
|
||||||
>
|
>
|
||||||
<template #selection="data">
|
<template #selection="data">
|
||||||
<v-chip
|
<v-chip
|
||||||
@@ -43,7 +46,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, useContext, computed, onMounted } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, useContext, computed, onMounted } from "@nuxtjs/composition-api";
|
||||||
import RecipeOrganizerDialog from "./RecipeOrganizerDialog.vue";
|
import RecipeOrganizerDialog from "./RecipeOrganizerDialog.vue";
|
||||||
import { RecipeCategory, RecipeTag } from "~/lib/api/types/user";
|
import { RecipeCategory, RecipeTag } from "~/lib/api/types/recipe";
|
||||||
import { RecipeTool } from "~/lib/api/types/admin";
|
import { RecipeTool } from "~/lib/api/types/admin";
|
||||||
import { useTagStore } from "~/composables/store/use-tag-store";
|
import { useTagStore } from "~/composables/store/use-tag-store";
|
||||||
import { useCategoryStore, useToolStore } from "~/composables/store";
|
import { useCategoryStore, useToolStore } from "~/composables/store";
|
||||||
@@ -138,7 +141,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function appendCreated(item: RecipeTag | RecipeCategory | RecipeTool) {
|
function appendCreated(item: RecipeTag | RecipeCategory | RecipeTool) {
|
||||||
console.log(item);
|
|
||||||
if (selected.value === undefined) {
|
if (selected.value === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,6 +150,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
const dialog = ref(false);
|
const dialog = ref(false);
|
||||||
|
|
||||||
|
const searchInput = ref("");
|
||||||
|
|
||||||
|
function resetSearchInput() {
|
||||||
|
searchInput.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Organizer,
|
Organizer,
|
||||||
appendCreated,
|
appendCreated,
|
||||||
@@ -156,6 +164,8 @@ export default defineComponent({
|
|||||||
label,
|
label,
|
||||||
selected,
|
selected,
|
||||||
removeByIndex,
|
removeByIndex,
|
||||||
|
searchInput,
|
||||||
|
resetSearchInput,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
/>
|
/>
|
||||||
<div v-if="isEditForm" class="d-flex">
|
<div v-if="isEditForm" class="d-flex">
|
||||||
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
||||||
<BaseButton class="my-2" @click="addStep()"> {{ $t("general.new") }}</BaseButton>
|
<BaseButton class="my-2" @click="addStep()"> {{ $t("general.add") }}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!$vuetify.breakpoint.mdAndUp">
|
<div v-if="!$vuetify.breakpoint.mdAndUp">
|
||||||
<RecipePageOrganizers :recipe="recipe" />
|
<RecipePageOrganizers :recipe="recipe" />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-card-title class="headline pa-0 flex-column align-center">
|
<v-card-title class="headline pa-0 flex-column align-center">
|
||||||
{{ recipe.name }}
|
{{ recipe.name }}
|
||||||
<RecipeRating :key="recipe.slug" v-model="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
|
<RecipeRating :key="recipe.slug" :value="recipe.rating" :recipe-id="recipe.id" :slug="recipe.slug" />
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider class="my-2"></v-divider>
|
<v-divider class="my-2"></v-divider>
|
||||||
<SafeMarkdown :source="recipe.description" />
|
<SafeMarkdown :source="recipe.description" />
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<span>{{ parserToolTip }}</span>
|
<span>{{ parserToolTip }}</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<RecipeDialogBulkAdd class="mx-1 mb-1" @bulk-data="addIngredient" />
|
<RecipeDialogBulkAdd class="mx-1 mb-1" @bulk-data="addIngredient" />
|
||||||
<BaseButton class="mb-1" @click="addIngredient" > {{ $t("general.new") }} </BaseButton>
|
<BaseButton class="mb-1" @click="addIngredient" > {{ $t("general.add") }} </BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
v-if="landscape && $vuetify.breakpoint.smAndUp"
|
v-if="landscape && $vuetify.breakpoint.smAndUp"
|
||||||
:key="recipe.slug"
|
:key="recipe.slug"
|
||||||
v-model="recipe.rating"
|
v-model="recipe.rating"
|
||||||
:name="recipe.name"
|
:recipe-id="recipe.id"
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
v-if="$vuetify.breakpoint.smAndDown"
|
v-if="$vuetify.breakpoint.smAndDown"
|
||||||
:key="recipe.slug"
|
:key="recipe.slug"
|
||||||
v-model="recipe.rating"
|
v-model="recipe.rating"
|
||||||
:name="recipe.name"
|
:recipe-id="recipe.id"
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,34 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click.prevent>
|
<div @click.prevent>
|
||||||
<v-rating
|
<!-- User Rating -->
|
||||||
v-model="rating"
|
<v-hover v-slot="{ hover }">
|
||||||
:readonly="!isOwnGroup"
|
<v-rating
|
||||||
color="secondary"
|
v-if="isOwnGroup && (userRating || hover || !ratingsLoaded)"
|
||||||
background-color="secondary lighten-3"
|
:value="userRating"
|
||||||
length="5"
|
color="secondary"
|
||||||
:dense="small ? true : undefined"
|
background-color="secondary lighten-3"
|
||||||
:size="small ? 15 : undefined"
|
length="5"
|
||||||
hover
|
:dense="small ? true : undefined"
|
||||||
:value="value"
|
:size="small ? 15 : undefined"
|
||||||
clearable
|
hover
|
||||||
@input="updateRating"
|
clearable
|
||||||
@click="updateRating"
|
@input="updateRating"
|
||||||
></v-rating>
|
@click="updateRating"
|
||||||
|
/>
|
||||||
|
<!-- Group Rating -->
|
||||||
|
<v-rating
|
||||||
|
v-else
|
||||||
|
:value="groupRating"
|
||||||
|
:half-increments="true"
|
||||||
|
:readonly="true"
|
||||||
|
color="grey darken-1"
|
||||||
|
background-color="secondary lighten-3"
|
||||||
|
length="5"
|
||||||
|
:dense="small ? true : undefined"
|
||||||
|
:size="small ? 15 : undefined"
|
||||||
|
hover
|
||||||
|
/>
|
||||||
|
</v-hover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, ref, watch } from "@nuxtjs/composition-api";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserSelfRatings } from "~/composables/use-users";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
emitOnly: {
|
emitOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
// TODO Remove name prop?
|
recipeId: {
|
||||||
name: {
|
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
@@ -47,23 +61,45 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
const { userRatings, setRating, ready: ratingsLoaded } = useUserSelfRatings();
|
||||||
|
|
||||||
const rating = ref(props.value);
|
const userRating = computed(() => {
|
||||||
|
return userRatings.value.find((r) => r.recipeId === props.recipeId)?.rating;
|
||||||
|
});
|
||||||
|
|
||||||
|
// if a user unsets their rating, we don't want to fall back to the group rating since it's out of sync
|
||||||
|
const hideGroupRating = ref(!!userRating.value);
|
||||||
|
watch(
|
||||||
|
() => userRating.value,
|
||||||
|
() => {
|
||||||
|
if (userRating.value) {
|
||||||
|
hideGroupRating.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const groupRating = computed(() => {
|
||||||
|
return hideGroupRating.value ? 0 : props.value;
|
||||||
|
});
|
||||||
|
|
||||||
const api = useUserApi();
|
|
||||||
function updateRating(val: number | null) {
|
function updateRating(val: number | null) {
|
||||||
if (val === 0) {
|
if (!isOwnGroup.value) {
|
||||||
val = null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.emitOnly) {
|
if (!props.emitOnly) {
|
||||||
api.recipes.patchOne(props.slug, {
|
setRating(props.slug, val || 0, null);
|
||||||
rating: val,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
context.emit("input", val);
|
context.emit("input", val);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isOwnGroup, rating, updateRating };
|
return {
|
||||||
|
isOwnGroup,
|
||||||
|
ratingsLoaded,
|
||||||
|
groupRating,
|
||||||
|
userRating,
|
||||||
|
updateRating,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,9 +3,53 @@
|
|||||||
<v-row class="my-0 mx-7">
|
<v-row class="my-0 mx-7">
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-col class="text-right">
|
<v-col class="text-right">
|
||||||
<v-btn fab small color="info" @click="reverseSort">
|
<!-- Filters -->
|
||||||
<v-icon> {{ preferences.orderDirection === "asc" ? $globals.icons.sortCalendarAscending : $globals.icons.sortCalendarDescending }} </v-icon>
|
<v-menu offset-y bottom left nudge-bottom="3" :close-on-content-click="false">
|
||||||
</v-btn>
|
<template #activator="{ on, attrs }">
|
||||||
|
<v-badge :content="filterBadgeCount" :value="filterBadgeCount" bordered overlap>
|
||||||
|
<v-btn fab small color="info" v-bind="attrs" v-on="on">
|
||||||
|
<v-icon> {{ $globals.icons.filter }} </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-badge>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item @click="reverseSort">
|
||||||
|
<v-icon left>
|
||||||
|
{{
|
||||||
|
preferences.orderDirection === "asc" ?
|
||||||
|
$globals.icons.sortCalendarDescending : $globals.icons.sortCalendarAscending
|
||||||
|
}}
|
||||||
|
</v-icon>
|
||||||
|
<v-list-item-title>
|
||||||
|
{{ preferences.orderDirection === "asc" ? $tc("general.sort-descending") : $tc("general.sort-ascending") }}
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-divider />
|
||||||
|
<v-list-item class="pa-0">
|
||||||
|
<v-list class="py-0" style="width: 100%;">
|
||||||
|
<v-list-item
|
||||||
|
v-for="option, idx in eventTypeFilterState"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<v-checkbox
|
||||||
|
:input-value="option.checked"
|
||||||
|
readonly
|
||||||
|
@click="toggleEventTypeOption(option.value)"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<v-icon left>
|
||||||
|
{{ option.icon }}
|
||||||
|
</v-icon>
|
||||||
|
{{ option.label }}
|
||||||
|
</template>
|
||||||
|
</v-checkbox>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-divider class="mx-2"/>
|
<v-divider class="mx-2"/>
|
||||||
@@ -29,9 +73,9 @@
|
|||||||
/>
|
/>
|
||||||
</v-timeline>
|
</v-timeline>
|
||||||
</div>
|
</div>
|
||||||
<v-card v-else-if="!loading">
|
<v-card v-else-if="!loading" class="mt-2">
|
||||||
<v-card-title class="justify-center pa-9">
|
<v-card-title class="justify-center pa-9">
|
||||||
{{ $t("recipe.timeline-is-empty") }}
|
{{ $t("recipe.timeline-no-events-found-try-adjusting-filters") }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
</v-card>
|
</v-card>
|
||||||
<div v-if="loading" class="mb-3 text-center">
|
<div v-if="loading" class="mb-3 text-center">
|
||||||
@@ -41,14 +85,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, ref, useAsync, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, onMounted, ref, useAsync, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useThrottleFn, whenever } from "@vueuse/core";
|
import { useThrottleFn, whenever } from "@vueuse/core";
|
||||||
import RecipeTimelineItem from "./RecipeTimelineItem.vue"
|
import RecipeTimelineItem from "./RecipeTimelineItem.vue"
|
||||||
import { useTimelinePreferences } from "~/composables/use-users/preferences";
|
import { useTimelinePreferences } from "~/composables/use-users/preferences";
|
||||||
|
import { useTimelineEventTypes } from "~/composables/recipes/use-recipe-timeline-events";
|
||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { Recipe, RecipeTimelineEventOut, RecipeTimelineEventUpdate } from "~/lib/api/types/recipe"
|
import { Recipe, RecipeTimelineEventOut, RecipeTimelineEventUpdate, TimelineEventType } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeTimelineItem },
|
components: { RecipeTimelineItem },
|
||||||
@@ -76,6 +121,7 @@ export default defineComponent({
|
|||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
const preferences = useTimelinePreferences();
|
const preferences = useTimelinePreferences();
|
||||||
|
const { eventTypeOptions } = useTimelineEventTypes();
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const ready = ref(false);
|
const ready = ref(false);
|
||||||
|
|
||||||
@@ -85,6 +131,15 @@ export default defineComponent({
|
|||||||
|
|
||||||
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
||||||
const recipes = new Map<string, Recipe>();
|
const recipes = new Map<string, Recipe>();
|
||||||
|
const filterBadgeCount = computed(() => eventTypeOptions.value.length - preferences.value.types.length);
|
||||||
|
const eventTypeFilterState = computed(() => {
|
||||||
|
return eventTypeOptions.value.map(option => {
|
||||||
|
return {
|
||||||
|
...option,
|
||||||
|
checked: preferences.value.types.includes(option.value),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
interface ScrollEvent extends Event {
|
interface ScrollEvent extends Event {
|
||||||
target: HTMLInputElement;
|
target: HTMLInputElement;
|
||||||
@@ -112,7 +167,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sorting
|
// Preferences
|
||||||
function reverseSort() {
|
function reverseSort() {
|
||||||
if (loading.value) {
|
if (loading.value) {
|
||||||
return;
|
return;
|
||||||
@@ -122,6 +177,21 @@ export default defineComponent({
|
|||||||
initializeTimelineEvents();
|
initializeTimelineEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleEventTypeOption(option: TimelineEventType) {
|
||||||
|
if (loading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = preferences.value.types.indexOf(option);
|
||||||
|
if (index === -1) {
|
||||||
|
preferences.value.types.push(option);
|
||||||
|
} else {
|
||||||
|
preferences.value.types.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeTimelineEvents();
|
||||||
|
}
|
||||||
|
|
||||||
// Timeline Actions
|
// Timeline Actions
|
||||||
async function updateTimelineEvent(index: number) {
|
async function updateTimelineEvent(index: number) {
|
||||||
const event = timelineEvents.value[index]
|
const event = timelineEvents.value[index]
|
||||||
@@ -179,8 +249,11 @@ export default defineComponent({
|
|||||||
async function scrollTimelineEvents() {
|
async function scrollTimelineEvents() {
|
||||||
const orderBy = "timestamp";
|
const orderBy = "timestamp";
|
||||||
const orderDirection = preferences.value.orderDirection === "asc" ? "asc" : "desc";
|
const orderDirection = preferences.value.orderDirection === "asc" ? "asc" : "desc";
|
||||||
|
// eslint-disable-next-line quotes
|
||||||
|
const eventTypeValue = `["${preferences.value.types.join('", "')}"]`;
|
||||||
|
const queryFilter = `(${props.queryFilter}) AND eventType IN ${eventTypeValue}`
|
||||||
|
|
||||||
const response = await api.recipes.getAllTimelineEvents(page.value, perPage, { orderBy, orderDirection, queryFilter: props.queryFilter });
|
const response = await api.recipes.getAllTimelineEvents(page.value, perPage, { orderBy, orderDirection, queryFilter });
|
||||||
page.value += 1;
|
page.value += 1;
|
||||||
if (!response?.data) {
|
if (!response?.data) {
|
||||||
return;
|
return;
|
||||||
@@ -256,11 +329,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
deleteTimelineEvent,
|
deleteTimelineEvent,
|
||||||
|
filterBadgeCount,
|
||||||
loading,
|
loading,
|
||||||
onScroll,
|
onScroll,
|
||||||
preferences,
|
preferences,
|
||||||
|
eventTypeFilterState,
|
||||||
recipes,
|
recipes,
|
||||||
reverseSort,
|
reverseSort,
|
||||||
|
toggleEventTypeOption,
|
||||||
timelineEvents,
|
timelineEvents,
|
||||||
updateTimelineEvent,
|
updateTimelineEvent,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ import { computed, defineComponent, ref, useContext, useRoute } from "@nuxtjs/co
|
|||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import RecipeTimelineContextMenu from "./RecipeTimelineContextMenu.vue";
|
import RecipeTimelineContextMenu from "./RecipeTimelineContextMenu.vue";
|
||||||
import { useStaticRoutes } from "~/composables/api";
|
import { useStaticRoutes } from "~/composables/api";
|
||||||
|
import { useTimelineEventTypes } from "~/composables/recipes/use-recipe-timeline-events";
|
||||||
import { Recipe, RecipeTimelineEventOut } from "~/lib/api/types/recipe"
|
import { Recipe, RecipeTimelineEventOut } from "~/lib/api/types/recipe"
|
||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||||
import SafeMarkdown from "~/components/global/SafeMarkdown.vue";
|
import SafeMarkdown from "~/components/global/SafeMarkdown.vue";
|
||||||
@@ -124,6 +125,7 @@ export default defineComponent({
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
const { $auth, $globals, $vuetify } = useContext();
|
const { $auth, $globals, $vuetify } = useContext();
|
||||||
const { recipeTimelineEventImage } = useStaticRoutes();
|
const { recipeTimelineEventImage } = useStaticRoutes();
|
||||||
|
const { eventTypeOptions } = useTimelineEventTypes();
|
||||||
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -164,21 +166,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const icon = computed( () => {
|
const icon = computed(() => {
|
||||||
switch (props.event.eventType) {
|
const option = eventTypeOptions.value.find((option) => option.value === props.event.eventType);
|
||||||
case "comment":
|
return option ? option.icon : $globals.icons.informationVariant;
|
||||||
return $globals.icons.commentTextMultiple;
|
});
|
||||||
|
|
||||||
case "info":
|
|
||||||
return $globals.icons.informationVariant;
|
|
||||||
|
|
||||||
case "system":
|
|
||||||
return $globals.icons.cog;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $globals.icons.informationVariant;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
const hideImage = ref(false);
|
const hideImage = ref(false);
|
||||||
const eventImageUrl = computed<string>( () => {
|
const eventImageUrl = computed<string>( () => {
|
||||||
|
|||||||
@@ -8,8 +8,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
// @ts-ignore missing color types
|
||||||
|
import Color from "@sphinxxxx/color-conversion";
|
||||||
import { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
|
import { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
label: {
|
label: {
|
||||||
@@ -34,19 +37,27 @@ export default defineComponent({
|
|||||||
const ACCESSIBILITY_THRESHOLD = 0.179;
|
const ACCESSIBILITY_THRESHOLD = 0.179;
|
||||||
|
|
||||||
function pickTextColorBasedOnBgColorAdvanced(bgColor: string, lightColor: string, darkColor: string) {
|
function pickTextColorBasedOnBgColorAdvanced(bgColor: string, lightColor: string, darkColor: string) {
|
||||||
const color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
|
try {
|
||||||
const r = parseInt(color.substring(0, 2), 16); // hexToR
|
const color = new Color(bgColor);
|
||||||
const g = parseInt(color.substring(2, 4), 16); // hexToG
|
|
||||||
const b = parseInt(color.substring(4, 6), 16); // hexToB
|
// if opacity is less than 0.3 always return dark color
|
||||||
const uicolors = [r / 255, g / 255, b / 255];
|
if (color._rgba[3] < 0.3) {
|
||||||
const c = uicolors.map((col) => {
|
return darkColor;
|
||||||
if (col <= 0.03928) {
|
|
||||||
return col / 12.92;
|
|
||||||
}
|
}
|
||||||
return Math.pow((col + 0.055) / 1.055, 2.4);
|
|
||||||
});
|
const uicolors = [color._rgba[0] / 255, color._rgba[1] / 255, color._rgba[2] / 255];
|
||||||
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
const c = uicolors.map((col) => {
|
||||||
return L > ACCESSIBILITY_THRESHOLD ? darkColor : lightColor;
|
if (col <= 0.03928) {
|
||||||
|
return col / 12.92;
|
||||||
|
}
|
||||||
|
return Math.pow((col + 0.055) / 1.055, 2.4);
|
||||||
|
});
|
||||||
|
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
||||||
|
return L > ACCESSIBILITY_THRESHOLD ? darkColor : lightColor;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
return "black";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
160
frontend/components/Domain/User/UserRegistrationForm.vue
Normal file
160
frontend/components/Domain/User/UserRegistrationForm.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-card-title>
|
||||||
|
<v-icon large class="mr-3"> {{ $globals.icons.user }}</v-icon>
|
||||||
|
<span class="headline"> {{ $t("user-registration.account-details") }}</span>
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider />
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="domAccountForm" @submit.prevent>
|
||||||
|
<v-text-field
|
||||||
|
v-model="accountDetails.username.value"
|
||||||
|
autofocus
|
||||||
|
v-bind="inputAttrs"
|
||||||
|
:label="$tc('user.username')"
|
||||||
|
:prepend-icon="$globals.icons.user"
|
||||||
|
:rules="[validators.required]"
|
||||||
|
:error-messages="usernameErrorMessages"
|
||||||
|
@blur="validateUsername"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="accountDetails.fullName.value"
|
||||||
|
v-bind="inputAttrs"
|
||||||
|
:label="$tc('user.full-name')"
|
||||||
|
:prepend-icon="$globals.icons.user"
|
||||||
|
:rules="[validators.required]"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="accountDetails.email.value"
|
||||||
|
v-bind="inputAttrs"
|
||||||
|
:prepend-icon="$globals.icons.email"
|
||||||
|
:label="$tc('user.email')"
|
||||||
|
:rules="[validators.required, validators.email]"
|
||||||
|
:error-messages="emailErrorMessages"
|
||||||
|
@blur="validateEmail"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="credentials.password1.value"
|
||||||
|
v-bind="inputAttrs"
|
||||||
|
:type="pwFields.inputType.value"
|
||||||
|
:append-icon="pwFields.passwordIcon.value"
|
||||||
|
:prepend-icon="$globals.icons.lock"
|
||||||
|
:label="$tc('user.password')"
|
||||||
|
:rules="[validators.required, validators.minLength(8), validators.maxLength(258)]"
|
||||||
|
@click:append="pwFields.togglePasswordShow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UserPasswordStrength :value="credentials.password1.value" />
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="credentials.password2.value"
|
||||||
|
v-bind="inputAttrs"
|
||||||
|
:type="pwFields.inputType.value"
|
||||||
|
:append-icon="pwFields.passwordIcon.value"
|
||||||
|
:prepend-icon="$globals.icons.lock"
|
||||||
|
:label="$tc('user.confirm-password')"
|
||||||
|
:rules="[validators.required, credentials.passwordMatch]"
|
||||||
|
@click:append="pwFields.togglePasswordShow"
|
||||||
|
/>
|
||||||
|
<div class="px-2">
|
||||||
|
<v-checkbox
|
||||||
|
v-model="accountDetails.advancedOptions.value"
|
||||||
|
:label="$tc('user.enable-advanced-content')"
|
||||||
|
/>
|
||||||
|
<p class="text-caption mt-n4">
|
||||||
|
{{ $tc("user.enable-advanced-content-description") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useDark } from "@vueuse/core";
|
||||||
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { useUserRegistrationForm } from "~/composables/use-users/user-registration-form";
|
||||||
|
import { usePasswordField } from "~/composables/use-passwords";
|
||||||
|
import UserPasswordStrength from "~/components/Domain/User/UserPasswordStrength.vue";
|
||||||
|
|
||||||
|
const inputAttrs = {
|
||||||
|
filled: true,
|
||||||
|
rounded: true,
|
||||||
|
validateOnBlur: true,
|
||||||
|
class: "rounded-lg",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { UserPasswordStrength },
|
||||||
|
layout: "blank",
|
||||||
|
setup() {
|
||||||
|
const isDark = useDark();
|
||||||
|
const langDialog = ref(false);
|
||||||
|
|
||||||
|
const pwFields = usePasswordField();
|
||||||
|
const {
|
||||||
|
accountDetails,
|
||||||
|
credentials,
|
||||||
|
emailErrorMessages,
|
||||||
|
usernameErrorMessages,
|
||||||
|
validateUsername,
|
||||||
|
validateEmail,
|
||||||
|
domAccountForm,
|
||||||
|
} = useUserRegistrationForm();
|
||||||
|
return {
|
||||||
|
accountDetails,
|
||||||
|
credentials,
|
||||||
|
emailErrorMessages,
|
||||||
|
inputAttrs,
|
||||||
|
isDark,
|
||||||
|
langDialog,
|
||||||
|
pwFields,
|
||||||
|
usernameErrorMessages,
|
||||||
|
validators,
|
||||||
|
// Validators
|
||||||
|
validateUsername,
|
||||||
|
validateEmail,
|
||||||
|
// Dom Refs
|
||||||
|
domAccountForm,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.icon-primary {
|
||||||
|
fill: var(--v-primary-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-white {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-divider {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: -2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-avatar {
|
||||||
|
border-color: rgba(0, 0, 0, 0.12);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-off-white {
|
||||||
|
background: #f5f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferred-width {
|
||||||
|
width: 840px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
v-model="sidebar"
|
v-model="sidebar"
|
||||||
absolute
|
absolute
|
||||||
:top-link="topLinks"
|
:top-link="topLinks"
|
||||||
:secondary-header="cookbookLinks.length ? $tc('sidebar.cookbooks') : undefined"
|
|
||||||
:secondary-header-link="isOwnGroup && cookbookLinks.length ? `/g/${groupSlug}/cookbooks` : undefined"
|
|
||||||
:secondary-links="cookbookLinks || []"
|
:secondary-links="cookbookLinks || []"
|
||||||
:bottom-links="isAdmin ? bottomLinks : []"
|
:bottom-links="isAdmin ? bottomLinks : []"
|
||||||
>
|
>
|
||||||
@@ -146,14 +144,6 @@
|
|||||||
to: `/g/${groupSlug.value}/r/create/new`,
|
to: `/g/${groupSlug.value}/r/create/new`,
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
insertDivider: true,
|
|
||||||
icon: $globals.icons.pages,
|
|
||||||
title: i18n.tc("sidebar.cookbook"),
|
|
||||||
subtitle: i18n.tc("sidebar.create-cookbook"),
|
|
||||||
to: `/g/${groupSlug.value}/cookbooks`,
|
|
||||||
restricted: true,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const bottomLinks = computed<SidebarLinks>(() => [
|
const bottomLinks = computed<SidebarLinks>(() => [
|
||||||
@@ -191,22 +181,35 @@
|
|||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.categories,
|
icon: $globals.icons.book,
|
||||||
to: `/g/${groupSlug.value}/recipes/categories`,
|
to: `/g/${groupSlug.value}/cookbooks`,
|
||||||
title: i18n.tc("sidebar.categories"),
|
title: i18n.tc("cookbook.cookbooks"),
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.tags,
|
icon: $globals.icons.organizers,
|
||||||
to: `/g/${groupSlug.value}/recipes/tags`,
|
title: i18n.tc("general.organizers"),
|
||||||
title: i18n.tc("sidebar.tags"),
|
|
||||||
restricted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: $globals.icons.potSteam,
|
|
||||||
to: `/g/${groupSlug.value}/recipes/tools`,
|
|
||||||
title: i18n.tc("tool.tools"),
|
|
||||||
restricted: true,
|
restricted: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
icon: $globals.icons.categories,
|
||||||
|
to: `/g/${groupSlug.value}/recipes/categories`,
|
||||||
|
title: i18n.tc("sidebar.categories"),
|
||||||
|
restricted: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: $globals.icons.tags,
|
||||||
|
to: `/g/${groupSlug.value}/recipes/tags`,
|
||||||
|
title: i18n.tc("sidebar.tags"),
|
||||||
|
restricted: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: $globals.icons.potSteam,
|
||||||
|
to: `/g/${groupSlug.value}/recipes/tools`,
|
||||||
|
title: i18n.tc("tool.tools"),
|
||||||
|
restricted: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -39,13 +39,12 @@
|
|||||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item v-for="child in nav.children" :key="child.title" exact :to="child.to">
|
<v-list-item v-for="child in nav.children" :key="child.title" exact :to="child.to" class="ml-2">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon>{{ child.icon }}</v-icon>
|
<v-icon>{{ child.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
<v-list-item-title>{{ child.title }}</v-list-item-title>
|
<v-list-item-title>{{ child.title }}</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-divider class="mb-4"></v-divider>
|
|
||||||
</v-list-group>
|
</v-list-group>
|
||||||
|
|
||||||
<!-- Single Item -->
|
<!-- Single Item -->
|
||||||
@@ -68,18 +67,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Secondary Links -->
|
<!-- Secondary Links -->
|
||||||
<template v-if="secondaryLinks">
|
<template v-if="secondaryLinks.length > 0">
|
||||||
<router-link v-if="secondaryHeader && secondaryHeaderLink" :to="secondaryHeaderLink" style="text-decoration: none;">
|
<v-divider class="mt-2"></v-divider>
|
||||||
<v-subheader :to="secondaryHeaderLink" class="pb-0">
|
|
||||||
{{ secondaryHeader }}
|
|
||||||
</v-subheader>
|
|
||||||
</router-link>
|
|
||||||
<div v-else-if="secondaryHeader">
|
|
||||||
<v-subheader :to="secondaryHeaderLink" class="pb-0">
|
|
||||||
{{ secondaryHeader }}
|
|
||||||
</v-subheader>
|
|
||||||
</div>
|
|
||||||
<v-divider v-if="secondaryHeader"></v-divider>
|
|
||||||
<v-list nav dense exact>
|
<v-list nav dense exact>
|
||||||
<template v-for="nav in secondaryLinks">
|
<template v-for="nav in secondaryLinks">
|
||||||
<div v-if="!nav.restricted || isOwnGroup" :key="nav.title">
|
<div v-if="!nav.restricted || isOwnGroup" :key="nav.title">
|
||||||
@@ -179,14 +168,6 @@ export default defineComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
secondaryHeader: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
secondaryHeaderLink: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
// V-Model Support
|
// V-Model Support
|
||||||
|
|||||||
@@ -15,12 +15,22 @@
|
|||||||
v-if="inputField.type === fieldTypes.BOOLEAN"
|
v-if="inputField.type === fieldTypes.BOOLEAN"
|
||||||
v-model="value[inputField.varName]"
|
v-model="value[inputField.varName]"
|
||||||
class="my-0 py-0"
|
class="my-0 py-0"
|
||||||
:label="inputField.label"
|
|
||||||
:name="inputField.varName"
|
:name="inputField.varName"
|
||||||
:hint="inputField.hint || ''"
|
|
||||||
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
|
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
|
||||||
@change="emitBlur"
|
@change="emitBlur"
|
||||||
/>
|
>
|
||||||
|
<template #label>
|
||||||
|
<div>
|
||||||
|
<v-card-text class="text-body-1 my-0 py-0">
|
||||||
|
{{ inputField.label }}
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-if="inputField.hint" class="text-caption my-0 py-0">
|
||||||
|
{{ inputField.hint }}
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-checkbox>
|
||||||
|
|
||||||
|
|
||||||
<!-- Text Field -->
|
<!-- Text Field -->
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
<v-icon>{{ item.icon }}</v-icon>
|
<v-icon>{{ item.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
||||||
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item-group>
|
</v-list-item-group>
|
||||||
</v-list>
|
</v-list>
|
||||||
@@ -47,8 +47,8 @@
|
|||||||
<v-icon>{{ item.icon }}</v-icon>
|
<v-icon>{{ item.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
||||||
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
|
||||||
</template>
|
</template>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
|
|||||||
259
frontend/components/global/BaseWizard.vue
Normal file
259
frontend/components/global/BaseWizard.vue
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="`width: ${width}; height: 100%;`">
|
||||||
|
<LanguageDialog v-model="langDialog" />
|
||||||
|
<v-card>
|
||||||
|
<div>
|
||||||
|
<v-toolbar width="100%" color="primary" class="d-flex justify-center" style="margin-bottom: 4rem" dark>
|
||||||
|
<v-toolbar-title class="headline text-h4"> Mealie </v-toolbar-title>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<div class="icon-container">
|
||||||
|
<v-divider class="icon-divider"></v-divider>
|
||||||
|
<v-avatar class="pa-2 icon-avatar" color="primary" size="75">
|
||||||
|
<svg class="icon-white" style="width: 75" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-center grow items-center my-4">
|
||||||
|
<slot :width="pageWidth"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="mx-2 my-4">
|
||||||
|
<v-progress-linear
|
||||||
|
v-if="value > 0"
|
||||||
|
:value="Math.ceil((value/maxPageNumber)*100)"
|
||||||
|
striped
|
||||||
|
height="10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<v-divider class="ma-2" />
|
||||||
|
<v-card-actions width="100%">
|
||||||
|
<v-btn
|
||||||
|
v-if="prevButtonShow"
|
||||||
|
:disabled="!prevButtonEnable"
|
||||||
|
:color="prevButtonColor"
|
||||||
|
@click="decrementPage"
|
||||||
|
>
|
||||||
|
<v-icon v-if="prevButtonIconRef">
|
||||||
|
{{ prevButtonIconRef }}
|
||||||
|
</v-icon>
|
||||||
|
{{ prevButtonTextRef }}
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
v-if="nextButtonShow"
|
||||||
|
:disabled="!nextButtonEnable"
|
||||||
|
:color="nextButtonColorRef"
|
||||||
|
@click="incrementPage"
|
||||||
|
>
|
||||||
|
<div v-if="isSubmitting">
|
||||||
|
<v-progress-circular indeterminate color="white" size="24" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<v-icon v-if="nextButtonIconRef && !nextButtonIconAfter">
|
||||||
|
{{ nextButtonIconRef }}
|
||||||
|
</v-icon>
|
||||||
|
{{ nextButtonTextRef }}
|
||||||
|
<v-icon v-if="nextButtonIconRef && nextButtonIconAfter">
|
||||||
|
{{ nextButtonIconRef }}
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
<v-card-actions class="justify-center flex-column py-8">
|
||||||
|
<BaseButton large color="primary" @click="langDialog = true">
|
||||||
|
<template #icon> {{ $globals.icons.translate }}</template>
|
||||||
|
{{ $t("language-dialog.choose-language") }}
|
||||||
|
</BaseButton>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
minPageNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
maxPageNumber: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "1200px",
|
||||||
|
},
|
||||||
|
pageWidth: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "600px",
|
||||||
|
},
|
||||||
|
prevButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
prevButtonIcon: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
prevButtonColor: {
|
||||||
|
type: String,
|
||||||
|
default: "grey-darken-3",
|
||||||
|
},
|
||||||
|
prevButtonShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
prevButtonEnable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
nextButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
nextButtonIcon: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
nextButtonIconAfter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
nextButtonColor: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
nextButtonShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
nextButtonEnable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
nextButtonIsSubmit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
isSubmitting: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
const { $globals, i18n } = useContext();
|
||||||
|
const ready = ref(false);
|
||||||
|
const langDialog = ref(false);
|
||||||
|
|
||||||
|
const prevButtonTextRef = computed(() => props.prevButtonText || i18n.tc("general.back"));
|
||||||
|
const prevButtonIconRef = computed(() => props.prevButtonIcon || $globals.icons.back);
|
||||||
|
const nextButtonTextRef = computed(
|
||||||
|
() => props.nextButtonText || (
|
||||||
|
props.nextButtonIsSubmit ? i18n.tc("general.submit") : i18n.tc("general.next")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const nextButtonIconRef = computed(
|
||||||
|
() => props.nextButtonIcon || (
|
||||||
|
props.nextButtonIsSubmit ? $globals.icons.createAlt : $globals.icons.forward
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const nextButtonColorRef = computed(
|
||||||
|
() => props.nextButtonColor || (props.nextButtonIsSubmit ? "success" : "info")
|
||||||
|
);
|
||||||
|
|
||||||
|
function goToPage(page: number) {
|
||||||
|
if (page < props.minPageNumber) {
|
||||||
|
goToPage(props.minPageNumber);
|
||||||
|
return;
|
||||||
|
} else if (page > props.maxPageNumber) {
|
||||||
|
goToPage(props.maxPageNumber);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.emit("input", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrementPage() {
|
||||||
|
goToPage(props.value - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementPage() {
|
||||||
|
if (props.nextButtonIsSubmit) {
|
||||||
|
context.emit("submit", props.value);
|
||||||
|
} else {
|
||||||
|
goToPage(props.value + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ready.value = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ready,
|
||||||
|
langDialog,
|
||||||
|
prevButtonTextRef,
|
||||||
|
prevButtonIconRef,
|
||||||
|
nextButtonTextRef,
|
||||||
|
nextButtonIconRef,
|
||||||
|
nextButtonColorRef,
|
||||||
|
decrementPage,
|
||||||
|
incrementPage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.icon-primary {
|
||||||
|
fill: var(--v-primary-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-white {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-divider {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: -2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-avatar {
|
||||||
|
border-color: rgba(0, 0, 0, 0.12);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-off-white {
|
||||||
|
background: #f5f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferred-width {
|
||||||
|
width: 840px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -37,6 +37,7 @@ export function usePublicStoreActions<T extends BoundT>(
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
const allItems = useAsync(async () => {
|
const allItems = useAsync(async () => {
|
||||||
const { data } = await api.getAll(page, perPage, params);
|
const { data } = await api.getAll(page, perPage, params);
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
if (data && allRef) {
|
if (data && allRef) {
|
||||||
allRef.value = data.items;
|
allRef.value = data.items;
|
||||||
@@ -49,7 +50,6 @@ export function usePublicStoreActions<T extends BoundT>(
|
|||||||
}
|
}
|
||||||
}, useAsyncKey());
|
}, useAsyncKey());
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
return allItems;
|
return allItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +88,7 @@ export function useStoreActions<T extends BoundT>(
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
const allItems = useAsync(async () => {
|
const allItems = useAsync(async () => {
|
||||||
const { data } = await api.getAll(page, perPage, params);
|
const { data } = await api.getAll(page, perPage, params);
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
if (data && allRef) {
|
if (data && allRef) {
|
||||||
allRef.value = data.items;
|
allRef.value = data.items;
|
||||||
@@ -100,7 +101,6 @@ export function useStoreActions<T extends BoundT>(
|
|||||||
}
|
}
|
||||||
}, useAsyncKey());
|
}, useAsyncKey());
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
return allItems;
|
return allItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
frontend/composables/recipes/use-recipe-timeline-events.ts
Normal file
35
frontend/composables/recipes/use-recipe-timeline-events.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { computed, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { TimelineEventType } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
export interface TimelineEventTypeData {
|
||||||
|
value: TimelineEventType;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTimelineEventTypes = () => {
|
||||||
|
const { $globals, i18n } = useContext();
|
||||||
|
const eventTypeOptions = computed<TimelineEventTypeData[]>(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: "comment",
|
||||||
|
label: i18n.tc("recipe.comment"),
|
||||||
|
icon: $globals.icons.commentTextMultiple,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "info",
|
||||||
|
label: i18n.tc("settings.theme.info"),
|
||||||
|
icon: $globals.icons.informationVariant,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system",
|
||||||
|
label: i18n.tc("general.system"),
|
||||||
|
icon: $globals.icons.cog,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
eventTypeOptions,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
|
|||||||
import { useAsyncKey } from "../use-utils";
|
import { useAsyncKey } from "../use-utils";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { VForm } from "~/types/vuetify";
|
import { VForm } from "~/types/vuetify";
|
||||||
import { RecipeTool } from "~/lib/api/types/user";
|
import { RecipeTool } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export const useTools = function (eager = true) {
|
export const useTools = function (eager = true) {
|
||||||
const workingToolData = reactive<RecipeTool>({
|
const workingToolData = reactive<RecipeTool>({
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { reactive, ref, Ref } from "@nuxtjs/composition-api";
|
|||||||
import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
|
import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
|
||||||
import { usePublicExploreApi } from "../api/api-client";
|
import { usePublicExploreApi } from "../api/api-client";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { RecipeCategory } from "~/lib/api/types/admin";
|
import { RecipeCategory } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
const categoryStore: Ref<RecipeCategory[]> = ref([]);
|
const categoryStore: Ref<RecipeCategory[]> = ref([]);
|
||||||
|
const publicStoreLoading = ref(false);
|
||||||
|
const storeLoading = ref(false);
|
||||||
|
|
||||||
export function useCategoryData() {
|
export function useCategoryData() {
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@@ -27,7 +29,7 @@ export function useCategoryData() {
|
|||||||
|
|
||||||
export function usePublicCategoryStore(groupSlug: string) {
|
export function usePublicCategoryStore(groupSlug: string) {
|
||||||
const api = usePublicExploreApi(groupSlug).explore;
|
const api = usePublicExploreApi(groupSlug).explore;
|
||||||
const loading = ref(false);
|
const loading = publicStoreLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...usePublicStoreActions<RecipeCategory>(api.categories, categoryStore, loading),
|
...usePublicStoreActions<RecipeCategory>(api.categories, categoryStore, loading),
|
||||||
@@ -36,7 +38,7 @@ export function usePublicCategoryStore(groupSlug: string) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!categoryStore.value || categoryStore.value?.length === 0) {
|
if (!loading.value && (!categoryStore.value || categoryStore.value?.length === 0)) {
|
||||||
actions.getAll();
|
actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ export function usePublicCategoryStore(groupSlug: string) {
|
|||||||
export function useCategoryStore() {
|
export function useCategoryStore() {
|
||||||
// passing the group slug switches to using the public API
|
// passing the group slug switches to using the public API
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = storeLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<RecipeCategory>(api.categories, categoryStore, loading),
|
...useStoreActions<RecipeCategory>(api.categories, categoryStore, loading),
|
||||||
@@ -59,7 +61,7 @@ export function useCategoryStore() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!categoryStore.value || categoryStore.value?.length === 0) {
|
if (!loading.value && (!categoryStore.value || categoryStore.value?.length === 0)) {
|
||||||
actions.getAll();
|
actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { IngredientFood } from "~/lib/api/types/recipe";
|
import { IngredientFood } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
let foodStore: Ref<IngredientFood[] | null> = ref([]);
|
let foodStore: Ref<IngredientFood[] | null> = ref([]);
|
||||||
|
const publicStoreLoading = ref(false);
|
||||||
|
const storeLoading = ref(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useFoodData returns a template reactive object
|
* useFoodData returns a template reactive object
|
||||||
@@ -34,7 +36,7 @@ export const useFoodData = function () {
|
|||||||
|
|
||||||
export const usePublicFoodStore = function (groupSlug: string) {
|
export const usePublicFoodStore = function (groupSlug: string) {
|
||||||
const api = usePublicExploreApi(groupSlug).explore;
|
const api = usePublicExploreApi(groupSlug).explore;
|
||||||
const loading = ref(false);
|
const loading = publicStoreLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...usePublicStoreActions(api.foods, foodStore, loading),
|
...usePublicStoreActions(api.foods, foodStore, loading),
|
||||||
@@ -43,7 +45,7 @@ export const usePublicFoodStore = function (groupSlug: string) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!foodStore.value || foodStore.value.length === 0) {
|
if (!loading.value && (!foodStore.value || foodStore.value.length === 0)) {
|
||||||
foodStore = actions.getAll();
|
foodStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ export const usePublicFoodStore = function (groupSlug: string) {
|
|||||||
|
|
||||||
export const useFoodStore = function () {
|
export const useFoodStore = function () {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = storeLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions(api.foods, foodStore, loading),
|
...useStoreActions(api.foods, foodStore, loading),
|
||||||
@@ -61,7 +63,7 @@ export const useFoodStore = function () {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!foodStore.value || foodStore.value.length === 0) {
|
if (!loading.value && (!foodStore.value || foodStore.value.length === 0)) {
|
||||||
foodStore = actions.getAll();
|
foodStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
|||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
let labelStore: Ref<MultiPurposeLabelOut[] | null> = ref([]);
|
let labelStore: Ref<MultiPurposeLabelOut[] | null> = ref([]);
|
||||||
|
const storeLoading = ref(false);
|
||||||
|
|
||||||
export function useLabelData() {
|
export function useLabelData() {
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@@ -28,7 +29,7 @@ export function useLabelData() {
|
|||||||
|
|
||||||
export function useLabelStore() {
|
export function useLabelStore() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = storeLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading),
|
...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading),
|
||||||
@@ -37,7 +38,7 @@ export function useLabelStore() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!labelStore.value || labelStore.value?.length === 0) {
|
if (!loading.value && (!labelStore.value || labelStore.value?.length === 0)) {
|
||||||
labelStore = actions.getAll();
|
labelStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { RecipeTag } from "~/lib/api/types/admin";
|
import { RecipeTag } from "~/lib/api/types/admin";
|
||||||
|
|
||||||
const items: Ref<RecipeTag[]> = ref([]);
|
const items: Ref<RecipeTag[]> = ref([]);
|
||||||
|
const publicStoreLoading = ref(false);
|
||||||
|
const storeLoading = ref(false);
|
||||||
|
|
||||||
export function useTagData() {
|
export function useTagData() {
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@@ -27,7 +29,7 @@ export function useTagData() {
|
|||||||
|
|
||||||
export function usePublicTagStore(groupSlug: string) {
|
export function usePublicTagStore(groupSlug: string) {
|
||||||
const api = usePublicExploreApi(groupSlug).explore;
|
const api = usePublicExploreApi(groupSlug).explore;
|
||||||
const loading = ref(false);
|
const loading = publicStoreLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...usePublicStoreActions<RecipeTag>(api.tags, items, loading),
|
...usePublicStoreActions<RecipeTag>(api.tags, items, loading),
|
||||||
@@ -36,7 +38,7 @@ export function usePublicTagStore(groupSlug: string) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!items.value || items.value?.length === 0) {
|
if (!loading.value && (!items.value || items.value?.length === 0)) {
|
||||||
actions.getAll();
|
actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ export function usePublicTagStore(groupSlug: string) {
|
|||||||
|
|
||||||
export function useTagStore() {
|
export function useTagStore() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = storeLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<RecipeTag>(api.tags, items, loading),
|
...useStoreActions<RecipeTag>(api.tags, items, loading),
|
||||||
@@ -58,7 +60,7 @@ export function useTagStore() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!items.value || items.value?.length === 0) {
|
if (!loading.value && (!items.value || items.value?.length === 0)) {
|
||||||
actions.getAll();
|
actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { RecipeTool } from "~/lib/api/types/recipe";
|
import { RecipeTool } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
const toolStore: Ref<RecipeTool[]> = ref([]);
|
const toolStore: Ref<RecipeTool[]> = ref([]);
|
||||||
|
const publicStoreLoading = ref(false);
|
||||||
|
const storeLoading = ref(false);
|
||||||
|
|
||||||
export function useToolData() {
|
export function useToolData() {
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
@@ -29,7 +31,7 @@ export function useToolData() {
|
|||||||
|
|
||||||
export function usePublicToolStore(groupSlug: string) {
|
export function usePublicToolStore(groupSlug: string) {
|
||||||
const api = usePublicExploreApi(groupSlug).explore;
|
const api = usePublicExploreApi(groupSlug).explore;
|
||||||
const loading = ref(false);
|
const loading = publicStoreLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...usePublicStoreActions<RecipeTool>(api.tools, toolStore, loading),
|
...usePublicStoreActions<RecipeTool>(api.tools, toolStore, loading),
|
||||||
@@ -38,7 +40,7 @@ export function usePublicToolStore(groupSlug: string) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!toolStore.value || toolStore.value?.length === 0) {
|
if (!loading.value && (!toolStore.value || toolStore.value?.length === 0)) {
|
||||||
actions.getAll();
|
actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ export function usePublicToolStore(groupSlug: string) {
|
|||||||
|
|
||||||
export function useToolStore() {
|
export function useToolStore() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = storeLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<RecipeTool>(api.tools, toolStore, loading),
|
...useStoreActions<RecipeTool>(api.tools, toolStore, loading),
|
||||||
@@ -60,7 +62,7 @@ export function useToolStore() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!toolStore.value || toolStore.value?.length === 0) {
|
if (!loading.value && (!toolStore.value || toolStore.value?.length === 0)) {
|
||||||
actions.getAll();
|
actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { IngredientUnit } from "~/lib/api/types/recipe";
|
import { IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
let unitStore: Ref<IngredientUnit[] | null> = ref([]);
|
let unitStore: Ref<IngredientUnit[] | null> = ref([]);
|
||||||
|
const storeLoading = ref(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* useUnitData returns a template reactive object
|
* useUnitData returns a template reactive object
|
||||||
@@ -35,7 +36,7 @@ export const useUnitData = function () {
|
|||||||
|
|
||||||
export const useUnitStore = function () {
|
export const useUnitStore = function () {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = storeLoading;
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
...useStoreActions<IngredientUnit>(api.units, unitStore, loading),
|
...useStoreActions<IngredientUnit>(api.units, unitStore, loading),
|
||||||
@@ -44,7 +45,7 @@ export const useUnitStore = function () {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!unitStore.value || unitStore.value.length === 0) {
|
if (!loading.value && (!unitStore.value || unitStore.value.length === 0)) {
|
||||||
unitStore = actions.getAll();
|
unitStore = actions.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ export const useCookbooks = function () {
|
|||||||
this.refreshAll();
|
this.refreshAll();
|
||||||
}
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateOrder() {
|
async updateOrder() {
|
||||||
|
|||||||
98
frontend/composables/use-group-recipe-actions.ts
Normal file
98
frontend/composables/use-group-recipe-actions.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { computed, reactive, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useStoreActions } from "./partials/use-actions-factory";
|
||||||
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { GroupRecipeActionOut, RecipeActionType } from "~/lib/api/types/group";
|
||||||
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
export function useGroupRecipeActionData() {
|
||||||
|
const data = reactive({
|
||||||
|
id: "",
|
||||||
|
actionType: "link" as RecipeActionType,
|
||||||
|
title: "",
|
||||||
|
url: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
data.id = "";
|
||||||
|
data.actionType = "link";
|
||||||
|
data.title = "";
|
||||||
|
data.url = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGroupRecipeActions = function (
|
||||||
|
orderBy: string | null = "title",
|
||||||
|
orderDirection: string | null = "asc",
|
||||||
|
) {
|
||||||
|
const api = useUserApi();
|
||||||
|
|
||||||
|
async function refreshGroupRecipeActions() {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.groupRecipeActions.getAll(1, -1, { orderBy, orderDirection });
|
||||||
|
groupRecipeActions.value = data?.items || null;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipeActions = computed<GroupRecipeActionOut[] | null>(() => {
|
||||||
|
return groupRecipeActions.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseRecipeActionUrl(url: string, recipe: Recipe): string {
|
||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
return url
|
||||||
|
.replace("${url}", window.location.href)
|
||||||
|
.replace("${id}", recipe.id || "")
|
||||||
|
.replace("${slug}", recipe.slug || "")
|
||||||
|
/* eslint-enable no-template-curly-in-string */
|
||||||
|
};
|
||||||
|
|
||||||
|
async function execute(action: GroupRecipeActionOut, recipe: Recipe): Promise<void | Response> {
|
||||||
|
const url = parseRecipeActionUrl(action.url, recipe);
|
||||||
|
|
||||||
|
switch (action.actionType) {
|
||||||
|
case "link":
|
||||||
|
window.open(url, "_blank")?.focus();
|
||||||
|
break;
|
||||||
|
case "post":
|
||||||
|
return await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
// The "text/plain" content type header is used here to skip the CORS preflight request,
|
||||||
|
// since it may fail. This is fine, since we don't care about the response, we just want
|
||||||
|
// the request to get sent.
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(recipe),
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!groupRecipeActions.value && !loading.value) {
|
||||||
|
refreshGroupRecipeActions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
...useStoreActions<GroupRecipeActionOut>(api.groupRecipeActions, groupRecipeActions, loading),
|
||||||
|
flushStore() {
|
||||||
|
groupRecipeActions.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions,
|
||||||
|
execute,
|
||||||
|
recipeActions,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,30 +1,39 @@
|
|||||||
import { useAsync, ref } from "@nuxtjs/composition-api";
|
import { useAsync, ref } from "@nuxtjs/composition-api";
|
||||||
import { useAsyncKey } from "./use-utils";
|
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { GroupBase } from "~/lib/api/types/user";
|
import { GroupBase, GroupSummary } from "~/lib/api/types/user";
|
||||||
|
|
||||||
|
const groupSelfRef = ref<GroupSummary | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
export const useGroupSelf = function () {
|
export const useGroupSelf = function () {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
async function refreshGroupSelf() {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.groups.getCurrentUserGroup();
|
||||||
|
groupSelfRef.value = data;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
get() {
|
get() {
|
||||||
const group = useAsync(async () => {
|
if (!(groupSelfRef.value || loading.value)) {
|
||||||
const { data } = await api.groups.getCurrentUserGroup();
|
refreshGroupSelf();
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return groupSelfRef;
|
||||||
}, useAsyncKey());
|
|
||||||
|
|
||||||
return group;
|
|
||||||
},
|
},
|
||||||
async updatePreferences() {
|
async updatePreferences() {
|
||||||
if (!group.value?.preferences) {
|
if (!groupSelfRef.value) {
|
||||||
|
await refreshGroupSelf();
|
||||||
|
}
|
||||||
|
if (!groupSelfRef.value?.preferences) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await api.groups.setPreferences(group.value.preferences);
|
const { data } = await api.groups.setPreferences(groupSelfRef.value.preferences);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
group.value.preferences = data;
|
groupSelfRef.value.preferences = data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// This Code is auto generated by gen_global_components.py
|
// This Code is auto generated by gen_ts_locales.py
|
||||||
export const LOCALES = [
|
export const LOCALES = [
|
||||||
{
|
{
|
||||||
name: "繁體中文 (Chinese traditional)",
|
name: "繁體中文 (Chinese traditional)",
|
||||||
value: "zh-TW",
|
value: "zh-TW",
|
||||||
progress: 30,
|
progress: 29,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Tiếng Việt (Vietnamese)",
|
name: "Tiếng Việt (Vietnamese)",
|
||||||
value: "vi-VN",
|
value: "vi-VN",
|
||||||
progress: 1,
|
progress: 0,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -27,43 +27,43 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Türkçe (Turkish)",
|
name: "Türkçe (Turkish)",
|
||||||
value: "tr-TR",
|
value: "tr-TR",
|
||||||
progress: 53,
|
progress: 62,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Svenska (Swedish)",
|
name: "Svenska (Swedish)",
|
||||||
value: "sv-SE",
|
value: "sv-SE",
|
||||||
progress: 94,
|
progress: 99,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "српски (Serbian)",
|
name: "српски (Serbian)",
|
||||||
value: "sr-SP",
|
value: "sr-SP",
|
||||||
progress: 32,
|
progress: 31,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Slovenian",
|
name: "Slovenian",
|
||||||
value: "sl-SI",
|
value: "sl-SI",
|
||||||
progress: 47,
|
progress: 49,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Slovak",
|
name: "Slovak",
|
||||||
value: "sk-SK",
|
value: "sk-SK",
|
||||||
progress: 93,
|
progress: 91,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pусский (Russian)",
|
name: "Pусский (Russian)",
|
||||||
value: "ru-RU",
|
value: "ru-RU",
|
||||||
progress: 98,
|
progress: 99,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Română (Romanian)",
|
name: "Română (Romanian)",
|
||||||
value: "ro-RO",
|
value: "ro-RO",
|
||||||
progress: 42,
|
progress: 44,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -75,19 +75,19 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Português do Brasil (Brazilian Portuguese)",
|
name: "Português do Brasil (Brazilian Portuguese)",
|
||||||
value: "pt-BR",
|
value: "pt-BR",
|
||||||
progress: 97,
|
progress: 95,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Polski (Polish)",
|
name: "Polski (Polish)",
|
||||||
value: "pl-PL",
|
value: "pl-PL",
|
||||||
progress: 98,
|
progress: 100,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Norsk (Norwegian)",
|
name: "Norsk (Norwegian)",
|
||||||
value: "no-NO",
|
value: "no-NO",
|
||||||
progress: 99,
|
progress: 97,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -99,25 +99,25 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Latvian",
|
name: "Latvian",
|
||||||
value: "lv-LV",
|
value: "lv-LV",
|
||||||
progress: 1,
|
progress: 0,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Lithuanian",
|
name: "Lithuanian",
|
||||||
value: "lt-LT",
|
value: "lt-LT",
|
||||||
progress: 93,
|
progress: 91,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "한국어 (Korean)",
|
name: "한국어 (Korean)",
|
||||||
value: "ko-KR",
|
value: "ko-KR",
|
||||||
progress: 5,
|
progress: 3,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "日本語 (Japanese)",
|
name: "日本語 (Japanese)",
|
||||||
value: "ja-JP",
|
value: "ja-JP",
|
||||||
progress: 12,
|
progress: 11,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -135,25 +135,25 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Magyar (Hungarian)",
|
name: "Magyar (Hungarian)",
|
||||||
value: "hu-HU",
|
value: "hu-HU",
|
||||||
progress: 100,
|
progress: 98,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Croatian",
|
name: "Croatian",
|
||||||
value: "hr-HR",
|
value: "hr-HR",
|
||||||
progress: 93,
|
progress: 91,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "עברית (Hebrew)",
|
name: "עברית (Hebrew)",
|
||||||
value: "he-IL",
|
value: "he-IL",
|
||||||
progress: 97,
|
progress: 98,
|
||||||
dir: "rtl",
|
dir: "rtl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Galician",
|
name: "Galician",
|
||||||
value: "gl-ES",
|
value: "gl-ES",
|
||||||
progress: 1,
|
progress: 3,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -165,19 +165,19 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "French, Canada",
|
name: "French, Canada",
|
||||||
value: "fr-CA",
|
value: "fr-CA",
|
||||||
progress: 97,
|
progress: 95,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Suomi (Finnish)",
|
name: "Suomi (Finnish)",
|
||||||
value: "fi-FI",
|
value: "fi-FI",
|
||||||
progress: 91,
|
progress: 89,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Español (Spanish)",
|
name: "Español (Spanish)",
|
||||||
value: "es-ES",
|
value: "es-ES",
|
||||||
progress: 79,
|
progress: 93,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -189,13 +189,13 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "British English",
|
name: "British English",
|
||||||
value: "en-GB",
|
value: "en-GB",
|
||||||
progress: 3,
|
progress: 2,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ελληνικά (Greek)",
|
name: "Ελληνικά (Greek)",
|
||||||
value: "el-GR",
|
value: "el-GR",
|
||||||
progress: 34,
|
progress: 33,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -219,7 +219,7 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "Català (Catalan)",
|
name: "Català (Catalan)",
|
||||||
value: "ca-ES",
|
value: "ca-ES",
|
||||||
progress: 75,
|
progress: 74,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,13 +231,13 @@ export const LOCALES = [
|
|||||||
{
|
{
|
||||||
name: "العربية (Arabic)",
|
name: "العربية (Arabic)",
|
||||||
value: "ar-SA",
|
value: "ar-SA",
|
||||||
progress: 20,
|
progress: 18,
|
||||||
dir: "rtl",
|
dir: "rtl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Afrikaans (Afrikaans)",
|
name: "Afrikaans (Afrikaans)",
|
||||||
value: "af-ZA",
|
value: "af-ZA",
|
||||||
progress: 92,
|
progress: 90,
|
||||||
dir: "ltr",
|
dir: "ltr",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
30
frontend/composables/use-setup/common-settings-form.ts
Normal file
30
frontend/composables/use-setup/common-settings-form.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { fieldTypes } from "../forms";
|
||||||
|
import { AutoFormItems } from "~/types/auto-forms";
|
||||||
|
|
||||||
|
export const useCommonSettingsForm = () => {
|
||||||
|
const { i18n } = useContext();
|
||||||
|
|
||||||
|
const commonSettingsForm: AutoFormItems = [
|
||||||
|
{
|
||||||
|
section: i18n.tc("profile.group-settings"),
|
||||||
|
label: i18n.tc("group.enable-public-access"),
|
||||||
|
hint: i18n.tc("group.enable-public-access-description"),
|
||||||
|
varName: "makeGroupRecipesPublic",
|
||||||
|
type: fieldTypes.BOOLEAN,
|
||||||
|
rules: ["required"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: i18n.tc("data-pages.data-management"),
|
||||||
|
label: i18n.tc("user-registration.use-seed-data"),
|
||||||
|
hint: i18n.tc("user-registration.use-seed-data-description"),
|
||||||
|
varName: "useSeedData",
|
||||||
|
type: fieldTypes.BOOLEAN,
|
||||||
|
rules: ["required"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
commonSettingsForm,
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/composables/use-setup/index.ts
Normal file
1
frontend/composables/use-setup/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { useCommonSettingsForm } from "./common-settings-form";
|
||||||
@@ -1 +1,3 @@
|
|||||||
export { useUserForm } from "./user-form";
|
export { useUserForm } from "./user-form";
|
||||||
|
export { useUserRegistrationForm } from "./user-registration-form";
|
||||||
|
export { useUserSelfRatings } from "./user-ratings";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Ref, useContext } from "@nuxtjs/composition-api";
|
import { Ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
|
||||||
|
import { TimelineEventType } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export interface UserPrintPreferences {
|
export interface UserPrintPreferences {
|
||||||
imagePosition: string;
|
imagePosition: string;
|
||||||
@@ -7,6 +8,10 @@ export interface UserPrintPreferences {
|
|||||||
showNotes: boolean;
|
showNotes: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserSearchQuery {
|
||||||
|
recipe: string;
|
||||||
|
}
|
||||||
|
|
||||||
export enum ImagePosition {
|
export enum ImagePosition {
|
||||||
hidden = "hidden",
|
hidden = "hidden",
|
||||||
left = "left",
|
left = "left",
|
||||||
@@ -28,6 +33,7 @@ export interface UserShoppingListPreferences {
|
|||||||
|
|
||||||
export interface UserTimelinePreferences {
|
export interface UserTimelinePreferences {
|
||||||
orderDirection: string;
|
orderDirection: string;
|
||||||
|
types: TimelineEventType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
|
export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
|
||||||
@@ -66,6 +72,20 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
|
|||||||
return fromStorage;
|
return fromStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useUserSearchQuerySession(): Ref<UserSearchQuery> {
|
||||||
|
const fromStorage = useSessionStorage(
|
||||||
|
"search-query",
|
||||||
|
{
|
||||||
|
recipe: "",
|
||||||
|
},
|
||||||
|
{ mergeDefaults: true }
|
||||||
|
// we cast to a Ref because by default it will return an optional type ref
|
||||||
|
// but since we pass defaults we know all properties are set.
|
||||||
|
) as unknown as Ref<UserSearchQuery>;
|
||||||
|
|
||||||
|
return fromStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
|
export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
|
||||||
const fromStorage = useLocalStorage(
|
const fromStorage = useLocalStorage(
|
||||||
@@ -87,6 +107,7 @@ export function useTimelinePreferences(): Ref<UserTimelinePreferences> {
|
|||||||
"timeline-preferences",
|
"timeline-preferences",
|
||||||
{
|
{
|
||||||
orderDirection: "asc",
|
orderDirection: "asc",
|
||||||
|
types: ["info", "system", "comment"] as TimelineEventType[],
|
||||||
},
|
},
|
||||||
{ mergeDefaults: true }
|
{ mergeDefaults: true }
|
||||||
// we cast to a Ref because by default it will return an optional type ref
|
// we cast to a Ref because by default it will return an optional type ref
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const useUserForm = () => {
|
|||||||
type: fieldTypes.SELECT,
|
type: fieldTypes.SELECT,
|
||||||
hint: i18n.tc("user.authentication-method-hint"),
|
hint: i18n.tc("user.authentication-method-hint"),
|
||||||
disableCreate: true,
|
disableCreate: true,
|
||||||
options: [{ text: "Mealie" }, { text: "LDAP" }],
|
options: [{ text: "Mealie" }, { text: "LDAP" }, { text: "OIDC" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
section: i18n.tc("user.permissions"),
|
section: i18n.tc("user.permissions"),
|
||||||
|
|||||||
40
frontend/composables/use-users/user-ratings.ts
Normal file
40
frontend/composables/use-users/user-ratings.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { ref, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { UserRatingSummary } from "~/lib/api/types/user";
|
||||||
|
|
||||||
|
const userRatings = ref<UserRatingSummary[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const ready = ref(false);
|
||||||
|
|
||||||
|
export const useUserSelfRatings = function () {
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const api = useUserApi();
|
||||||
|
|
||||||
|
async function refreshUserRatings() {
|
||||||
|
if (loading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.users.getSelfRatings();
|
||||||
|
userRatings.value = data?.ratings || [];
|
||||||
|
loading.value = false;
|
||||||
|
ready.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setRating(slug: string, rating: number | null, isFavorite: boolean | null) {
|
||||||
|
loading.value = true;
|
||||||
|
const userId = $auth.user?.id || "";
|
||||||
|
await api.users.setRating(userId, slug, rating, isFavorite);
|
||||||
|
loading.value = false;
|
||||||
|
await refreshUserRatings();
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshUserRatings();
|
||||||
|
return {
|
||||||
|
userRatings,
|
||||||
|
refreshUserRatings,
|
||||||
|
setRating,
|
||||||
|
ready,
|
||||||
|
}
|
||||||
|
}
|
||||||
87
frontend/composables/use-users/user-registration-form.ts
Normal file
87
frontend/composables/use-users/user-registration-form.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { ref, Ref, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { useAsyncValidator } from "~/composables/use-validators";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
import { usePublicApi } from "~/composables/api/api-client";
|
||||||
|
|
||||||
|
const domAccountForm = ref<VForm | null>(null);
|
||||||
|
const username = ref("");
|
||||||
|
const fullName = ref("");
|
||||||
|
const email = ref("");
|
||||||
|
const password1 = ref("");
|
||||||
|
const password2 = ref("");
|
||||||
|
const advancedOptions = ref(false);
|
||||||
|
|
||||||
|
export const useUserRegistrationForm = () => {
|
||||||
|
const { i18n } = useContext();
|
||||||
|
function safeValidate(form: Ref<VForm | null>) {
|
||||||
|
if (form.value && form.value.validate) {
|
||||||
|
return form.value.validate();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// ================================================================
|
||||||
|
// Provide Group Details
|
||||||
|
const publicApi = usePublicApi();
|
||||||
|
// ================================================================
|
||||||
|
// Provide Account Details
|
||||||
|
|
||||||
|
const usernameErrorMessages = ref<string[]>([]);
|
||||||
|
const { validate: validateUsername, valid: validUsername } = useAsyncValidator(
|
||||||
|
username,
|
||||||
|
(v: string) => publicApi.validators.username(v),
|
||||||
|
i18n.tc("validation.username-is-taken"),
|
||||||
|
usernameErrorMessages
|
||||||
|
);
|
||||||
|
const emailErrorMessages = ref<string[]>([]);
|
||||||
|
const { validate: validateEmail, valid: validEmail } = useAsyncValidator(
|
||||||
|
email,
|
||||||
|
(v: string) => publicApi.validators.email(v),
|
||||||
|
i18n.tc("validation.email-is-taken"),
|
||||||
|
emailErrorMessages
|
||||||
|
);
|
||||||
|
const accountDetails = {
|
||||||
|
username,
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
advancedOptions,
|
||||||
|
validate: async () => {
|
||||||
|
if (!(validUsername.value && validEmail.value)) {
|
||||||
|
await Promise.all([validateUsername(), validateEmail()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (safeValidate(domAccountForm as Ref<VForm>) && validUsername.value && validEmail.value);
|
||||||
|
},
|
||||||
|
reset: () => {
|
||||||
|
accountDetails.username.value = "";
|
||||||
|
accountDetails.fullName.value = "";
|
||||||
|
accountDetails.email.value = "";
|
||||||
|
accountDetails.advancedOptions.value = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// ================================================================
|
||||||
|
// Provide Credentials
|
||||||
|
const passwordMatch = () => password1.value === password2.value || i18n.tc("user.password-must-match");
|
||||||
|
const credentials = {
|
||||||
|
password1,
|
||||||
|
password2,
|
||||||
|
passwordMatch,
|
||||||
|
reset: () => {
|
||||||
|
credentials.password1.value = "";
|
||||||
|
credentials.password2.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
accountDetails,
|
||||||
|
credentials,
|
||||||
|
emailErrorMessages,
|
||||||
|
usernameErrorMessages,
|
||||||
|
// Fields
|
||||||
|
advancedOptions,
|
||||||
|
// Validators
|
||||||
|
validateUsername,
|
||||||
|
validateEmail,
|
||||||
|
// Dom Refs
|
||||||
|
domAccountForm,
|
||||||
|
};
|
||||||
|
};
|
||||||
21
frontend/lang/dateTimeFormats/gl-ES.json
Normal file
21
frontend/lang/dateTimeFormats/gl-ES.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"short": {
|
||||||
|
"month": "short",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"year": "numeric"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"year": "numeric",
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"hour": "numeric",
|
||||||
|
"minute": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/lang/dateTimeFormats/hr-HR.json
Normal file
21
frontend/lang/dateTimeFormats/hr-HR.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"short": {
|
||||||
|
"month": "short",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"year": "numeric"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"year": "numeric",
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"hour": "numeric",
|
||||||
|
"minute": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/lang/dateTimeFormats/is-IS.json
Normal file
21
frontend/lang/dateTimeFormats/is-IS.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"short": {
|
||||||
|
"month": "short",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"year": "numeric"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"year": "numeric",
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"hour": "numeric",
|
||||||
|
"minute": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/lang/dateTimeFormats/lt-LT.json
Normal file
21
frontend/lang/dateTimeFormats/lt-LT.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"short": {
|
||||||
|
"month": "short",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"year": "numeric"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"year": "numeric",
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"hour": "numeric",
|
||||||
|
"minute": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/lang/dateTimeFormats/lv-LV.json
Normal file
21
frontend/lang/dateTimeFormats/lv-LV.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"short": {
|
||||||
|
"month": "short",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"year": "numeric"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"year": "numeric",
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"hour": "numeric",
|
||||||
|
"minute": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/lang/dateTimeFormats/sl-SI.json
Normal file
21
frontend/lang/dateTimeFormats/sl-SI.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"short": {
|
||||||
|
"month": "short",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"year": "numeric"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"year": "numeric",
|
||||||
|
"month": "long",
|
||||||
|
"day": "numeric",
|
||||||
|
"weekday": "long",
|
||||||
|
"hour": "numeric",
|
||||||
|
"minute": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Iets het verkeerd geloop!",
|
"something-went-wrong": "Iets het verkeerd geloop!",
|
||||||
"subscribed-events": "Ingetekende Gebeure",
|
"subscribed-events": "Ingetekende Gebeure",
|
||||||
"test-message-sent": "Toets Boodskap Gestuur",
|
"test-message-sent": "Toets Boodskap Gestuur",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "Nuwe kennisgewing",
|
"new-notification": "Nuwe kennisgewing",
|
||||||
"event-notifiers": "Gebeurteniskennisgewers",
|
"event-notifiers": "Gebeurteniskennisgewers",
|
||||||
"apprise-url-skipped-if-blank": "Apprise URL (oorgeslaan indien leeg)",
|
"apprise-url-skipped-if-blank": "Apprise URL (oorgeslaan indien leeg)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "Kanselleer",
|
"cancel": "Kanselleer",
|
||||||
"clear": "Maak skoon",
|
"clear": "Maak skoon",
|
||||||
"close": "Maak toe",
|
"close": "Maak toe",
|
||||||
"confirm": "Bevestig",
|
"confirm": "Bevestig",
|
||||||
|
"confirm-how-does-everything-look": "How does everything look?",
|
||||||
"confirm-delete-generic": "Is jy seker jy wil dit uitvee?",
|
"confirm-delete-generic": "Is jy seker jy wil dit uitvee?",
|
||||||
"copied_message": "Gekopieër!",
|
"copied_message": "Gekopieër!",
|
||||||
"create": "Skep",
|
"create": "Skep",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Stoor",
|
"save": "Stoor",
|
||||||
"settings": "Verstellings",
|
"settings": "Verstellings",
|
||||||
"share": "Deel",
|
"share": "Deel",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "Skommel",
|
"shuffle": "Skommel",
|
||||||
"sort": "Sorteer",
|
"sort": "Sorteer",
|
||||||
|
"sort-ascending": "Sort Ascending",
|
||||||
|
"sort-descending": "Sort Descending",
|
||||||
"sort-alphabetically": "Alfabeties",
|
"sort-alphabetically": "Alfabeties",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"subject": "Onderwerp",
|
"subject": "Onderwerp",
|
||||||
"submit": "Dien in",
|
"submit": "Dien in",
|
||||||
"success-count": "Sukses: {count}",
|
"success-count": "Sukses: {count}",
|
||||||
"sunday": "Sondag",
|
"sunday": "Sondag",
|
||||||
|
"system": "System",
|
||||||
"templates": "Sjablone:",
|
"templates": "Sjablone:",
|
||||||
"test": "Toets",
|
"test": "Toets",
|
||||||
"themes": "Temas",
|
"themes": "Temas",
|
||||||
"thursday": "Donderdag",
|
"thursday": "Donderdag",
|
||||||
|
"title": "Title",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Dinsdag",
|
"tuesday": "Dinsdag",
|
||||||
"type": "Tipe",
|
"type": "Tipe",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Eenhede",
|
"units": "Eenhede",
|
||||||
"back": "Terug",
|
"back": "Terug",
|
||||||
"next": "Volgende",
|
"next": "Volgende",
|
||||||
|
"start": "Start",
|
||||||
"toggle-view": "Wissel aansig",
|
"toggle-view": "Wissel aansig",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Geskep op: {0}",
|
"created-on-date": "Geskep op: {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
|
||||||
|
"organizers": "Organizers"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Is jy seker jy wil <b>{groupName}<b/> uitvee?",
|
"are-you-sure-you-want-to-delete-the-group": "Is jy seker jy wil <b>{groupName}<b/> uitvee?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Groepvoorkeure",
|
"group-preferences": "Groepvoorkeure",
|
||||||
"private-group": "Privaat group",
|
"private-group": "Privaat group",
|
||||||
"private-group-description": "Deur jou groep op privaat te stel, stel alle publieke kykopsies op verstek. Dit oorskryf die verstellings per resep.",
|
"private-group-description": "Deur jou groep op privaat te stel, stel alle publieke kykopsies op verstek. Dit oorskryf die verstellings per resep.",
|
||||||
|
"enable-public-access": "Enable Public Access",
|
||||||
|
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Laat gebruikers buite jou groep toe om jou resepte te sien",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Laat gebruikers buite jou groep toe om jou resepte te sien",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Indien hierdie opsie aangeskakel is, kan jy 'n resep met enigiemand deel. Sonder dat hulle 'n rekening het. As hierdie opsie afgeskakel is, kan jy net 'n resep met mense in jou groep deel. Of met 'n voorafgemaakte private skakel",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Indien hierdie opsie aangeskakel is, kan jy 'n resep met enigiemand deel. Sonder dat hulle 'n rekening het. As hierdie opsie afgeskakel is, kan jy net 'n resep met mense in jou groep deel. Of met 'n voorafgemaakte private skakel",
|
||||||
"show-nutrition-information": "Wys voedingsinligting",
|
"show-nutrition-information": "Wys voedingsinligting",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Resep migrasie",
|
"recipe-data-migrations": "Resep migrasie",
|
||||||
"recipe-data-migrations-explanation": "Jy kan resepte vanaf 'n ander program in Mealie invoer. Op hierdie manier kan jy vinnig aan die gang kom.",
|
"recipe-data-migrations-explanation": "Jy kan resepte vanaf 'n ander program in Mealie invoer. Op hierdie manier kan jy vinnig aan die gang kom.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.",
|
||||||
"choose-migration-type": "Kies die tipe migrasie",
|
"choose-migration-type": "Kies die tipe migrasie",
|
||||||
"tag-all-recipes": "Merk alle resepte met {tag-name}-merker",
|
"tag-all-recipes": "Merk alle resepte met {tag-name}-merker",
|
||||||
"nextcloud-text": "Nextcloud-resepte kan ingevoer word vanaf 'n zip-dokument wat die data bevat wat in Nextcloud gestoor is. Sien die voorbeeldstruktuur hieronder om te verseker dat jou resepte ingevoer kan word.",
|
"nextcloud-text": "Nextcloud-resepte kan ingevoer word vanaf 'n zip-dokument wat die data bevat wat in Nextcloud gestoor is. Sien die voorbeeldstruktuur hieronder om te verseker dat jou resepte ingevoer kan word.",
|
||||||
"chowdown-text": "Mealie kan invoer vanaf Chowdown. Laai die .zip-dokument daar af. En laai dit hier op",
|
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||||
"recipe-1": "Resep 1",
|
"recipe-1": "Resep 1",
|
||||||
"recipe-2": "Resep 2",
|
"recipe-2": "Resep 2",
|
||||||
"paprika-text": "Mealie kan resepte vanaf die Paprika-program invoer. Voer jou resepte uit Paprika uit, hernoem die uitvoeruitbreiding na .zip en laai dit hieronder op.",
|
"paprika-text": "Mealie kan resepte vanaf die Paprika-program invoer. Voer jou resepte uit Paprika uit, hernoem die uitvoeruitbreiding na .zip en laai dit hieronder op.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie kan resepte invoer vanaf Plan to Eat."
|
"description-long": "Mealie kan resepte invoer vanaf Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Wysig tydlyn gebeurtenis",
|
"edit-timeline-event": "Wysig tydlyn gebeurtenis",
|
||||||
"timeline": "Tydlyn",
|
"timeline": "Tydlyn",
|
||||||
"timeline-is-empty": "Nog niks op die tydlyn nie. Probeer hierdie resep maak!",
|
"timeline-is-empty": "Nog niks op die tydlyn nie. Probeer hierdie resep maak!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Algemene tydlyn",
|
"group-global-timeline": "{groupName} Algemene tydlyn",
|
||||||
"open-timeline": "Maak tydlyn oop",
|
"open-timeline": "Maak tydlyn oop",
|
||||||
"made-this": "Ek het dit gemaak",
|
"made-this": "Ek het dit gemaak",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Op soek na migrasies?",
|
"looking-for-migrations": "Op soek na migrasies?",
|
||||||
"import-with-url": "Voer in via URL",
|
"import-with-url": "Voer in via URL",
|
||||||
"create-recipe": "Skep resep",
|
"create-recipe": "Skep resep",
|
||||||
|
"create-recipe-description": "Create a new recipe from scratch.",
|
||||||
|
"create-recipes": "Create Recipes",
|
||||||
"import-with-zip": "Voer in met .zip",
|
"import-with-zip": "Voer in met .zip",
|
||||||
"create-recipe-from-an-image": "Skep 'n resep vanaf 'n foto",
|
"create-recipe-from-an-image": "Skep 'n resep vanaf 'n foto",
|
||||||
"bulk-url-import": "Grootmaat-URL-invoer",
|
"bulk-url-import": "Grootmaat-URL-invoer",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Laai prent",
|
"upload-image": "Laai prent",
|
||||||
"screen-awake": "Hou die skerm aan",
|
"screen-awake": "Hou die skerm aan",
|
||||||
"remove-image": "Verwyder prent",
|
"remove-image": "Verwyder prent",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Next step",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Gevorderde soek",
|
"advanced-search": "Gevorderde soek",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Nie alle LDAP-waardes is opgestel nie. Dit kan geïgnoreer word as jy nie LDAP-verifikasie gebruik nie.",
|
"ldap-ready-error-text": "Nie alle LDAP-waardes is opgestel nie. Dit kan geïgnoreer word as jy nie LDAP-verifikasie gebruik nie.",
|
||||||
"ldap-ready-success-text": "Vereiste LDAP-veranderlikes is volledig ingestel.",
|
"ldap-ready-success-text": "Vereiste LDAP-veranderlikes is volledig ingestel.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Resep skraper weergawe"
|
"recipe-scraper-version": "Resep skraper weergawe",
|
||||||
|
"oidc-ready": "OIDC Klar",
|
||||||
|
"oidc-ready-error-text": "Ikke alle OIDC værdier er konfigureret. Dette kan ignoreres hvis du ikke bruger OIDC godkendelse.",
|
||||||
|
"oidc-ready-success-text": "Krævede OIDC variabler er udfyldt."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Alle lyste",
|
"all-lists": "Alle lyste",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Skakel ID",
|
"link-id": "Skakel ID",
|
||||||
"link-name": "Skakel naam",
|
"link-name": "Skakel naam",
|
||||||
"login": "Meld aan",
|
"login": "Meld aan",
|
||||||
|
"login-oidc": "Log ind med",
|
||||||
|
"or": "eller",
|
||||||
"logout": "Teken uit",
|
"logout": "Teken uit",
|
||||||
"manage-users": "Bestuur gebruikers",
|
"manage-users": "Bestuur gebruikers",
|
||||||
|
"manage-users-description": "Create and manage users.",
|
||||||
"new-password": "Nuwe wagwoord",
|
"new-password": "Nuwe wagwoord",
|
||||||
"new-user": "Nuwe gebruiker",
|
"new-user": "Nuwe gebruiker",
|
||||||
"password-has-been-reset-to-the-default-password": "Wagwoord is teruggestel na die verstekwagwoord",
|
"password-has-been-reset-to-the-default-password": "Wagwoord is teruggestel na die verstekwagwoord",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Verwyder resepte",
|
"delete-recipes": "Verwyder resepte",
|
||||||
"source-unit-will-be-deleted": "Bron-eenheid sal verwyder word"
|
"source-unit-will-be-deleted": "Bron-eenheid sal verwyder word"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Create Alias",
|
"create-alias": "Create Alias",
|
||||||
"manage-aliases": "Manage Aliases",
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Voorbeeld data",
|
"seed-data": "Voorbeeld data",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Agtergrond take",
|
"background-tasks": "Agtergrond take",
|
||||||
"background-tasks-description": "Hier kan jy al die lopende agtergrond take en hul status sien",
|
"background-tasks-description": "Hier kan jy al die lopende agtergrond take en hul status sien",
|
||||||
"no-logs-found": "Geen logs gevind",
|
"no-logs-found": "Geen logs gevind",
|
||||||
"tasks": "Take"
|
"tasks": "Take",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "First Time Setup",
|
||||||
|
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "Welkom, {0}",
|
"welcome-user": "👋 Welcome, {0}!",
|
||||||
"description": "Bestuur jou profiel, resepte en groepverstellings.",
|
"description": "Bestuur jou profiel, resepte en groepverstellings.",
|
||||||
"get-invite-link": "Kry uitnodigingskakel",
|
"get-invite-link": "Kry uitnodigingskakel",
|
||||||
"get-public-link": "Kry openbare skakel",
|
"get-public-link": "Kry openbare skakel",
|
||||||
"account-summary": "Rekeningopsomming",
|
"account-summary": "Rekeningopsomming",
|
||||||
"account-summary-description": "Hier is 'n opsomming van jou groep se inligting",
|
"account-summary-description": "Here's a summary of your group's information.",
|
||||||
"group-statistics": "Groepstatistieke",
|
"group-statistics": "Groepstatistieke",
|
||||||
"group-statistics-description": "Jou groepstatistieke gee 'n bietjie insig hoe jy Mealie gebruik.",
|
"group-statistics-description": "Jou groepstatistieke gee 'n bietjie insig hoe jy Mealie gebruik.",
|
||||||
"storage-capacity": "Stoorkapasiteit",
|
"storage-capacity": "Stoorkapasiteit",
|
||||||
"storage-capacity-description": "Jou stoorkapasiteit is 'n berekening van die prente en dokumente wat jy opgelaai het.",
|
"storage-capacity-description": "Jou stoorkapasiteit is 'n berekening van die prente en dokumente wat jy opgelaai het.",
|
||||||
"personal": "Persoonlik",
|
"personal": "Persoonlik",
|
||||||
"personal-description": "Dit is jou persoonlike verstellings. Veranderinge hier sal nie ander gebruikers beïnvloed nie",
|
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
|
||||||
"user-settings": "Gebruikers verstellings",
|
"user-settings": "Gebruikers verstellings",
|
||||||
"user-settings-description": "Bestuur jou voorkeure, verander jou wagwoord en werk jou e-posadres op",
|
"user-settings-description": "Manage your preferences, change your password, and update your email.",
|
||||||
"api-tokens-description": "Bestuur jou API-tokens vir toegang vanaf eksterne toepassings",
|
"api-tokens-description": "Manage your API Tokens for access from external applications.",
|
||||||
"group-description": "Hierdie items word binne jou groep gedeel. Deur een van hulle te wysig, sal dit vir die hele groep verander!",
|
"group-description": "Hierdie items word binne jou groep gedeel. Deur een van hulle te wysig, sal dit vir die hele groep verander!",
|
||||||
"group-settings": "Groep verstellings",
|
"group-settings": "Groep verstellings",
|
||||||
"group-settings-description": "Bestuur jou algemene groep verstellings soos eetplan en privaatheidinstellings.",
|
"group-settings-description": "Bestuur jou algemene groep verstellings soos eetplan en privaatheidinstellings.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Kennisgewers",
|
"notifiers": "Kennisgewers",
|
||||||
"notifiers-description": "Stel e-pos- en stootkennisgewings op wat deur spesifieke gebeurtenisse geaktiveer word.",
|
"notifiers-description": "Stel e-pos- en stootkennisgewings op wat deur spesifieke gebeurtenisse geaktiveer word.",
|
||||||
"manage-data": "Databestuur",
|
"manage-data": "Databestuur",
|
||||||
"manage-data-description": "Bestuur jou kos en eenhede (meer opsies kom binnekort)",
|
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
|
||||||
"data-migrations": "Data-migrasies",
|
"data-migrations": "Data-migrasies",
|
||||||
"data-migrations-description": "Migreer jou bestaande data vanaf ander toepassings, soos Nextcloud-resepte en Chowdown",
|
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
|
||||||
"email-sent": "E-pos gestuur",
|
"email-sent": "E-pos gestuur",
|
||||||
"error-sending-email": "Kon nie die e-pos stuur nie",
|
"error-sending-email": "Kon nie die e-pos stuur nie",
|
||||||
"personal-information": "Persoonlike inligting",
|
"personal-information": "Persoonlike inligting",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "حدث خطأ ما!",
|
"something-went-wrong": "حدث خطأ ما!",
|
||||||
"subscribed-events": "الأحداث التي تم الاشتراك فيها",
|
"subscribed-events": "الأحداث التي تم الاشتراك فيها",
|
||||||
"test-message-sent": "تم إرسال رسالة تجريبية",
|
"test-message-sent": "تم إرسال رسالة تجريبية",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "إشعار جديد",
|
"new-notification": "إشعار جديد",
|
||||||
"event-notifiers": "إشعار الحدث",
|
"event-notifiers": "إشعار الحدث",
|
||||||
"apprise-url-skipped-if-blank": "الرابط Apprise (يتم تجاهله إذا ما كان فارغً)",
|
"apprise-url-skipped-if-blank": "الرابط Apprise (يتم تجاهله إذا ما كان فارغً)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"clear": "مسح",
|
"clear": "مسح",
|
||||||
"close": "إغلاق",
|
"close": "إغلاق",
|
||||||
"confirm": "تأكيد",
|
"confirm": "تأكيد",
|
||||||
|
"confirm-how-does-everything-look": "How does everything look?",
|
||||||
"confirm-delete-generic": "هل انت متأكد من حذف هذا؟",
|
"confirm-delete-generic": "هل انت متأكد من حذف هذا؟",
|
||||||
"copied_message": "تم النسخ!",
|
"copied_message": "تم النسخ!",
|
||||||
"create": "إنشاء",
|
"create": "إنشاء",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "حفظ",
|
"save": "حفظ",
|
||||||
"settings": "الإعدادات",
|
"settings": "الإعدادات",
|
||||||
"share": "مشاركة",
|
"share": "مشاركة",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "ترتيب عشوائي",
|
"shuffle": "ترتيب عشوائي",
|
||||||
"sort": "ترتيب",
|
"sort": "ترتيب",
|
||||||
|
"sort-ascending": "Sort Ascending",
|
||||||
|
"sort-descending": "Sort Descending",
|
||||||
"sort-alphabetically": "ترتيب حَسَبَ الحروف الأبجدية",
|
"sort-alphabetically": "ترتيب حَسَبَ الحروف الأبجدية",
|
||||||
"status": "الحالة",
|
"status": "الحالة",
|
||||||
"subject": "الموضوع",
|
"subject": "الموضوع",
|
||||||
"submit": "إرسال",
|
"submit": "إرسال",
|
||||||
"success-count": "نجحت: {count}",
|
"success-count": "نجحت: {count}",
|
||||||
"sunday": "الأحد",
|
"sunday": "الأحد",
|
||||||
|
"system": "System",
|
||||||
"templates": "القوالب:",
|
"templates": "القوالب:",
|
||||||
"test": "تجربة",
|
"test": "تجربة",
|
||||||
"themes": "السمات",
|
"themes": "السمات",
|
||||||
"thursday": "الخميس",
|
"thursday": "الخميس",
|
||||||
|
"title": "Title",
|
||||||
"token": "الرمز التعريفي",
|
"token": "الرمز التعريفي",
|
||||||
"tuesday": "الثلاثاء",
|
"tuesday": "الثلاثاء",
|
||||||
"type": "النوع",
|
"type": "النوع",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "الوحدات",
|
"units": "الوحدات",
|
||||||
"back": "عودة",
|
"back": "عودة",
|
||||||
"next": "التالي",
|
"next": "التالي",
|
||||||
|
"start": "Start",
|
||||||
"toggle-view": "تبديل طريقة العرض",
|
"toggle-view": "تبديل طريقة العرض",
|
||||||
"date": "التاريخ",
|
"date": "التاريخ",
|
||||||
"id": "المعرف",
|
"id": "المعرف",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "تم الإنشاء في {0}",
|
"created-on-date": "تم الإنشاء في {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
|
||||||
|
"organizers": "Organizers"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "هل انت متأكد من رغبتك في حذف <b>{groupName}<b/>؟",
|
"are-you-sure-you-want-to-delete-the-group": "هل انت متأكد من رغبتك في حذف <b>{groupName}<b/>؟",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "إعدادات المجموعة",
|
"group-preferences": "إعدادات المجموعة",
|
||||||
"private-group": "مجموعة خاصة",
|
"private-group": "مجموعة خاصة",
|
||||||
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
||||||
|
"enable-public-access": "Enable Public Access",
|
||||||
|
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "السماح للمستخدمين خارج مجموعتك لمشاهدة وصفاتك",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "السماح للمستخدمين خارج مجموعتك لمشاهدة وصفاتك",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
||||||
"show-nutrition-information": "عرض معلومات التغذية",
|
"show-nutrition-information": "عرض معلومات التغذية",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Recipe Data Migrations",
|
"recipe-data-migrations": "Recipe Data Migrations",
|
||||||
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.",
|
||||||
"choose-migration-type": "Choose Migration Type",
|
"choose-migration-type": "Choose Migration Type",
|
||||||
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
||||||
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
||||||
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below",
|
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||||
"recipe-1": "Recipe 1",
|
"recipe-1": "Recipe 1",
|
||||||
"recipe-2": "Recipe 2",
|
"recipe-2": "Recipe 2",
|
||||||
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie can import recipies from Plan to Eat."
|
"description-long": "Mealie can import recipies from Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Edit Timeline Event",
|
"edit-timeline-event": "Edit Timeline Event",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Global Timeline",
|
"group-global-timeline": "{groupName} Global Timeline",
|
||||||
"open-timeline": "Open Timeline",
|
"open-timeline": "Open Timeline",
|
||||||
"made-this": "I Made This",
|
"made-this": "I Made This",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Looking For Migrations?",
|
"looking-for-migrations": "Looking For Migrations?",
|
||||||
"import-with-url": "Import with URL",
|
"import-with-url": "Import with URL",
|
||||||
"create-recipe": "Create Recipe",
|
"create-recipe": "Create Recipe",
|
||||||
|
"create-recipe-description": "Create a new recipe from scratch.",
|
||||||
|
"create-recipes": "Create Recipes",
|
||||||
"import-with-zip": "Import with .zip",
|
"import-with-zip": "Import with .zip",
|
||||||
"create-recipe-from-an-image": "Create recipe from an image",
|
"create-recipe-from-an-image": "Create recipe from an image",
|
||||||
"bulk-url-import": "Bulk URL Import",
|
"bulk-url-import": "Bulk URL Import",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Upload image",
|
"upload-image": "Upload image",
|
||||||
"screen-awake": "Keep Screen Awake",
|
"screen-awake": "Keep Screen Awake",
|
||||||
"remove-image": "Remove image",
|
"remove-image": "Remove image",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Next step",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Advanced Search",
|
"advanced-search": "Advanced Search",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
||||||
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Recipe Scraper Version"
|
"recipe-scraper-version": "Recipe Scraper Version",
|
||||||
|
"oidc-ready": "OIDC Ready",
|
||||||
|
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
|
||||||
|
"oidc-ready-success-text": "Required OIDC variables are all set."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "All Lists",
|
"all-lists": "All Lists",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Link ID",
|
"link-id": "Link ID",
|
||||||
"link-name": "Link Name",
|
"link-name": "Link Name",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
|
"login-oidc": "Login with",
|
||||||
|
"or": "or",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"manage-users": "Manage Users",
|
"manage-users": "Manage Users",
|
||||||
|
"manage-users-description": "Create and manage users.",
|
||||||
"new-password": "New Password",
|
"new-password": "New Password",
|
||||||
"new-user": "New User",
|
"new-user": "New User",
|
||||||
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
|
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Delete Recipes",
|
"delete-recipes": "Delete Recipes",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Create Alias",
|
"create-alias": "Create Alias",
|
||||||
"manage-aliases": "Manage Aliases",
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Seed Data",
|
"seed-data": "Seed Data",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Background Tasks",
|
"background-tasks": "Background Tasks",
|
||||||
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
||||||
"no-logs-found": "No Logs Found",
|
"no-logs-found": "No Logs Found",
|
||||||
"tasks": "Tasks"
|
"tasks": "Tasks",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "First Time Setup",
|
||||||
|
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Welcome, {0}",
|
"welcome-user": "👋 Welcome, {0}!",
|
||||||
"description": "Manage your profile, recipes, and group settings.",
|
"description": "Manage your profile, recipes, and group settings.",
|
||||||
"get-invite-link": "Get Invite Link",
|
"get-invite-link": "Get Invite Link",
|
||||||
"get-public-link": "Get Public Link",
|
"get-public-link": "Get Public Link",
|
||||||
"account-summary": "Account Summary",
|
"account-summary": "Account Summary",
|
||||||
"account-summary-description": "Here's a summary of your group's information",
|
"account-summary-description": "Here's a summary of your group's information.",
|
||||||
"group-statistics": "Group Statistics",
|
"group-statistics": "Group Statistics",
|
||||||
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
||||||
"storage-capacity": "Storage Capacity",
|
"storage-capacity": "Storage Capacity",
|
||||||
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
||||||
"personal": "Personal",
|
"personal": "Personal",
|
||||||
"personal-description": "These are settings that are personal to you. Changes here won't affect other users",
|
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
|
||||||
"user-settings": "User Settings",
|
"user-settings": "User Settings",
|
||||||
"user-settings-description": "Manage your preferences, change your password, and update your email",
|
"user-settings-description": "Manage your preferences, change your password, and update your email.",
|
||||||
"api-tokens-description": "Manage your API Tokens for access from external applications",
|
"api-tokens-description": "Manage your API Tokens for access from external applications.",
|
||||||
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
||||||
"group-settings": "Group Settings",
|
"group-settings": "Group Settings",
|
||||||
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Notifiers",
|
"notifiers": "Notifiers",
|
||||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||||
"manage-data": "Manage Data",
|
"manage-data": "Manage Data",
|
||||||
"manage-data-description": "Manage your Food and Units (more options coming soon)",
|
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
|
||||||
"data-migrations": "Data Migrations",
|
"data-migrations": "Data Migrations",
|
||||||
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown",
|
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
|
||||||
"email-sent": "Email Sent",
|
"email-sent": "Email Sent",
|
||||||
"error-sending-email": "Error Sending Email",
|
"error-sending-email": "Error Sending Email",
|
||||||
"personal-information": "Personal Information",
|
"personal-information": "Personal Information",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Нещо се обърка!",
|
"something-went-wrong": "Нещо се обърка!",
|
||||||
"subscribed-events": "Планирани събития",
|
"subscribed-events": "Планирани събития",
|
||||||
"test-message-sent": "Тестово съобщение е изпратено",
|
"test-message-sent": "Тестово съобщение е изпратено",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "Ново известие",
|
"new-notification": "Ново известие",
|
||||||
"event-notifiers": "Известия за събитие",
|
"event-notifiers": "Известия за събитие",
|
||||||
"apprise-url-skipped-if-blank": "URL за известяване (пропуска се ако е празно)",
|
"apprise-url-skipped-if-blank": "URL за известяване (пропуска се ако е празно)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Събития на рецептата"
|
"recipe-events": "Събития на рецептата"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Добави",
|
||||||
"cancel": "Откажи",
|
"cancel": "Откажи",
|
||||||
"clear": "Изчисти",
|
"clear": "Изчисти",
|
||||||
"close": "Затвори",
|
"close": "Затвори",
|
||||||
"confirm": "Потвърди",
|
"confirm": "Потвърди",
|
||||||
|
"confirm-how-does-everything-look": "Как изглежда всичко?",
|
||||||
"confirm-delete-generic": "Сигурни ли сте, че желаете да изтриете това?",
|
"confirm-delete-generic": "Сигурни ли сте, че желаете да изтриете това?",
|
||||||
"copied_message": "Копирано!",
|
"copied_message": "Копирано!",
|
||||||
"create": "Добави",
|
"create": "Добави",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Запази",
|
"save": "Запази",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"share": "Сподели",
|
"share": "Сподели",
|
||||||
|
"show-all": "Покажи всички",
|
||||||
"shuffle": "Разбъркано",
|
"shuffle": "Разбъркано",
|
||||||
"sort": "Сортирай",
|
"sort": "Сортирай",
|
||||||
|
"sort-ascending": "Сортирай възходящо",
|
||||||
|
"sort-descending": "Сортирай низходящо",
|
||||||
"sort-alphabetically": "По азбучен ред",
|
"sort-alphabetically": "По азбучен ред",
|
||||||
"status": "състояние",
|
"status": "състояние",
|
||||||
"subject": "Относно",
|
"subject": "Относно",
|
||||||
"submit": "Изпрати",
|
"submit": "Изпрати",
|
||||||
"success-count": "Успешни: {count}",
|
"success-count": "Успешни: {count}",
|
||||||
"sunday": "Неделя",
|
"sunday": "Неделя",
|
||||||
|
"system": "Система",
|
||||||
"templates": "Шаблони:",
|
"templates": "Шаблони:",
|
||||||
"test": "Тест",
|
"test": "Тест",
|
||||||
"themes": "Теми",
|
"themes": "Теми",
|
||||||
"thursday": "четвъртък",
|
"thursday": "четвъртък",
|
||||||
|
"title": "Title",
|
||||||
"token": "Токън",
|
"token": "Токън",
|
||||||
"tuesday": "Вторник",
|
"tuesday": "Вторник",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Мерни единици",
|
"units": "Мерни единици",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"next": "Напред",
|
"next": "Напред",
|
||||||
|
"start": "Начало",
|
||||||
"toggle-view": "Превключване на режим",
|
"toggle-view": "Превключване на режим",
|
||||||
"date": "Дата",
|
"date": "Дата",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Създадено на {0}",
|
"created-on-date": "Създадено на {0}",
|
||||||
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
||||||
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
|
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
|
||||||
"confirm-delete-generic-items": "Сигурни ли сте, че желаете да изтриете следните елементи?"
|
"confirm-delete-generic-items": "Сигурни ли сте, че желаете да изтриете следните елементи?",
|
||||||
|
"organizers": "Органайзер"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Настройки на групите",
|
"group-preferences": "Настройки на групите",
|
||||||
"private-group": "Частна група",
|
"private-group": "Частна група",
|
||||||
"private-group-description": "Задаването на групата като лична ще зададе всички настройки за публично виждане към стандартните. Това е с по-висок приоритет от индивидуалните настройки за публично виждане на всяка една рецепта.",
|
"private-group-description": "Задаването на групата като лична ще зададе всички настройки за публично виждане към стандартните. Това е с по-висок приоритет от индивидуалните настройки за публично виждане на всяка една рецепта.",
|
||||||
|
"enable-public-access": "Включи публичния достъп",
|
||||||
|
"enable-public-access-description": "Направи груповите рецепти публични по подразбиране и позволи на посетителите да преглеждат рецептите без да се вписват",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Разрешете на потребители извън вашата група да виждат рецептите Ви",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Разрешете на потребители извън вашата група да виждат рецептите Ви",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Когато е пуснато ще може да генерирате публичен линк за споделяне на рецепти без да е нужно потребителя да се нуждае от вписване. Когато е изключено, ще можете да споделяте рецепти само с потребители, които са във Вашата група или чрез предварително генериран личен линк за споделяне.",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Когато е пуснато ще може да генерирате публичен линк за споделяне на рецепти без да е нужно потребителя да се нуждае от вписване. Когато е изключено, ще можете да споделяте рецепти само с потребители, които са във Вашата група или чрез предварително генериран личен линк за споделяне.",
|
||||||
"show-nutrition-information": "Показвай информация за хранителните стойности",
|
"show-nutrition-information": "Показвай информация за хранителните стойности",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Миграция на данни на рецепти",
|
"recipe-data-migrations": "Миграция на данни на рецепти",
|
||||||
"recipe-data-migrations-explanation": "Рецептите могат да бъдат мигрирани от други приложения поддържани от Mealie. Това е добър начин да започнете използването си на Mealie.",
|
"recipe-data-migrations-explanation": "Рецептите могат да бъдат мигрирани от други приложения поддържани от Mealie. Това е добър начин да започнете използването си на Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Идваш от друго приложение или дори по-стара версия на Mealie? Провери миграциите и виж дали данните ти могат да бъдат импорторани.",
|
||||||
"choose-migration-type": "Избери тип на миграцията",
|
"choose-migration-type": "Избери тип на миграцията",
|
||||||
"tag-all-recipes": "Добави {tag-name} като етикет във всички рецепти",
|
"tag-all-recipes": "Добави {tag-name} като етикет във всички рецепти",
|
||||||
"nextcloud-text": "Nextcloud рецептите могат да бъдат импортирани от .zip файл, който съдържа данни съхранени в Nextcloud. Вижте примерната структура на папките по-долу за да се подсигурите, че рецептите Ви могат да бъдат импортирани.",
|
"nextcloud-text": "Nextcloud рецептите могат да бъдат импортирани от .zip файл, който съдържа данни съхранени в Nextcloud. Вижте примерната структура на папките по-долу за да се подсигурите, че рецептите Ви могат да бъдат импортирани.",
|
||||||
"chowdown-text": "Mealie поддържа формата на хранилището на Chowdown. Свалете кода на хранилището като .zip файл и го качете по-долу",
|
"chowdown-text": "Mealie поддържа формата на хранилището на Chowdown. Свалете кода на хранилището като .zip файл и го качете по-долу.",
|
||||||
"recipe-1": "Рецепта 1",
|
"recipe-1": "Рецепта 1",
|
||||||
"recipe-2": "Рецепта 2",
|
"recipe-2": "Рецепта 2",
|
||||||
"paprika-text": "Mealie може да импортирай рецепти от приложението Paprika. Експортирайте рецептите си от Paprika, преименувате файловото разширение на .zip и го качете по-долу.",
|
"paprika-text": "Mealie може да импортирай рецепти от приложението Paprika. Експортирайте рецептите си от Paprika, преименувате файловото разширение на .zip и го качете по-долу.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Планиране на меню",
|
"title": "Планиране на меню",
|
||||||
"description-long": "Mealie може да импортира рецепти от Plan to Eat."
|
"description-long": "Mealie може да импортира рецепти от Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "Моята кутия с рецепти",
|
||||||
|
"description-long": "Mealie може да импортира рецепти от Моята кутия за рецепти. Експортирайте рецептите си в CSV формат, после качете .csv файлът по-долу."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Редактирай събитие",
|
"edit-timeline-event": "Редактирай събитие",
|
||||||
"timeline": "Хронология на събитията",
|
"timeline": "Хронология на събитията",
|
||||||
"timeline-is-empty": "Няма история на събитията. Опитайте да приготвите рецептата!",
|
"timeline-is-empty": "Няма история на събитията. Опитайте да приготвите рецептата!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "Няма намерени събития. Опитайте да промените филтрите си за търсене.",
|
||||||
"group-global-timeline": "{groupName} История на събитията",
|
"group-global-timeline": "{groupName} История на събитията",
|
||||||
"open-timeline": "Отвори историята на събитията",
|
"open-timeline": "Отвори историята на събитията",
|
||||||
"made-this": "Сготвих рецептата",
|
"made-this": "Сготвих рецептата",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Миграция на данни",
|
"looking-for-migrations": "Миграция на данни",
|
||||||
"import-with-url": "Импортирай от линк",
|
"import-with-url": "Импортирай от линк",
|
||||||
"create-recipe": "Добави рецепта",
|
"create-recipe": "Добави рецепта",
|
||||||
|
"create-recipe-description": "Създайте нова рецепта от чернова.",
|
||||||
|
"create-recipes": "Създайте рецепти",
|
||||||
"import-with-zip": "Импортирай от .zip",
|
"import-with-zip": "Импортирай от .zip",
|
||||||
"create-recipe-from-an-image": "Добави рецепта от снимка",
|
"create-recipe-from-an-image": "Добави рецепта от снимка",
|
||||||
"bulk-url-import": "Импортиране на рецепти от линк",
|
"bulk-url-import": "Импортиране на рецепти от линк",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Качване на изображение",
|
"upload-image": "Качване на изображение",
|
||||||
"screen-awake": "Запази екрана активен",
|
"screen-awake": "Запази екрана активен",
|
||||||
"remove-image": "Премахване на изображение",
|
"remove-image": "Премахване на изображение",
|
||||||
"nextStep": "Следваща стъпка"
|
"nextStep": "Следваща стъпка",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Разширено търсене",
|
"advanced-search": "Разширено търсене",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Не всички LDAP стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате LDAP удостоверяване.",
|
"ldap-ready-error-text": "Не всички LDAP стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате LDAP удостоверяване.",
|
||||||
"ldap-ready-success-text": "Задължителните LDAP променливи са зададени.",
|
"ldap-ready-success-text": "Задължителните LDAP променливи са зададени.",
|
||||||
"build": "Компилинирана версия",
|
"build": "Компилинирана версия",
|
||||||
"recipe-scraper-version": "Версия на скрепер на рецепти"
|
"recipe-scraper-version": "Версия на скрепер на рецепти",
|
||||||
|
"oidc-ready": "Готов за OIDC",
|
||||||
|
"oidc-ready-error-text": "Не всички OIDC стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате OIDC удостоверяване.",
|
||||||
|
"oidc-ready-success-text": "Задължителните OIDC променливи са зададени."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Всички списъци",
|
"all-lists": "Всички списъци",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "ID на линка",
|
"link-id": "ID на линка",
|
||||||
"link-name": "Има на линка",
|
"link-name": "Има на линка",
|
||||||
"login": "Вход",
|
"login": "Вход",
|
||||||
|
"login-oidc": "Впиши се с",
|
||||||
|
"or": "или",
|
||||||
"logout": "Изход",
|
"logout": "Изход",
|
||||||
"manage-users": "Управление на потребителите",
|
"manage-users": "Управление на потребителите",
|
||||||
|
"manage-users-description": "Създавайте и управлявайте потребители.",
|
||||||
"new-password": "Нова парола",
|
"new-password": "Нова парола",
|
||||||
"new-user": "Нов потребител",
|
"new-user": "Нов потребител",
|
||||||
"password-has-been-reset-to-the-default-password": "Паролата беше възстановена до паролата по подразбиране",
|
"password-has-been-reset-to-the-default-password": "Паролата беше възстановена до паролата по подразбиране",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Изтрий рецепти",
|
"delete-recipes": "Изтрий рецепти",
|
||||||
"source-unit-will-be-deleted": "Изходната мерна единица ще бъде изтрита"
|
"source-unit-will-be-deleted": "Изходната мерна единица ще бъде изтрита"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Създаване на псевдоним",
|
"create-alias": "Създаване на псевдоним",
|
||||||
"manage-aliases": "Управление на псевдоними",
|
"manage-aliases": "Управление на псевдоними",
|
||||||
"seed-data": "Зареждане на данни",
|
"seed-data": "Зареждане на данни",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Задачи на заден фон",
|
"background-tasks": "Задачи на заден фон",
|
||||||
"background-tasks-description": "Тук можете да видите всички изпълнявани задачи на заден фон и техния статус",
|
"background-tasks-description": "Тук можете да видите всички изпълнявани задачи на заден фон и техния статус",
|
||||||
"no-logs-found": "Няма намерени логове",
|
"no-logs-found": "Няма намерени логове",
|
||||||
"tasks": "Задачи"
|
"tasks": "Задачи",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "Настройка за първи път",
|
||||||
|
"welcome-to-mealie-get-started": "Добре дошли в Mealie! Да започваме",
|
||||||
|
"already-set-up-bring-to-homepage": "Вече съм настроен, просто ме отведете до началната страница",
|
||||||
|
"common-settings-for-new-sites": "Ето някои общи настройки за нови сайтове",
|
||||||
|
"setup-complete": "Настройката е завършена!",
|
||||||
|
"here-are-a-few-things-to-help-you-get-started": "Ето няколко неща, които ще Ви помогнат да започнете с Mealie",
|
||||||
|
"restore-from-v1-backup": "Имате резервно копие от предишна инстанция на Mealie v1? Можете да го възстановите тук.",
|
||||||
|
"manage-profile-or-get-invite-link": "Управлявайте собствения си профил или вземете връзка за покана, която да споделите с други."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Добре дошъл(а), {0}",
|
"welcome-user": "👋 Добре дошъл(а), {0}!",
|
||||||
"description": "Настройки на профил, рецепти и настройки на групата.",
|
"description": "Настройки на профил, рецепти и настройки на групата.",
|
||||||
"get-invite-link": "Вземи линк за покана",
|
"get-invite-link": "Вземи линк за покана",
|
||||||
"get-public-link": "Вземи публичен линк",
|
"get-public-link": "Вземи публичен линк",
|
||||||
"account-summary": "Обобщение на акаунта",
|
"account-summary": "Обобщение на акаунта",
|
||||||
"account-summary-description": "Обобщение на информацията за Вашата група",
|
"account-summary-description": "Обобщение на информацията за Вашата група.",
|
||||||
"group-statistics": "Статистики на групата",
|
"group-statistics": "Статистики на групата",
|
||||||
"group-statistics-description": "Вашата статистика на групата дава известна представа как използвате Mealie.",
|
"group-statistics-description": "Вашата статистика на групата дава известна представа как използвате Mealie.",
|
||||||
"storage-capacity": "Капацитет за съхранение",
|
"storage-capacity": "Капацитет за съхранение",
|
||||||
"storage-capacity-description": "Вашият капацитет за съхранение е изчисление на изображенията и активите, които сте качили.",
|
"storage-capacity-description": "Вашият капацитет за съхранение е изчисление на изображенията и активите, които сте качили.",
|
||||||
"personal": "Лични",
|
"personal": "Лични",
|
||||||
"personal-description": "Това са настройки, които са лични за Вас. Промените тук няма да засегнат други потребители",
|
"personal-description": "Това са настройки, които са лични за Вас. Промените тук няма да засегнат други потребители.",
|
||||||
"user-settings": "Потребителски настройки",
|
"user-settings": "Потребителски настройки",
|
||||||
"user-settings-description": "Нстройки на предпочитанията, смяна на парола и актуализация на имей адрес",
|
"user-settings-description": "Нстройки на предпочитанията, смяна на парола и актуализация на имей адрес.",
|
||||||
"api-tokens-description": "Управление на API токени за достъп от външни приложения",
|
"api-tokens-description": "Управление на API токени за достъп от външни приложения.",
|
||||||
"group-description": "Тези елементи се споделят във вашата група. Редактирането на един от тях ще го промени за цялата група!",
|
"group-description": "Тези елементи се споделят във вашата група. Редактирането на един от тях ще го промени за цялата група!",
|
||||||
"group-settings": "Настройки на групата",
|
"group-settings": "Настройки на групата",
|
||||||
"group-settings-description": "Общи групови настройки като седмично меню и настройки за поверителност.",
|
"group-settings-description": "Общи групови настройки като седмично меню и настройки за поверителност.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Уведомители",
|
"notifiers": "Уведомители",
|
||||||
"notifiers-description": "Настройте имейл и push известия, които се задействат при конкретни събития.",
|
"notifiers-description": "Настройте имейл и push известия, които се задействат при конкретни събития.",
|
||||||
"manage-data": "Управление на данни",
|
"manage-data": "Управление на данни",
|
||||||
"manage-data-description": "Настройки на продукти и мерни единици (очаквайте добавяне на още възможности)",
|
"manage-data-description": "Управлявай данните в Mealie: Храни, Единици, Категории, Тагове и други.",
|
||||||
"data-migrations": "Миграция на данни",
|
"data-migrations": "Миграция на данни",
|
||||||
"data-migrations-description": "Мигрирайте вашите съществуващи данни от други приложения като Nextcloud Recipes и Chowdown",
|
"data-migrations-description": "Мигрирайте вашите съществуващи данни от други приложения като Nextcloud Recipes и Chowdown.",
|
||||||
"email-sent": "Имейлът е изпратен",
|
"email-sent": "Имейлът е изпратен",
|
||||||
"error-sending-email": "Грешка при изпращане на имейл",
|
"error-sending-email": "Грешка при изпращане на имейл",
|
||||||
"personal-information": "Лична информация",
|
"personal-information": "Лична информация",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Alguna cosa ha anat malament!",
|
"something-went-wrong": "Alguna cosa ha anat malament!",
|
||||||
"subscribed-events": "Esdeveniments subscrits",
|
"subscribed-events": "Esdeveniments subscrits",
|
||||||
"test-message-sent": "S'ha enviat el missatge",
|
"test-message-sent": "S'ha enviat el missatge",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "Nova notificació",
|
"new-notification": "Nova notificació",
|
||||||
"event-notifiers": "Notificacions d'esdeveniments",
|
"event-notifiers": "Notificacions d'esdeveniments",
|
||||||
"apprise-url-skipped-if-blank": "Apprise URL (si es deixa buit, s'ignorarà)",
|
"apprise-url-skipped-if-blank": "Apprise URL (si es deixa buit, s'ignorarà)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Esdeveniments de receptes"
|
"recipe-events": "Esdeveniments de receptes"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "Anuŀla",
|
"cancel": "Anuŀla",
|
||||||
"clear": "Neteja",
|
"clear": "Neteja",
|
||||||
"close": "Tanca",
|
"close": "Tanca",
|
||||||
"confirm": "Confirma",
|
"confirm": "Confirma",
|
||||||
|
"confirm-how-does-everything-look": "How does everything look?",
|
||||||
"confirm-delete-generic": "Esteu segur de voler suprimir-lo?",
|
"confirm-delete-generic": "Esteu segur de voler suprimir-lo?",
|
||||||
"copied_message": "S'ha copiat!",
|
"copied_message": "S'ha copiat!",
|
||||||
"create": "Crea",
|
"create": "Crea",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Desa",
|
"save": "Desa",
|
||||||
"settings": "Configuració",
|
"settings": "Configuració",
|
||||||
"share": "Compartiu",
|
"share": "Compartiu",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "Barreja",
|
"shuffle": "Barreja",
|
||||||
"sort": "Ordena",
|
"sort": "Ordena",
|
||||||
|
"sort-ascending": "Sort Ascending",
|
||||||
|
"sort-descending": "Sort Descending",
|
||||||
"sort-alphabetically": "Alfabèticament",
|
"sort-alphabetically": "Alfabèticament",
|
||||||
"status": "Estat",
|
"status": "Estat",
|
||||||
"subject": "Assumpte",
|
"subject": "Assumpte",
|
||||||
"submit": "Envia",
|
"submit": "Envia",
|
||||||
"success-count": "Amb èxit: {count}",
|
"success-count": "Amb èxit: {count}",
|
||||||
"sunday": "Diumenge",
|
"sunday": "Diumenge",
|
||||||
|
"system": "System",
|
||||||
"templates": "Plantilles:",
|
"templates": "Plantilles:",
|
||||||
"test": "Prova",
|
"test": "Prova",
|
||||||
"themes": "Temes",
|
"themes": "Temes",
|
||||||
"thursday": "Dijous",
|
"thursday": "Dijous",
|
||||||
|
"title": "Title",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Dimarts",
|
"tuesday": "Dimarts",
|
||||||
"type": "Tipus",
|
"type": "Tipus",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Unitats",
|
"units": "Unitats",
|
||||||
"back": "Torna",
|
"back": "Torna",
|
||||||
"next": "Següent",
|
"next": "Següent",
|
||||||
|
"start": "Start",
|
||||||
"toggle-view": "Commuta la visualització",
|
"toggle-view": "Commuta la visualització",
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Creat el: {0}",
|
"created-on-date": "Creat el: {0}",
|
||||||
"unsaved-changes": "Tens canvis que no estan guardats. Vols guardar-los abans de sortir? Clica d'acord per guardar-los o cancel·lar per descartar els canvis.",
|
"unsaved-changes": "Tens canvis que no estan guardats. Vols guardar-los abans de sortir? Clica d'acord per guardar-los o cancel·lar per descartar els canvis.",
|
||||||
"clipboard-copy-failure": "No s'ha pogut copiar al porta-retalls.",
|
"clipboard-copy-failure": "No s'ha pogut copiar al porta-retalls.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
|
||||||
|
"organizers": "Organizers"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup <b>{groupName}<b/>?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Preferències per grup",
|
"group-preferences": "Preferències per grup",
|
||||||
"private-group": "Grup privat",
|
"private-group": "Grup privat",
|
||||||
"private-group-description": "Marcar el grup com a privat afectarà a totes les opcions de visualització pública per defecte. Podeu canviar-les individualment en les opcions de visualització de cada recepta.",
|
"private-group-description": "Marcar el grup com a privat afectarà a totes les opcions de visualització pública per defecte. Podeu canviar-les individualment en les opcions de visualització de cada recepta.",
|
||||||
|
"enable-public-access": "Enable Public Access",
|
||||||
|
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Permeteu als usuaris d'altres grups, visualitzar les vostres receptes",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Permeteu als usuaris d'altres grups, visualitzar les vostres receptes",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Si ho habiliteu, podreu compartir enllaços públics de receptes específiques sense autoritzar l'usuari. Si està deshabilitat, només podreu compartir amb usuaris del vostre grup o generant enllaços privats",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Si ho habiliteu, podreu compartir enllaços públics de receptes específiques sense autoritzar l'usuari. Si està deshabilitat, només podreu compartir amb usuaris del vostre grup o generant enllaços privats",
|
||||||
"show-nutrition-information": "Mostra la informació nutricional",
|
"show-nutrition-information": "Mostra la informació nutricional",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Migració de receptes",
|
"recipe-data-migrations": "Migració de receptes",
|
||||||
"recipe-data-migrations-explanation": "Les receptes es poden migrar des d'una altra aplicació suportada cap a Mealie. És una manera genial de començar a utilitzar el Mealie.",
|
"recipe-data-migrations-explanation": "Les receptes es poden migrar des d'una altra aplicació suportada cap a Mealie. És una manera genial de començar a utilitzar el Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.",
|
||||||
"choose-migration-type": "Elegeix un tipus de migració",
|
"choose-migration-type": "Elegeix un tipus de migració",
|
||||||
"tag-all-recipes": "Etiqueta totes les receptes amb {tag-name}",
|
"tag-all-recipes": "Etiqueta totes les receptes amb {tag-name}",
|
||||||
"nextcloud-text": "Les receptes de Nextcloud poden ser importades d'un fitxer ZIP que contingui les dades emmagatzemades en Nextcloud. Segueix l'exemple d'estructura de directori de sota per assegurar que les receptes podran ser importades.",
|
"nextcloud-text": "Les receptes de Nextcloud poden ser importades d'un fitxer ZIP que contingui les dades emmagatzemades en Nextcloud. Segueix l'exemple d'estructura de directori de sota per assegurar que les receptes podran ser importades.",
|
||||||
"chowdown-text": "Mealie suporta de forma nativa el format de Chowdown. Descarrega el codi del repositori com a .zip i carrega'l a sota",
|
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||||
"recipe-1": "Recepta 1",
|
"recipe-1": "Recepta 1",
|
||||||
"recipe-2": "Recepta 2",
|
"recipe-2": "Recepta 2",
|
||||||
"paprika-text": "Mealie pot importar receptes des de l'aplicació Paprika. Exporta les teves receptes de Paprika, reanomena l'extensió de l'arxiu a .zip i penja'l aquí sota.",
|
"paprika-text": "Mealie pot importar receptes des de l'aplicació Paprika. Exporta les teves receptes de Paprika, reanomena l'extensió de l'arxiu a .zip i penja'l aquí sota.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie pot importar receptes de Plan to Eat."
|
"description-long": "Mealie pot importar receptes de Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Edita l'esdeveniment de la cronologia",
|
"edit-timeline-event": "Edita l'esdeveniment de la cronologia",
|
||||||
"timeline": "Cronologia",
|
"timeline": "Cronologia",
|
||||||
"timeline-is-empty": "Encara no hi ha res a la cronologia. Prova de fer aquesta recepta!",
|
"timeline-is-empty": "Encara no hi ha res a la cronologia. Prova de fer aquesta recepta!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Global Timeline",
|
"group-global-timeline": "{groupName} Global Timeline",
|
||||||
"open-timeline": "Obrir la cronologia",
|
"open-timeline": "Obrir la cronologia",
|
||||||
"made-this": "Ho he fet",
|
"made-this": "Ho he fet",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Estàs buscant migracions?",
|
"looking-for-migrations": "Estàs buscant migracions?",
|
||||||
"import-with-url": "Importar amb l'URL",
|
"import-with-url": "Importar amb l'URL",
|
||||||
"create-recipe": "Crea la recepta",
|
"create-recipe": "Crea la recepta",
|
||||||
|
"create-recipe-description": "Create a new recipe from scratch.",
|
||||||
|
"create-recipes": "Create Recipes",
|
||||||
"import-with-zip": "Importar amb un .zip",
|
"import-with-zip": "Importar amb un .zip",
|
||||||
"create-recipe-from-an-image": "Crea la recepta a partir d'una imatge",
|
"create-recipe-from-an-image": "Crea la recepta a partir d'una imatge",
|
||||||
"bulk-url-import": "Importació d'URL en massa",
|
"bulk-url-import": "Importació d'URL en massa",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Puja una imatge",
|
"upload-image": "Puja una imatge",
|
||||||
"screen-awake": "Mantenir la pantalla encesa",
|
"screen-awake": "Mantenir la pantalla encesa",
|
||||||
"remove-image": "Esborrar la imatge",
|
"remove-image": "Esborrar la imatge",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Next step",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Cerca avançada",
|
"advanced-search": "Cerca avançada",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
||||||
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Recipe Scraper Version"
|
"recipe-scraper-version": "Recipe Scraper Version",
|
||||||
|
"oidc-ready": "OIDC Ready",
|
||||||
|
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
|
||||||
|
"oidc-ready-success-text": "Required OIDC variables are all set."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Totes les llistes",
|
"all-lists": "Totes les llistes",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Id de l'enllaç",
|
"link-id": "Id de l'enllaç",
|
||||||
"link-name": "Nom de l'enllaç",
|
"link-name": "Nom de l'enllaç",
|
||||||
"login": "Inicieu sessió",
|
"login": "Inicieu sessió",
|
||||||
|
"login-oidc": "Login with",
|
||||||
|
"or": "or",
|
||||||
"logout": "Tanca la sessió",
|
"logout": "Tanca la sessió",
|
||||||
"manage-users": "Gestionar usuaris",
|
"manage-users": "Gestionar usuaris",
|
||||||
|
"manage-users-description": "Create and manage users.",
|
||||||
"new-password": "Nova contrasenya",
|
"new-password": "Nova contrasenya",
|
||||||
"new-user": "Nou Usuari",
|
"new-user": "Nou Usuari",
|
||||||
"password-has-been-reset-to-the-default-password": "S'ha restablert la contrasenya al seu valor per defecte",
|
"password-has-been-reset-to-the-default-password": "S'ha restablert la contrasenya al seu valor per defecte",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Delete Recipes",
|
"delete-recipes": "Delete Recipes",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Create Alias",
|
"create-alias": "Create Alias",
|
||||||
"manage-aliases": "Manage Aliases",
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Dades d'exemple",
|
"seed-data": "Dades d'exemple",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Background Tasks",
|
"background-tasks": "Background Tasks",
|
||||||
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
||||||
"no-logs-found": "No Logs Found",
|
"no-logs-found": "No Logs Found",
|
||||||
"tasks": "Tasks"
|
"tasks": "Tasks",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "First Time Setup",
|
||||||
|
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Benvingut, {0}",
|
"welcome-user": "👋 Welcome, {0}!",
|
||||||
"description": "Manage your profile, recipes, and group settings.",
|
"description": "Manage your profile, recipes, and group settings.",
|
||||||
"get-invite-link": "Get Invite Link",
|
"get-invite-link": "Get Invite Link",
|
||||||
"get-public-link": "Enllaç públic",
|
"get-public-link": "Enllaç públic",
|
||||||
"account-summary": "Account Summary",
|
"account-summary": "Account Summary",
|
||||||
"account-summary-description": "Here's a summary of your group's information",
|
"account-summary-description": "Here's a summary of your group's information.",
|
||||||
"group-statistics": "Group Statistics",
|
"group-statistics": "Group Statistics",
|
||||||
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
||||||
"storage-capacity": "Storage Capacity",
|
"storage-capacity": "Storage Capacity",
|
||||||
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
||||||
"personal": "Personal",
|
"personal": "Personal",
|
||||||
"personal-description": "These are settings that are personal to you. Changes here won't affect other users",
|
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
|
||||||
"user-settings": "User Settings",
|
"user-settings": "User Settings",
|
||||||
"user-settings-description": "Manage your preferences, change your password, and update your email",
|
"user-settings-description": "Manage your preferences, change your password, and update your email.",
|
||||||
"api-tokens-description": "Manage your API Tokens for access from external applications",
|
"api-tokens-description": "Manage your API Tokens for access from external applications.",
|
||||||
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
||||||
"group-settings": "Group Settings",
|
"group-settings": "Group Settings",
|
||||||
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Notifiers",
|
"notifiers": "Notifiers",
|
||||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||||
"manage-data": "Manage Data",
|
"manage-data": "Manage Data",
|
||||||
"manage-data-description": "Manage your Food and Units (more options coming soon)",
|
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
|
||||||
"data-migrations": "Data Migrations",
|
"data-migrations": "Data Migrations",
|
||||||
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown",
|
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
|
||||||
"email-sent": "Email Sent",
|
"email-sent": "Email Sent",
|
||||||
"error-sending-email": "Error Sending Email",
|
"error-sending-email": "Error Sending Email",
|
||||||
"personal-information": "Personal Information",
|
"personal-information": "Personal Information",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Něco se nepovedlo!",
|
"something-went-wrong": "Něco se nepovedlo!",
|
||||||
"subscribed-events": "Odebírané události",
|
"subscribed-events": "Odebírané události",
|
||||||
"test-message-sent": "Testovací zpráva odeslána",
|
"test-message-sent": "Testovací zpráva odeslána",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "Nové oznámení",
|
"new-notification": "Nové oznámení",
|
||||||
"event-notifiers": "Notifikace událostí",
|
"event-notifiers": "Notifikace událostí",
|
||||||
"apprise-url-skipped-if-blank": "Apprise URL (přeskočeno pokud je prázdné)",
|
"apprise-url-skipped-if-blank": "Apprise URL (přeskočeno pokud je prázdné)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "Zrušit",
|
"cancel": "Zrušit",
|
||||||
"clear": "Vymazat",
|
"clear": "Vymazat",
|
||||||
"close": "Zavřít",
|
"close": "Zavřít",
|
||||||
"confirm": "Potvrdit",
|
"confirm": "Potvrdit",
|
||||||
|
"confirm-how-does-everything-look": "How does everything look?",
|
||||||
"confirm-delete-generic": "Jste si jistý, že to chcete smazat?",
|
"confirm-delete-generic": "Jste si jistý, že to chcete smazat?",
|
||||||
"copied_message": "Zkopírováno!",
|
"copied_message": "Zkopírováno!",
|
||||||
"create": "Vytvořit",
|
"create": "Vytvořit",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Uložit",
|
"save": "Uložit",
|
||||||
"settings": "Nastavení",
|
"settings": "Nastavení",
|
||||||
"share": "Sdílet",
|
"share": "Sdílet",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "Náhodně",
|
"shuffle": "Náhodně",
|
||||||
"sort": "Seřadit",
|
"sort": "Seřadit",
|
||||||
|
"sort-ascending": "Sort Ascending",
|
||||||
|
"sort-descending": "Sort Descending",
|
||||||
"sort-alphabetically": "Abecedně",
|
"sort-alphabetically": "Abecedně",
|
||||||
"status": "Stav",
|
"status": "Stav",
|
||||||
"subject": "Předmět",
|
"subject": "Předmět",
|
||||||
"submit": "Odeslat",
|
"submit": "Odeslat",
|
||||||
"success-count": "Úspěšné: {count}",
|
"success-count": "Úspěšné: {count}",
|
||||||
"sunday": "Neděle",
|
"sunday": "Neděle",
|
||||||
|
"system": "System",
|
||||||
"templates": "Šablony:",
|
"templates": "Šablony:",
|
||||||
"test": "Test",
|
"test": "Test",
|
||||||
"themes": "Motivy",
|
"themes": "Motivy",
|
||||||
"thursday": "Čtvrtek",
|
"thursday": "Čtvrtek",
|
||||||
|
"title": "Title",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Úterý",
|
"tuesday": "Úterý",
|
||||||
"type": "Typ",
|
"type": "Typ",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Jednotky",
|
"units": "Jednotky",
|
||||||
"back": "Zpět",
|
"back": "Zpět",
|
||||||
"next": "Další",
|
"next": "Další",
|
||||||
|
"start": "Start",
|
||||||
"toggle-view": "Přepnout zobrazení",
|
"toggle-view": "Přepnout zobrazení",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Vytvořeno dne: {0}",
|
"created-on-date": "Vytvořeno dne: {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
|
||||||
|
"organizers": "Organizers"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Jste si jisti, že chcete smazat <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Jste si jisti, že chcete smazat <b>{groupName}<b/>?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Nastavení skupiny",
|
"group-preferences": "Nastavení skupiny",
|
||||||
"private-group": "Soukromá skupina",
|
"private-group": "Soukromá skupina",
|
||||||
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
||||||
|
"enable-public-access": "Enable Public Access",
|
||||||
|
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Povolit uživatelům mimo vaši skupinu vidět vaše recepty",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Povolit uživatelům mimo vaši skupinu vidět vaše recepty",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
||||||
"show-nutrition-information": "Show nutrition information",
|
"show-nutrition-information": "Show nutrition information",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Recipe Data Migrations",
|
"recipe-data-migrations": "Recipe Data Migrations",
|
||||||
"recipe-data-migrations-explanation": "Recepty mohou být migrovány z jiné podporované aplikace na Mealie. To je skvělý způsob, jak začít s Mealie.",
|
"recipe-data-migrations-explanation": "Recepty mohou být migrovány z jiné podporované aplikace na Mealie. To je skvělý způsob, jak začít s Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.",
|
||||||
"choose-migration-type": "Zvolte si typ migrace",
|
"choose-migration-type": "Zvolte si typ migrace",
|
||||||
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
||||||
"nextcloud-text": "Nextcloud recepty lze importovat ze souboru zip, který obsahuje data uložená v Nextcloudu. Podívejte se na příklad struktury složek níže, abyste se ujistili, že vaše recepty lze importovat.",
|
"nextcloud-text": "Nextcloud recepty lze importovat ze souboru zip, který obsahuje data uložená v Nextcloudu. Podívejte se na příklad struktury složek níže, abyste se ujistili, že vaše recepty lze importovat.",
|
||||||
"chowdown-text": "Mealie nativně podporuje formát chowdown. Stáhněte si z repozitáře kód jako .zip soubor a nahrajte ho níže",
|
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||||
"recipe-1": "Recept 1",
|
"recipe-1": "Recept 1",
|
||||||
"recipe-2": "Recept 2",
|
"recipe-2": "Recept 2",
|
||||||
"paprika-text": "Mealie může importovat recepty z aplikace Paprika. Exportujte své recepty z papriky, přejmenujte příponu exportovaného souboru na .zip a nahrajte jej níže.",
|
"paprika-text": "Mealie může importovat recepty z aplikace Paprika. Exportujte své recepty z papriky, přejmenujte příponu exportovaného souboru na .zip a nahrajte jej níže.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie can import recipies from Plan to Eat."
|
"description-long": "Mealie can import recipies from Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Upravit událost časové osy",
|
"edit-timeline-event": "Upravit událost časové osy",
|
||||||
"timeline": "Časová osa",
|
"timeline": "Časová osa",
|
||||||
"timeline-is-empty": "Zatím nic na časové ose není. Zkuste vytvořit tento recept!",
|
"timeline-is-empty": "Zatím nic na časové ose není. Zkuste vytvořit tento recept!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Globální časová osa",
|
"group-global-timeline": "{groupName} Globální časová osa",
|
||||||
"open-timeline": "Otevřít časovou osu",
|
"open-timeline": "Otevřít časovou osu",
|
||||||
"made-this": "Toto jsem uvařil",
|
"made-this": "Toto jsem uvařil",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Looking For Migrations?",
|
"looking-for-migrations": "Looking For Migrations?",
|
||||||
"import-with-url": "Import with URL",
|
"import-with-url": "Import with URL",
|
||||||
"create-recipe": "Vytvořit recept",
|
"create-recipe": "Vytvořit recept",
|
||||||
|
"create-recipe-description": "Create a new recipe from scratch.",
|
||||||
|
"create-recipes": "Create Recipes",
|
||||||
"import-with-zip": "Importovat pomocí .zip",
|
"import-with-zip": "Importovat pomocí .zip",
|
||||||
"create-recipe-from-an-image": "Vytvořit recept z obrázku",
|
"create-recipe-from-an-image": "Vytvořit recept z obrázku",
|
||||||
"bulk-url-import": "Bulk URL Import",
|
"bulk-url-import": "Bulk URL Import",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Nahrát obrázek",
|
"upload-image": "Nahrát obrázek",
|
||||||
"screen-awake": "Keep Screen Awake",
|
"screen-awake": "Keep Screen Awake",
|
||||||
"remove-image": "Remove image",
|
"remove-image": "Remove image",
|
||||||
"nextStep": "Další krok"
|
"nextStep": "Další krok",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Pokročilé vyhledávání",
|
"advanced-search": "Pokročilé vyhledávání",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
||||||
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Recipe Scraper Version"
|
"recipe-scraper-version": "Recipe Scraper Version",
|
||||||
|
"oidc-ready": "OIDC Ready",
|
||||||
|
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
|
||||||
|
"oidc-ready-success-text": "Required OIDC variables are all set."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Všechny seznamy",
|
"all-lists": "Všechny seznamy",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "ID odkazu",
|
"link-id": "ID odkazu",
|
||||||
"link-name": "Název odkazu",
|
"link-name": "Název odkazu",
|
||||||
"login": "Přihlášení",
|
"login": "Přihlášení",
|
||||||
|
"login-oidc": "Login with",
|
||||||
|
"or": "or",
|
||||||
"logout": "Odhlášení",
|
"logout": "Odhlášení",
|
||||||
"manage-users": "Spravovat uživatele",
|
"manage-users": "Spravovat uživatele",
|
||||||
|
"manage-users-description": "Create and manage users.",
|
||||||
"new-password": "Nové heslo",
|
"new-password": "Nové heslo",
|
||||||
"new-user": "Nový uživatel",
|
"new-user": "Nový uživatel",
|
||||||
"password-has-been-reset-to-the-default-password": "Heslo bylo obnoveno na výchozí heslo",
|
"password-has-been-reset-to-the-default-password": "Heslo bylo obnoveno na výchozí heslo",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Smazat recepty",
|
"delete-recipes": "Smazat recepty",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Create Alias",
|
"create-alias": "Create Alias",
|
||||||
"manage-aliases": "Manage Aliases",
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Seed Data",
|
"seed-data": "Seed Data",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Úlohy na pozadí",
|
"background-tasks": "Úlohy na pozadí",
|
||||||
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
||||||
"no-logs-found": "Nenalezeny žádné záznamy",
|
"no-logs-found": "Nenalezeny žádné záznamy",
|
||||||
"tasks": "Tasks"
|
"tasks": "Tasks",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "First Time Setup",
|
||||||
|
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Vítejte, {0}",
|
"welcome-user": "👋 Welcome, {0}!",
|
||||||
"description": "Manage your profile, recipes, and group settings.",
|
"description": "Manage your profile, recipes, and group settings.",
|
||||||
"get-invite-link": "Get Invite Link",
|
"get-invite-link": "Get Invite Link",
|
||||||
"get-public-link": "Get Public Link",
|
"get-public-link": "Get Public Link",
|
||||||
"account-summary": "Přehled účtu",
|
"account-summary": "Přehled účtu",
|
||||||
"account-summary-description": "Here's a summary of your group's information",
|
"account-summary-description": "Here's a summary of your group's information.",
|
||||||
"group-statistics": "Statistiky skupiny",
|
"group-statistics": "Statistiky skupiny",
|
||||||
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
||||||
"storage-capacity": "Kapacita úložiště",
|
"storage-capacity": "Kapacita úložiště",
|
||||||
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
||||||
"personal": "Osobní",
|
"personal": "Osobní",
|
||||||
"personal-description": "These are settings that are personal to you. Changes here won't affect other users",
|
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
|
||||||
"user-settings": "Uživatelské nastavení",
|
"user-settings": "Uživatelské nastavení",
|
||||||
"user-settings-description": "Manage your preferences, change your password, and update your email",
|
"user-settings-description": "Manage your preferences, change your password, and update your email.",
|
||||||
"api-tokens-description": "Manage your API Tokens for access from external applications",
|
"api-tokens-description": "Manage your API Tokens for access from external applications.",
|
||||||
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
||||||
"group-settings": "Nastavení skupiny",
|
"group-settings": "Nastavení skupiny",
|
||||||
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Oznámení",
|
"notifiers": "Oznámení",
|
||||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||||
"manage-data": "Spravovat data",
|
"manage-data": "Spravovat data",
|
||||||
"manage-data-description": "Manage your Food and Units (more options coming soon)",
|
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
|
||||||
"data-migrations": "Migrace dat",
|
"data-migrations": "Migrace dat",
|
||||||
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown",
|
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
|
||||||
"email-sent": "E-mail odeslán",
|
"email-sent": "E-mail odeslán",
|
||||||
"error-sending-email": "Nastala chyba při odesílání e-mailu",
|
"error-sending-email": "Nastala chyba při odesílání e-mailu",
|
||||||
"personal-information": "Osobní údaje",
|
"personal-information": "Osobní údaje",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Noget gik galt!",
|
"something-went-wrong": "Noget gik galt!",
|
||||||
"subscribed-events": "Abonnerede begivenheder",
|
"subscribed-events": "Abonnerede begivenheder",
|
||||||
"test-message-sent": "Testbesked sendt",
|
"test-message-sent": "Testbesked sendt",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "Ny notifikation",
|
"new-notification": "Ny notifikation",
|
||||||
"event-notifiers": "Notifikation om begivenheder",
|
"event-notifiers": "Notifikation om begivenheder",
|
||||||
"apprise-url-skipped-if-blank": "Informations link (sprunget over hvis ladet være tomt)",
|
"apprise-url-skipped-if-blank": "Informations link (sprunget over hvis ladet være tomt)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Hændelser for opskrifter"
|
"recipe-events": "Hændelser for opskrifter"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Tilføj",
|
||||||
"cancel": "Annuller",
|
"cancel": "Annuller",
|
||||||
"clear": "Ryd",
|
"clear": "Ryd",
|
||||||
"close": "Luk",
|
"close": "Luk",
|
||||||
"confirm": "Bekræft",
|
"confirm": "Bekræft",
|
||||||
|
"confirm-how-does-everything-look": "How does everything look?",
|
||||||
"confirm-delete-generic": "Er du sikker på, du vil slette dette?",
|
"confirm-delete-generic": "Er du sikker på, du vil slette dette?",
|
||||||
"copied_message": "Kopieret!",
|
"copied_message": "Kopieret!",
|
||||||
"create": "Opret",
|
"create": "Opret",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Gem",
|
"save": "Gem",
|
||||||
"settings": "Indstillinger",
|
"settings": "Indstillinger",
|
||||||
"share": "Del",
|
"share": "Del",
|
||||||
|
"show-all": "Vis alle",
|
||||||
"shuffle": "Bland",
|
"shuffle": "Bland",
|
||||||
"sort": "Sorter",
|
"sort": "Sorter",
|
||||||
|
"sort-ascending": "Sorter stigende",
|
||||||
|
"sort-descending": "Sorter Faldende",
|
||||||
"sort-alphabetically": "Alfabetisk",
|
"sort-alphabetically": "Alfabetisk",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"subject": "Emne",
|
"subject": "Emne",
|
||||||
"submit": "Indsend",
|
"submit": "Indsend",
|
||||||
"success-count": "Succes: {count}",
|
"success-count": "Succes: {count}",
|
||||||
"sunday": "Søndag",
|
"sunday": "Søndag",
|
||||||
|
"system": "System",
|
||||||
"templates": "Skabeloner:",
|
"templates": "Skabeloner:",
|
||||||
"test": "Afprøv",
|
"test": "Afprøv",
|
||||||
"themes": "Temaer",
|
"themes": "Temaer",
|
||||||
"thursday": "Torsdag",
|
"thursday": "Torsdag",
|
||||||
|
"title": "Title",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Tirsdag",
|
"tuesday": "Tirsdag",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Enheder",
|
"units": "Enheder",
|
||||||
"back": "Tilbage",
|
"back": "Tilbage",
|
||||||
"next": "Næste",
|
"next": "Næste",
|
||||||
|
"start": "Start",
|
||||||
"toggle-view": "Skift visning",
|
"toggle-view": "Skift visning",
|
||||||
"date": "Dato",
|
"date": "Dato",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Oprettet den: {0}",
|
"created-on-date": "Oprettet den: {0}",
|
||||||
"unsaved-changes": "Du har ændringer som ikke er gemt. Vil du gemme før du forlader? Vælg \"Okay\" for at gemme, eller \"Annullér\" for at kassere ændringer.",
|
"unsaved-changes": "Du har ændringer som ikke er gemt. Vil du gemme før du forlader? Vælg \"Okay\" for at gemme, eller \"Annullér\" for at kassere ændringer.",
|
||||||
"clipboard-copy-failure": "Kopiering til udklipsholderen mislykkedes.",
|
"clipboard-copy-failure": "Kopiering til udklipsholderen mislykkedes.",
|
||||||
"confirm-delete-generic-items": "Er du sikker på at du ønsker at slette de valgte emner?"
|
"confirm-delete-generic-items": "Er du sikker på at du ønsker at slette de valgte emner?",
|
||||||
|
"organizers": "Organizers"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på, du vil slette <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på, du vil slette <b>{groupName}<b/>?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Gruppe Indstillinger",
|
"group-preferences": "Gruppe Indstillinger",
|
||||||
"private-group": "Privat Gruppe",
|
"private-group": "Privat Gruppe",
|
||||||
"private-group-description": "Indstilling af din gruppe til privat, vil ændre alle indstillinger for offentlig visning. Dette tilsidesætter individuelle opskrifters indstillinger for offentlig visning.",
|
"private-group-description": "Indstilling af din gruppe til privat, vil ændre alle indstillinger for offentlig visning. Dette tilsidesætter individuelle opskrifters indstillinger for offentlig visning.",
|
||||||
|
"enable-public-access": "Aktiver Offentlig Adgang",
|
||||||
|
"enable-public-access-description": "Gør gruppeopskrifter offentlige som standard, og tillade besøgende at se opskrifter uden at logge ind",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Tillad brugere udenfor din gruppe at se dine opskrifter",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Tillad brugere udenfor din gruppe at se dine opskrifter",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Når aktiveret kan du bruge et link til offentlig deling til at dele specifikke opskrifter uden at oprette en bruger. Når deaktiveret, kan du kun dele opskrifter med brugere, der er i din gruppe eller med et prægenereret privat link",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Når aktiveret kan du bruge et link til offentlig deling til at dele specifikke opskrifter uden at oprette en bruger. Når deaktiveret, kan du kun dele opskrifter med brugere, der er i din gruppe eller med et prægenereret privat link",
|
||||||
"show-nutrition-information": "Vis ernæringsoplysninger",
|
"show-nutrition-information": "Vis ernæringsoplysninger",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Migrering af opskrifter",
|
"recipe-data-migrations": "Migrering af opskrifter",
|
||||||
"recipe-data-migrations-explanation": "Opskrifter kan migreres fra et andet understøttet program til Mealie. Dette er en fantastisk måde at komme i gang med Mealie.",
|
"recipe-data-migrations-explanation": "Opskrifter kan migreres fra et andet understøttet program til Mealie. Dette er en fantastisk måde at komme i gang med Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Kommer fra en anden applikation eller en endnu ældre version af Mealie? Tjek migrationer og se om dine data kan importeres.",
|
||||||
"choose-migration-type": "Vælg Migreringstype",
|
"choose-migration-type": "Vælg Migreringstype",
|
||||||
"tag-all-recipes": "Tag alle opskrifter med {tag-name} tag",
|
"tag-all-recipes": "Tag alle opskrifter med {tag-name} tag",
|
||||||
"nextcloud-text": "Nextcloud opskrifter kan importeres fra en zip-fil, der indeholder data lagret i Nextcloud. Se eksempelmappestrukturen nedenfor for at sikre, at dine opskrifter kan importeres.",
|
"nextcloud-text": "Nextcloud opskrifter kan importeres fra en zip-fil, der indeholder data lagret i Nextcloud. Se eksempelmappestrukturen nedenfor for at sikre, at dine opskrifter kan importeres.",
|
||||||
"chowdown-text": "Mealie understøtter indbygget chowdown repository format. Download repositoriet som en .zip-fil og upload den nedenfor",
|
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||||
"recipe-1": "Opskrift 1",
|
"recipe-1": "Opskrift 1",
|
||||||
"recipe-2": "Opskrift 2",
|
"recipe-2": "Opskrift 2",
|
||||||
"paprika-text": "Mealie kan importere opskrifter fra Paprika applikationen. Eksporter dine opskrifter fra paprika, omdøbe eksportudvidelsen til .zip og uploade den nedenfor.",
|
"paprika-text": "Mealie kan importere opskrifter fra Paprika applikationen. Eksporter dine opskrifter fra paprika, omdøbe eksportudvidelsen til .zip og uploade den nedenfor.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Planlæg at spise",
|
"title": "Planlæg at spise",
|
||||||
"description-long": "Mealie kan importere opskrifter, der er markeret som \"Planlæg at spise\"."
|
"description-long": "Mealie kan importere opskrifter, der er markeret som \"Planlæg at spise\"."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Rediger tidslinjebegivenhed",
|
"edit-timeline-event": "Rediger tidslinjebegivenhed",
|
||||||
"timeline": "Tidslinje",
|
"timeline": "Tidslinje",
|
||||||
"timeline-is-empty": "Intet på tidslinjen endnu. Prøv at lave denne opskrift!",
|
"timeline-is-empty": "Intet på tidslinjen endnu. Prøv at lave denne opskrift!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Global Tidslinje",
|
"group-global-timeline": "{groupName} Global Tidslinje",
|
||||||
"open-timeline": "Åbn tidslinje",
|
"open-timeline": "Åbn tidslinje",
|
||||||
"made-this": "Jeg har lavet denne",
|
"made-this": "Jeg har lavet denne",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Kigger du efter data migrering?",
|
"looking-for-migrations": "Kigger du efter data migrering?",
|
||||||
"import-with-url": "Importér via URL",
|
"import-with-url": "Importér via URL",
|
||||||
"create-recipe": "Opret opskrift",
|
"create-recipe": "Opret opskrift",
|
||||||
|
"create-recipe-description": "Opret ny opskrift fra bunden.",
|
||||||
|
"create-recipes": "Opret opskrift",
|
||||||
"import-with-zip": "Importér fra ZIP-fil",
|
"import-with-zip": "Importér fra ZIP-fil",
|
||||||
"create-recipe-from-an-image": "Opret opskrift ud fra et billede",
|
"create-recipe-from-an-image": "Opret opskrift ud fra et billede",
|
||||||
"bulk-url-import": "Import fra flere URL-adresser",
|
"bulk-url-import": "Import fra flere URL-adresser",
|
||||||
@@ -540,7 +560,7 @@
|
|||||||
"scrape-recipe": "Scrape Opskrift",
|
"scrape-recipe": "Scrape Opskrift",
|
||||||
"scrape-recipe-description": "Hent en opskrift fra en hjemmeside. Angiv URL'en til den hjemmeside, du vil hente data fra, og Mealie vil forsøge at hente opskriften og tilføje den til din samling.",
|
"scrape-recipe-description": "Hent en opskrift fra en hjemmeside. Angiv URL'en til den hjemmeside, du vil hente data fra, og Mealie vil forsøge at hente opskriften og tilføje den til din samling.",
|
||||||
"scrape-recipe-have-a-lot-of-recipes": "Har du en masse opskrifter, du ønsker at scrappe på en gang?",
|
"scrape-recipe-have-a-lot-of-recipes": "Har du en masse opskrifter, du ønsker at scrappe på en gang?",
|
||||||
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
|
"scrape-recipe-suggest-bulk-importer": "Prøv masse import",
|
||||||
"import-original-keywords-as-tags": "Importér originale nøgleord som mærker",
|
"import-original-keywords-as-tags": "Importér originale nøgleord som mærker",
|
||||||
"stay-in-edit-mode": "Bliv i redigeringstilstand",
|
"stay-in-edit-mode": "Bliv i redigeringstilstand",
|
||||||
"import-from-zip": "Importer fra zip-fil",
|
"import-from-zip": "Importer fra zip-fil",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Upload billede",
|
"upload-image": "Upload billede",
|
||||||
"screen-awake": "Hold skærmen tændt",
|
"screen-awake": "Hold skærmen tændt",
|
||||||
"remove-image": "Fjern billede",
|
"remove-image": "Fjern billede",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Næste trin",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Avanceret søgning",
|
"advanced-search": "Avanceret søgning",
|
||||||
@@ -601,7 +636,7 @@
|
|||||||
"import-summary": "Importer resumé",
|
"import-summary": "Importer resumé",
|
||||||
"partial-backup": "Delvis backup",
|
"partial-backup": "Delvis backup",
|
||||||
"unable-to-delete-backup": "Ude af stand til at slette backup.",
|
"unable-to-delete-backup": "Ude af stand til at slette backup.",
|
||||||
"experimental-description": "Backups are total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.",
|
"experimental-description": "Backups er samlede snapshots af databasen og datamappe på installationen. Dette omfatter alle data og kan ikke indstilles til at udelukke undergrupper af data. Du kan tænke på dette som et øjebliksbillede af Mealie på et bestemt tidspunkt. Disse fungerer som en database agnostisk måde at eksportere og importere data, eller sikkerhedskopiere installationen til en ekstern placering.",
|
||||||
"backup-restore": "Backup / gendannelse",
|
"backup-restore": "Backup / gendannelse",
|
||||||
"back-restore-description": "Gendannelse af denne sikkerhedskopi vil overskrive alle de aktuelle data i din database og i datamappen og erstatte dem med indholdet af denne sikkerhedskopi. {cannot-be-undone} Hvis gendannelsen lykkes, vil du blive logget ud.",
|
"back-restore-description": "Gendannelse af denne sikkerhedskopi vil overskrive alle de aktuelle data i din database og i datamappen og erstatte dem med indholdet af denne sikkerhedskopi. {cannot-be-undone} Hvis gendannelsen lykkes, vil du blive logget ud.",
|
||||||
"cannot-be-undone": "Denne handling kan ikke fortrydes - brug med forsigtighed.",
|
"cannot-be-undone": "Denne handling kan ikke fortrydes - brug med forsigtighed.",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Ikke alle LDAP- værdier er konfigureret. Dette kan ignoreres, hvis du ikke bruger LDAP- godkendelse.",
|
"ldap-ready-error-text": "Ikke alle LDAP- værdier er konfigureret. Dette kan ignoreres, hvis du ikke bruger LDAP- godkendelse.",
|
||||||
"ldap-ready-success-text": "Påkrævede LDAP-variabler er alle angivet.",
|
"ldap-ready-success-text": "Påkrævede LDAP-variabler er alle angivet.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Version på opskrift-indsamler"
|
"recipe-scraper-version": "Version på opskrift-indsamler",
|
||||||
|
"oidc-ready": "OIDC er Klar",
|
||||||
|
"oidc-ready-error-text": "Ikke alle OIDC værdier er konfigureret. Dette kan ignoreres, hvis du ikke bruger OIDC godkendelse.",
|
||||||
|
"oidc-ready-success-text": "Alle påkrævede OIDC værdier er angivet."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Alle lister",
|
"all-lists": "Alle lister",
|
||||||
@@ -809,7 +847,7 @@
|
|||||||
"create-new-tool": "Opret et nyt værktøj",
|
"create-new-tool": "Opret et nyt værktøj",
|
||||||
"on-hand-checkbox-label": "Vis som \"Har allerede\" (afkrydset)",
|
"on-hand-checkbox-label": "Vis som \"Har allerede\" (afkrydset)",
|
||||||
"required-tools": "Nødvendige Værktøjer",
|
"required-tools": "Nødvendige Værktøjer",
|
||||||
"tool": "Tool"
|
"tool": "Værktøj"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"admin": "Administrator",
|
"admin": "Administrator",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Link ID",
|
"link-id": "Link ID",
|
||||||
"link-name": "Linknavn",
|
"link-name": "Linknavn",
|
||||||
"login": "Log på",
|
"login": "Log på",
|
||||||
|
"login-oidc": "Log ind med",
|
||||||
|
"or": "eller",
|
||||||
"logout": "Log ud",
|
"logout": "Log ud",
|
||||||
"manage-users": "Administrer brugere",
|
"manage-users": "Administrer brugere",
|
||||||
|
"manage-users-description": "Opret og administrér brugere.",
|
||||||
"new-password": "Ny adgangskode",
|
"new-password": "Ny adgangskode",
|
||||||
"new-user": "Ny bruger",
|
"new-user": "Ny bruger",
|
||||||
"password-has-been-reset-to-the-default-password": "Adgangskode er blevet nulstillet til systemstandarden",
|
"password-has-been-reset-to-the-default-password": "Adgangskode er blevet nulstillet til systemstandarden",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Slet Opskrifter",
|
"delete-recipes": "Slet Opskrifter",
|
||||||
"source-unit-will-be-deleted": "Kildeenhed vil blive slettet"
|
"source-unit-will-be-deleted": "Kildeenhed vil blive slettet"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Opret alias",
|
"create-alias": "Opret alias",
|
||||||
"manage-aliases": "Administrer Aliaser",
|
"manage-aliases": "Administrer Aliaser",
|
||||||
"seed-data": "Opret standard data",
|
"seed-data": "Opret standard data",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Baggrundsopgaver",
|
"background-tasks": "Baggrundsopgaver",
|
||||||
"background-tasks-description": "Her kan du se status på alle opgaver, der afvikles i baggrunden",
|
"background-tasks-description": "Her kan du se status på alle opgaver, der afvikles i baggrunden",
|
||||||
"no-logs-found": "Ingen logfiler fundet",
|
"no-logs-found": "Ingen logfiler fundet",
|
||||||
"tasks": "Opgaver"
|
"tasks": "Opgaver",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "Første Opsætning",
|
||||||
|
"welcome-to-mealie-get-started": "Velkommen til Mealie! Lad os starte",
|
||||||
|
"already-set-up-bring-to-homepage": "Jeg er allerede oprettet, bare bringe mig til hjemmesiden",
|
||||||
|
"common-settings-for-new-sites": "Her er nogle almindelige indstillinger for nye sites",
|
||||||
|
"setup-complete": "Opsætning fuldført!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Velkommen, {0}",
|
"welcome-user": "👋 Velkommen, {0}!",
|
||||||
"description": "Administrer din profil, opskrifter og gruppeindstillinger.",
|
"description": "Administrer din profil, opskrifter og gruppeindstillinger.",
|
||||||
"get-invite-link": "Få Invitationslink",
|
"get-invite-link": "Få Invitationslink",
|
||||||
"get-public-link": "Offentligt link",
|
"get-public-link": "Offentligt link",
|
||||||
"account-summary": "Konto oversigt",
|
"account-summary": "Konto oversigt",
|
||||||
"account-summary-description": "Her er en oversigt over din gruppes oplysninger",
|
"account-summary-description": "Her er en oversigt over din gruppes oplysninger.",
|
||||||
"group-statistics": "Gruppestatistik",
|
"group-statistics": "Gruppestatistik",
|
||||||
"group-statistics-description": "Din gruppestatistik giver indsigt i, hvordan du bruger Mealie.",
|
"group-statistics-description": "Din gruppestatistik giver indsigt i, hvordan du bruger Mealie.",
|
||||||
"storage-capacity": "Lagerkapacitet",
|
"storage-capacity": "Lagerkapacitet",
|
||||||
"storage-capacity-description": "Din lagerkapacitet er en beregning af de billeder og elementer, du har uploadet.",
|
"storage-capacity-description": "Din lagerkapacitet er en beregning af de billeder og elementer, du har uploadet.",
|
||||||
"personal": "Personlig",
|
"personal": "Personlig",
|
||||||
"personal-description": "Disse indstillinger er personlige for dig. Ændringer her vil ikke påvirke andre brugere",
|
"personal-description": "Disse indstillinger er personlige for dig. Ændringer vil ikke påvirke andre brugere.",
|
||||||
"user-settings": "Brugerindstillinger",
|
"user-settings": "Brugerindstillinger",
|
||||||
"user-settings-description": "Administrer dine indstillinger, din adgangskode eller din e-mail",
|
"user-settings-description": "Administrer dine indstillinger, din adgangskode eller din e-mail.",
|
||||||
"api-tokens-description": "Administrer dine API Tokens, der giver adgang til eksterne applikationer",
|
"api-tokens-description": "Administrer dine API Tokens for adgang fra eksterne applikationer.",
|
||||||
"group-description": "Disse elementer deles i din gruppe. Redigering af et af dem vil ændre det for hele gruppen!",
|
"group-description": "Disse elementer deles i din gruppe. Redigering af et af dem vil ændre det for hele gruppen!",
|
||||||
"group-settings": "Gruppeindstillinger",
|
"group-settings": "Gruppeindstillinger",
|
||||||
"group-settings-description": "Administrer dine fælles gruppeindstillinger såsom madplaner og privatlivsindstillinger.",
|
"group-settings-description": "Administrer dine fælles gruppeindstillinger såsom madplaner og privatlivsindstillinger.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Notifikationer",
|
"notifiers": "Notifikationer",
|
||||||
"notifiers-description": "Opsæt e-mail og push-notifikationer, der udløser på specifikke begivenheder.",
|
"notifiers-description": "Opsæt e-mail og push-notifikationer, der udløser på specifikke begivenheder.",
|
||||||
"manage-data": "Administrer data",
|
"manage-data": "Administrer data",
|
||||||
"manage-data-description": "Administrer dine fødevarer og enheder (flere muligheder kommer snart)",
|
"manage-data-description": "Administrer dine Mealie data; Mad, Enheder, Kategorier, Tags og meget mere.",
|
||||||
"data-migrations": "Migrering af data",
|
"data-migrations": "Migrering af data",
|
||||||
"data-migrations-description": "Migrer dine eksisterende data fra andre programmer som Nextcloud Recipes og Chowdown",
|
"data-migrations-description": "Migrér dine eksisterende data fra andre programmer som Nextcloud Opskrifter og Chowdown.",
|
||||||
"email-sent": "E-mail sendt",
|
"email-sent": "E-mail sendt",
|
||||||
"error-sending-email": "Fejl ved afsendelse af email",
|
"error-sending-email": "Fejl ved afsendelse af email",
|
||||||
"personal-information": "Personlige oplysninger",
|
"personal-information": "Personlige oplysninger",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Etwas ist schief gelaufen!",
|
"something-went-wrong": "Etwas ist schief gelaufen!",
|
||||||
"subscribed-events": "Abonnierte Ereignisse",
|
"subscribed-events": "Abonnierte Ereignisse",
|
||||||
"test-message-sent": "Testnachricht gesendet",
|
"test-message-sent": "Testnachricht gesendet",
|
||||||
|
"message-sent": "Daten gesendet",
|
||||||
"new-notification": "Neue Benachrichtigung",
|
"new-notification": "Neue Benachrichtigung",
|
||||||
"event-notifiers": "Ereignis-Benachrichtigungen",
|
"event-notifiers": "Ereignis-Benachrichtigungen",
|
||||||
"apprise-url-skipped-if-blank": "Apprise-URL (wird übersprungen, wenn leer)",
|
"apprise-url-skipped-if-blank": "Apprise-URL (wird übersprungen, wenn leer)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Rezept-Ereignisse"
|
"recipe-events": "Rezept-Ereignisse"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Hinzufügen",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"clear": "Zurücksetzen",
|
"clear": "Zurücksetzen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
|
"confirm-how-does-everything-look": "Wie sieht alles aus?",
|
||||||
"confirm-delete-generic": "Bist du dir sicher, dass du dies löschen möchtest?",
|
"confirm-delete-generic": "Bist du dir sicher, dass du dies löschen möchtest?",
|
||||||
"copied_message": "Kopiert!",
|
"copied_message": "Kopiert!",
|
||||||
"create": "Erstellen",
|
"create": "Erstellen",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"share": "Teilen",
|
"share": "Teilen",
|
||||||
|
"show-all": "Alle anzeigen",
|
||||||
"shuffle": "Mischen",
|
"shuffle": "Mischen",
|
||||||
"sort": "Sortierung",
|
"sort": "Sortierung",
|
||||||
|
"sort-ascending": "Aufsteigend sortieren",
|
||||||
|
"sort-descending": "Absteigend sortieren",
|
||||||
"sort-alphabetically": "Alphabetisch",
|
"sort-alphabetically": "Alphabetisch",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"subject": "Ereignis",
|
"subject": "Ereignis",
|
||||||
"submit": "Absenden",
|
"submit": "Absenden",
|
||||||
"success-count": "Erfolgreich: {count}",
|
"success-count": "Erfolgreich: {count}",
|
||||||
"sunday": "Sonntag",
|
"sunday": "Sonntag",
|
||||||
|
"system": "System",
|
||||||
"templates": "Vorlagen:",
|
"templates": "Vorlagen:",
|
||||||
"test": "Testen",
|
"test": "Testen",
|
||||||
"themes": "Themen",
|
"themes": "Themen",
|
||||||
"thursday": "Donnerstag",
|
"thursday": "Donnerstag",
|
||||||
|
"title": "Titel",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Dienstag",
|
"tuesday": "Dienstag",
|
||||||
"type": "Typ",
|
"type": "Typ",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Einheiten",
|
"units": "Einheiten",
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
"next": "Weiter",
|
"next": "Weiter",
|
||||||
|
"start": "Starten",
|
||||||
"toggle-view": "Ansicht wechseln",
|
"toggle-view": "Ansicht wechseln",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
@@ -178,7 +187,7 @@
|
|||||||
"menu": "Menü",
|
"menu": "Menü",
|
||||||
"a-name-is-required": "Ein Name wird benötigt",
|
"a-name-is-required": "Ein Name wird benötigt",
|
||||||
"delete-with-name": "{name} löschen",
|
"delete-with-name": "{name} löschen",
|
||||||
"confirm-delete-generic-with-name": "Bist du dir sicher, dass du \"{name}\" löschen möchtest?",
|
"confirm-delete-generic-with-name": "Bist du dir sicher, dass du dies löschen möchtest?",
|
||||||
"confirm-delete-own-admin-account": "Bitte beachte, dass du versuchst, dein eigenes Administrator-Konto zu löschen! Diese Aktion kann nicht rückgängig gemacht werden und wird dein Konto dauerhaft löschen!",
|
"confirm-delete-own-admin-account": "Bitte beachte, dass du versuchst, dein eigenes Administrator-Konto zu löschen! Diese Aktion kann nicht rückgängig gemacht werden und wird dein Konto dauerhaft löschen!",
|
||||||
"organizer": "Organisator",
|
"organizer": "Organisator",
|
||||||
"transfer": "Übertragen",
|
"transfer": "Übertragen",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Erstellt am: {0}",
|
"created-on-date": "Erstellt am: {0}",
|
||||||
"unsaved-changes": "Du hast ungespeicherte Änderungen. Möchtest du vor dem Verlassen speichern? OK um zu speichern, Cancel um Änderungen zu verwerfen.",
|
"unsaved-changes": "Du hast ungespeicherte Änderungen. Möchtest du vor dem Verlassen speichern? OK um zu speichern, Cancel um Änderungen zu verwerfen.",
|
||||||
"clipboard-copy-failure": "Fehler beim Kopieren in die Zwischenablage.",
|
"clipboard-copy-failure": "Fehler beim Kopieren in die Zwischenablage.",
|
||||||
"confirm-delete-generic-items": "Bist du dir sicher, dass du die folgenden Einträge löschen möchtest?"
|
"confirm-delete-generic-items": "Bist du dir sicher, dass du die folgenden Einträge löschen möchtest?",
|
||||||
|
"organizers": "Organisieren"
|
||||||
},
|
},
|
||||||
"group": {
|
"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?",
|
"are-you-sure-you-want-to-delete-the-group": "Bist du dir sicher, dass du die Gruppe <b>{groupName}<b/> löschen möchtest?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Gruppeneinstellungen",
|
"group-preferences": "Gruppeneinstellungen",
|
||||||
"private-group": "Private Gruppe",
|
"private-group": "Private Gruppe",
|
||||||
"private-group-description": "Wenn du deine Gruppe auf privat stellst, werden alle Einstellungen für den öffentlichen Zugriff zurückgesetzt. Individuelle Einstellungen für einzelne Rezepte werden überschrieben.",
|
"private-group-description": "Wenn du deine Gruppe auf privat stellst, werden alle Einstellungen für den öffentlichen Zugriff zurückgesetzt. Individuelle Einstellungen für einzelne Rezepte werden überschrieben.",
|
||||||
|
"enable-public-access": "Öffentlichen Zugriff aktivieren",
|
||||||
|
"enable-public-access-description": "Gruppenrezepte standardmäßig öffentlich machen und es Besuchern erlauben, Rezepte ohne Anmeldung anzuzeigen",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Erlaube Benutzern außerhalb deiner Gruppe deine Rezepte zu sehen",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Erlaube Benutzern außerhalb deiner Gruppe deine Rezepte zu sehen",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Wenn aktiviert, kannst du einen öffentlichen Link verwenden, um ausgewählte Rezepte zu teilen, ohne dass Benutzer sich anmelden müssen. Wenn deaktiviert, kannst du nur Rezepte mit Benutzern teilen, die in deiner Gruppe sind oder einen vorher erstellten privaten Link verwenden",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Wenn aktiviert, kannst du einen öffentlichen Link verwenden, um ausgewählte Rezepte zu teilen, ohne dass Benutzer sich anmelden müssen. Wenn deaktiviert, kannst du nur Rezepte mit Benutzern teilen, die in deiner Gruppe sind oder einen vorher erstellten privaten Link verwenden",
|
||||||
"show-nutrition-information": "Nährwerttabelle anzeigen",
|
"show-nutrition-information": "Nährwerttabelle anzeigen",
|
||||||
@@ -349,6 +361,7 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Rezeptdatenmigration",
|
"recipe-data-migrations": "Rezeptdatenmigration",
|
||||||
"recipe-data-migrations-explanation": "Rezepte können aus unterstützten Programmen nach Mealie migriert werden. Das ist eine gute Möglichkeit, um mit Mealie loszulegen.",
|
"recipe-data-migrations-explanation": "Rezepte können aus unterstützten Programmen nach Mealie migriert werden. Das ist eine gute Möglichkeit, um mit Mealie loszulegen.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Wechselst du von einer anderen Anwendung oder einer noch älteren Version von Mealie? Prüfe die Migrations-Optionen, vielleicht können deine Daten importiert werden.",
|
||||||
"choose-migration-type": "Migrationsart wählen",
|
"choose-migration-type": "Migrationsart wählen",
|
||||||
"tag-all-recipes": "Alle Rezepte mit Schlagwort {tag-name} versehen",
|
"tag-all-recipes": "Alle Rezepte mit Schlagwort {tag-name} versehen",
|
||||||
"nextcloud-text": "Nextcloud Rezepte können aus einer Zip-Datei importiert werden, die die in Nextcloud gespeicherten Daten enthält. Vergleiche die Beispiel-Ordnerstruktur unten um sicherzustellen, dass deine Rezepte importiert werden können.",
|
"nextcloud-text": "Nextcloud Rezepte können aus einer Zip-Datei importiert werden, die die in Nextcloud gespeicherten Daten enthält. Vergleiche die Beispiel-Ordnerstruktur unten um sicherzustellen, dass deine Rezepte importiert werden können.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie kann Rezepte von Plan to Eat importieren."
|
"description-long": "Mealie kann Rezepte von Plan to Eat importieren."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie kann Rezepte von My Recipe Box importieren. Exportiere deine Rezepte im CSV-Format und lade dann unten die .csv Datei hoch."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -408,7 +425,7 @@
|
|||||||
"carbohydrate-content": "Kohlenhydrate",
|
"carbohydrate-content": "Kohlenhydrate",
|
||||||
"categories": "Kategorien",
|
"categories": "Kategorien",
|
||||||
"comment-action": "Kommentieren",
|
"comment-action": "Kommentieren",
|
||||||
"comment": "Kommentieren",
|
"comment": "Kommentar",
|
||||||
"comments": "Kommentare",
|
"comments": "Kommentare",
|
||||||
"delete-confirmation": "Bist du dir sicher, dass du dieses Rezept löschen möchtest?",
|
"delete-confirmation": "Bist du dir sicher, dass du dieses Rezept löschen möchtest?",
|
||||||
"delete-recipe": "Rezept löschen",
|
"delete-recipe": "Rezept löschen",
|
||||||
@@ -494,8 +511,8 @@
|
|||||||
"cook-mode": "Koch-Modus",
|
"cook-mode": "Koch-Modus",
|
||||||
"link-ingredients": "Zutaten verlinken",
|
"link-ingredients": "Zutaten verlinken",
|
||||||
"merge-above": "Mit Eintrag darüber zusammenführen",
|
"merge-above": "Mit Eintrag darüber zusammenführen",
|
||||||
"move-to-bottom": "Move To Bottom",
|
"move-to-bottom": "Ganz nach unten",
|
||||||
"move-to-top": "Move To Top",
|
"move-to-top": "Ganz nach oben",
|
||||||
"reset-scale": "Maßstab zurücksetzen",
|
"reset-scale": "Maßstab zurücksetzen",
|
||||||
"decrease-scale-label": "Maßstab um 1 verringern",
|
"decrease-scale-label": "Maßstab um 1 verringern",
|
||||||
"increase-scale-label": "Maßstab um 1 erhöhen",
|
"increase-scale-label": "Maßstab um 1 erhöhen",
|
||||||
@@ -511,15 +528,16 @@
|
|||||||
"edit-timeline-event": "Zeitstrahl-Ereignis bearbeiten",
|
"edit-timeline-event": "Zeitstrahl-Ereignis bearbeiten",
|
||||||
"timeline": "Zeitstrahl",
|
"timeline": "Zeitstrahl",
|
||||||
"timeline-is-empty": "Noch nichts auf dem Zeitstrahl. Probier dieses Rezept aus!",
|
"timeline-is-empty": "Noch nichts auf dem Zeitstrahl. Probier dieses Rezept aus!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "Keine Ereignisse gefunden. Probiere es mit anderen Suchfiltern.",
|
||||||
"group-global-timeline": "{groupName} Globaler Zeitstrahl",
|
"group-global-timeline": "{groupName} Globaler Zeitstrahl",
|
||||||
"open-timeline": "Zeitstrahl öffnen",
|
"open-timeline": "Zeitstrahl öffnen",
|
||||||
"made-this": "Ich hab's gemacht",
|
"made-this": "Ich hab's gemacht",
|
||||||
"how-did-it-turn-out": "Wie ist es geworden?",
|
"how-did-it-turn-out": "Wie ist es geworden?",
|
||||||
"user-made-this": "{user} hat's gemacht",
|
"user-made-this": "{user} hat's gemacht",
|
||||||
"last-made-date": "Zuletzt gemacht {date}",
|
"last-made-date": "Zuletzt gemacht {date}",
|
||||||
"api-extras-description": "Rezepte-Extras sind ein Hauptmerkmal der Mealie API. Sie ermöglichen es dir, benutzerdefinierte JSON Key-Value-Paare zu einem Rezept zu erstellen, um Drittanbietern-Anwendungen zu steuern. Du kannst diese dazu verwenden, um Automatisierungen auszulösen oder benutzerdefinierte Nachrichten an bestimmte Geräte zu senden.",
|
"api-extras-description": "Rezepte-Extras sind ein Hauptmerkmal der Mealie API. Sie ermöglichen es dir, benutzerdefinierte JSON Key-Value-Paare zu einem Rezept zu erstellen, um Drittanbieter-Anwendungen zu steuern. Du kannst diese dazu verwenden, um Automatisierungen auszulösen oder benutzerdefinierte Nachrichten an bestimmte Geräte zu senden.",
|
||||||
"message-key": "Nachrichten-Schlüssel",
|
"message-key": "Nachrichten-Schlüssel",
|
||||||
"parse": "Parse",
|
"parse": "Parsen",
|
||||||
"attach-images-hint": "Bilder durch Ziehen & Ablegen in den Editor hinzufügen",
|
"attach-images-hint": "Bilder durch Ziehen & Ablegen in den Editor hinzufügen",
|
||||||
"drop-image": "Bild hier ablegen",
|
"drop-image": "Bild hier ablegen",
|
||||||
"enable-ingredient-amounts-to-use-this-feature": "Aktiviere Zutatenmengen, um diese Funktion zu nutzen",
|
"enable-ingredient-amounts-to-use-this-feature": "Aktiviere Zutatenmengen, um diese Funktion zu nutzen",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Suchst du nach Migrationen?",
|
"looking-for-migrations": "Suchst du nach Migrationen?",
|
||||||
"import-with-url": "Von URL importieren",
|
"import-with-url": "Von URL importieren",
|
||||||
"create-recipe": "Rezept erstellen",
|
"create-recipe": "Rezept erstellen",
|
||||||
|
"create-recipe-description": "Erstelle ein neues Rezept von Grund auf.",
|
||||||
|
"create-recipes": "Rezepte erstellen",
|
||||||
"import-with-zip": "Von .zip importieren",
|
"import-with-zip": "Von .zip importieren",
|
||||||
"create-recipe-from-an-image": "Rezept von Foto erstellen",
|
"create-recipe-from-an-image": "Rezept von Foto erstellen",
|
||||||
"bulk-url-import": "URL Massenimport",
|
"bulk-url-import": "URL Massenimport",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Bild hochladen",
|
"upload-image": "Bild hochladen",
|
||||||
"screen-awake": "Bildschirm nicht abschalten",
|
"screen-awake": "Bildschirm nicht abschalten",
|
||||||
"remove-image": "Bild entfernen",
|
"remove-image": "Bild entfernen",
|
||||||
"nextStep": "Nächster Schritt"
|
"nextStep": "Nächster Schritt",
|
||||||
|
"recipe-actions": "Rezept-Aktionen",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie verwendet natürliche Sprachverarbeitung (NLP), um Einheiten und Lebensmittel für deine Zutatenliste zu parsen. Diese Funktion ist experimentell und funktioniert möglicherweise nicht immer wie sie sollte. Wenn du die Parser-Ergebnisse nicht verwenden möchtest, wähle 'Abbrechen' und deine Änderungen werden nicht gespeichert.",
|
||||||
|
"ingredient-parser": "Zutaten-Parser",
|
||||||
|
"explanation": "Um den Zutaten-Parser zu verwenden, klicke auf den Button 'Alles parsen', um den Vorgang zu starten. Nachdem die Zutaten analysiert worden sind, kannst du überprüfen, ob die Einträge korrekt erkannt wurden. Der vom Modell errechnete Zuverlässigkeitswert wird rechts neben der Zutat angezeigt. Diese Angabe ist ein Durchschnitt der Einzelwerte und möglicherweise nicht immer ganz korrekt.",
|
||||||
|
"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 (NLP)",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Alles parsen",
|
||||||
|
"no-unit": "Keine Einheit",
|
||||||
|
"missing-unit": "Fehlende Einheit erstellen: {unit}",
|
||||||
|
"missing-food": "Fehlendes Lebensmittel erstellen: {food}",
|
||||||
|
"no-food": "Kein Lebensmittel"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Erweiterte Suche",
|
"advanced-search": "Erweiterte Suche",
|
||||||
@@ -601,7 +636,7 @@
|
|||||||
"import-summary": "Zusammenfassung des Imports",
|
"import-summary": "Zusammenfassung des Imports",
|
||||||
"partial-backup": "Teilsicherung",
|
"partial-backup": "Teilsicherung",
|
||||||
"unable-to-delete-backup": "Sicherung kann nicht gelöscht werden.",
|
"unable-to-delete-backup": "Sicherung kann nicht gelöscht werden.",
|
||||||
"experimental-description": "Backups are total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.",
|
"experimental-description": "Backups sind vollständige Schnappschüsse der Datenbank und des Datenverzeichnisses der Website. Darin sind sämtliche Daten enthalten, es können keine Teile von Daten ausgeschlossen werden. Es ist wie ein Schnappschuss von Mealie zu einem bestimmten Zeitpunkt. Es handelt sich um eine von der Datenbank unabhängige Möglichkeit, Daten zu exportieren und zu importieren oder die Webseite an einem externen Ort zu sichern.",
|
||||||
"backup-restore": "Wiederherstellen aus Sicherung",
|
"backup-restore": "Wiederherstellen aus Sicherung",
|
||||||
"back-restore-description": "Das Wiederherstellen dieser Sicherung wird alle vorhandenen Daten in deiner Datenbank und im Datenverzeichnis überschreiben und durch den Inhalt dieser Sicherung ersetzen. {cannot-be-undone} Wenn die Wiederherstellung erfolgreich war, wirst du abgemeldet.",
|
"back-restore-description": "Das Wiederherstellen dieser Sicherung wird alle vorhandenen Daten in deiner Datenbank und im Datenverzeichnis überschreiben und durch den Inhalt dieser Sicherung ersetzen. {cannot-be-undone} Wenn die Wiederherstellung erfolgreich war, wirst du abgemeldet.",
|
||||||
"cannot-be-undone": "Diese Aktion kann nicht rückgängig gemacht werden - verwende sie mit Vorsicht.",
|
"cannot-be-undone": "Diese Aktion kann nicht rückgängig gemacht werden - verwende sie mit Vorsicht.",
|
||||||
@@ -723,10 +758,13 @@
|
|||||||
"server-side-base-url-error-text": "`BASE_URL` ist immer noch der Standardwert auf dem API-Server. Das verursacht Probleme mit Benachrichtigungslinks auf dem Server für E-Mails, etc.",
|
"server-side-base-url-error-text": "`BASE_URL` ist immer noch der Standardwert auf dem API-Server. Das verursacht Probleme mit Benachrichtigungslinks auf dem Server für E-Mails, etc.",
|
||||||
"server-side-base-url-success-text": "Serverseitige URL entspricht nicht der Standardeinstellung",
|
"server-side-base-url-success-text": "Serverseitige URL entspricht nicht der Standardeinstellung",
|
||||||
"ldap-ready": "LDAP bereit",
|
"ldap-ready": "LDAP bereit",
|
||||||
"ldap-ready-error-text": "Nicht alle LDAP-Werte sind konfiguriert. Das kann ignoriert werden, wenn du keine LDAP-Authentifizierung verwendest.",
|
"ldap-ready-error-text": "Es sind nicht alle LDAP-Werte konfiguriert. Wenn du keine LDAP-Authentifizierung benutzt, kannst du das ignorieren.",
|
||||||
"ldap-ready-success-text": "Alle erforderlichen LDAP-Variablen sind hinterlegt.",
|
"ldap-ready-success-text": "Alle erforderlichen LDAP-Variablen sind hinterlegt.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Rezept Scraper Version"
|
"recipe-scraper-version": "Rezept Scraper Version",
|
||||||
|
"oidc-ready": "OIDC bereit",
|
||||||
|
"oidc-ready-error-text": "Es sind nicht alle OIDC-Werte konfiguriert. Wenn du keine OIDC-Authentifizierung benutzt, kannst du das ignorieren.",
|
||||||
|
"oidc-ready-success-text": "Alle erforderlichen OIDC-Variablen sind gesetzt."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Alle Listen",
|
"all-lists": "Alle Listen",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Linkkennung",
|
"link-id": "Linkkennung",
|
||||||
"link-name": "Linkname",
|
"link-name": "Linkname",
|
||||||
"login": "Anmeldung",
|
"login": "Anmeldung",
|
||||||
|
"login-oidc": "Anmelden mit",
|
||||||
|
"or": "oder",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"manage-users": "Benutzer verwalten",
|
"manage-users": "Benutzer verwalten",
|
||||||
|
"manage-users-description": "Benutzer erstellen und verwalten.",
|
||||||
"new-password": "Neues Passwort",
|
"new-password": "Neues Passwort",
|
||||||
"new-user": "Neuer Benutzer",
|
"new-user": "Neuer Benutzer",
|
||||||
"password-has-been-reset-to-the-default-password": "Passwort wurde auf das Standardpasswort zurückgesetzt",
|
"password-has-been-reset-to-the-default-password": "Passwort wurde auf das Standardpasswort zurückgesetzt",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Rezepte löschen",
|
"delete-recipes": "Rezepte löschen",
|
||||||
"source-unit-will-be-deleted": "Quell-Einheit wird gelöscht"
|
"source-unit-will-be-deleted": "Quell-Einheit wird gelöscht"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Rezept-Aktionen Daten",
|
||||||
|
"new-recipe-action": "Neue Rezept-Aktion",
|
||||||
|
"edit-recipe-action": "Rezept-Aktion bearbeiten",
|
||||||
|
"action-type": "Aktionstyp"
|
||||||
|
},
|
||||||
"create-alias": "Alias erstellen",
|
"create-alias": "Alias erstellen",
|
||||||
"manage-aliases": "Aliasse verwalten",
|
"manage-aliases": "Aliasse verwalten",
|
||||||
"seed-data": "Musterdaten",
|
"seed-data": "Musterdaten",
|
||||||
@@ -1095,10 +1142,10 @@
|
|||||||
"info-description-cleanable-images": "Löschbare Bilder",
|
"info-description-cleanable-images": "Löschbare Bilder",
|
||||||
"storage": {
|
"storage": {
|
||||||
"title-temporary-directory": "Temporäres Verzeichnis (.temp)",
|
"title-temporary-directory": "Temporäres Verzeichnis (.temp)",
|
||||||
"title-backups-directory": "Sicherungsverzeichnis (backups)",
|
"title-backups-directory": "Sicherungen-Verzeichnis (backups)",
|
||||||
"title-groups-directory": "Gruppen-Verzeichnis (groups)",
|
"title-groups-directory": "Gruppen-Verzeichnis (groups)",
|
||||||
"title-recipes-directory": "Rezept-Verzeichnis (recipes)",
|
"title-recipes-directory": "Rezept-Verzeichnis (recipes)",
|
||||||
"title-user-directory": "Benutzerverzeichnis (user)"
|
"title-user-directory": "Benutzer-Verzeichnis (user)"
|
||||||
},
|
},
|
||||||
"action-delete-log-files-name": "Logs löschen",
|
"action-delete-log-files-name": "Logs löschen",
|
||||||
"action-delete-log-files-description": "Löscht alle Logdateien",
|
"action-delete-log-files-description": "Löscht alle Logdateien",
|
||||||
@@ -1123,32 +1170,42 @@
|
|||||||
"ingredients-natural-language-processor-explanation-2": "Es ist nicht perfekt, aber es erzeugt meist sehr gute Ergebnisse und ist ein guter Anfang, um Zutaten manuell den einzelnen Feldern zuzuordnen. Alternativ kannst du auch den \"Brute\" Prozessor benutzen, der eine Musterabgleich-Technik verwendet, um Zutaten zu identifizieren.",
|
"ingredients-natural-language-processor-explanation-2": "Es ist nicht perfekt, aber es erzeugt meist sehr gute Ergebnisse und ist ein guter Anfang, um Zutaten manuell den einzelnen Feldern zuzuordnen. Alternativ kannst du auch den \"Brute\" Prozessor benutzen, der eine Musterabgleich-Technik verwendet, um Zutaten zu identifizieren.",
|
||||||
"nlp": "NLP",
|
"nlp": "NLP",
|
||||||
"brute": "Brute",
|
"brute": "Brute",
|
||||||
"show-individual-confidence": "Zeige individuelle Überzeugungswerte an",
|
"show-individual-confidence": "Zeige individuelle Zuverlässigkeitswerte an",
|
||||||
"ingredient-text": "Zutaten-Angabe",
|
"ingredient-text": "Zutaten-Angabe",
|
||||||
"average-confident": "{0} überzeugt",
|
"average-confident": "{0} zuverlässig",
|
||||||
"try-an-example": "Probier ein Beispiel aus",
|
"try-an-example": "Probier ein Beispiel aus",
|
||||||
"parser": "Parser",
|
"parser": "Parser",
|
||||||
"background-tasks": "Hintergrundaufgaben",
|
"background-tasks": "Hintergrundaufgaben",
|
||||||
"background-tasks-description": "Hier kannst du alle laufenden Hintergrundaufgaben und ihren Status beobachten",
|
"background-tasks-description": "Hier kannst du alle laufenden Hintergrundaufgaben und ihren Status beobachten",
|
||||||
"no-logs-found": "Keine Protokolle gefunden",
|
"no-logs-found": "Keine Protokolle gefunden",
|
||||||
"tasks": "Aufgaben"
|
"tasks": "Aufgaben",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "Erstmalige Einrichtung",
|
||||||
|
"welcome-to-mealie-get-started": "Willkommen bei Mealie! Los geht's",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Willkommen, {0}",
|
"welcome-user": "👋 Willkommen, {0}!",
|
||||||
"description": "Verwalte dein Profil, Rezepte und Gruppeneinstellungen.",
|
"description": "Verwalte dein Profil, Rezepte und Gruppeneinstellungen.",
|
||||||
"get-invite-link": "Einladungslink erzeugen",
|
"get-invite-link": "Einladungslink erzeugen",
|
||||||
"get-public-link": "Öffentlichen Link abrufen",
|
"get-public-link": "Öffentlichen Link abrufen",
|
||||||
"account-summary": "Kontoübersicht",
|
"account-summary": "Kontoübersicht",
|
||||||
"account-summary-description": "Hier ist eine Übersicht der Informationen deiner Gruppe",
|
"account-summary-description": "Hier ist eine Zusammenfassung der Details deiner Gruppe.",
|
||||||
"group-statistics": "Gruppenstatistik",
|
"group-statistics": "Gruppenstatistik",
|
||||||
"group-statistics-description": "Deine Gruppenstatistik gibt dir einen Einblick, wie du Mealie verwendest.",
|
"group-statistics-description": "Deine Gruppenstatistik gibt dir einen Einblick, wie du Mealie verwendest.",
|
||||||
"storage-capacity": "Speicherplatz",
|
"storage-capacity": "Speicherplatz",
|
||||||
"storage-capacity-description": "Dein Speicherplatz wird anhand der Bilder und Anhänge berechnet, die du hochgeladen hast.",
|
"storage-capacity-description": "Dein Speicherplatz wird anhand der Bilder und Anhänge berechnet, die du hochgeladen hast.",
|
||||||
"personal": "Persönlich",
|
"personal": "Persönlich",
|
||||||
"personal-description": "Das sind deine persönlichen Einstellungen. Änderungen hier wirken sich nicht auf andere Benutzer aus",
|
"personal-description": "Das sind deine persönlichen Einstellungen. Änderungen hier wirken sich nicht auf andere Benutzer aus.",
|
||||||
"user-settings": "Benutzereinstellungen",
|
"user-settings": "Benutzereinstellungen",
|
||||||
"user-settings-description": "Verwalte deine Einstellungen, ändere dein Passwort und aktualisiere deine E-Mail-Adresse",
|
"user-settings-description": "Verwalte deine Einstellungen, ändere dein Passwort und aktualisiere deine E-Mail-Adresse.",
|
||||||
"api-tokens-description": "Verwalte deine API Token für den Zugriff von externen Anwendungen",
|
"api-tokens-description": "Verwalte deine API Token für den Zugriff von externen Anwendungen.",
|
||||||
"group-description": "Diese Elemente werden innerhalb deiner Gruppe geteilt. Änderungen wirken sich auf die gesamte Gruppe aus!",
|
"group-description": "Diese Elemente werden innerhalb deiner Gruppe geteilt. Änderungen wirken sich auf die gesamte Gruppe aus!",
|
||||||
"group-settings": "Gruppen-Einstellungen",
|
"group-settings": "Gruppen-Einstellungen",
|
||||||
"group-settings-description": "Verwalte deine allgemeinen Gruppeneinstellungen wie Essenspläne und Privatsphäre.",
|
"group-settings-description": "Verwalte deine allgemeinen Gruppeneinstellungen wie Essenspläne und Privatsphäre.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Benachrichtigungen",
|
"notifiers": "Benachrichtigungen",
|
||||||
"notifiers-description": "Richte E-Mail und Push-Benachrichtigungen ein, die bei bestimmten Ereignissen ausgelöst werden.",
|
"notifiers-description": "Richte E-Mail und Push-Benachrichtigungen ein, die bei bestimmten Ereignissen ausgelöst werden.",
|
||||||
"manage-data": "Daten verwalten",
|
"manage-data": "Daten verwalten",
|
||||||
"manage-data-description": "Verwalte deine Rezepte, Lebensmittel, Einheiten und Etiketten (weitere Optionen kommen bald)",
|
"manage-data-description": "Verwalte deine Mealie-Daten: Lebensmittel, Einheiten, Kategorien, Schlagworte und mehr.",
|
||||||
"data-migrations": "Datenmigration",
|
"data-migrations": "Datenmigration",
|
||||||
"data-migrations-description": "Migriere deine vorhandenen Daten aus anderen Anwendungen wie Nextcloud-Rezepte und Chowdown",
|
"data-migrations-description": "Migriere deine vorhandenen Daten aus anderen Anwendungen wie Nextcloud-Rezepte und Chowdown.",
|
||||||
"email-sent": "E-Mail gesendet",
|
"email-sent": "E-Mail gesendet",
|
||||||
"error-sending-email": "Fehler beim Senden der E-Mail",
|
"error-sending-email": "Fehler beim Senden der E-Mail",
|
||||||
"personal-information": "Persönliche Daten",
|
"personal-information": "Persönliche Daten",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"apprise-url": "URL Έκπληξης",
|
"apprise-url": "URL Έκπληξης",
|
||||||
"database": "Βάση δεδομένων",
|
"database": "Βάση δεδομένων",
|
||||||
"delete-event": "Διαγραφή γεγονότος",
|
"delete-event": "Διαγραφή γεγονότος",
|
||||||
"event-delete-confirmation": "Σίγουρα θέλετε να διαγράψετε αυτό το γεγονός;",
|
"event-delete-confirmation": "Θέλετε σίγουρα να διαγράψετε αυτό το γεγονός;",
|
||||||
"event-deleted": "Το γεγονός Διαγράφηκε",
|
"event-deleted": "Το γεγονός Διαγράφηκε",
|
||||||
"event-updated": "Το γεγονός ενημερώθηκε",
|
"event-updated": "Το γεγονός ενημερώθηκε",
|
||||||
"new-notification-form-description": "Η Mealie χρησιμοποιεί τη βιβλιοθήκη Apprise για τη δημιουργία ειδοποιήσεων. Προσφέρουν πολλές επιλογές για τις υπηρεσίες που θα χρησιμοποιηθούν για ειδοποιήσεις. Ανατρέξτε στο wiki τους για έναν ολοκληρωμένο οδηγό για το πώς να δημιουργήσετε τη διεύθυνση URL για την υπηρεσία σας. Αν είναι διαθέσιμο, η επιλογή του τύπου της ειδοποίησής σας μπορεί να περιλαμβάνει επιπλέον χαρακτηριστικά.",
|
"new-notification-form-description": "Η Mealie χρησιμοποιεί τη βιβλιοθήκη Apprise για τη δημιουργία ειδοποιήσεων. Προσφέρουν πολλές επιλογές για τις υπηρεσίες που θα χρησιμοποιηθούν για ειδοποιήσεις. Ανατρέξτε στο wiki τους για έναν ολοκληρωμένο οδηγό για το πώς να δημιουργήσετε τη διεύθυνση URL για την υπηρεσία σας. Αν είναι διαθέσιμο, η επιλογή του τύπου της ειδοποίησής σας μπορεί να περιλαμβάνει επιπλέον χαρακτηριστικά.",
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Κάτι δεν πήγε καλά!",
|
"something-went-wrong": "Κάτι δεν πήγε καλά!",
|
||||||
"subscribed-events": "Εγγεγραμμένα Γεγονότα",
|
"subscribed-events": "Εγγεγραμμένα Γεγονότα",
|
||||||
"test-message-sent": "Το δοκιμαστικό μήνυμα εστάλη",
|
"test-message-sent": "Το δοκιμαστικό μήνυμα εστάλη",
|
||||||
|
"message-sent": "Το μήνυμα εστάλη",
|
||||||
"new-notification": "Νέα ειδοποίηση",
|
"new-notification": "Νέα ειδοποίηση",
|
||||||
"event-notifiers": "Ειδοποιητές Συμβάντος",
|
"event-notifiers": "Ειδοποιητές Συμβάντος",
|
||||||
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
|
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
|
||||||
@@ -76,15 +77,17 @@
|
|||||||
"cookbook-events": "Cookbook Events",
|
"cookbook-events": "Cookbook Events",
|
||||||
"tag-events": "Tag Events",
|
"tag-events": "Tag Events",
|
||||||
"category-events": "Category Events",
|
"category-events": "Category Events",
|
||||||
"when-a-new-user-joins-your-group": "When a new user joins your group",
|
"when-a-new-user-joins-your-group": "Οταν ένας νέος χρήστης ενταχθεί στην ομάδα σας",
|
||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Προσθήκη",
|
||||||
"cancel": "Άκυρο",
|
"cancel": "Άκυρο",
|
||||||
"clear": "Εκκαθάριση",
|
"clear": "Εκκαθάριση",
|
||||||
"close": "Κλείσιμο",
|
"close": "Κλείσιμο",
|
||||||
"confirm": "Επιβεβαίωση",
|
"confirm": "Επιβεβαίωση",
|
||||||
"confirm-delete-generic": "Είστε βέβαιοι ότι θέλετε να γίνει διαγραφή;",
|
"confirm-how-does-everything-look": "Πώς φαίνονται τα πράγματα;",
|
||||||
|
"confirm-delete-generic": "Θέλετε σίγουρα να γίνει διαγραφή;",
|
||||||
"copied_message": "Αντιγράφηκε!",
|
"copied_message": "Αντιγράφηκε!",
|
||||||
"create": "Δημιουργία",
|
"create": "Δημιουργία",
|
||||||
"created": "Δημιουργήθηκε",
|
"created": "Δημιουργήθηκε",
|
||||||
@@ -114,10 +117,10 @@
|
|||||||
"json": "JSON",
|
"json": "JSON",
|
||||||
"keyword": "Λέξη-κλειδί",
|
"keyword": "Λέξη-κλειδί",
|
||||||
"link-copied": "Ο Σύνδεσμος Αντιγράφηκε",
|
"link-copied": "Ο Σύνδεσμος Αντιγράφηκε",
|
||||||
"loading": "Loading",
|
"loading": "Φόρτωση",
|
||||||
"loading-events": "Loading Events",
|
"loading-events": "Loading Events",
|
||||||
"loading-recipe": "Loading recipe...",
|
"loading-recipe": "Φόρτωση συνταγών...",
|
||||||
"loading-ocr-data": "Loading OCR data...",
|
"loading-ocr-data": "Φόρτωση δεδομένων OCR...",
|
||||||
"loading-recipes": "Φόρτωση Συνταγών",
|
"loading-recipes": "Φόρτωση Συνταγών",
|
||||||
"message": "Μήνυμα",
|
"message": "Μήνυμα",
|
||||||
"monday": "Δευτέρα",
|
"monday": "Δευτέρα",
|
||||||
@@ -128,7 +131,7 @@
|
|||||||
"no-recipe-found": "Δεν βρέθηκαν συνταγές",
|
"no-recipe-found": "Δεν βρέθηκαν συνταγές",
|
||||||
"ok": "ΟΚ",
|
"ok": "ΟΚ",
|
||||||
"options": "Επιλογές:",
|
"options": "Επιλογές:",
|
||||||
"plural-name": "Plural Name",
|
"plural-name": "Ονομα στον πληθυντικό",
|
||||||
"print": "Εκτύπωση",
|
"print": "Εκτύπωση",
|
||||||
"print-preferences": "Προτιμήσεις Εκτύπωσης",
|
"print-preferences": "Προτιμήσεις Εκτύπωσης",
|
||||||
"random": "Τυχαίο",
|
"random": "Τυχαίο",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Αποθήκευση",
|
"save": "Αποθήκευση",
|
||||||
"settings": "Ρυθμίσεις",
|
"settings": "Ρυθμίσεις",
|
||||||
"share": "Κοινοποίηση",
|
"share": "Κοινοποίηση",
|
||||||
|
"show-all": "Προβολή Ολων",
|
||||||
"shuffle": "Τυχαία",
|
"shuffle": "Τυχαία",
|
||||||
"sort": "Ταξινόμηση",
|
"sort": "Ταξινόμηση",
|
||||||
|
"sort-ascending": "Αύξουσα Ταξινόμηση",
|
||||||
|
"sort-descending": "Φθίνουσα Ταξινόμηση",
|
||||||
"sort-alphabetically": "Αλφαβητική",
|
"sort-alphabetically": "Αλφαβητική",
|
||||||
"status": "Κατάσταση",
|
"status": "Κατάσταση",
|
||||||
"subject": "Θέμα",
|
"subject": "Θέμα",
|
||||||
"submit": "Υποβολή",
|
"submit": "Υποβολή",
|
||||||
"success-count": "Επιτυχία: {count}",
|
"success-count": "Επιτυχία: {count}",
|
||||||
"sunday": "Κυριακή",
|
"sunday": "Κυριακή",
|
||||||
|
"system": "Σύστημα",
|
||||||
"templates": "Πρότυπα:",
|
"templates": "Πρότυπα:",
|
||||||
"test": "Δοκιμή",
|
"test": "Δοκιμή",
|
||||||
"themes": "Θέματα",
|
"themes": "Θέματα",
|
||||||
"thursday": "Τρίτη",
|
"thursday": "Τρίτη",
|
||||||
|
"title": "Τίτλος",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Τρίτη",
|
"tuesday": "Τρίτη",
|
||||||
"type": "Τύπος",
|
"type": "Τύπος",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Μονάδες",
|
"units": "Μονάδες",
|
||||||
"back": "Πίσω",
|
"back": "Πίσω",
|
||||||
"next": "Επόμενο",
|
"next": "Επόμενο",
|
||||||
|
"start": "Εναρξη",
|
||||||
"toggle-view": "Εναλλαγή προβολής",
|
"toggle-view": "Εναλλαγή προβολής",
|
||||||
"date": "Ημερομηνία",
|
"date": "Ημερομηνία",
|
||||||
"id": "Αναγνωριστικό",
|
"id": "Αναγνωριστικό",
|
||||||
@@ -178,19 +187,19 @@
|
|||||||
"menu": "Μενού",
|
"menu": "Μενού",
|
||||||
"a-name-is-required": "Απαιτείται όνομα",
|
"a-name-is-required": "Απαιτείται όνομα",
|
||||||
"delete-with-name": "Διαγραφή {name}",
|
"delete-with-name": "Διαγραφή {name}",
|
||||||
"confirm-delete-generic-with-name": "Σίγουρα θέλετε να διαγράψετε το εξής: {name};",
|
"confirm-delete-generic-with-name": "Θέλετε σίγουρα να διαγράψετε το εξής: {name};",
|
||||||
"confirm-delete-own-admin-account": "Παρακαλώ σημειώστε ότι προσπαθείτε να διαγράψετε το δικό σας λογαριασμό διαχειριστή! Αυτή η ενέργεια δεν μπορεί να αναιρεθεί και θα διαγράψει οριστικά τον λογαριασμό σας;",
|
"confirm-delete-own-admin-account": "Παρακαλώ σημειώστε ότι προσπαθείτε να διαγράψετε το δικό σας λογαριασμό διαχειριστή! Αυτή η ενέργεια δεν μπορεί να αναιρεθεί και θα διαγράψει οριστικά τον λογαριασμό σας;",
|
||||||
"organizer": "Μηχανισμός οργάνωσης",
|
"organizer": "Μηχανισμός οργάνωσης",
|
||||||
"transfer": "Μεταφορά",
|
"transfer": "Μεταφορά",
|
||||||
"copy": "Αντιγραφή",
|
"copy": "Αντιγραφή",
|
||||||
"color": "Χρώμα",
|
"color": "Χρώμα",
|
||||||
"timestamp": "Χρονική σήμανση",
|
"timestamp": "Χρονική σήμανση",
|
||||||
"last-made": "Τελευταία Δημιουργία",
|
"last-made": "Τελευταία παρασκευή",
|
||||||
"learn-more": "Μάθετε Περισσότερα",
|
"learn-more": "Μάθετε Περισσότερα",
|
||||||
"this-feature-is-currently-inactive": "Αυτή η λειτουργία είναι αυτή τη στιγμή απενεργοποιημένη",
|
"this-feature-is-currently-inactive": "Αυτή η λειτουργία είναι αυτή τη στιγμή απενεργοποιημένη",
|
||||||
"clipboard-not-supported": "Δεν υποστηρίζεται το πρόχειρο",
|
"clipboard-not-supported": "Δεν υποστηρίζεται το πρόχειρο",
|
||||||
"copied-to-clipboard": "Αντιγράφηκε στο πρόχειρο",
|
"copied-to-clipboard": "Αντιγράφηκε στο πρόχειρο",
|
||||||
"your-browser-does-not-support-clipboard": "Your browser does not support clipboard",
|
"your-browser-does-not-support-clipboard": "Το πρόγραμμα περιήγησής σας δεν υποστηρίζει πρόχειρο",
|
||||||
"copied-items-to-clipboard": "Κανένα στοιχείο δεν αντιγράφηκε στο πρόχειρο| Ένα στοιχείο αντιγράφηκε στο πρόχειρο|Αντιγράφηκαν {count} στοιχεία στο πρόχειρο",
|
"copied-items-to-clipboard": "Κανένα στοιχείο δεν αντιγράφηκε στο πρόχειρο| Ένα στοιχείο αντιγράφηκε στο πρόχειρο|Αντιγράφηκαν {count} στοιχεία στο πρόχειρο",
|
||||||
"actions": "Ενέργειες",
|
"actions": "Ενέργειες",
|
||||||
"selected-count": "Επιλεγμένα: {count}",
|
"selected-count": "Επιλεγμένα: {count}",
|
||||||
@@ -198,12 +207,13 @@
|
|||||||
"refresh": "Ανανέωση",
|
"refresh": "Ανανέωση",
|
||||||
"upload-file": "Μεταφόρτωση αρχείου",
|
"upload-file": "Μεταφόρτωση αρχείου",
|
||||||
"created-on-date": "Δημιουργήθηκε στις: {0}",
|
"created-on-date": "Δημιουργήθηκε στις: {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "Εχετε μη αποθηκευμένες αλλαγές. Θέλετε να κάνετε αποθήκευση πριν από την αποχώρηση; Εντάξει για αποθήκευση, Ακυρο για απόρριψη των αλλαγών.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
"clipboard-copy-failure": "Η αντιγραφή στο πρόχειρο απέτυχε.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Θέλετε σίγουρα να διαγράψετε τα ακόλουθα αντικείμενα;",
|
||||||
|
"organizers": "Οργανωτές"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό τον ασφαλή σύνδεσμο <b>{groupName}<b/>;",
|
"are-you-sure-you-want-to-delete-the-group": "Θέλετε σίγουρα να διαγράψετε αυτό τον ασφαλή σύνδεσμο <b>{groupName}<b/>;",
|
||||||
"cannot-delete-default-group": "Δεν μπορείς να διαγράψεις την προεπιλεγμένη ομάδα",
|
"cannot-delete-default-group": "Δεν μπορείς να διαγράψεις την προεπιλεγμένη ομάδα",
|
||||||
"cannot-delete-group-with-users": "Δεν είναι δυνατή η διαγραφή ομάδας με χρήστες",
|
"cannot-delete-group-with-users": "Δεν είναι δυνατή η διαγραφή ομάδας με χρήστες",
|
||||||
"confirm-group-deletion": "Επιβεβαίωση Διαγραφής Ομάδας",
|
"confirm-group-deletion": "Επιβεβαίωση Διαγραφής Ομάδας",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Προτιμήσεις Ομάδας",
|
"group-preferences": "Προτιμήσεις Ομάδας",
|
||||||
"private-group": "Ιδιωτική Ομάδα",
|
"private-group": "Ιδιωτική Ομάδα",
|
||||||
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
||||||
|
"enable-public-access": "Ενεργοποίηση Δημόσιας Πρόσβασης",
|
||||||
|
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
||||||
"show-nutrition-information": "Εμφάνιση διατροφικών πληροφοριών",
|
"show-nutrition-information": "Εμφάνιση διατροφικών πληροφοριών",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Recipe Data Migrations",
|
"recipe-data-migrations": "Recipe Data Migrations",
|
||||||
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.",
|
||||||
"choose-migration-type": "Choose Migration Type",
|
"choose-migration-type": "Choose Migration Type",
|
||||||
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
||||||
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
||||||
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below",
|
"chowdown-text": "Το Mealie υποστηρίζει εγγενώς τη μορφή αποθετηρίου. Κατεβάστε το αποθετήριο κώδικα ως αρχείο .zip και ανεβάστε το παρακάτω.",
|
||||||
"recipe-1": "Recipe 1",
|
"recipe-1": "Recipe 1",
|
||||||
"recipe-2": "Recipe 2",
|
"recipe-2": "Recipe 2",
|
||||||
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
||||||
@@ -360,12 +373,16 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie can import recipies from Plan to Eat."
|
"description-long": "Mealie can import recipies from Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "Κιβώτιο Συνταγών",
|
||||||
|
"description-long": "Το Mealie μπορεί να εισάγει συνταγές από το Κουτί Συνταγών. Εξάγετε τις συνταγές σας σε μορφή CSV, στη συνέχεια, ανεβάστε το αρχείο .csv παρακάτω."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
"bulk-add": "Μαζική Προσθήκη",
|
"bulk-add": "Μαζική Προσθήκη",
|
||||||
"error-details": "Μόνο ιστοσελίδες που περιέχουν ld+json ή μικροδεδομένα μπορούν να εισαχθούν από την Mealie. Οι πιο σημαντικές ιστοσελίδες συνταγών υποστηρίζουν αυτή τη δομή δεδομένων. Αν το site σας δεν μπορεί να εισαχθεί, αλλά υπάρχουν δεδομένα json στο αρχείο καταγραφής, παρακαλούμε να υποβάλετε ένα github πρόβλημα με το URL και τα δεδομένα.",
|
"error-details": "Μόνο ιστοσελίδες που περιέχουν ld+json ή μικροδεδομένα μπορούν να εισαχθούν από την Mealie. Οι πιο σημαντικές ιστοσελίδες συνταγών υποστηρίζουν αυτή τη δομή δεδομένων. Αν το site σας δεν μπορεί να εισαχθεί, αλλά υπάρχουν δεδομένα json στο αρχείο καταγραφής, παρακαλούμε να υποβάλετε ένα github πρόβλημα με το URL και τα δεδομένα.",
|
||||||
"error-title": "Φαίνεται Όπως Δεν Μπορούσαμε Να βρούμε Οτιδήποτε",
|
"error-title": "Φαίνεται πως δεν μπορέσαμε να βρούμε τίποτα",
|
||||||
"from-url": "Εισαγωγή συνταγής",
|
"from-url": "Εισαγωγή συνταγής",
|
||||||
"github-issues": "Σφάλματα GitHub",
|
"github-issues": "Σφάλματα GitHub",
|
||||||
"google-ld-json-info": "Google ld+json Info",
|
"google-ld-json-info": "Google ld+json Info",
|
||||||
@@ -377,9 +394,9 @@
|
|||||||
"upload-individual-zip-file": "Ανεβάστε ένα μεμονωμένο αρχείο .zip που εξάγεται από μια άλλη περίπτωση Mealie.",
|
"upload-individual-zip-file": "Ανεβάστε ένα μεμονωμένο αρχείο .zip που εξάγεται από μια άλλη περίπτωση Mealie.",
|
||||||
"url-form-hint": "Αντιγράψτε και επικολλήστε έναν σύνδεσμο από την αγαπημένη σας ιστοσελίδα συνταγών",
|
"url-form-hint": "Αντιγράψτε και επικολλήστε έναν σύνδεσμο από την αγαπημένη σας ιστοσελίδα συνταγών",
|
||||||
"view-scraped-data": "Προβολή Παραγόμενων Δεδομένων",
|
"view-scraped-data": "Προβολή Παραγόμενων Δεδομένων",
|
||||||
"trim-whitespace-description": "Περικοπή αιχμής και διαδρομής κενών καθώς και κενών γραμμών",
|
"trim-whitespace-description": "Περικοπή κενών στην αρχή και το τέλος καθώς και των κενών γραμμών",
|
||||||
"trim-prefix-description": "Περικοπή πρώτου χαρακτήρα από κάθε γραμμή",
|
"trim-prefix-description": "Περικοπή πρώτου χαρακτήρα από κάθε γραμμή",
|
||||||
"split-by-numbered-line-description": "Προσπάθεια για χωρισμό μιας παραγράφου ταιριάζοντας μοτίβα '1)' ή '1'",
|
"split-by-numbered-line-description": "Προσπάθεια για χωρισμό μιας παραγράφου ταιριάζοντας μοτίβα '1)' ή '1.'",
|
||||||
"import-by-url": "Import a recipe by URL",
|
"import-by-url": "Import a recipe by URL",
|
||||||
"create-manually": "Create a recipe manually",
|
"create-manually": "Create a recipe manually",
|
||||||
"make-recipe-image": "Make this the recipe image"
|
"make-recipe-image": "Make this the recipe image"
|
||||||
@@ -401,7 +418,7 @@
|
|||||||
},
|
},
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"add-key": "Προσθήκη Κλειδιού",
|
"add-key": "Προσθήκη Κλειδιού",
|
||||||
"add-to-favorites": "Προσθήκη στα αγαπημένα",
|
"add-to-favorites": "Προσθήκη στα Aγαπημένα",
|
||||||
"api-extras": "API Extras",
|
"api-extras": "API Extras",
|
||||||
"calories": "Θερμίδες",
|
"calories": "Θερμίδες",
|
||||||
"calories-suffix": "θερμίδες",
|
"calories-suffix": "θερμίδες",
|
||||||
@@ -410,7 +427,7 @@
|
|||||||
"comment-action": "Σχόλιο",
|
"comment-action": "Σχόλιο",
|
||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"comments": "Σχόλια",
|
"comments": "Σχόλια",
|
||||||
"delete-confirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη συνταγή;",
|
"delete-confirmation": "Θέλετε σίγουρα να διαγράψετε αυτή τη συνταγή;",
|
||||||
"delete-recipe": "Διαγραφή Συνταγής",
|
"delete-recipe": "Διαγραφή Συνταγής",
|
||||||
"description": "Περιγραφή",
|
"description": "Περιγραφή",
|
||||||
"disable-amount": "Απενεργοποίηση Ποσών Συστατικών",
|
"disable-amount": "Απενεργοποίηση Ποσών Συστατικών",
|
||||||
@@ -435,7 +452,7 @@
|
|||||||
"nutrition": "Διατροφή",
|
"nutrition": "Διατροφή",
|
||||||
"object-key": "Κλειδί Αντικειμένου",
|
"object-key": "Κλειδί Αντικειμένου",
|
||||||
"object-value": "Τιμή αντικειμένου",
|
"object-value": "Τιμή αντικειμένου",
|
||||||
"original-url": "Αρχική URL",
|
"original-url": "Αρχική διεύθυνση URL",
|
||||||
"perform-time": "Χρόνος Μαγειρέματος",
|
"perform-time": "Χρόνος Μαγειρέματος",
|
||||||
"prep-time": "Χρόνος Προετοιμασίας",
|
"prep-time": "Χρόνος Προετοιμασίας",
|
||||||
"protein-content": "Πρωτεΐνες",
|
"protein-content": "Πρωτεΐνες",
|
||||||
@@ -471,7 +488,7 @@
|
|||||||
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD format",
|
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD format",
|
||||||
"add-to-list": "Add to List",
|
"add-to-list": "Add to List",
|
||||||
"add-to-plan": "Add to Plan",
|
"add-to-plan": "Add to Plan",
|
||||||
"add-to-timeline": "Add to Timeline",
|
"add-to-timeline": "Προσθήκη στο χρονοδιάγραμμα",
|
||||||
"recipe-added-to-list": "Recipe added to list",
|
"recipe-added-to-list": "Recipe added to list",
|
||||||
"recipes-added-to-list": "Recipes added to list",
|
"recipes-added-to-list": "Recipes added to list",
|
||||||
"successfully-added-to-list": "Successfully added to list",
|
"successfully-added-to-list": "Successfully added to list",
|
||||||
@@ -482,7 +499,7 @@
|
|||||||
"yield": "Yield",
|
"yield": "Yield",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
"choose-unit": "Choose Unit",
|
"choose-unit": "Choose Unit",
|
||||||
"press-enter-to-create": "Press Enter to Create",
|
"press-enter-to-create": "Πατήστε Enter για δημιουργία",
|
||||||
"choose-food": "Choose Food",
|
"choose-food": "Choose Food",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
"toggle-section": "Toggle Section",
|
"toggle-section": "Toggle Section",
|
||||||
@@ -508,29 +525,32 @@
|
|||||||
"resume-timer": "Resume Timer",
|
"resume-timer": "Resume Timer",
|
||||||
"stop-timer": "Stop Timer"
|
"stop-timer": "Stop Timer"
|
||||||
},
|
},
|
||||||
"edit-timeline-event": "Edit Timeline Event",
|
"edit-timeline-event": "Επεξεργασία συμβάντος χρονοδιαγράμματος",
|
||||||
"timeline": "Timeline",
|
"timeline": "Χρονοδιάγραμμα",
|
||||||
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
"timeline-is-empty": "Δεν υπάρχει τίποτα ακόμα στο χρονοδιάγραμμα. Δοκιμάστε να κάνετε αυτή τη συνταγή!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Global Timeline",
|
"group-global-timeline": "{groupName} Global Timeline",
|
||||||
"open-timeline": "Open Timeline",
|
"open-timeline": "Ανοιγμα χρονοδιαγράμματος",
|
||||||
"made-this": "I Made This",
|
"made-this": "Το έφτιαξα",
|
||||||
"how-did-it-turn-out": "How did it turn out?",
|
"how-did-it-turn-out": "How did it turn out?",
|
||||||
"user-made-this": "{user} made this",
|
"user-made-this": "Ο/η {user} το έφτιαξε αυτό",
|
||||||
"last-made-date": "Last Made {date}",
|
"last-made-date": "Τελευταία παρασκευή {date}",
|
||||||
"api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom JSON key/value pairs within a recipe, to reference from 3rd party applications. You can use these keys to provide information, for example to trigger automations or custom messages to relay to your desired device.",
|
"api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom JSON key/value pairs within a recipe, to reference from 3rd party applications. You can use these keys to provide information, for example to trigger automations or custom messages to relay to your desired device.",
|
||||||
"message-key": "Message Key",
|
"message-key": "Message Key",
|
||||||
"parse": "Parse",
|
"parse": "Ανάλυση",
|
||||||
"attach-images-hint": "Attach images by dragging & dropping them into the editor",
|
"attach-images-hint": "Attach images by dragging & dropping them into the editor",
|
||||||
"drop-image": "Drop image",
|
"drop-image": "Drop image",
|
||||||
"enable-ingredient-amounts-to-use-this-feature": "Enable ingredient amounts to use this feature",
|
"enable-ingredient-amounts-to-use-this-feature": "Enable ingredient amounts to use this feature",
|
||||||
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Recipes with units or foods defined cannot be parsed.",
|
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Δεν είναι δυνατή η ανάλυση συνταγών με καθορισμένες μονάδες ή φαγητά.",
|
||||||
"parse-ingredients": "Parse ingredients",
|
"parse-ingredients": "Ανάλυση συστατικών",
|
||||||
"edit-markdown": "Edit Markdown",
|
"edit-markdown": "Edit Markdown",
|
||||||
"recipe-creation": "Recipe Creation",
|
"recipe-creation": "Recipe Creation",
|
||||||
"select-one-of-the-various-ways-to-create-a-recipe": "Select one of the various ways to create a recipe",
|
"select-one-of-the-various-ways-to-create-a-recipe": "Select one of the various ways to create a recipe",
|
||||||
"looking-for-migrations": "Looking For Migrations?",
|
"looking-for-migrations": "Looking For Migrations?",
|
||||||
"import-with-url": "Import with URL",
|
"import-with-url": "Import with URL",
|
||||||
"create-recipe": "Create Recipe",
|
"create-recipe": "Create Recipe",
|
||||||
|
"create-recipe-description": "Create a new recipe from scratch.",
|
||||||
|
"create-recipes": "Create Recipes",
|
||||||
"import-with-zip": "Import with .zip",
|
"import-with-zip": "Import with .zip",
|
||||||
"create-recipe-from-an-image": "Create recipe from an image",
|
"create-recipe-from-an-image": "Create recipe from an image",
|
||||||
"bulk-url-import": "Bulk URL Import",
|
"bulk-url-import": "Bulk URL Import",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Upload image",
|
"upload-image": "Upload image",
|
||||||
"screen-awake": "Keep Screen Awake",
|
"screen-awake": "Keep Screen Awake",
|
||||||
"remove-image": "Remove image",
|
"remove-image": "Remove image",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Next step",
|
||||||
|
"recipe-actions": "Ενέργειες Συνταγής",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Το Mealie χρησιμοποιεί επεξεργασία φυσικής γλώσσας για να αναλύσει και να δημιουργήσει μονάδες και είδη διατροφής για τα συστατικά της συνταγής σας. Αυτή η λειτουργία είναι πειραματική και μπορεί να μην λειτουργεί πάντα όπως πρέπει. Αν προτιμάτε να μην χρησιμοποιείτε τα αναλυμένα αποτελέσματα, μπορείτε να επιλέξετε 'Ακύρωση' και οι αλλαγές σας δεν θα αποθηκευτούν.",
|
||||||
|
"ingredient-parser": "Αναλυτής Συστατικών",
|
||||||
|
"explanation": "Για να χρησιμοποιήσετε τον αναλυτή συστατικών, κάντε κλικ στο πλήκτρο 'Ανάλυση Ολων' για να ξεκινήσετε τη διαδικασία. Μόλις τα αναλυμένα συστατικά είναι διαθέσιμα, μπορείτε να τα επανεξετάσετε και να βεβαιωθείτε ότι έχουν αναλυθεί σωστά. Η βαθμολογία εμπιστοσύνης του μοντέλου εμφανίζεται στα δεξιά του τίτλου αντικειμένου. Αυτό το σκορ είναι ένας μέσος όρος όλων των επιμέρους βαθμολογιών και μπορεί να μην είναι πάντα εντελώς ακριβής.",
|
||||||
|
"alerts-explainer": "Θα εμφανίζονται ειδοποιήσεις αν βρεθεί ένα αντίστοιχο φαγητό ή μονάδα αλλά δεν υπάρχει στη βάση δεδομένων.",
|
||||||
|
"select-parser": "Επιλέξτε Αναλυτή",
|
||||||
|
"natural-language-processor": "Επεξεργαστής Φυσικής Γλώσσας",
|
||||||
|
"brute-parser": "Αναλυτής Ωμής Βίας",
|
||||||
|
"parse-all": "Ανάλυση Ολων",
|
||||||
|
"no-unit": "Καμία μονάδα",
|
||||||
|
"missing-unit": "Δημιουργία μονάδας που λείπει: {unit}",
|
||||||
|
"missing-food": "Δημιουργία φαγητού που λείπει: {food}",
|
||||||
|
"no-food": "Χωρίς Φαγητό"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Σύνθετη Αναζήτηση",
|
"advanced-search": "Σύνθετη Αναζήτηση",
|
||||||
@@ -577,11 +612,11 @@
|
|||||||
"has-all": "Has All",
|
"has-all": "Has All",
|
||||||
"results": "Αποτελέσματα",
|
"results": "Αποτελέσματα",
|
||||||
"search": "Αναζήτηση",
|
"search": "Αναζήτηση",
|
||||||
"search-mealie": "Αναζήτηση Mealie (πατήστε /)",
|
"search-mealie": "Αναζήτηση στο Mealie (πατήστε /)",
|
||||||
"search-placeholder": "Αναζήτηση...",
|
"search-placeholder": "Αναζήτηση...",
|
||||||
"tag-filter": "Φίλτρο Ετικέτας",
|
"tag-filter": "Φίλτρο Ετικέτας",
|
||||||
"search-hint": "Press '/'",
|
"search-hint": "Πατήστε '/'",
|
||||||
"advanced": "Advanced",
|
"advanced": "Για προχωρημένους",
|
||||||
"auto-search": "Auto Search",
|
"auto-search": "Auto Search",
|
||||||
"no-results": "No results found"
|
"no-results": "No results found"
|
||||||
},
|
},
|
||||||
@@ -590,14 +625,14 @@
|
|||||||
"admin-settings": "Ρυθμίσεις Διαχειριστή",
|
"admin-settings": "Ρυθμίσεις Διαχειριστή",
|
||||||
"backup": {
|
"backup": {
|
||||||
"backup-created": "Backup created successfully",
|
"backup-created": "Backup created successfully",
|
||||||
"backup-created-at-response-export_path": "Αντίγραφο ασφαλείας αποθηκεύτηκαν στο: {path}",
|
"backup-created-at-response-export_path": "Δημιουργήθηκε αντίγραφο ασφαλείας στο: {path}",
|
||||||
"backup-deleted": "Το Αντίγραφο διαγράφηκε",
|
"backup-deleted": "Το αντίγραφο ασφαλείας διαγράφηκε",
|
||||||
"restore-success": "Restore successful",
|
"restore-success": "Restore successful",
|
||||||
"backup-tag": "Αντίγραφο Ετικέτας",
|
"backup-tag": "Ετικέτα Αντιγράφου Ασφαλείας",
|
||||||
"create-heading": "Δημιουργία αντιγράφου ασφαλείας",
|
"create-heading": "Δημιουργία αντιγράφου ασφαλείας",
|
||||||
"delete-backup": "Διαγραφή Αντιγράφου Ασφαλείας",
|
"delete-backup": "Διαγραφή Αντιγράφου Ασφαλείας",
|
||||||
"error-creating-backup-see-log-file": "Σφάλμα Δημιουργίας Αντιγράφου Ασφαλείας. Δείτε Το Αρχείο Καταγραφής",
|
"error-creating-backup-see-log-file": "Σφάλμα Δημιουργίας Αντιγράφου Ασφαλείας. Δείτε Το Αρχείο Καταγραφής",
|
||||||
"full-backup": "Πλήρες αντίγραφο ασφαλείας",
|
"full-backup": "Πλήρης Αντίγραφο Ασφαλείας",
|
||||||
"import-summary": "Εισαγωγή Περίληψης",
|
"import-summary": "Εισαγωγή Περίληψης",
|
||||||
"partial-backup": "Μερικό Αντίγραφο Ασφαλείας",
|
"partial-backup": "Μερικό Αντίγραφο Ασφαλείας",
|
||||||
"unable-to-delete-backup": "Αδυναμία διαγραφής αντιγράφου ασφαλείας.",
|
"unable-to-delete-backup": "Αδυναμία διαγραφής αντιγράφου ασφαλείας.",
|
||||||
@@ -643,14 +678,14 @@
|
|||||||
"site-settings": "Ρυθμίσεις Ιστοσελίδας",
|
"site-settings": "Ρυθμίσεις Ιστοσελίδας",
|
||||||
"theme": {
|
"theme": {
|
||||||
"accent": "Έμφαση",
|
"accent": "Έμφαση",
|
||||||
"dark": "Σκούρο",
|
"dark": "Σκοτεινό",
|
||||||
"default-to-system": "Προεπιλογή στο σύστημα",
|
"default-to-system": "Προεπιλογή στο σύστημα",
|
||||||
"error": "Σφάλμα",
|
"error": "Σφάλμα",
|
||||||
"error-creating-theme-see-log-file": "Σφάλμα δημιουργίας θέματος. Δείτε το αρχείο καταγραφής.",
|
"error-creating-theme-see-log-file": "Σφάλμα δημιουργίας θέματος. Δείτε το αρχείο καταγραφής.",
|
||||||
"error-deleting-theme": "Σφάλμα διαγραφής θέματος",
|
"error-deleting-theme": "Σφάλμα διαγραφής θέματος",
|
||||||
"error-updating-theme": "Σφάλμα ενημέρωσης θέματος",
|
"error-updating-theme": "Σφάλμα ενημέρωσης θέματος",
|
||||||
"info": "Πληροφορίες",
|
"info": "Πληροφορίες",
|
||||||
"light": "Ελαφρύ",
|
"light": "Φωτεινό",
|
||||||
"primary": "Πρωτεύον",
|
"primary": "Πρωτεύον",
|
||||||
"secondary": "Δευτερεύων",
|
"secondary": "Δευτερεύων",
|
||||||
"success": "Επιτυχής",
|
"success": "Επιτυχής",
|
||||||
@@ -662,8 +697,8 @@
|
|||||||
"theme-saved": "Το Θέμα Αποθηκεύτηκε",
|
"theme-saved": "Το Θέμα Αποθηκεύτηκε",
|
||||||
"theme-updated": "Το θέμα ενημερώθηκε",
|
"theme-updated": "Το θέμα ενημερώθηκε",
|
||||||
"warning": "Προσοχή",
|
"warning": "Προσοχή",
|
||||||
"light-mode": "Light Mode",
|
"light-mode": "Φωτεινή Λειτουργία",
|
||||||
"dark-mode": "Dark Mode"
|
"dark-mode": "Σκοτεινή Λειτουργία"
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"active-tokens": "ΕΝΕΡΓΑ TOKENS",
|
"active-tokens": "ΕΝΕΡΓΑ TOKENS",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
||||||
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Recipe Scraper Version"
|
"recipe-scraper-version": "Recipe Scraper Version",
|
||||||
|
"oidc-ready": "OIDC Ready",
|
||||||
|
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
|
||||||
|
"oidc-ready-success-text": "Required OIDC variables are all set."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "Όλες οι λίστες",
|
"all-lists": "Όλες οι λίστες",
|
||||||
@@ -773,7 +811,7 @@
|
|||||||
"language": "Γλώσσα",
|
"language": "Γλώσσα",
|
||||||
"maintenance": "Maintenance",
|
"maintenance": "Maintenance",
|
||||||
"background-tasks": "Background Tasks",
|
"background-tasks": "Background Tasks",
|
||||||
"parser": "Parser",
|
"parser": "Αναλυτής",
|
||||||
"developer": "Developer",
|
"developer": "Developer",
|
||||||
"cookbook": "Cookbook",
|
"cookbook": "Cookbook",
|
||||||
"create-cookbook": "Create a new cookbook"
|
"create-cookbook": "Create a new cookbook"
|
||||||
@@ -803,18 +841,18 @@
|
|||||||
},
|
},
|
||||||
"tool": {
|
"tool": {
|
||||||
"tools": "Εργαλεία",
|
"tools": "Εργαλεία",
|
||||||
"on-hand": "On Hand",
|
"on-hand": "Το έχω στο χέρι",
|
||||||
"create-a-tool": "Create a Tool",
|
"create-a-tool": "Create a Tool",
|
||||||
"tool-name": "Tool Name",
|
"tool-name": "Tool Name",
|
||||||
"create-new-tool": "Create New Tool",
|
"create-new-tool": "Create New Tool",
|
||||||
"on-hand-checkbox-label": "Show as On Hand (Checked)",
|
"on-hand-checkbox-label": "Εμφάνιση ως Στο Χέρι (επιλεγμένο)",
|
||||||
"required-tools": "Required Tools",
|
"required-tools": "Απαιτούμενα Εργαλεία",
|
||||||
"tool": "Tool"
|
"tool": "Tool"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"admin": "Διαχειριστής",
|
"admin": "Διαχειριστής",
|
||||||
"are-you-sure-you-want-to-delete-the-link": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό τον ασφαλή σύνδεσμο <b>{link}<b/>;",
|
"are-you-sure-you-want-to-delete-the-link": "Θέλετε σίγουρα να διαγράψετε αυτό τον ασφαλή σύνδεσμο <b>{link}<b/>;",
|
||||||
"are-you-sure-you-want-to-delete-the-user": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον χρήστη <b>{activeName} ID: {activeId}<b/>;",
|
"are-you-sure-you-want-to-delete-the-user": "Θέλετε σίγουρα να διαγράψετε τον χρήστη <b>{activeName} ID: {activeId}<b/>;",
|
||||||
"auth-method": "Auth Method",
|
"auth-method": "Auth Method",
|
||||||
"confirm-link-deletion": "Επιβεβαίωση διαγραφής",
|
"confirm-link-deletion": "Επιβεβαίωση διαγραφής",
|
||||||
"confirm-password": "Επιβεβαίωση Κωδικού",
|
"confirm-password": "Επιβεβαίωση Κωδικού",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Σύνδεσμος ID",
|
"link-id": "Σύνδεσμος ID",
|
||||||
"link-name": "Όνομα συνδέσμου",
|
"link-name": "Όνομα συνδέσμου",
|
||||||
"login": "Σύνδεση",
|
"login": "Σύνδεση",
|
||||||
|
"login-oidc": "Login with",
|
||||||
|
"or": "ή",
|
||||||
"logout": "Αποσύνδεση",
|
"logout": "Αποσύνδεση",
|
||||||
"manage-users": "Διαχείριση χρηστών",
|
"manage-users": "Διαχείριση χρηστών",
|
||||||
|
"manage-users-description": "Create and manage users.",
|
||||||
"new-password": "Νέος Κωδικός",
|
"new-password": "Νέος Κωδικός",
|
||||||
"new-user": "Νέος χρήστης",
|
"new-user": "Νέος χρήστης",
|
||||||
"password-has-been-reset-to-the-default-password": "Ο κωδικός πρόσβασης έχει επαναφερθεί στον προεπιλεγμένο κωδικό",
|
"password-has-been-reset-to-the-default-password": "Ο κωδικός πρόσβασης έχει επαναφερθεί στον προεπιλεγμένο κωδικό",
|
||||||
@@ -844,7 +885,7 @@
|
|||||||
"password-updated": "Ο κωδικός πρόσβασης ενημερώθηκε",
|
"password-updated": "Ο κωδικός πρόσβασης ενημερώθηκε",
|
||||||
"password": "Κωδικός",
|
"password": "Κωδικός",
|
||||||
"password-strength": "Ο κωδικός είναι {strength}",
|
"password-strength": "Ο κωδικός είναι {strength}",
|
||||||
"please-enter-password": "Please enter your new password.",
|
"please-enter-password": "Παρακαλούμε εισάγετε το νέο κωδικό πρόσβασης.",
|
||||||
"register": "Εγγραφή",
|
"register": "Εγγραφή",
|
||||||
"reset-password": "Επαναφορά Κωδικού",
|
"reset-password": "Επαναφορά Κωδικού",
|
||||||
"sign-in": "Είσοδος",
|
"sign-in": "Είσοδος",
|
||||||
@@ -872,18 +913,18 @@
|
|||||||
"you-are-not-allowed-to-delete-this-user": "Δεν επιτρέπεται να διαγράψετε αυτόν τον χρήστη",
|
"you-are-not-allowed-to-delete-this-user": "Δεν επιτρέπεται να διαγράψετε αυτόν τον χρήστη",
|
||||||
"enable-advanced-content": "Ενεργοποίηση Προηγμένου Περιεχομένου",
|
"enable-advanced-content": "Ενεργοποίηση Προηγμένου Περιεχομένου",
|
||||||
"enable-advanced-content-description": "Ενεργοποιεί προηγμένες λειτουργίες όπως κλιμάκωση συνταγής, κλειδιά API, Webhooks και διαχείριση δεδομένων. Μην ανησυχείτε, μπορείτε πάντα να το αλλάξετε αργότερα",
|
"enable-advanced-content-description": "Ενεργοποιεί προηγμένες λειτουργίες όπως κλιμάκωση συνταγής, κλειδιά API, Webhooks και διαχείριση δεδομένων. Μην ανησυχείτε, μπορείτε πάντα να το αλλάξετε αργότερα",
|
||||||
"favorite-recipes": "Favorite Recipes",
|
"favorite-recipes": "Αγαπημένες Συνταγές",
|
||||||
"email-or-username": "Email or Username",
|
"email-or-username": "Email or Username",
|
||||||
"remember-me": "Remember Me",
|
"remember-me": "Remember Me",
|
||||||
"please-enter-your-email-and-password": "Please enter your email and password",
|
"please-enter-your-email-and-password": "Please enter your email and password",
|
||||||
"invalid-credentials": "Invalid Credentials",
|
"invalid-credentials": "Invalid Credentials",
|
||||||
"account-locked-please-try-again-later": "Account Locked. Please try again later",
|
"account-locked-please-try-again-later": "Account Locked. Please try again later",
|
||||||
"user-favorites": "User Favorites",
|
"user-favorites": "Αγαπημένα Χρήστη",
|
||||||
"password-strength-values": {
|
"password-strength-values": {
|
||||||
"weak": "Weak",
|
"weak": "Αδύναμος",
|
||||||
"good": "Good",
|
"good": "Καλός",
|
||||||
"strong": "Strong",
|
"strong": "Ισχυρός",
|
||||||
"very-strong": "Very Strong"
|
"very-strong": "Πολύ Ισχυρός"
|
||||||
},
|
},
|
||||||
"user-management": "User Management",
|
"user-management": "User Management",
|
||||||
"reset-locked-users": "Reset Locked Users",
|
"reset-locked-users": "Reset Locked Users",
|
||||||
@@ -892,13 +933,13 @@
|
|||||||
"user-details": "User Details",
|
"user-details": "User Details",
|
||||||
"user-name": "User Name",
|
"user-name": "User Name",
|
||||||
"authentication-method": "Authentication Method",
|
"authentication-method": "Authentication Method",
|
||||||
"authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie",
|
"authentication-method-hint": "Αυτό καθορίζει τον τρόπο με τον οποίο ένας χρήστης θα ταυτοποιηθεί με το Mealie. Αν δεν είστε σίγουροι, επιλέξτε 'Mealie'",
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"user-can-invite-other-to-group": "User can invite other to group",
|
"user-can-invite-other-to-group": "User can invite other to group",
|
||||||
"user-can-manage-group": "User can manage group",
|
"user-can-manage-group": "User can manage group",
|
||||||
"user-can-organize-group-data": "User can organize group data",
|
"user-can-organize-group-data": "User can organize group data",
|
||||||
"enable-advanced-features": "Enable advanced features",
|
"enable-advanced-features": "Ενεργοποίηση χαρακτηριστικών για προχωρημένους",
|
||||||
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.",
|
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.",
|
||||||
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!",
|
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!",
|
||||||
"forgot-password": "Forgot Password",
|
"forgot-password": "Forgot Password",
|
||||||
@@ -945,8 +986,8 @@
|
|||||||
"unit-data": "Unit Data",
|
"unit-data": "Unit Data",
|
||||||
"use-abbv": "Use Abbv.",
|
"use-abbv": "Use Abbv.",
|
||||||
"fraction": "Fraction",
|
"fraction": "Fraction",
|
||||||
"example-unit-singular": "ex: Tablespoon",
|
"example-unit-singular": "π.χ.: Κουταλιά της σούπας",
|
||||||
"example-unit-plural": "ex: Tablespoons",
|
"example-unit-plural": "π.χ.: Κουταλιές της σούπας",
|
||||||
"example-unit-abbreviation-singular": "ex: Tbsp",
|
"example-unit-abbreviation-singular": "ex: Tbsp",
|
||||||
"example-unit-abbreviation-plural": "ex: Tbsps"
|
"example-unit-abbreviation-plural": "ex: Tbsps"
|
||||||
},
|
},
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Delete Recipes",
|
"delete-recipes": "Delete Recipes",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Δεδομένα Ενεργειών Συνταγής",
|
||||||
|
"new-recipe-action": "Νέα Ενέργεια Συνταγής",
|
||||||
|
"edit-recipe-action": "Επεξεργασία Ενέργειας Συνταγής",
|
||||||
|
"action-type": "Τύπος Ενέργειας"
|
||||||
|
},
|
||||||
"create-alias": "Create Alias",
|
"create-alias": "Create Alias",
|
||||||
"manage-aliases": "Manage Aliases",
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Seed Data",
|
"seed-data": "Seed Data",
|
||||||
@@ -1004,7 +1051,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user-registration": {
|
"user-registration": {
|
||||||
"user-registration": "User Registration",
|
"user-registration": "Εγγραφή χρήστη",
|
||||||
"registration-success": "Registration Success",
|
"registration-success": "Registration Success",
|
||||||
"join-a-group": "Join a Group",
|
"join-a-group": "Join a Group",
|
||||||
"create-a-new-group": "Create a New Group",
|
"create-a-new-group": "Create a New Group",
|
||||||
@@ -1085,7 +1132,7 @@
|
|||||||
"admin": {
|
"admin": {
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"storage-details": "Storage Details",
|
"storage-details": "Storage Details",
|
||||||
"page-title": "Site Maintenance",
|
"page-title": "Συντήρηση Ιστοσελίδας",
|
||||||
"summary-title": "Summary",
|
"summary-title": "Summary",
|
||||||
"button-label-get-summary": "Get Summary",
|
"button-label-get-summary": "Get Summary",
|
||||||
"button-label-open-details": "Details",
|
"button-label-open-details": "Details",
|
||||||
@@ -1127,28 +1174,38 @@
|
|||||||
"ingredient-text": "Ingredient Text",
|
"ingredient-text": "Ingredient Text",
|
||||||
"average-confident": "{0} Confident",
|
"average-confident": "{0} Confident",
|
||||||
"try-an-example": "Try an example",
|
"try-an-example": "Try an example",
|
||||||
"parser": "Parser",
|
"parser": "Αναλυτής",
|
||||||
"background-tasks": "Background Tasks",
|
"background-tasks": "Background Tasks",
|
||||||
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
||||||
"no-logs-found": "No Logs Found",
|
"no-logs-found": "No Logs Found",
|
||||||
"tasks": "Tasks"
|
"tasks": "Tasks",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "First Time Setup",
|
||||||
|
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Welcome, {0}",
|
"welcome-user": "👋 Καλώς ορίσατε, {0}!",
|
||||||
"description": "Manage your profile, recipes, and group settings.",
|
"description": "Manage your profile, recipes, and group settings.",
|
||||||
"get-invite-link": "Get Invite Link",
|
"get-invite-link": "Get Invite Link",
|
||||||
"get-public-link": "Get Public Link",
|
"get-public-link": "Get Public Link",
|
||||||
"account-summary": "Account Summary",
|
"account-summary": "Account Summary",
|
||||||
"account-summary-description": "Here's a summary of your group's information",
|
"account-summary-description": "Ακολουθεί μια περίληψη των πληροφοριών της ομάδας σας.",
|
||||||
"group-statistics": "Group Statistics",
|
"group-statistics": "Group Statistics",
|
||||||
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
||||||
"storage-capacity": "Storage Capacity",
|
"storage-capacity": "Storage Capacity",
|
||||||
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
||||||
"personal": "Personal",
|
"personal": "Personal",
|
||||||
"personal-description": "These are settings that are personal to you. Changes here won't affect other users",
|
"personal-description": "Αυτές είναι προσωπικές ρυθμίσεις σας. Οι αλλαγές εδώ δεν θα επηρεάσουν άλλους χρήστες.",
|
||||||
"user-settings": "User Settings",
|
"user-settings": "User Settings",
|
||||||
"user-settings-description": "Manage your preferences, change your password, and update your email",
|
"user-settings-description": "Διαχειριστείτε τις προτιμήσεις σας, αλλάξτε τον κωδικό πρόσβασής σας και ενημερώστε το email σας.",
|
||||||
"api-tokens-description": "Manage your API Tokens for access from external applications",
|
"api-tokens-description": "Διαχειριστείτε τα API Tokens σας για πρόσβαση από εξωτερικές εφαρμογές.",
|
||||||
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
||||||
"group-settings": "Group Settings",
|
"group-settings": "Group Settings",
|
||||||
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
||||||
@@ -1159,14 +1216,14 @@
|
|||||||
"notifiers": "Notifiers",
|
"notifiers": "Notifiers",
|
||||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||||
"manage-data": "Manage Data",
|
"manage-data": "Manage Data",
|
||||||
"manage-data-description": "Manage your Food and Units (more options coming soon)",
|
"manage-data-description": "Διαχειριστείτε τα δεδομένα σας στο Mealie. Τρόφιμα, Μονάδες, Κατηγορίες, Ετικέτες και πολλά άλλα.",
|
||||||
"data-migrations": "Data Migrations",
|
"data-migrations": "Data Migrations",
|
||||||
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown",
|
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
|
||||||
"email-sent": "Email Sent",
|
"email-sent": "Email Sent",
|
||||||
"error-sending-email": "Error Sending Email",
|
"error-sending-email": "Error Sending Email",
|
||||||
"personal-information": "Personal Information",
|
"personal-information": "Personal Information",
|
||||||
"preferences": "Preferences",
|
"preferences": "Προτιμήσεις",
|
||||||
"show-advanced-description": "Show advanced features (API Keys, Webhooks, and Data Management)",
|
"show-advanced-description": "Εμφάνιση χαρακτηριστικών για προχωρημένους (Κλειδιά API, Webhooks, και Διαχείριση Δεδομένων)",
|
||||||
"back-to-profile": "Back to Profile",
|
"back-to-profile": "Back to Profile",
|
||||||
"looking-for-privacy-settings": "Looking for Privacy Settings?",
|
"looking-for-privacy-settings": "Looking for Privacy Settings?",
|
||||||
"manage-your-api-tokens": "Manage Your API Tokens",
|
"manage-your-api-tokens": "Manage Your API Tokens",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"something-went-wrong": "Something Went Wrong!",
|
"something-went-wrong": "Something Went Wrong!",
|
||||||
"subscribed-events": "Subscribed Events",
|
"subscribed-events": "Subscribed Events",
|
||||||
"test-message-sent": "Test Message Sent",
|
"test-message-sent": "Test Message Sent",
|
||||||
|
"message-sent": "Message Sent",
|
||||||
"new-notification": "New Notification",
|
"new-notification": "New Notification",
|
||||||
"event-notifiers": "Event Notifiers",
|
"event-notifiers": "Event Notifiers",
|
||||||
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
|
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
|
||||||
@@ -80,10 +81,12 @@
|
|||||||
"recipe-events": "Recipe Events"
|
"recipe-events": "Recipe Events"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"add": "Add",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
"confirm-how-does-everything-look": "How does everything look?",
|
||||||
"confirm-delete-generic": "Are you sure you want to delete this?",
|
"confirm-delete-generic": "Are you sure you want to delete this?",
|
||||||
"copied_message": "Copied!",
|
"copied_message": "Copied!",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
@@ -142,18 +145,23 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
|
"show-all": "Show All",
|
||||||
"shuffle": "Shuffle",
|
"shuffle": "Shuffle",
|
||||||
"sort": "Sort",
|
"sort": "Sort",
|
||||||
|
"sort-ascending": "Sort Ascending",
|
||||||
|
"sort-descending": "Sort Descending",
|
||||||
"sort-alphabetically": "Alphabetical",
|
"sort-alphabetically": "Alphabetical",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"subject": "Subject",
|
"subject": "Subject",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"success-count": "Success: {count}",
|
"success-count": "Success: {count}",
|
||||||
"sunday": "Sunday",
|
"sunday": "Sunday",
|
||||||
|
"system": "System",
|
||||||
"templates": "Templates:",
|
"templates": "Templates:",
|
||||||
"test": "Test",
|
"test": "Test",
|
||||||
"themes": "Themes",
|
"themes": "Themes",
|
||||||
"thursday": "Thursday",
|
"thursday": "Thursday",
|
||||||
|
"title": "Title",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tuesday": "Tuesday",
|
"tuesday": "Tuesday",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
@@ -168,6 +176,7 @@
|
|||||||
"units": "Units",
|
"units": "Units",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
|
"start": "Start",
|
||||||
"toggle-view": "Toggle View",
|
"toggle-view": "Toggle View",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"id": "Id",
|
"id": "Id",
|
||||||
@@ -200,7 +209,8 @@
|
|||||||
"created-on-date": "Created on: {0}",
|
"created-on-date": "Created on: {0}",
|
||||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
|
||||||
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
"clipboard-copy-failure": "Failed to copy to the clipboard.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
|
||||||
|
"organizers": "Organisers"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
|
||||||
@@ -236,6 +246,8 @@
|
|||||||
"group-preferences": "Group Preferences",
|
"group-preferences": "Group Preferences",
|
||||||
"private-group": "Private Group",
|
"private-group": "Private Group",
|
||||||
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
||||||
|
"enable-public-access": "Enable Public Access",
|
||||||
|
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
||||||
"show-nutrition-information": "Show nutrition information",
|
"show-nutrition-information": "Show nutrition information",
|
||||||
@@ -349,10 +361,11 @@
|
|||||||
},
|
},
|
||||||
"recipe-data-migrations": "Recipe Data Migrations",
|
"recipe-data-migrations": "Recipe Data Migrations",
|
||||||
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
||||||
|
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.",
|
||||||
"choose-migration-type": "Choose Migration Type",
|
"choose-migration-type": "Choose Migration Type",
|
||||||
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
|
||||||
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
||||||
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below",
|
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||||
"recipe-1": "Recipe 1",
|
"recipe-1": "Recipe 1",
|
||||||
"recipe-2": "Recipe 2",
|
"recipe-2": "Recipe 2",
|
||||||
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
||||||
@@ -360,6 +373,10 @@
|
|||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie can import recipies from Plan to Eat."
|
"description-long": "Mealie can import recipies from Plan to Eat."
|
||||||
|
},
|
||||||
|
"myrecipebox": {
|
||||||
|
"title": "My Recipe Box",
|
||||||
|
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
@@ -511,6 +528,7 @@
|
|||||||
"edit-timeline-event": "Edit Timeline Event",
|
"edit-timeline-event": "Edit Timeline Event",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
|
||||||
|
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
|
||||||
"group-global-timeline": "{groupName} Global Timeline",
|
"group-global-timeline": "{groupName} Global Timeline",
|
||||||
"open-timeline": "Open Timeline",
|
"open-timeline": "Open Timeline",
|
||||||
"made-this": "I Made This",
|
"made-this": "I Made This",
|
||||||
@@ -531,6 +549,8 @@
|
|||||||
"looking-for-migrations": "Looking For Migrations?",
|
"looking-for-migrations": "Looking For Migrations?",
|
||||||
"import-with-url": "Import with URL",
|
"import-with-url": "Import with URL",
|
||||||
"create-recipe": "Create Recipe",
|
"create-recipe": "Create Recipe",
|
||||||
|
"create-recipe-description": "Create a new recipe from scratch.",
|
||||||
|
"create-recipes": "Create Recipes",
|
||||||
"import-with-zip": "Import with .zip",
|
"import-with-zip": "Import with .zip",
|
||||||
"create-recipe-from-an-image": "Create recipe from an image",
|
"create-recipe-from-an-image": "Create recipe from an image",
|
||||||
"bulk-url-import": "Bulk URL Import",
|
"bulk-url-import": "Bulk URL Import",
|
||||||
@@ -564,7 +584,22 @@
|
|||||||
"upload-image": "Upload image",
|
"upload-image": "Upload image",
|
||||||
"screen-awake": "Keep Screen Awake",
|
"screen-awake": "Keep Screen Awake",
|
||||||
"remove-image": "Remove image",
|
"remove-image": "Remove image",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Next step",
|
||||||
|
"recipe-actions": "Recipe Actions",
|
||||||
|
"parser": {
|
||||||
|
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.",
|
||||||
|
"ingredient-parser": "Ingredient Parser",
|
||||||
|
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
|
||||||
|
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
|
||||||
|
"select-parser": "Select Parser",
|
||||||
|
"natural-language-processor": "Natural Language Processor",
|
||||||
|
"brute-parser": "Brute Parser",
|
||||||
|
"parse-all": "Parse All",
|
||||||
|
"no-unit": "No unit",
|
||||||
|
"missing-unit": "Create missing unit: {unit}",
|
||||||
|
"missing-food": "Create missing food: {food}",
|
||||||
|
"no-food": "No Food"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Advanced Search",
|
"advanced-search": "Advanced Search",
|
||||||
@@ -726,7 +761,10 @@
|
|||||||
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
||||||
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
"ldap-ready-success-text": "Required LDAP variables are all set.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"recipe-scraper-version": "Recipe Scraper Version"
|
"recipe-scraper-version": "Recipe Scraper Version",
|
||||||
|
"oidc-ready": "OIDC Ready",
|
||||||
|
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
|
||||||
|
"oidc-ready-success-text": "Required OIDC variables are all set."
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
"all-lists": "All Lists",
|
"all-lists": "All Lists",
|
||||||
@@ -834,8 +872,11 @@
|
|||||||
"link-id": "Link ID",
|
"link-id": "Link ID",
|
||||||
"link-name": "Link Name",
|
"link-name": "Link Name",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
|
"login-oidc": "Login with",
|
||||||
|
"or": "or",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"manage-users": "Manage Users",
|
"manage-users": "Manage Users",
|
||||||
|
"manage-users-description": "Create and manage users.",
|
||||||
"new-password": "New Password",
|
"new-password": "New Password",
|
||||||
"new-user": "New User",
|
"new-user": "New User",
|
||||||
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
|
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
|
||||||
@@ -977,6 +1018,12 @@
|
|||||||
"delete-recipes": "Delete Recipes",
|
"delete-recipes": "Delete Recipes",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
|
"recipe-actions": {
|
||||||
|
"recipe-actions-data": "Recipe Actions Data",
|
||||||
|
"new-recipe-action": "New Recipe Action",
|
||||||
|
"edit-recipe-action": "Edit Recipe Action",
|
||||||
|
"action-type": "Action Type"
|
||||||
|
},
|
||||||
"create-alias": "Create Alias",
|
"create-alias": "Create Alias",
|
||||||
"manage-aliases": "Manage Aliases",
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Seed Data",
|
"seed-data": "Seed Data",
|
||||||
@@ -1131,24 +1178,34 @@
|
|||||||
"background-tasks": "Background Tasks",
|
"background-tasks": "Background Tasks",
|
||||||
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
||||||
"no-logs-found": "No Logs Found",
|
"no-logs-found": "No Logs Found",
|
||||||
"tasks": "Tasks"
|
"tasks": "Tasks",
|
||||||
|
"setup": {
|
||||||
|
"first-time-setup": "First Time Setup",
|
||||||
|
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||||
|
"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!",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Welcome, {0}",
|
"welcome-user": "👋 Welcome, {0}!",
|
||||||
"description": "Manage your profile, recipes, and group settings.",
|
"description": "Manage your profile, recipes, and group settings.",
|
||||||
"get-invite-link": "Get Invite Link",
|
"get-invite-link": "Get Invite Link",
|
||||||
"get-public-link": "Get Public Link",
|
"get-public-link": "Get Public Link",
|
||||||
"account-summary": "Account Summary",
|
"account-summary": "Account Summary",
|
||||||
"account-summary-description": "Here's a summary of your group's information",
|
"account-summary-description": "Here's a summary of your group's information.",
|
||||||
"group-statistics": "Group Statistics",
|
"group-statistics": "Group Statistics",
|
||||||
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
|
||||||
"storage-capacity": "Storage Capacity",
|
"storage-capacity": "Storage Capacity",
|
||||||
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
|
||||||
"personal": "Personal",
|
"personal": "Personal",
|
||||||
"personal-description": "These are settings that are personal to you. Changes here won't affect other users",
|
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
|
||||||
"user-settings": "User Settings",
|
"user-settings": "User Settings",
|
||||||
"user-settings-description": "Manage your preferences, change your password, and update your email",
|
"user-settings-description": "Manage your preferences, change your password, and update your email.",
|
||||||
"api-tokens-description": "Manage your API Tokens for access from external applications",
|
"api-tokens-description": "Manage your API Tokens for access from external applications.",
|
||||||
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
||||||
"group-settings": "Group Settings",
|
"group-settings": "Group Settings",
|
||||||
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
||||||
@@ -1159,9 +1216,9 @@
|
|||||||
"notifiers": "Notifiers",
|
"notifiers": "Notifiers",
|
||||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||||
"manage-data": "Manage Data",
|
"manage-data": "Manage Data",
|
||||||
"manage-data-description": "Manage your Food and Units (more options coming soon)",
|
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
|
||||||
"data-migrations": "Data Migrations",
|
"data-migrations": "Data Migrations",
|
||||||
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown",
|
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
|
||||||
"email-sent": "Email Sent",
|
"email-sent": "Email Sent",
|
||||||
"error-sending-email": "Error Sending Email",
|
"error-sending-email": "Error Sending Email",
|
||||||
"personal-information": "Personal Information",
|
"personal-information": "Personal Information",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user