mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-27 02:03:13 -05:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7b191a5ee | ||
|
|
5620370ade | ||
|
|
d333d47e34 | ||
|
|
b34b1c9be3 | ||
|
|
8c5010148d | ||
|
|
a17b0e329e | ||
|
|
8ab69a7d7a | ||
|
|
f4ecf74b91 | ||
|
|
ba9d816f64 | ||
|
|
6895b49543 | ||
|
|
fffe7b05e0 | ||
|
|
1271e0e49b | ||
|
|
478054b724 | ||
|
|
57d259a7a3 | ||
|
|
a4a6d4dfb1 | ||
|
|
f7b4f79312 | ||
|
|
434d312f7c | ||
|
|
bda460b49e | ||
|
|
d3e1c48655 | ||
|
|
b2a3430f2c | ||
|
|
3d792d9333 | ||
|
|
2e028d7e12 | ||
|
|
c63932e8b3 | ||
|
|
3ba2227bc7 | ||
|
|
67af391c6b | ||
|
|
70ae0dac25 | ||
|
|
e15a9c3c9f | ||
|
|
9d40d60b3b | ||
|
|
e2760f7247 | ||
|
|
83bf21b947 | ||
|
|
d1824affff | ||
|
|
4827e1092f | ||
|
|
7db767b075 | ||
|
|
afdd0b15dc | ||
|
|
37c9166a77 | ||
|
|
ba0b9d4cd9 | ||
|
|
9fd99a86b8 | ||
|
|
824603a578 | ||
|
|
e3f120c680 | ||
|
|
d16a10440d | ||
|
|
ecdf7de386 | ||
|
|
0e10ed8461 | ||
|
|
1684169e7b | ||
|
|
3d9f2bef82 | ||
|
|
a722b05fb5 | ||
|
|
187e83eeb5 | ||
|
|
f3cc51190c | ||
|
|
33aedd6904 | ||
|
|
ea9a25a891 | ||
|
|
3a237258a1 | ||
|
|
d29de8e679 | ||
|
|
79367872ac | ||
|
|
f058dec27b | ||
|
|
c87acf54db | ||
|
|
84c144e40f | ||
|
|
474cf299cd | ||
|
|
1cababc5a5 | ||
|
|
8705bcf195 | ||
|
|
bdb511c1c8 | ||
|
|
c9f3f65f36 | ||
|
|
3ec55f0e48 | ||
|
|
7d43c7c7a2 | ||
|
|
c710e9d3f5 | ||
|
|
0313e6b3b8 | ||
|
|
24b890136d | ||
|
|
4b67554b36 | ||
|
|
679a42a7cc | ||
|
|
4dfc32a314 | ||
|
|
96acc6fc4b | ||
|
|
249c9e8f23 | ||
|
|
7413185300 | ||
|
|
6168ea0150 | ||
|
|
f7ba7862d4 | ||
|
|
cec6d2c5ec | ||
|
|
b27977fbdf | ||
|
|
2a60b330ac | ||
|
|
72ec5bd13e | ||
|
|
bb45cbb0a2 | ||
|
|
c929a03b57 | ||
|
|
9e5a54477f | ||
|
|
078b4563b3 | ||
|
|
a9090bc2bd | ||
|
|
cb8c1423c5 | ||
|
|
f6a1b5f4eb | ||
|
|
7623b72c4c | ||
|
|
17d40e34df | ||
|
|
bade6968a3 | ||
|
|
92a142125f | ||
|
|
d39c2a2874 | ||
|
|
324de7fb10 | ||
|
|
c4544ea042 | ||
|
|
a5dda74812 | ||
|
|
fd7e58e40c | ||
|
|
5e42841a7d | ||
|
|
ae9306b8c2 | ||
|
|
7f0c5cbcc4 | ||
|
|
a7d8bcc6ba | ||
|
|
b94ef78a12 | ||
|
|
db2c14093d | ||
|
|
9a0525c3a0 | ||
|
|
a2e5826da0 |
@@ -11,7 +11,7 @@
|
||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
||||
"VARIANT": "3.12-bullseye",
|
||||
// Options
|
||||
"NODE_VERSION": "20"
|
||||
"NODE_VERSION": "22"
|
||||
}
|
||||
},
|
||||
"mounts": [
|
||||
|
||||
2
.github/workflows/build-package.yml
vendored
2
.github/workflows/build-package.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Setup node env 🏗
|
||||
uses: actions/setup-node@v4.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
check-latest: true
|
||||
|
||||
- name: Get yarn cache directory path 🛠
|
||||
|
||||
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./tests/e2e/yarn.lock
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/sqlite.md
|
||||
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/postgres.md
|
||||
sed -i 's/^version = "[^"]*"/version = "${{ env.VERSION_NUM }}"/' pyproject.toml
|
||||
sed -i 's/^\s*"version": "[^"]*"/"version": "${{ env.VERSION_NUM }}"/' frontend/package.json
|
||||
sed -i 's/\("version": "\)[^"]*"/\1${{ env.VERSION_NUM }}"/' frontend/package.json
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
|
||||
2
.github/workflows/test-frontend.yml
vendored
2
.github/workflows/test-frontend.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Setup node env 🏗
|
||||
uses: actions/setup-node@v4.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
check-latest: true
|
||||
|
||||
- name: Get yarn cache directory path 🛠
|
||||
|
||||
@@ -12,7 +12,7 @@ repos:
|
||||
exclude: ^tests/data/
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.12
|
||||
rev: v0.13.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -59,8 +59,11 @@
|
||||
"netlify.toml": "runtime.txt",
|
||||
"README.md": "LICENSE, SECURITY.md"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.formatOnSave": false
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[python]": {
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -105,12 +106,16 @@ def main():
|
||||
# Flatten list of lists
|
||||
all_children = [item for sublist in all_children for item in sublist]
|
||||
|
||||
out_path = GENERATED / "__init__.py"
|
||||
render_python_template(
|
||||
TEMPLATE,
|
||||
GENERATED / "__init__.py",
|
||||
out_path,
|
||||
{"children": all_children},
|
||||
)
|
||||
|
||||
subprocess.run(["poetry", "run", "ruff", "check", str(out_path), "--fix"])
|
||||
subprocess.run(["poetry", "run", "ruff", "format", str(out_path)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from utils import PROJECT_DIR, log, render_python_template
|
||||
@@ -84,16 +85,23 @@ def find_modules(root: pathlib.Path) -> list[Modules]:
|
||||
return modules
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
modules = find_modules(SCHEMA_PATH)
|
||||
|
||||
template_paths: list[pathlib.Path] = []
|
||||
for module in modules:
|
||||
log.debug(f"Module: {module.directory.name}")
|
||||
for file in module.files:
|
||||
log.debug(f" File: {file.import_path}")
|
||||
log.debug(f" Classes: [{', '.join(file.classes)}]")
|
||||
|
||||
render_python_template(template, module.directory / "__init__.py", {"module": module})
|
||||
template_path = module.directory / "__init__.py"
|
||||
template_paths.append(template_path)
|
||||
render_python_template(template, template_path, {"module": module})
|
||||
|
||||
path_args = (str(p) for p in template_paths)
|
||||
subprocess.run(["poetry", "run", "ruff", "check", *path_args, "--fix"])
|
||||
subprocess.run(["poetry", "run", "ruff", "format", *path_args])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from jinja2 import Template
|
||||
@@ -189,6 +190,7 @@ def generate_typescript_types() -> None: # noqa: C901
|
||||
skipped_dirs: list[Path] = []
|
||||
failed_modules: list[Path] = []
|
||||
|
||||
out_paths: list[Path] = []
|
||||
for module in schema_path.iterdir():
|
||||
if module.is_dir() and module.stem in ignore_dirs:
|
||||
skipped_dirs.append(module)
|
||||
@@ -205,10 +207,18 @@ def generate_typescript_types() -> None: # noqa: C901
|
||||
path_as_module = path_to_module(module)
|
||||
generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore
|
||||
clean_output_file(out_path)
|
||||
out_paths.append(out_path)
|
||||
except Exception:
|
||||
failed_modules.append(module)
|
||||
log.exception(f"Module Error: {module}")
|
||||
|
||||
# Run ESLint --fix on the files to clean up any formatting issues
|
||||
subprocess.run(
|
||||
["yarn", "lint", "--fix", *(str(path) for path in out_paths)],
|
||||
check=True,
|
||||
cwd=PROJECT_DIR / "frontend",
|
||||
)
|
||||
|
||||
log.debug("\n📁 Skipped Directories:")
|
||||
for skipped_dir in skipped_dirs:
|
||||
log.debug(f" 📁 {skipped_dir.name}")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -23,11 +22,6 @@ def render_python_template(template_file: Path | str, dest: Path, data: dict):
|
||||
|
||||
dest.write_text(text)
|
||||
|
||||
# lint/format file with Ruff
|
||||
log.info(f"Formatting {dest}")
|
||||
subprocess.run(["poetry", "run", "ruff", "check", str(dest), "--fix"])
|
||||
subprocess.run(["poetry", "run", "ruff", "format", str(dest)])
|
||||
|
||||
|
||||
@dataclass
|
||||
class CodeSlicer:
|
||||
@@ -37,7 +31,7 @@ class CodeSlicer:
|
||||
indentation: str | None
|
||||
text: list[str]
|
||||
|
||||
_next_line = None
|
||||
_next_line: int | None = None
|
||||
|
||||
def purge_lines(self) -> None:
|
||||
start = self.start + 1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
###############################################
|
||||
# Frontend Build
|
||||
###############################################
|
||||
FROM node:20@sha256:f3e50c7689a1b6982fab45b1b23ba5adf1fd725e233dc640918fb59f7a57b174 \
|
||||
FROM node:22@sha256:2bb201f33898d2c0ce638505b426f4dd038cc00e5b2b4cbba17b069f0fff1496 \
|
||||
AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
@@ -34,7 +34,7 @@ Make sure the VSCode Dev Containers extension is installed, then select "Dev Con
|
||||
|
||||
- [Python 3.12](https://www.python.org/downloads/)
|
||||
- [Poetry](https://python-poetry.org/docs/#installation)
|
||||
- [Node v16.x](https://nodejs.org/en/)
|
||||
- [Node](https://nodejs.org/en/)
|
||||
- [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable)
|
||||
- [task](https://taskfile.dev/#/installation)
|
||||
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
|
||||
In a lot of ways, Home Assistant is why this project exists! Since Mealie has a robust API it makes it a great fit for interacting with Home Assistant and pulling information into your dashboard.
|
||||
|
||||
### Display Today's Meal in Lovelace
|
||||
## Display Today's Meal in Lovelace
|
||||
|
||||
You can use the Mealie API to get access to meal plans in Home Assistant like in the image below.
|
||||
|
||||

|
||||
|
||||
Steps:
|
||||
## Steps:
|
||||
|
||||
#### 1. Get your API Token
|
||||
### 1. Get your API Token
|
||||
|
||||
Create an API token from Mealie's User Settings page (https://docs.mealie.io/documentation/getting-started/api-usage/#getting-a-token)
|
||||
Create an API token from Mealie's User Settings page (see [this page](https://docs.mealie.io/documentation/getting-started/api-usage/#getting-a-token) to learn how).
|
||||
|
||||
#### 2. Create Home Assistant Sensors
|
||||
### 2. Create Home Assistant Sensors
|
||||
|
||||
Create REST sensors in home assistant to get the details of today's meal.
|
||||
We will create sensors to get the name and ID of the first meal in today's meal plan (note that this may not be what is wanted if there is more than one meal planned for the day). We need the ID as well as the name to be able to retrieve the image for the meal.
|
||||
@@ -40,7 +40,7 @@ rest:
|
||||
unique_id: mealie_todays_meal_id
|
||||
```
|
||||
|
||||
#### 3. Create a Camera Entity
|
||||
### 3. Create a Camera Entity
|
||||
|
||||
We will create a camera entity to display the image of today's meal in Lovelace.
|
||||
|
||||
@@ -52,7 +52,7 @@ In the still image url field put in:
|
||||
Under the entity page for the new camera, rename it.
|
||||
e.g. `camera.mealie_todays_meal_image`
|
||||
|
||||
#### 4. Create a Lovelace Card
|
||||
### 4. Create a Lovelace Card
|
||||
|
||||
Create a picture entity card and set the entity to `mealie_todays_meal` and the camera entity to `camera.mealie_todays_meal_image` or set in the yaml directly.
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@ var url = document.URL.endsWith('/') ?
|
||||
document.URL;
|
||||
var mealie = "http://localhost:8080";
|
||||
var group_slug = "home" // Change this to your group slug. You can obtain this from your URL after logging-in to Mealie
|
||||
var use_keywords= "&use_keywords=1" // Optional - use keywords from recipe - update to "" if you don't want that
|
||||
var edity = "&edit=1" // Optional - keep in edit mode - update to "" if you don't want that
|
||||
|
||||
if (mealie.slice(-1) === "/") {
|
||||
mealie = mealie.slice(0, -1)
|
||||
}
|
||||
var dest = mealie + "/g/" + group_slug + "/r/create/url?recipe_import_url=" + url + use_keywords + edity;
|
||||
var dest = mealie + "/g/" + group_slug + "/r/create/url?recipe_import_url=" + url;
|
||||
window.open(dest, "_blank");
|
||||
```
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
An easy way to add recipes to Mealie from an Apple device is via an Apple Shortcut. This is a short guide to install an configure a shortcut able to add recipes via a link or image(s).
|
||||
|
||||
*Note: if adding via images make sure to enable [Mealie's openai integration](https://docs.mealie.io/documentation/getting-started/installation/open-ai/)*
|
||||
!!! note
|
||||
If adding via images make sure to enable [Mealie's OpenAI Integration](https://docs.mealie.io/documentation/getting-started/installation/open-ai/)
|
||||
|
||||
## Javascript can only be run via Shortcuts on the Safari browser on MacOS and iOS. If you do not use Safari you may skip this section
|
||||
Some sites have begun blocking AI scraping bots, inadvertently blocking the recipe scraping library Mealie uses as well. To circumvent this, the shortcut uses javascript to capture the raw html loaded in the browser and sends that to mealie when possible.
|
||||
@@ -16,12 +17,13 @@ Settings app -> apps -> Shortcuts -> Advanced -> Allow Running Scripts
|
||||
|
||||
Shortcuts app -> Settings (CMD ,) -> Advanced -> Allow Running Scripts
|
||||
|
||||
## Initial setup
|
||||
## Initial Setup
|
||||
An API key is needed to authenticate with mealie. To create an api key for a user, navigate to http://YOUR_MEALIE_URL/user/profile/api-tokens. Alternatively you can create a key via the mealie home page by clicking the user's profile pic in the top left -> Api Tokens
|
||||
|
||||
The shortcut can be installed via **[This link](https://www.icloud.com/shortcuts/52834724050b42aebe0f2efd8d067360)**. Upon install, replace "MEALIE_API_KEY" with the API key generated previously and "MEALIE_URI" with the full URL used to access your mealie instance e.g. "http://10.0.0.5:9000" or "https://mealie.domain.com".
|
||||
|
||||
## Using the shortcut
|
||||
## Using the Shortcut
|
||||
Once installed, the shortcut will automatically appear as an option when sharing an image or webpage. It can also be useful to add the shortcut to the home screen of your device. If selected from the home screen or shortcuts app, a menu will appear with prompts to import via **taking photo(s)**, **selecting photo(s)**, **scanning a URL**, or **pasting a URL**.
|
||||
|
||||
*Note: despite the mealie API being able to accept multiple recipe images for import it is currently impossible to send multiple files in 1 web request via Shortcuts. Instead, the shortcut combines the images into a singular, vertically-concatenated image to send to mealie. This can result in slightly less-accurate text recognition.*
|
||||
!!! note
|
||||
Despite the Mealie API being able to accept multiple recipe images for import it is currently impossible to send multiple files in 1 web request via Shortcuts. Instead, the shortcut combines the images into a singular, vertically-concatenated image to send to mealie. This can result in slightly less-accurate text recognition.
|
||||
@@ -3,50 +3,54 @@
|
||||
!!! info
|
||||
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
|
||||
|
||||
> [n8n](https://github.com/n8n-io/n8n) is a free and source-available fair-code licensed workflow automation tool. Alternative to Zapier or Make, allowing you to use a UI to create automated workflows.
|
||||
[n8n](https://github.com/n8n-io/n8n) is a free and source-available fair-code licensed workflow automation tool. It's an alternative to tools like Zapier or Make, allowing you to use a UI to create automated workflows.
|
||||
|
||||
This example workflow:
|
||||
|
||||
1. Backups Mealie every morning via an API call
|
||||
2. Deletes all but the last 7 backups
|
||||
1. Creates a Mealie backup every morning via an API call
|
||||
2. Keeps the last 7 backups, deleting older ones
|
||||
|
||||
> [!CAUTION]
|
||||
> This only automates the backup function, this does not backup your data to anywhere except your local instance. Please make sure you are backing up your data to an external source.
|
||||
!!! warning "Important"
|
||||
This only automates the backup function, this does not backup your data to anywhere except your local instance. Please make sure you are backing up your data to an external source.
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
# Setup
|
||||
## Setup
|
||||
|
||||
## Deploying n8n
|
||||
### Deploying n8n
|
||||
|
||||
Follow the relevant guide in the [n8n Documentation](https://docs.n8n.io/)
|
||||
|
||||
## Importing n8n workflow
|
||||
### Importing n8n workflow
|
||||
|
||||
1. In n8n, add a new workflow
|
||||
2. In the top right hit the 3 dot menu and select 'Import from URL...'
|
||||
|
||||

|
||||
|
||||
3. Paste `https://github.com/mealie-recipes/mealie/blob/mealie-next/docs/docs/assets/other/n8n/n8n-mealie-backup.json` and click Import
|
||||
3. Paste `https://github.com/mealie-recipes/mealie/blob/mealie-next/docs/docs/assets/other/n8n/n8n-mealie-backup.json` and click 'Import'
|
||||
4. Click through the nodes and update the URLs for your environment
|
||||
|
||||
## API Credentials
|
||||
### API Credentials
|
||||
|
||||
#### Generate Mealie API Token
|
||||
|
||||
1. Head to https://mealie.example.com/user/profile/api-tokens
|
||||
> If you dont see this screen make sure that "Show advanced features" is checked under https://mealie.example.com/user/profile/edit
|
||||
2. Under token name, enter the name of the token i.e. 'n8n' and hit Generate
|
||||
1. Head to `<YOUR MEALIE INSTANCE>/user/profile/api-tokens`
|
||||
|
||||
!!! tip
|
||||
If you dont see this screen make sure that "Show advanced features" is checked under `<YOUR MEALIE INSTANCE>/user/profile/edit`
|
||||
|
||||
2. Under token name, enter the name of the token (for example, 'n8n') and hit 'Generate'
|
||||
|
||||
3. Copy and keep this API Token somewhere safe, this is like your password!
|
||||
|
||||
> You can use your normal user for this, but assuming you're an admin you could also choose to create a user named n8n and generate the API key against that user.
|
||||
!!! tip
|
||||
You can use your normal user for this, but assuming you're an admin you could also choose to create a user named n8n and generate the API key against that user.
|
||||
|
||||
#### Setup Credentials in n8n
|
||||
|
||||
> [n8n Docs](https://docs.n8n.io/credentials/add-edit-credentials/)
|
||||
See also [n8n Docs](https://docs.n8n.io/credentials/add-edit-credentials/).
|
||||
|
||||
1. Create a new "Header Auth" Credential
|
||||
|
||||
@@ -57,15 +61,17 @@ Follow the relevant guide in the [n8n Documentation](https://docs.n8n.io/)
|
||||

|
||||
|
||||
3. In the workflow you created, for the "Run Backup", "Get All backups", and "Delete Oldies" nodes, update:
|
||||
|
||||
- Authentication to `Generic Credential Type`
|
||||
- Generic Auth Type to `Header Auth`
|
||||
- Header Auth to `Mealie API` or whatever you named your credentials
|
||||
|
||||

|
||||
|
||||
## Notification Node
|
||||
### Notification Node
|
||||
|
||||
> Please use error notifications of some kind. It's very easy to set and forget an automation, then have the worst happen and lose data.
|
||||
!!! warning "Important"
|
||||
Please use error notifications of some kind. It's very easy to set and forget an automation, then have the worst happen and lose data.
|
||||
|
||||
[ntfy](https://github.com/binwiederhier/ntfy) is a great open source, self-hostable tool for sending notifications.
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
!!! info
|
||||
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
|
||||
|
||||
To make the setup of a Reverse Proxy much easier, Linuxserver.io developed [SWAG](https://github.com/linuxserver/docker-swag).
|
||||
|
||||
|
||||
To make the setup of a Reverse Proxy much easier, Linuxserver.io developed [SWAG](https://github.com/linuxserver/docker-swag)
|
||||
SWAG - Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let's Encrypt™) sets up an Nginx web server and reverse proxy with PHP support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let's Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.
|
||||
|
||||
## Step 1: Get a domain
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
|
||||
You can change the theme by settings the environment variables.
|
||||
|
||||
- [Backend Config - Themeing](./installation/backend-config.md#themeing)
|
||||
- [Backend Config - Theming](./installation/backend-config.md#theming)
|
||||
|
||||
|
||||
??? question "How can I change the login session timeout?"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
| DEFAULT_GROUP | Home | The default group for users |
|
||||
| DEFAULT_HOUSEHOLD | Family | The default household for users in each group |
|
||||
| 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. Must be <= 87600 (10 years, in hours). |
|
||||
| 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 |
|
||||
| TZ | UTC | Must be set to get correct date/time on the server |
|
||||
@@ -138,6 +138,13 @@ For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values
|
||||
|
||||
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.
|
||||
|
||||
!!! info
|
||||
If you're setting these variables but not seeing these changes persist, try removing the `#` character. Also, depending on which syntax you're using, double-check you're using quotes correctly.
|
||||
|
||||
If using YAML mapping syntax, be sure to include quotes around these values, otherwise they will be treated as comments in your YAML file:<br>`THEME_LIGHT_PRIMARY: '#E58325'` or `THEME_LIGHT_PRIMARY: 'E58325'`
|
||||
|
||||
If using YAML sequence syntax, don't include any quotes:<br>`THEME_LIGHT_PRIMARY=#E58325` or `THEME_LIGHT_PRIMARY=E58325`
|
||||
|
||||
| Variables | Default | Description |
|
||||
| --------------------- | :-----: | --------------------------- |
|
||||
| THEME_LIGHT_PRIMARY | #E58325 | Light Theme Config Variable |
|
||||
|
||||
@@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
|
||||
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
|
||||
|
||||
1. Take a backup just in case!
|
||||
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.1.2`
|
||||
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.3.1`
|
||||
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
|
||||
4. Restart the container
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
|
||||
```yaml
|
||||
services:
|
||||
mealie:
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.1.2 # (3)
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.3.1 # (3)
|
||||
container_name: mealie
|
||||
restart: always
|
||||
ports:
|
||||
|
||||
@@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
|
||||
```yaml
|
||||
services:
|
||||
mealie:
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.1.2 # (3)
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.3.1 # (3)
|
||||
container_name: mealie
|
||||
restart: always
|
||||
ports:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Mealie provides an integrated mechanic for doing full installation backups of the database.
|
||||
|
||||
Navigate to Settings > Backups or manually by adding `/admin/backups` to your instance URL.
|
||||
Navigate to Settings > Admin Settings > Backups or manually by adding `/admin/backups` to your instance URL.
|
||||
|
||||
From this page, you will be able to:
|
||||
|
||||
@@ -39,7 +39,7 @@ Restoring the Database when using Postgres requires Mealie to be configured with
|
||||
```sql
|
||||
ALTER USER mealie WITH SUPERUSER;
|
||||
|
||||
# Run restore from Mealie
|
||||
-- Run restore from Mealie
|
||||
|
||||
ALTER USER mealie WITH NOSUPERUSER;
|
||||
```
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Permissions and Public Access
|
||||
|
||||
Mealie provides various levels of user access and permissions. This includes:
|
||||
|
||||
- Authentication and registration ([LDAP](../authentication/ldap.md) and [OpenID Connect](../authentication/oidc.md) are both supported)
|
||||
- Customizable user permissions
|
||||
- Fine-tuned public access for non-users
|
||||
@@ -8,12 +9,12 @@ Mealie provides various levels of user access and permissions. This includes:
|
||||
## Customizable User Permissions
|
||||
|
||||
Each user can be configured to have varying levels of access. Some of these permissions include:
|
||||
|
||||
- Access to Administrator tools
|
||||
- Access to inviting other users
|
||||
- Access to manage their group and group data
|
||||
|
||||
Administrators can navigate to the Settings page and access the User Management page to configure these settings.
|
||||
|
||||
Administrators can configure these settings on the User Management page (navigate to Settings > Admin Settings > Users or append `/admin/manage/users` to your instance URL).
|
||||
|
||||
[User Management Demo](https://demo.mealie.io/admin/manage/users){ .md-button .md-button--primary }
|
||||
|
||||
@@ -22,8 +23,8 @@ Administrators can navigate to the Settings page and access the User Management
|
||||
By default, groups and households are set to private, meaning only logged-in users may access the group/household. In order for a recipe to be viewable by public (not logged-in) users, three criteria must be met:
|
||||
|
||||
1. The group must not be private
|
||||
2. The household must not be private, *and* the household setting for allowing users outside of your group to see your recipes must be enabled. These can be toggled on the Household Settings page
|
||||
2. The recipe must be set to public. This can be toggled for each recipe individually, or in bulk using the Recipe Data Management page
|
||||
2. The household must not be private, _and_ the household setting for allowing users outside of your group to see your recipes must be enabled. These can be toggled on the Household Management page (navigate to Settings > Admin Settings > Households or append `/admin/manage/households` to your instance URL)
|
||||
3. The recipe must be set to public. This can be toggled for each recipe individually, or in bulk using the Recipe Data Management page
|
||||
|
||||
Additionally, if the group is not private, public users can view all public group data (public recipes, public cookbooks, etc.) from the home page ([e.g. the demo home page](https://demo.mealie.io/g/home)).
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -86,7 +86,7 @@ nav:
|
||||
|
||||
- Community Guides:
|
||||
- Bring API without internet exposure: "documentation/community-guide/bring-api.md"
|
||||
- Automate Backups with n8n: "documentation/community-guide/n8n-backup-automation.md"
|
||||
- Automating Backups with n8n: "documentation/community-guide/n8n-backup-automation.md"
|
||||
- Bulk Url Import: "documentation/community-guide/bulk-url-import.md"
|
||||
- Home Assistant: "documentation/community-guide/home-assistant.md"
|
||||
- Import Bookmarklet: "documentation/community-guide/import-recipe-bookmarklet.md"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
|
||||
.handle {
|
||||
cursor: grab;
|
||||
cursor: grab !important;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
density="compact"
|
||||
elevation="0"
|
||||
>
|
||||
<BaseDialog v-model="deleteDialog" :title="$t('recipe.delete-recipe')" color="error"
|
||||
:icon="$globals.icons.alertCircle" can-confirm @confirm="emitDelete()">
|
||||
<BaseDialog
|
||||
v-model="deleteDialog"
|
||||
:title="$t('recipe.delete-recipe')"
|
||||
color="error"
|
||||
:icon="$globals.icons.alertCircle"
|
||||
can-confirm
|
||||
@confirm="emitDelete()"
|
||||
>
|
||||
<v-card-text>
|
||||
{{ $t("recipe.delete-confirmation") }}
|
||||
</v-card-text>
|
||||
@@ -15,7 +21,14 @@
|
||||
<v-spacer />
|
||||
<div v-if="!open" class="custom-btn-group ma-1">
|
||||
<RecipeFavoriteBadge v-if="loggedIn" color="info" button-style :recipe-id="recipe.id!" show-always />
|
||||
<RecipeTimelineBadge v-if="loggedIn" class="ml-1" color="info" button-style :slug="recipe.slug" :recipe-name="recipe.name!" />
|
||||
<RecipeTimelineBadge
|
||||
v-if="loggedIn"
|
||||
class="ml-1"
|
||||
color="info"
|
||||
button-style
|
||||
:slug="recipe.slug"
|
||||
:recipe-name="recipe.name!"
|
||||
/>
|
||||
<div v-if="loggedIn">
|
||||
<v-tooltip v-if="canEdit" location="bottom" color="info">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<RecipeContextMenuContent
|
||||
v-if="isMenuContentLoaded"
|
||||
v-bind="contentProps"
|
||||
@print="$emit('print')"
|
||||
@deleted="$emit('deleted', $event)"
|
||||
/>
|
||||
</v-menu>
|
||||
@@ -108,6 +109,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
defineEmits<{
|
||||
[key: string]: any;
|
||||
print: [];
|
||||
deleted: [slug: string];
|
||||
}>();
|
||||
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<BaseDialog v-model="dialog" :title="$t('data-pages.manage-aliases')" :icon="$globals.icons.edit"
|
||||
:submit-icon="$globals.icons.check" :submit-text="$t('general.confirm')" can-submit @submit="saveAliases"
|
||||
@cancel="$emit('cancel')">
|
||||
<BaseDialog
|
||||
v-model="dialog"
|
||||
:title="$t('data-pages.manage-aliases')"
|
||||
:icon="$globals.icons.edit"
|
||||
:submit-icon="$globals.icons.check"
|
||||
:submit-text="$t('general.confirm')"
|
||||
can-submit
|
||||
@submit="saveAliases"
|
||||
@cancel="$emit('cancel')"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-row v-for="alias, i in aliases" :key="i">
|
||||
@@ -10,13 +17,16 @@
|
||||
<v-text-field v-model="alias.name" :label="$t('general.name')" :rules="[validators.required]" />
|
||||
</v-col>
|
||||
<v-col cols="2">
|
||||
<BaseButtonGroup :buttons="[
|
||||
<BaseButtonGroup
|
||||
:buttons="[
|
||||
{
|
||||
icon: $globals.icons.delete,
|
||||
text: $t('general.delete'),
|
||||
event: 'delete',
|
||||
},
|
||||
]" @delete="deleteAlias(i)" />
|
||||
]"
|
||||
@delete="deleteAlias(i)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import RecipeExplorerPageSearchFilters from "./RecipeExplorerPageSearchFilters.vue";
|
||||
import { useRecipeExplorerSearch } from "~/composables/use-recipe-explorer-search";
|
||||
import { useRecipeExplorerSearch, clearRecipeExplorerSearchState } from "~/composables/use-recipe-explorer-search";
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: [];
|
||||
@@ -155,6 +155,11 @@ onMounted(async () => {
|
||||
emit("ready");
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// Clear the cache when component unmounts to ensure fresh state on remount
|
||||
clearRecipeExplorerSearchState(groupSlug.value);
|
||||
});
|
||||
|
||||
const sortText = computed(() => {
|
||||
const sort = sortable.value.find(s => s.value === state.value.orderBy);
|
||||
if (!sort) return "";
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
:placeholder="$t('recipe.quantity')"
|
||||
@keypress="quantityFilter"
|
||||
>
|
||||
<template #prepend>
|
||||
<template v-if="enableDragHandle" #prepend>
|
||||
<v-icon
|
||||
class="mr-n1 handle"
|
||||
>
|
||||
@@ -59,6 +59,7 @@
|
||||
class="mx-1"
|
||||
:placeholder="$t('recipe.choose-unit')"
|
||||
clearable
|
||||
:menu-props="{ attach: props.menuAttachTarget, maxHeight: '250px' }"
|
||||
@keyup.enter="handleUnitEnter"
|
||||
>
|
||||
<template #prepend>
|
||||
@@ -115,6 +116,7 @@
|
||||
class="mx-1 py-0"
|
||||
:placeholder="$t('recipe.choose-food')"
|
||||
clearable
|
||||
:menu-props="{ attach: props.menuAttachTarget, maxHeight: '250px' }"
|
||||
@keyup.enter="handleFoodEnter"
|
||||
>
|
||||
<template #prepend>
|
||||
@@ -165,12 +167,12 @@
|
||||
@click="$emit('clickIngredientField', 'note')"
|
||||
/>
|
||||
<BaseButtonGroup
|
||||
v-if="enableContextMenu"
|
||||
hover
|
||||
:large="false"
|
||||
class="my-auto d-flex"
|
||||
:buttons="btns"
|
||||
@toggle-section="toggleTitle"
|
||||
@toggle-original="toggleOriginalText"
|
||||
@insert-above="$emit('insert-above')"
|
||||
@insert-below="$emit('insert-below')"
|
||||
@delete="$emit('delete')"
|
||||
@@ -178,13 +180,7 @@
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<p
|
||||
v-if="showOriginalText"
|
||||
class="text-caption"
|
||||
>
|
||||
{{ $t("recipe.original-text-with-value", { originalText: model.originalText }) }}
|
||||
</p>
|
||||
|
||||
<slot name="before-divider" />
|
||||
<v-divider
|
||||
v-if="!mdAndUp"
|
||||
class="my-4"
|
||||
@@ -203,7 +199,11 @@ import type { RecipeIngredient } from "~/lib/api/types/recipe";
|
||||
// defineModel replaces modelValue prop
|
||||
const model = defineModel<RecipeIngredient>({ required: true });
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
menuAttachTarget: {
|
||||
type: String,
|
||||
default: "body",
|
||||
},
|
||||
unitError: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -220,6 +220,18 @@ defineProps({
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
enableContextMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
enableDragHandle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
deleteDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits([
|
||||
@@ -235,7 +247,6 @@ const { $globals } = useNuxtApp();
|
||||
|
||||
const state = reactive({
|
||||
showTitle: false,
|
||||
showOriginalText: false,
|
||||
});
|
||||
|
||||
const contextMenuOptions = computed(() => {
|
||||
@@ -254,13 +265,6 @@ const contextMenuOptions = computed(() => {
|
||||
},
|
||||
];
|
||||
|
||||
if (model.value.originalText) {
|
||||
options.push({
|
||||
text: i18n.t("recipe.see-original-text"),
|
||||
event: "toggle-original",
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
});
|
||||
|
||||
@@ -281,8 +285,8 @@ const btns = computed(() => {
|
||||
text: i18n.t("general.delete"),
|
||||
event: "delete",
|
||||
children: undefined,
|
||||
disabled: props.deleteDisabled,
|
||||
});
|
||||
|
||||
return out;
|
||||
});
|
||||
|
||||
@@ -319,10 +323,6 @@ function toggleTitle() {
|
||||
state.showTitle = !state.showTitle;
|
||||
}
|
||||
|
||||
function toggleOriginalText() {
|
||||
state.showOriginalText = !state.showOriginalText;
|
||||
}
|
||||
|
||||
function handleUnitEnter() {
|
||||
if (
|
||||
model.value.unit === undefined
|
||||
@@ -349,7 +349,7 @@ function quantityFilter(e: KeyboardEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
const { showTitle, showOriginalText } = toRefs(state);
|
||||
const { showTitle } = toRefs(state);
|
||||
|
||||
const foods = foodStore.store;
|
||||
const units = unitStore.store;
|
||||
|
||||
@@ -1,15 +1,44 @@
|
||||
<template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="safeMarkup" />
|
||||
<div class="ingredient-link-label links-disabled">
|
||||
<SafeMarkdown v-if="baseText" :source="baseText" />
|
||||
<SafeMarkdown
|
||||
v-if="ingredient?.note"
|
||||
class="d-inline"
|
||||
:source="` ${ingredient.note}`"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { sanitizeIngredientHTML } from "~/composables/recipes/use-recipe-ingredients";
|
||||
import { computed } from "vue";
|
||||
import type { RecipeIngredient } from "~/lib/api/types/recipe";
|
||||
import { useParsedIngredientText } from "~/composables/recipes";
|
||||
|
||||
interface Props {
|
||||
markup: string;
|
||||
ingredient?: RecipeIngredient;
|
||||
scale?: number;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const safeMarkup = computed(() => sanitizeIngredientHTML(props.markup));
|
||||
const { ingredient, scale = 1 } = defineProps<Props>();
|
||||
|
||||
const baseText = computed(() => {
|
||||
if (!ingredient) return "";
|
||||
const parsed = useParsedIngredientText(ingredient, scale);
|
||||
return [parsed.quantity, parsed.unit, parsed.name].filter(Boolean).join(" ").trim();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ingredient-link-label {
|
||||
display: block;
|
||||
line-height: 1.25;
|
||||
word-break: break-word;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.links-disabled :deep(a) {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
color: var(--v-theme-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<RecipePageParseDialog
|
||||
:model-value="isParsing"
|
||||
:ingredients="recipe.recipeIngredient"
|
||||
:width="$vuetify.display.smAndDown ? '100%' : '80%'"
|
||||
@update:model-value="toggleIsParsing"
|
||||
@save="saveParsedIngredients"
|
||||
/>
|
||||
<v-container v-show="!isCookMode" key="recipe-page" class="px-0" :class="{ 'pa-0': $vuetify.display.smAndDown }">
|
||||
<v-card :flat="$vuetify.display.smAndDown" class="d-print-none">
|
||||
<RecipePageHeader
|
||||
@@ -106,9 +113,13 @@
|
||||
/>
|
||||
<v-divider />
|
||||
</v-col>
|
||||
<v-col class="overflow-y-auto"
|
||||
<v-col
|
||||
class="overflow-y-auto"
|
||||
:class="$vuetify.display.smAndDown ? 'py-2': 'py-6'"
|
||||
style="height: 100%" cols="12" sm="7">
|
||||
style="height: 100%"
|
||||
cols="12"
|
||||
sm="7"
|
||||
>
|
||||
<h2 class="text-h5 px-4 font-weight-medium opacity-80">
|
||||
{{ $t('recipe.instructions') }}
|
||||
</h2>
|
||||
@@ -168,6 +179,7 @@ import RecipePageIngredientEditor from "./RecipePageParts/RecipePageIngredientEd
|
||||
import RecipePageIngredientToolsView from "./RecipePageParts/RecipePageIngredientToolsView.vue";
|
||||
import RecipePageInstructions from "./RecipePageParts/RecipePageInstructions.vue";
|
||||
import RecipePageOrganizers from "./RecipePageParts/RecipePageOrganizers.vue";
|
||||
import RecipePageParseDialog from "./RecipePageParts/RecipePageParseDialog.vue";
|
||||
import RecipePageScale from "./RecipePageParts/RecipePageScale.vue";
|
||||
import RecipePageInfoEditor from "./RecipePageParts/RecipePageInfoEditor.vue";
|
||||
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
|
||||
@@ -178,12 +190,13 @@ import {
|
||||
usePageState,
|
||||
} from "~/composables/recipe-page/shared-state";
|
||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import type { Recipe, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||
import type { Recipe, RecipeCategory, RecipeIngredient, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||
import { useRouteQuery } from "~/composables/use-router";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { uuid4, deepCopy } from "~/composables/use-utils";
|
||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
|
||||
import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue";
|
||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||
import { useNavigationWarning } from "~/composables/use-navigation-warning";
|
||||
|
||||
const recipe = defineModel<NoUndefinedField<Recipe>>({ required: true });
|
||||
@@ -192,12 +205,13 @@ const display = useDisplay();
|
||||
const i18n = useI18n();
|
||||
const $auth = useMealieAuth();
|
||||
const route = useRoute();
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
|
||||
const groupSlug = computed(() => (route.params.groupSlug as string) || $auth.user?.value?.groupSlug || "");
|
||||
|
||||
const router = useRouter();
|
||||
const api = useUserApi();
|
||||
const { setMode, isEditForm, isEditJSON, isCookMode, isEditMode, toggleCookMode }
|
||||
const { setMode, isEditForm, isEditJSON, isCookMode, isEditMode, isParsing, toggleCookMode, toggleIsParsing }
|
||||
= usePageState(recipe.value.slug);
|
||||
const { deactivateNavigationWarning } = useNavigationWarning();
|
||||
const notLinkedIngredients = computed(() => {
|
||||
@@ -246,12 +260,29 @@ const hasLinkedIngredients = computed(() => {
|
||||
|
||||
type BooleanString = "true" | "false" | "";
|
||||
|
||||
const edit = useRouteQuery<BooleanString>("edit", "");
|
||||
const paramsEdit = useRouteQuery<BooleanString>("edit", "");
|
||||
const paramsParse = useRouteQuery<BooleanString>("parse", "");
|
||||
|
||||
onMounted(() => {
|
||||
if (edit.value === "true") {
|
||||
if (paramsEdit.value === "true" && isOwnGroup.value) {
|
||||
setMode(PageMode.EDIT);
|
||||
}
|
||||
|
||||
if (paramsParse.value === "true" && isOwnGroup.value) {
|
||||
toggleIsParsing(true);
|
||||
}
|
||||
});
|
||||
|
||||
watch(isEditMode, (newVal) => {
|
||||
if (!newVal) {
|
||||
paramsEdit.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
watch(isParsing, () => {
|
||||
if (!isParsing.value) {
|
||||
paramsParse.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
/** =============================================================
|
||||
@@ -266,6 +297,12 @@ async function saveRecipe() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveParsedIngredients(ingredients: NoUndefinedField<RecipeIngredient[]>) {
|
||||
recipe.value.recipeIngredient = ingredients;
|
||||
await saveRecipe();
|
||||
toggleIsParsing(false);
|
||||
}
|
||||
|
||||
async function deleteRecipe() {
|
||||
const { data } = await api.recipes.deleteOne(recipe.value.slug);
|
||||
if (data?.slug) {
|
||||
@@ -302,7 +339,7 @@ function addStep(steps: Array<string> | null = null) {
|
||||
|
||||
if (steps) {
|
||||
const cleanedSteps = steps.map((step) => {
|
||||
return { id: uuid4(), text: step, title: "", ingredientReferences: [] };
|
||||
return { id: uuid4(), text: step, title: "", summary: "", ingredientReferences: [] };
|
||||
});
|
||||
|
||||
recipe.value.recipeInstructions.push(...cleanedSteps);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
@@ -31,6 +30,8 @@
|
||||
v-for="(ingredient, index) in recipe.recipeIngredient"
|
||||
:key="ingredient.referenceId"
|
||||
v-model="recipe.recipeIngredient[index]"
|
||||
enable-drag-handle
|
||||
enable-context-menu
|
||||
class="list-group-item"
|
||||
@delete="recipe.recipeIngredient.splice(index, 1)"
|
||||
@insert-above="insertNewIngredient(index)"
|
||||
@@ -55,8 +56,8 @@
|
||||
class="mb-1"
|
||||
:disabled="hasFoodOrUnit"
|
||||
color="accent"
|
||||
:to="`/g/${groupSlug}/r/${recipe.slug}/ingredient-parser`"
|
||||
v-bind="props"
|
||||
@click="toggleIsParsing(true)"
|
||||
>
|
||||
<template #icon>
|
||||
{{ $globals.icons.foods }}
|
||||
@@ -87,16 +88,14 @@ import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import type { Recipe } from "~/lib/api/types/recipe";
|
||||
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
|
||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
|
||||
import { usePageState } from "~/composables/recipe-page/shared-state";
|
||||
import { uuid4 } from "~/composables/use-utils";
|
||||
|
||||
const recipe = defineModel<NoUndefinedField<Recipe>>({ required: true });
|
||||
const i18n = useI18n();
|
||||
const $auth = useMealieAuth();
|
||||
|
||||
const drag = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
||||
const { toggleIsParsing } = usePageState(recipe.value.slug);
|
||||
|
||||
const hasFoodOrUnit = computed(() => {
|
||||
if (!recipe.value) {
|
||||
@@ -128,7 +127,7 @@ function addIngredient(ingredients: Array<string> | null = null) {
|
||||
note: x,
|
||||
unit: undefined,
|
||||
food: undefined,
|
||||
quantity: 1,
|
||||
quantity: 0,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -146,7 +145,7 @@ function addIngredient(ingredients: Array<string> | null = null) {
|
||||
unit: undefined,
|
||||
// @ts-expect-error - prop can be null-type by NoUndefinedField type forces it to be set
|
||||
food: undefined,
|
||||
quantity: 1,
|
||||
quantity: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -160,7 +159,7 @@ function insertNewIngredient(dest: number) {
|
||||
unit: undefined,
|
||||
// @ts-expect-error - prop can be null-type by NoUndefinedField type forces it to be set
|
||||
food: undefined,
|
||||
quantity: 1,
|
||||
quantity: 0,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
class="ml-4"
|
||||
>
|
||||
<template #label>
|
||||
<RecipeIngredientHtml :markup="parseIngredientText(ing)" />
|
||||
<RecipeIngredientHtml :ingredient="ing" :scale="scale" />
|
||||
</template>
|
||||
</v-checkbox-btn>
|
||||
</template>
|
||||
@@ -67,7 +67,7 @@
|
||||
class="ml-4"
|
||||
>
|
||||
<template #label>
|
||||
<RecipeIngredientHtml :markup="parseIngredientText(ing)" />
|
||||
<RecipeIngredientHtml :ingredient="ing" :scale="scale" />
|
||||
</template>
|
||||
</v-checkbox-btn>
|
||||
</template>
|
||||
@@ -184,17 +184,17 @@
|
||||
<v-hover v-slot="{ isHovering }">
|
||||
<v-card
|
||||
class="my-3"
|
||||
:class="[{ 'on-hover': isHovering }, isChecked(index)]"
|
||||
:class="[{ 'on-hover': isHovering }, { 'cursor-default': isEditForm }, isChecked(index)]"
|
||||
:elevation="isHovering ? 12 : 2"
|
||||
:ripple="false"
|
||||
@click="toggleDisabled(index)"
|
||||
>
|
||||
<v-card-title :class="{ 'pb-0': !isChecked(index) }">
|
||||
<div class="d-flex align-center">
|
||||
<v-card-title class="recipe-step-title pt-3" :class="!isChecked(index) ? 'pb-0' : 'pb-3'">
|
||||
<div class="d-flex align-center w-100">
|
||||
<v-text-field
|
||||
v-if="isEditForm"
|
||||
v-model="step.summary"
|
||||
class="headline handle"
|
||||
class="headline"
|
||||
hide-details
|
||||
density="compact"
|
||||
variant="solo"
|
||||
@@ -202,14 +202,27 @@
|
||||
:placeholder="$t('recipe.step-index', { step: index + 1 })"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon size="26">
|
||||
<v-icon size="26" class="handle">
|
||||
{{ $globals.icons.arrowUpDown }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
<span v-else>
|
||||
{{ step.summary ? step.summary : $t("recipe.step-index", { step: index + 1 }) }}
|
||||
<div
|
||||
v-else
|
||||
class="summary-wrapper"
|
||||
>
|
||||
<template v-if="step.summary">
|
||||
<SafeMarkdown
|
||||
class="pr-2"
|
||||
:source="step.summary"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>
|
||||
{{ $t('recipe.step-index', { step: index + 1 }) }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="isEditForm">
|
||||
<div class="ml-auto">
|
||||
<BaseButtonGroup
|
||||
@@ -314,11 +327,22 @@
|
||||
persistentHint: true,
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
v-if="step.ingredientReferences && step.ingredientReferences.length"
|
||||
class="linked-ingredients-editor"
|
||||
>
|
||||
<div
|
||||
v-for="(linkRef, i) in step.ingredientReferences"
|
||||
:key="linkRef.referenceId ?? i"
|
||||
class="mb-1"
|
||||
>
|
||||
<RecipeIngredientHtml
|
||||
v-for="ing in step.ingredientReferences"
|
||||
:key="ing.referenceId!"
|
||||
:markup="getIngredientByRefId(ing.referenceId!)"
|
||||
v-if="linkRef.referenceId && ingredientLookup[linkRef.referenceId]"
|
||||
:ingredient="ingredientLookup[linkRef.referenceId]"
|
||||
:scale="scale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</DropZone>
|
||||
<v-expand-transition>
|
||||
@@ -373,9 +397,7 @@
|
||||
<script setup lang="ts">
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import RecipeIngredientHtml from "../../RecipeIngredientHtml.vue";
|
||||
import type { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset, Recipe } from "~/lib/api/types/recipe";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { uuid4 } from "~/composables/use-utils";
|
||||
import { useUserApi, useStaticRoutes } from "~/composables/api";
|
||||
import { usePageState } from "~/composables/recipe-page/shared-state";
|
||||
@@ -383,6 +405,7 @@ import { useExtractIngredientReferences } from "~/composables/recipe-page/use-ex
|
||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import DropZone from "~/components/global/DropZone.vue";
|
||||
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
|
||||
import RecipeIngredientHtml from "~/components/Domain/Recipe/RecipeIngredientHtml.vue";
|
||||
|
||||
interface MergerHistory {
|
||||
target: number;
|
||||
@@ -500,10 +523,9 @@ function openDialog(idx: number, text: string, refs?: IngredientReferences[]) {
|
||||
instructionList.value[idx].ingredientReferences = [];
|
||||
refs = instructionList.value[idx].ingredientReferences as IngredientReferences[];
|
||||
}
|
||||
|
||||
setUsedIngredients();
|
||||
activeText.value = text;
|
||||
activeIndex.value = idx;
|
||||
activeText.value = text;
|
||||
setUsedIngredients();
|
||||
dialog.value = true;
|
||||
activeRefs.value = refs.map(ref => ref.referenceId ?? "");
|
||||
}
|
||||
@@ -544,29 +566,26 @@ function saveAndOpenNextLinkIngredients() {
|
||||
function setUsedIngredients() {
|
||||
const usedRefs: { [key: string]: boolean } = {};
|
||||
|
||||
instructionList.value.forEach((element) => {
|
||||
instructionList.value.forEach((element, idx) => {
|
||||
if (idx === activeIndex.value) return;
|
||||
element.ingredientReferences?.forEach((ref) => {
|
||||
if (ref.referenceId !== undefined) {
|
||||
usedRefs[ref.referenceId!] = true;
|
||||
}
|
||||
if (ref.referenceId) usedRefs[ref.referenceId] = true;
|
||||
});
|
||||
});
|
||||
|
||||
usedIngredients.value = props.recipe.recipeIngredient.filter((ing) => {
|
||||
return ing.referenceId !== undefined && ing.referenceId in usedRefs;
|
||||
});
|
||||
usedIngredients.value = props.recipe.recipeIngredient.filter(ing => !!ing.referenceId && ing.referenceId in usedRefs);
|
||||
|
||||
unusedIngredients.value = props.recipe.recipeIngredient.filter((ing) => {
|
||||
return !(ing.referenceId !== undefined && ing.referenceId in usedRefs);
|
||||
});
|
||||
unusedIngredients.value = props.recipe.recipeIngredient.filter(ing => !!ing.referenceId && !(ing.referenceId in usedRefs));
|
||||
}
|
||||
|
||||
watch(activeRefs, () => setUsedIngredients());
|
||||
|
||||
function autoSetReferences() {
|
||||
useExtractIngredientReferences(
|
||||
props.recipe.recipeIngredient,
|
||||
activeRefs.value,
|
||||
activeText.value,
|
||||
).forEach((ingredient: string) => activeRefs.value.push(ingredient));
|
||||
).forEach(ingredient => activeRefs.value.push(ingredient));
|
||||
}
|
||||
|
||||
const ingredientLookup = computed(() => {
|
||||
@@ -603,8 +622,8 @@ const ingredientSectionTitles = computed(() => {
|
||||
return titleMap;
|
||||
});
|
||||
|
||||
const groupedUnusedIngredients = computed(() => {
|
||||
const groups: { [key: string]: RecipeIngredient[] } = {};
|
||||
const groupedUnusedIngredients = computed((): Record<string, RecipeIngredient[]> => {
|
||||
const groups: Record<string, RecipeIngredient[]> = {};
|
||||
|
||||
// Group ingredients by section title
|
||||
unusedIngredients.value.forEach((ingredient) => {
|
||||
@@ -614,20 +633,14 @@ const groupedUnusedIngredients = computed(() => {
|
||||
|
||||
// Use the section title from the mapping, or fallback to the ingredient's own title
|
||||
const title = ingredientSectionTitles.value[ingredient.referenceId] || ingredient.title || "";
|
||||
|
||||
if (!groups[title]) {
|
||||
groups[title] = [];
|
||||
}
|
||||
groups[title].push(ingredient);
|
||||
(groups[title] ||= []).push(ingredient);
|
||||
});
|
||||
|
||||
return groups;
|
||||
});
|
||||
|
||||
const groupedUsedIngredients = computed(() => {
|
||||
const groups: { [key: string]: RecipeIngredient[] } = {};
|
||||
|
||||
// Group ingredients by section title
|
||||
const groupedUsedIngredients = computed((): Record<string, RecipeIngredient[]> => {
|
||||
const groups: Record<string, RecipeIngredient[]> = {};
|
||||
usedIngredients.value.forEach((ingredient) => {
|
||||
if (ingredient.referenceId === undefined) {
|
||||
return;
|
||||
@@ -635,26 +648,12 @@ const groupedUsedIngredients = computed(() => {
|
||||
|
||||
// Use the section title from the mapping, or fallback to the ingredient's own title
|
||||
const title = ingredientSectionTitles.value[ingredient.referenceId] || ingredient.title || "";
|
||||
|
||||
if (!groups[title]) {
|
||||
groups[title] = [];
|
||||
}
|
||||
groups[title].push(ingredient);
|
||||
(groups[title] ||= []).push(ingredient);
|
||||
});
|
||||
|
||||
return groups;
|
||||
});
|
||||
|
||||
function getIngredientByRefId(refId: string | undefined) {
|
||||
if (refId === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const ing = ingredientLookup.value[refId];
|
||||
if (!ing) return "";
|
||||
return parseIngredientText(ing, props.scale);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// Instruction Merger
|
||||
const mergeHistory = ref<MergerHistory[]>([]);
|
||||
@@ -847,7 +846,21 @@ function openImageUpload(index: number) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.v-text-field >>> input {
|
||||
.v-text-field :deep(input) {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.recipe-step-title {
|
||||
/* Multiline display */
|
||||
white-space: normal;
|
||||
line-height: 1.25;
|
||||
word-break: break-word;
|
||||
}
|
||||
.summary-wrapper {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0; /* wrapping in flex container */
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,538 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
:title="$t('recipe.parse-ingredients')"
|
||||
:icon="$globals.icons.fileSign"
|
||||
disable-submit-on-enter
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<v-container fluid class="pa-2 ma-0" style="background-color: rgb(var(--v-theme-background));">
|
||||
<div v-if="state.loading.parser" class="my-6">
|
||||
<AppLoader waiting-text="" class="my-6" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<BaseCardSectionTitle :title="$t('recipe.parser.ingredient-parser')">
|
||||
<div v-if="!state.allReviewed" class="mb-4">
|
||||
<p>{{ $t("recipe.parser.ingredient-parser-description") }}</p>
|
||||
<p>{{ $t("recipe.parser.ingredient-parser-final-review-description") }}</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-center">
|
||||
<div class="text-body-2 mr-2">
|
||||
{{ $t("recipe.parser.select-parser") }}
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<BaseOverflowButton
|
||||
v-model="parser"
|
||||
:disabled="state.loading.parser"
|
||||
btn-class="mx-2"
|
||||
:items="availableParsers"
|
||||
/>
|
||||
<v-btn
|
||||
icon
|
||||
size="40"
|
||||
color="info"
|
||||
:disabled="state.loading.parser"
|
||||
@click="parseIngredients"
|
||||
>
|
||||
<v-icon>{{ $globals.icons.refresh }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</BaseCardSectionTitle>
|
||||
<v-card v-if="!state.allReviewed && currentIng">
|
||||
<v-card-text class="pb-0 mb-0">
|
||||
<div class="text-center px-8 py-4 mb-6">
|
||||
<p class="text-h5 font-italic">
|
||||
{{ currentIng.input }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-0 ma-0">
|
||||
<v-icon
|
||||
:color="(currentIng.confidence?.average || 0) < confidenceThreshold ? 'error' : 'success'"
|
||||
>
|
||||
{{ (currentIng.confidence?.average || 0) < confidenceThreshold ? $globals.icons.alert : $globals.icons.check }}
|
||||
</v-icon>
|
||||
<span
|
||||
class="ml-2"
|
||||
:color="currentIngHasError ? 'error-text' : 'success-text'"
|
||||
>
|
||||
{{ $t("recipe.parser.confidence-score") }}: {{ currentIng.confidence ? asPercentage(currentIng.confidence?.average!) : "" }}
|
||||
</span>
|
||||
</div>
|
||||
<RecipeIngredientEditor
|
||||
v-model="currentIng.ingredient"
|
||||
:unit-error="!!currentMissingUnit"
|
||||
:unit-error-tooltip="$t('recipe.parser.this-unit-could-not-be-parsed-automatically')"
|
||||
:food-error="!!currentMissingFood"
|
||||
:food-error-tooltip="$t('recipe.parser.this-food-could-not-be-parsed-automatically')"
|
||||
/>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<BaseButton
|
||||
v-if="currentMissingUnit && !currentIng.ingredient.unit?.id"
|
||||
color="warning"
|
||||
size="small"
|
||||
@click="createMissingUnit"
|
||||
>
|
||||
{{ i18n.t("recipe.parser.missing-unit", { unit: currentMissingUnit }) }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="
|
||||
currentMissingUnit
|
||||
&& currentIng.ingredient.unit?.id
|
||||
&& currentMissingUnit.toLowerCase() != currentIng.ingredient.unit?.name.toLowerCase()
|
||||
"
|
||||
color="warning"
|
||||
size="small"
|
||||
@click="addMissingUnitAsAlias"
|
||||
>
|
||||
{{ i18n.t("recipe.parser.add-text-as-alias-for-item", { text: currentMissingUnit, item: currentIng.ingredient.unit.name }) }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="currentMissingFood && !currentIng.ingredient.food?.id"
|
||||
color="warning"
|
||||
size="small"
|
||||
@click="createMissingFood"
|
||||
>
|
||||
{{ i18n.t("recipe.parser.missing-food", { food: currentMissingFood }) }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="
|
||||
currentMissingFood
|
||||
&& currentIng.ingredient.food?.id
|
||||
&& currentMissingFood.toLowerCase() != currentIng.ingredient.food?.name.toLowerCase()
|
||||
"
|
||||
color="warning"
|
||||
size="small"
|
||||
@click="addMissingFoodAsAlias"
|
||||
>
|
||||
{{ i18n.t("recipe.parser.add-text-as-alias-for-item", { text: currentMissingFood, item: currentIng.ingredient.food.name }) }}
|
||||
</BaseButton>
|
||||
</v-card-actions>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<div v-else>
|
||||
<v-card-title class="text-center pt-0 pb-8">
|
||||
{{ $t("recipe.parser.review-parsed-ingredients") }}
|
||||
</v-card-title>
|
||||
<v-card-text style="max-height: 60vh; overflow-y: auto;">
|
||||
<VueDraggable
|
||||
v-model="parsedIngs"
|
||||
handle=".handle"
|
||||
:delay="250"
|
||||
:delay-on-touch-only="true"
|
||||
v-bind="{
|
||||
animation: 200,
|
||||
group: 'recipe-ingredients',
|
||||
disabled: false,
|
||||
ghostClass: 'ghost',
|
||||
}"
|
||||
class="px-6"
|
||||
@start="drag = true"
|
||||
@end="drag = false"
|
||||
>
|
||||
<TransitionGroup
|
||||
type="transition"
|
||||
>
|
||||
<v-lazy v-for="(ingredient, index) in parsedIngs" :key="index">
|
||||
<RecipeIngredientEditor
|
||||
v-model="ingredient.ingredient"
|
||||
enable-drag-handle
|
||||
enable-context-menu
|
||||
class="list-group-item pb-8"
|
||||
:delete-disabled="parsedIngs.length <= 1"
|
||||
@delete="parsedIngs.splice(index, 1)"
|
||||
@insert-above="insertNewIngredient(index)"
|
||||
@insert-below="insertNewIngredient(index + 1)"
|
||||
>
|
||||
<template #before-divider>
|
||||
<p v-if="ingredient.input" class="py-0 my-0 text-caption">
|
||||
{{ $t("recipe.original-text-with-value", { originalText: ingredient.input }) }}
|
||||
</p>
|
||||
</template>
|
||||
</RecipeIngredientEditor>
|
||||
</v-lazy>
|
||||
</TransitionGroup>
|
||||
</VueDraggable>
|
||||
</v-card-text>
|
||||
</div>
|
||||
</div>
|
||||
</v-container>
|
||||
<template v-if="!state.loading.parser" #custom-card-action>
|
||||
<!-- Parse -->
|
||||
<div v-if="!state.allReviewed" class="d-flex justify-space-between align-center">
|
||||
<v-checkbox
|
||||
v-model="currentIngShouldDelete"
|
||||
color="error"
|
||||
hide-details
|
||||
density="compact"
|
||||
:label="i18n.t('recipe.parser.delete-item')"
|
||||
class="mr-4"
|
||||
/>
|
||||
<BaseButton
|
||||
:color="currentIngShouldDelete ? 'error' : 'info'"
|
||||
:icon="currentIngShouldDelete ? $globals.icons.delete : $globals.icons.arrowRightBold"
|
||||
:icon-right="!currentIngShouldDelete"
|
||||
:text="$t(currentIngShouldDelete ? 'recipe.parser.delete-item' : 'general.next')"
|
||||
@click="nextIngredient"
|
||||
/>
|
||||
</div>
|
||||
<!-- Review -->
|
||||
<div v-else>
|
||||
<BaseButton
|
||||
create
|
||||
:text="$t('general.save')"
|
||||
:icon="$globals.icons.save"
|
||||
:loading="state.loading.save"
|
||||
@click="saveIngs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import type { IngredientFood, IngredientUnit, ParsedIngredient, RecipeIngredient } from "~/lib/api/types/recipe";
|
||||
import type { Parser } from "~/lib/api/user/recipes/recipe";
|
||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { useAppInfo, useUserApi } from "~/composables/api";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { useFoodData, useFoodStore, useUnitData, useUnitStore } from "~/composables/store";
|
||||
import { useGlobalI18n } from "~/composables/use-global-i18n";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { useParsingPreferences } from "~/composables/use-users/preferences";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
ingredients: NoUndefinedField<RecipeIngredient[]>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
(e: "save", value: NoUndefinedField<RecipeIngredient[]>): void;
|
||||
}>();
|
||||
|
||||
const i18n = useGlobalI18n();
|
||||
const api = useUserApi();
|
||||
const appInfo = useAppInfo();
|
||||
const drag = ref(false);
|
||||
|
||||
const unitStore = useUnitStore();
|
||||
const unitData = useUnitData();
|
||||
const foodStore = useFoodStore();
|
||||
const foodData = useFoodData();
|
||||
|
||||
const parserPreferences = useParsingPreferences();
|
||||
const parser = ref<Parser>(parserPreferences.value.parser || "nlp");
|
||||
const availableParsers = computed(() => {
|
||||
return [
|
||||
{
|
||||
text: i18n.t("recipe.parser.natural-language-processor"),
|
||||
value: "nlp",
|
||||
},
|
||||
{
|
||||
text: i18n.t("recipe.parser.brute-parser"),
|
||||
value: "brute",
|
||||
},
|
||||
{
|
||||
text: i18n.t("recipe.parser.openai-parser"),
|
||||
value: "openai",
|
||||
hide: !appInfo.value?.enableOpenai,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
/**
|
||||
* If confidence of parsing is below this threshold,
|
||||
* we will prompt the user to review the parsed ingredient.
|
||||
*/
|
||||
const confidenceThreshold = 0.85;
|
||||
const parsedIngs = ref<ParsedIngredient[]>([]);
|
||||
|
||||
const currentIng = ref<ParsedIngredient | null>(null);
|
||||
const currentMissingUnit = ref("");
|
||||
const currentMissingFood = ref("");
|
||||
const currentIngHasError = computed(() => currentMissingUnit.value || currentMissingFood.value);
|
||||
const currentIngShouldDelete = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
currentParsedIndex: -1,
|
||||
allReviewed: false,
|
||||
loading: {
|
||||
parser: false,
|
||||
save: false,
|
||||
},
|
||||
});
|
||||
|
||||
function shouldReview(ing: ParsedIngredient): boolean {
|
||||
console.debug(`Checking if ingredient needs review (input="${ing.input})":`, ing);
|
||||
|
||||
if ((ing.confidence?.average || 0) < confidenceThreshold) {
|
||||
console.debug("Needs review due to low confidence:", ing.confidence?.average);
|
||||
return true;
|
||||
}
|
||||
|
||||
const food = ing.ingredient.food;
|
||||
if (food && !food.id) {
|
||||
console.debug("Needs review due to missing food ID:", food);
|
||||
return true;
|
||||
}
|
||||
|
||||
const unit = ing.ingredient.unit;
|
||||
if (unit && !unit.id) {
|
||||
console.debug("Needs review due to missing unit ID:", unit);
|
||||
return true;
|
||||
}
|
||||
|
||||
console.debug("No review needed");
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkUnit(ing: ParsedIngredient) {
|
||||
const unit = ing.ingredient.unit?.name;
|
||||
if (!unit || ing.ingredient.unit?.id) {
|
||||
currentMissingUnit.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialMatch = createdUnits.get(unit.toLowerCase());
|
||||
if (potentialMatch) {
|
||||
ing.ingredient.unit = potentialMatch;
|
||||
currentMissingUnit.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
currentMissingUnit.value = unit;
|
||||
ing.ingredient.unit = undefined;
|
||||
}
|
||||
|
||||
function checkFood(ing: ParsedIngredient) {
|
||||
const food = ing.ingredient.food?.name;
|
||||
if (!food || ing.ingredient.food?.id) {
|
||||
currentMissingFood.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialMatch = createdFoods.get(food.toLowerCase());
|
||||
if (potentialMatch) {
|
||||
ing.ingredient.food = potentialMatch;
|
||||
currentMissingFood.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
currentMissingFood.value = food;
|
||||
ing.ingredient.food = undefined;
|
||||
}
|
||||
|
||||
function nextIngredient() {
|
||||
let nextIndex = state.currentParsedIndex;
|
||||
if (currentIngShouldDelete.value) {
|
||||
parsedIngs.value.splice(state.currentParsedIndex, 1);
|
||||
currentIngShouldDelete.value = false;
|
||||
}
|
||||
else {
|
||||
nextIndex += 1;
|
||||
}
|
||||
|
||||
while (nextIndex < parsedIngs.value.length) {
|
||||
const current = parsedIngs.value[nextIndex];
|
||||
if (shouldReview(current)) {
|
||||
state.currentParsedIndex = nextIndex;
|
||||
currentIng.value = current;
|
||||
currentIngShouldDelete.value = false;
|
||||
checkUnit(current);
|
||||
checkFood(current);
|
||||
return;
|
||||
}
|
||||
|
||||
nextIndex += 1;
|
||||
}
|
||||
|
||||
// No more to review
|
||||
state.allReviewed = true;
|
||||
}
|
||||
|
||||
async function parseIngredients() {
|
||||
if (state.loading.parser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!props.ingredients || props.ingredients.length === 0) {
|
||||
state.loading.parser = false;
|
||||
return;
|
||||
}
|
||||
state.loading.parser = true;
|
||||
try {
|
||||
const ingsAsString = props.ingredients.map(ing => parseIngredientText(ing, 1, false) ?? "");
|
||||
const { data, error } = await api.recipes.parseIngredients(parser.value, ingsAsString);
|
||||
if (error || !data) {
|
||||
throw new Error("Failed to parse ingredients");
|
||||
}
|
||||
parsedIngs.value = data;
|
||||
state.currentParsedIndex = -1;
|
||||
state.allReviewed = false;
|
||||
createdUnits.clear();
|
||||
createdFoods.clear();
|
||||
currentIngShouldDelete.value = false;
|
||||
nextIngredient();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error parsing ingredients:", error);
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
}
|
||||
finally {
|
||||
state.loading.parser = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Cache of lowercased created units to avoid duplicate creations */
|
||||
const createdUnits = new Map<string, IngredientUnit>();
|
||||
/** Cache of lowercased created foods to avoid duplicate creations */
|
||||
const createdFoods = new Map<string, IngredientFood>();
|
||||
|
||||
async function createMissingUnit() {
|
||||
if (!currentMissingUnit.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
unitData.reset();
|
||||
unitData.data.name = currentMissingUnit.value;
|
||||
|
||||
let newUnit: IngredientUnit | null = null;
|
||||
if (createdUnits.has(unitData.data.name)) {
|
||||
newUnit = createdUnits.get(unitData.data.name)!;
|
||||
}
|
||||
else {
|
||||
newUnit = await unitStore.actions.createOne(unitData.data);
|
||||
}
|
||||
|
||||
if (!newUnit) {
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
return;
|
||||
}
|
||||
|
||||
currentIng.value!.ingredient.unit = newUnit;
|
||||
createdUnits.set(newUnit.name.toLowerCase(), newUnit);
|
||||
currentMissingUnit.value = "";
|
||||
}
|
||||
|
||||
async function createMissingFood() {
|
||||
if (!currentMissingFood.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
foodData.reset();
|
||||
foodData.data.name = currentMissingFood.value;
|
||||
|
||||
let newFood: IngredientFood | null = null;
|
||||
if (createdFoods.has(foodData.data.name)) {
|
||||
newFood = createdFoods.get(foodData.data.name)!;
|
||||
}
|
||||
else {
|
||||
newFood = await foodStore.actions.createOne(foodData.data);
|
||||
}
|
||||
|
||||
if (!newFood) {
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
return;
|
||||
}
|
||||
|
||||
currentIng.value!.ingredient.food = newFood;
|
||||
createdFoods.set(newFood.name.toLowerCase(), newFood);
|
||||
currentMissingFood.value = "";
|
||||
}
|
||||
|
||||
async function addMissingUnitAsAlias() {
|
||||
const unit = currentIng.value?.ingredient.unit as IngredientUnit | undefined;
|
||||
if (!currentMissingUnit.value || !unit?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
unit.aliases = unit.aliases || [];
|
||||
if (unit.aliases.map(a => a.name).includes(currentMissingUnit.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unit.aliases.push({ name: currentMissingUnit.value });
|
||||
const updated = await unitStore.actions.updateOne(unit);
|
||||
if (!updated) {
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
return;
|
||||
}
|
||||
|
||||
currentIng.value!.ingredient.unit = updated;
|
||||
currentMissingUnit.value = "";
|
||||
}
|
||||
|
||||
async function addMissingFoodAsAlias() {
|
||||
const food = currentIng.value?.ingredient.food as IngredientFood | undefined;
|
||||
if (!currentMissingFood.value || !food?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
food.aliases = food.aliases || [];
|
||||
if (food.aliases.map(a => a.name).includes(currentMissingFood.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
food.aliases.push({ name: currentMissingFood.value });
|
||||
const updated = await foodStore.actions.updateOne(food);
|
||||
if (!updated) {
|
||||
alert.error(i18n.t("events.something-went-wrong"));
|
||||
return;
|
||||
}
|
||||
|
||||
currentIng.value!.ingredient.food = updated;
|
||||
currentMissingFood.value = "";
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
if (!props.modelValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
parseIngredients();
|
||||
});
|
||||
|
||||
watch(parser, () => {
|
||||
parserPreferences.value.parser = parser.value;
|
||||
parseIngredients();
|
||||
});
|
||||
|
||||
watch([parsedIngs, () => state.allReviewed], () => {
|
||||
if (!state.allReviewed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parsedIngs.value.length) {
|
||||
insertNewIngredient(0);
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
function asPercentage(num: number | undefined): string {
|
||||
if (!num) {
|
||||
return "0%";
|
||||
}
|
||||
|
||||
return Math.round(num * 100).toFixed(2) + "%";
|
||||
}
|
||||
|
||||
function insertNewIngredient(index: number) {
|
||||
const ing = {
|
||||
input: "",
|
||||
confidence: {},
|
||||
ingredient: {
|
||||
quantity: 0,
|
||||
referenceId: uuid4(),
|
||||
},
|
||||
} as ParsedIngredient;
|
||||
|
||||
parsedIngs.value.splice(index, 0, ing);
|
||||
}
|
||||
|
||||
function saveIngs() {
|
||||
emit("save", parsedIngs.value.map(x => x.ingredient as NoUndefinedField<RecipeIngredient>));
|
||||
state.loading.save = true;
|
||||
}
|
||||
</script>
|
||||
@@ -4,19 +4,22 @@
|
||||
<section>
|
||||
<v-container class="ma-0 pa-0">
|
||||
<v-row>
|
||||
<v-col v-if="preferences.imagePosition && preferences.imagePosition != ImagePosition.hidden"
|
||||
<v-col
|
||||
v-if="preferences.imagePosition && preferences.imagePosition != ImagePosition.hidden"
|
||||
:order="preferences.imagePosition == ImagePosition.left ? -1 : 1"
|
||||
cols="4"
|
||||
align-self="center"
|
||||
>
|
||||
<img :key="imageKey"
|
||||
<img
|
||||
:key="imageKey"
|
||||
:src="recipeImageUrl"
|
||||
style="min-height: 50; max-width: 100%;"
|
||||
>
|
||||
</v-col>
|
||||
<v-col order="0">
|
||||
<v-card-title class="headline pl-0">
|
||||
<v-icon start
|
||||
<v-icon
|
||||
start
|
||||
color="primary"
|
||||
>
|
||||
{{ $globals.icons.primary }}
|
||||
@@ -36,7 +39,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<v-row class="d-flex justify-start">
|
||||
<RecipeTimeCard :prep-time="recipe.prepTime"
|
||||
<RecipeTimeCard
|
||||
:prep-time="recipe.prepTime"
|
||||
:total-time="recipe.totalTime"
|
||||
:perform-time="recipe.performTime"
|
||||
small
|
||||
@@ -45,7 +49,8 @@
|
||||
/>
|
||||
</v-row>
|
||||
|
||||
<v-card-text v-if="preferences.showDescription"
|
||||
<v-card-text
|
||||
v-if="preferences.showDescription"
|
||||
class="px-0"
|
||||
>
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
@@ -60,23 +65,28 @@
|
||||
<v-card-title class="headline pl-0">
|
||||
{{ $t("recipe.ingredients") }}
|
||||
</v-card-title>
|
||||
<div v-for="(ingredientSection, sectionIndex) in ingredientSections"
|
||||
<div
|
||||
v-for="(ingredientSection, sectionIndex) in ingredientSections"
|
||||
:key="`ingredient-section-${sectionIndex}`"
|
||||
class="print-section"
|
||||
>
|
||||
<h4 v-if="ingredientSection.ingredients[0].title"
|
||||
<h4
|
||||
v-if="ingredientSection.ingredients[0].title"
|
||||
class="ingredient-title mt-2"
|
||||
>
|
||||
{{ ingredientSection.ingredients[0].title }}
|
||||
</h4>
|
||||
<div class="ingredient-grid"
|
||||
<div
|
||||
class="ingredient-grid"
|
||||
:style="{ gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
|
||||
>
|
||||
<template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients"
|
||||
<template
|
||||
v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients"
|
||||
:key="`ingredient-${ingredientIndex}`"
|
||||
>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<p class="ingredient-body"
|
||||
<p
|
||||
class="ingredient-body"
|
||||
v-html="parseText(ingredient)"
|
||||
/>
|
||||
</template>
|
||||
@@ -86,20 +96,24 @@
|
||||
|
||||
<!-- Instructions -->
|
||||
<section>
|
||||
<div v-for="(instructionSection, sectionIndex) in instructionSections"
|
||||
<div
|
||||
v-for="(instructionSection, sectionIndex) in instructionSections"
|
||||
:key="`instruction-section-${sectionIndex}`"
|
||||
:class="{ 'print-section': instructionSection.sectionName }"
|
||||
>
|
||||
<v-card-title v-if="!sectionIndex"
|
||||
<v-card-title
|
||||
v-if="!sectionIndex"
|
||||
class="headline pl-0"
|
||||
>
|
||||
{{ $t("recipe.instructions") }}
|
||||
</v-card-title>
|
||||
<div v-for="(step, stepIndex) in instructionSection.instructions"
|
||||
<div
|
||||
v-for="(step, stepIndex) in instructionSection.instructions"
|
||||
:key="`instruction-${stepIndex}`"
|
||||
>
|
||||
<div class="print-section">
|
||||
<h4 v-if="step.title"
|
||||
<h4
|
||||
v-if="step.title"
|
||||
:key="`instruction-title-${stepIndex}`"
|
||||
class="instruction-title mb-2"
|
||||
>
|
||||
@@ -112,7 +126,8 @@
|
||||
+ 1,
|
||||
}) }}
|
||||
</h5>
|
||||
<SafeMarkdown :source="step.text"
|
||||
<SafeMarkdown
|
||||
:source="step.text"
|
||||
class="recipe-step-body"
|
||||
/>
|
||||
</div>
|
||||
@@ -122,17 +137,20 @@
|
||||
|
||||
<!-- Notes -->
|
||||
<div v-if="preferences.showNotes">
|
||||
<v-divider v-if="hasNotes"
|
||||
<v-divider
|
||||
v-if="hasNotes"
|
||||
class="grey my-4"
|
||||
/>
|
||||
|
||||
<section>
|
||||
<div v-for="(note, index) in recipe.notes"
|
||||
<div
|
||||
v-for="(note, index) in recipe.notes"
|
||||
:key="index + 'note'"
|
||||
>
|
||||
<div class="print-section">
|
||||
<h4>{{ note.title }}</h4>
|
||||
<SafeMarkdown :source="note.text"
|
||||
<SafeMarkdown
|
||||
:source="note.text"
|
||||
class="note-body"
|
||||
/>
|
||||
</div>
|
||||
@@ -150,7 +168,8 @@
|
||||
<div class="print-section">
|
||||
<table class="nutrition-table">
|
||||
<tbody>
|
||||
<tr v-for="(value, key) in recipe.nutrition"
|
||||
<tr
|
||||
v-for="(value, key) in recipe.nutrition"
|
||||
:key="key"
|
||||
>
|
||||
<template v-if="value">
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div @click.prevent>
|
||||
<!-- User Rating -->
|
||||
<v-hover v-slot="{ isHovering, props }">
|
||||
<v-rating v-if="isOwnGroup && (userRating || isHovering || !ratingsLoaded)"
|
||||
<v-rating
|
||||
v-if="isOwnGroup && (userRating || isHovering || !ratingsLoaded)"
|
||||
v-bind="props"
|
||||
:model-value="userRating"
|
||||
active-color="secondary"
|
||||
@@ -13,10 +14,10 @@
|
||||
hover
|
||||
clearable
|
||||
@update:model-value="updateRating(+$event)"
|
||||
@click="updateRating"
|
||||
/>
|
||||
<!-- Group Rating -->
|
||||
<v-rating v-else
|
||||
<v-rating
|
||||
v-else
|
||||
v-bind="props"
|
||||
:model-value="groupRating"
|
||||
:half-increments="true"
|
||||
@@ -83,7 +84,7 @@ export default defineNuxtComponent({
|
||||
});
|
||||
|
||||
function updateRating(val?: number) {
|
||||
if (!isOwnGroup.value) {
|
||||
if (!isOwnGroup.value || !val) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,9 +100,7 @@ import type { SideBarLink } from "~/types/application-types";
|
||||
import { useAppInfo } from "~/composables/api";
|
||||
import { useCookbookPreferences } from "~/composables/use-users/preferences";
|
||||
import { useCookbookStore, usePublicCookbookStore } from "~/composables/store/use-cookbook-store";
|
||||
import { useHouseholdStore, usePublicHouseholdStore } from "~/composables/store/use-household-store";
|
||||
import type { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||
import type { HouseholdSummary } from "~/lib/api/types/household";
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup() {
|
||||
@@ -116,12 +114,8 @@ export default defineNuxtComponent({
|
||||
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
||||
|
||||
const cookbookPreferences = useCookbookPreferences();
|
||||
|
||||
const ownCookbookStore = useCookbookStore(i18n);
|
||||
const ownHouseholdStore = useHouseholdStore(i18n);
|
||||
|
||||
const publicCookbookStoreCache = ref<Record<string, ReturnType<typeof usePublicCookbookStore>>>({});
|
||||
const publicHouseholdStoreCache = ref<Record<string, ReturnType<typeof usePublicHouseholdStore>>>({});
|
||||
|
||||
function getPublicCookbookStore(slug: string) {
|
||||
if (!publicCookbookStoreCache.value[slug]) {
|
||||
@@ -130,13 +124,6 @@ export default defineNuxtComponent({
|
||||
return publicCookbookStoreCache.value[slug];
|
||||
}
|
||||
|
||||
function getPublicHouseholdStore(slug: string) {
|
||||
if (!publicHouseholdStoreCache.value[slug]) {
|
||||
publicHouseholdStoreCache.value[slug] = usePublicHouseholdStore(slug, i18n);
|
||||
}
|
||||
return publicHouseholdStoreCache.value[slug];
|
||||
}
|
||||
|
||||
const cookbooks = computed(() => {
|
||||
if (isOwnGroup.value) {
|
||||
return ownCookbookStore.store.value;
|
||||
@@ -148,24 +135,6 @@ export default defineNuxtComponent({
|
||||
return [];
|
||||
});
|
||||
|
||||
const households = computed(() => {
|
||||
if (isOwnGroup.value) {
|
||||
return ownHouseholdStore.store.value;
|
||||
}
|
||||
else if (groupSlug.value) {
|
||||
const publicStore = getPublicHouseholdStore(groupSlug.value);
|
||||
return unref(publicStore.store);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const householdsById = computed(() => {
|
||||
return households.value.reduce((acc, household) => {
|
||||
acc[household.id] = household;
|
||||
return acc;
|
||||
}, {} as { [key: string]: HouseholdSummary });
|
||||
});
|
||||
|
||||
const appInfo = useAppInfo();
|
||||
const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices);
|
||||
|
||||
@@ -197,11 +166,8 @@ export default defineNuxtComponent({
|
||||
const ownLinks: SideBarLink[] = [];
|
||||
const links: SideBarLink[] = [];
|
||||
const cookbooksByHousehold = sortedCookbooks.reduce((acc, cookbook) => {
|
||||
const householdName = householdsById.value[cookbook.householdId]?.name || "";
|
||||
if (!acc[householdName]) {
|
||||
acc[householdName] = [];
|
||||
}
|
||||
acc[householdName].push(cookbook);
|
||||
const householdName = cookbook.household?.name || "";
|
||||
(acc[householdName] ||= []).push(cookbook);
|
||||
return acc;
|
||||
}, {} as Record<string, ReadCookBook[]>);
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ export default defineNuxtComponent({
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await $auth.signOut({ callbackUrl: "/login?direct=1" });
|
||||
await $auth.signOut("/login?direct=1");
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -33,20 +33,39 @@
|
||||
<template v-for="nav in topLink">
|
||||
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
||||
<!-- Multi Items -->
|
||||
<v-list-group v-if="nav.children" :key="(nav.key || nav.title) + 'multi-item'"
|
||||
v-model="dropDowns[nav.title]" color="primary" :prepend-icon="nav.icon" :fluid="true">
|
||||
<v-list-group
|
||||
v-if="nav.children"
|
||||
:key="(nav.key || nav.title) + 'multi-item'"
|
||||
v-model="dropDowns[nav.title]"
|
||||
color="primary"
|
||||
:prepend-icon="nav.icon"
|
||||
:fluid="true"
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
<v-list-item v-bind="props" :prepend-icon="nav.icon" :title="nav.title" />
|
||||
</template>
|
||||
|
||||
<v-list-item v-for="child in nav.children" :key="child.key || child.title" exact :to="child.to"
|
||||
:prepend-icon="child.icon" :title="child.title" class="ml-4" />
|
||||
<v-list-item
|
||||
v-for="child in nav.children"
|
||||
:key="child.key || child.title"
|
||||
exact
|
||||
:to="child.to"
|
||||
:prepend-icon="child.icon"
|
||||
:title="child.title"
|
||||
class="ml-4"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Single Item -->
|
||||
<template v-else>
|
||||
<v-list-item :key="(nav.key || nav.title) + 'single-item'" exact link :to="nav.to"
|
||||
:prepend-icon="nav.icon" :title="nav.title" />
|
||||
<v-list-item
|
||||
:key="(nav.key || nav.title) + 'single-item'"
|
||||
exact
|
||||
link
|
||||
:to="nav.to"
|
||||
:prepend-icon="nav.icon"
|
||||
:title="nav.title"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -60,14 +79,27 @@
|
||||
<template v-for="nav in secondaryLinks">
|
||||
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
||||
<!-- Multi Items -->
|
||||
<v-list-group v-if="nav.children" :key="(nav.key || nav.title) + 'multi-item'"
|
||||
v-model="dropDowns[nav.title]" color="primary" :prepend-icon="nav.icon" fluid>
|
||||
<v-list-group
|
||||
v-if="nav.children"
|
||||
:key="(nav.key || nav.title) + 'multi-item'"
|
||||
v-model="dropDowns[nav.title]"
|
||||
color="primary"
|
||||
:prepend-icon="nav.icon"
|
||||
fluid
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
<v-list-item v-bind="props" :prepend-icon="nav.icon" :title="nav.title" />
|
||||
</template>
|
||||
|
||||
<v-list-item v-for="child in nav.children" :key="child.key || child.title" exact :to="child.to"
|
||||
class="ml-2" :prepend-icon="child.icon" :title="child.title" />
|
||||
<v-list-item
|
||||
v-for="child in nav.children"
|
||||
:key="child.key || child.title"
|
||||
exact
|
||||
:to="child.to"
|
||||
class="ml-2"
|
||||
:prepend-icon="child.icon"
|
||||
:title="child.title"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Single Item -->
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { size } = withDefaults(defineProps<{ size?: number }>(), { size: 75 });
|
||||
withDefaults(defineProps<{ size?: number }>(), { size: 75 });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
:hide-details="!inputField.hint"
|
||||
:persistent-hint="!!inputField.hint"
|
||||
density="comfortable"
|
||||
@change="emitBlur">
|
||||
@change="emitBlur"
|
||||
>
|
||||
<template #label>
|
||||
<span class="ml-4">
|
||||
{{ inputField.label }}
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
:max-width="maxWidth ?? undefined"
|
||||
:content-class="top ? 'top-dialog' : undefined"
|
||||
:fullscreen="$vuetify.display.xs"
|
||||
@keydown.enter="() => {
|
||||
emit('submit'); dialog = false;
|
||||
}"
|
||||
@keydown.enter="submitOnEnter"
|
||||
@click:outside="emit('cancel')"
|
||||
@keydown.esc="emit('cancel')"
|
||||
>
|
||||
@@ -127,6 +125,7 @@ interface DialogProps {
|
||||
canDelete?: boolean;
|
||||
canConfirm?: boolean;
|
||||
canSubmit?: boolean;
|
||||
disableSubmitOnEnter?: boolean;
|
||||
}
|
||||
|
||||
interface DialogEmits {
|
||||
@@ -150,6 +149,7 @@ const props = withDefaults(defineProps<DialogProps>(), {
|
||||
canDelete: false,
|
||||
canConfirm: false,
|
||||
canSubmit: false,
|
||||
disableSubmitOnEnter: false,
|
||||
});
|
||||
const emit = defineEmits<DialogEmits>();
|
||||
|
||||
@@ -181,6 +181,14 @@ function submitEvent() {
|
||||
submitted.value = true;
|
||||
}
|
||||
|
||||
function submitOnEnter() {
|
||||
if (props.disableSubmitOnEnter) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitEvent();
|
||||
}
|
||||
|
||||
function deleteEvent() {
|
||||
emit("delete");
|
||||
submitted.value = true;
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { useAsyncKey } from "../use-utils";
|
||||
import type { AppInfo } from "~/lib/api/types/admin";
|
||||
|
||||
export function useAppInfo(): Ref<AppInfo | null> {
|
||||
const appInfo = ref<null | AppInfo>(null);
|
||||
|
||||
const i18n = useI18n();
|
||||
const { $axios } = useNuxtApp();
|
||||
$axios.defaults.headers.common["Accept-Language"] = i18n.locale.value;
|
||||
|
||||
useAsyncData(useAsyncKey(), async () => {
|
||||
const { data: appInfo } = useAsyncData("app-info", async () => {
|
||||
const data = await $axios.get<AppInfo>("/api/app/about");
|
||||
appInfo.value = data.data;
|
||||
return data.data;
|
||||
});
|
||||
|
||||
return appInfo;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useAsyncKey } from "../use-utils";
|
||||
import type { AsyncData, NuxtError } from "#app";
|
||||
import type { BoundT } from "./types";
|
||||
import type { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
|
||||
import type { QueryValue } from "~/lib/api/base/route";
|
||||
|
||||
interface ReadOnlyStoreActions<T extends BoundT> {
|
||||
getAll(page?: number, perPage?: number, params?: any): Ref<T[] | null>;
|
||||
getAll(page?: number, perPage?: number, params?: any): AsyncData<T[] | null, NuxtError<unknown> | null>;
|
||||
refresh(page?: number, perPage?: number, params?: any): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ interface StoreActions<T extends BoundT> extends ReadOnlyStoreActions<T> {
|
||||
* a lot of refreshing hooks to be called on operations
|
||||
*/
|
||||
export function useReadOnlyActions<T extends BoundT>(
|
||||
storeKey: string,
|
||||
api: BaseCRUDAPIReadOnly<T>,
|
||||
allRef: Ref<T[] | null> | null,
|
||||
loading: Ref<boolean>,
|
||||
@@ -29,10 +30,10 @@ export function useReadOnlyActions<T extends BoundT>(
|
||||
params.orderBy ??= "name";
|
||||
params.orderDirection ??= "asc";
|
||||
|
||||
const allItems = useAsyncData(storeKey, async () => {
|
||||
loading.value = true;
|
||||
const allItems = useAsyncData(useAsyncKey(), async () => {
|
||||
try {
|
||||
const { data } = await api.getAll(page, perPage, params);
|
||||
loading.value = false;
|
||||
|
||||
if (data && allRef) {
|
||||
allRef.value = data.items;
|
||||
@@ -44,6 +45,10 @@ export function useReadOnlyActions<T extends BoundT>(
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
return allItems;
|
||||
@@ -76,6 +81,7 @@ export function useReadOnlyActions<T extends BoundT>(
|
||||
* a lot of refreshing hooks to be called on operations
|
||||
*/
|
||||
export function useStoreActions<T extends BoundT>(
|
||||
storeKey: string,
|
||||
api: BaseCRUDAPI<unknown, T, unknown>,
|
||||
allRef: Ref<T[] | null> | null,
|
||||
loading: Ref<boolean>,
|
||||
@@ -84,10 +90,10 @@ export function useStoreActions<T extends BoundT>(
|
||||
params.orderBy ??= "name";
|
||||
params.orderDirection ??= "asc";
|
||||
|
||||
const allItems = useAsyncData(storeKey, async () => {
|
||||
loading.value = true;
|
||||
const allItems = useAsyncData(useAsyncKey(), async () => {
|
||||
try {
|
||||
const { data } = await api.getAll(page, perPage, params);
|
||||
loading.value = false;
|
||||
|
||||
if (data && allRef) {
|
||||
allRef.value = data.items;
|
||||
@@ -99,6 +105,10 @@ export function useStoreActions<T extends BoundT>(
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
return allItems;
|
||||
|
||||
@@ -13,12 +13,13 @@ export const useData = function <T extends BoundT>(defaultObject: T) {
|
||||
};
|
||||
|
||||
export const useReadOnlyStore = function <T extends BoundT>(
|
||||
storeKey: string,
|
||||
store: Ref<T[]>,
|
||||
loading: Ref<boolean>,
|
||||
api: BaseCRUDAPIReadOnly<T>,
|
||||
params = {} as Record<string, QueryValue>,
|
||||
) {
|
||||
const storeActions = useReadOnlyActions(api, store, loading);
|
||||
const storeActions = useReadOnlyActions(`${storeKey}-store-readonly`, api, store, loading);
|
||||
const actions = {
|
||||
...storeActions,
|
||||
async refresh() {
|
||||
@@ -29,21 +30,22 @@ export const useReadOnlyStore = function <T extends BoundT>(
|
||||
},
|
||||
};
|
||||
|
||||
if (!loading.value && (!store.value || store.value.length === 0)) {
|
||||
const result = actions.getAll(1, -1, params);
|
||||
store.value = result.value || [];
|
||||
// initial hydration
|
||||
if (!loading.value && !store.value.length) {
|
||||
actions.refresh();
|
||||
}
|
||||
|
||||
return { store, actions };
|
||||
};
|
||||
|
||||
export const useStore = function <T extends BoundT>(
|
||||
storeKey: string,
|
||||
store: Ref<T[]>,
|
||||
loading: Ref<boolean>,
|
||||
api: BaseCRUDAPI<unknown, T, unknown>,
|
||||
params = {} as Record<string, QueryValue>,
|
||||
) {
|
||||
const storeActions = useStoreActions(api, store, loading);
|
||||
const storeActions = useStoreActions(`${storeKey}-store`, api, store, loading);
|
||||
const actions = {
|
||||
...storeActions,
|
||||
async refresh() {
|
||||
@@ -54,9 +56,9 @@ export const useStore = function <T extends BoundT>(
|
||||
},
|
||||
};
|
||||
|
||||
if (!loading.value && (!store.value || store.value.length === 0)) {
|
||||
const result = actions.getAll(1, -1, params);
|
||||
store.value = result.value || [];
|
||||
// initial hydration
|
||||
if (!loading.value && !store.value.length) {
|
||||
actions.refresh();
|
||||
}
|
||||
|
||||
return { store, actions };
|
||||
|
||||
@@ -44,11 +44,16 @@ interface PageState {
|
||||
* true is the page is in cook mode.
|
||||
*/
|
||||
isCookMode: ComputedRef<boolean>;
|
||||
/**
|
||||
* true if the recipe is currently being parsed.
|
||||
*/
|
||||
isParsing: ComputedRef<boolean>;
|
||||
|
||||
setMode: (v: PageMode) => void;
|
||||
setEditMode: (v: EditorMode) => void;
|
||||
toggleEditMode: () => void;
|
||||
toggleCookMode: () => void;
|
||||
toggleIsParsing: (v?: boolean) => void;
|
||||
}
|
||||
|
||||
type PageRefs = ReturnType<typeof pageRefs>;
|
||||
@@ -60,11 +65,12 @@ function pageRefs(slug: string) {
|
||||
slugRef: ref(slug),
|
||||
pageModeRef: ref(PageMode.VIEW),
|
||||
editModeRef: ref(EditorMode.FORM),
|
||||
isParsingRef: ref(false),
|
||||
imageKey: ref(1),
|
||||
};
|
||||
}
|
||||
|
||||
function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): PageState {
|
||||
function pageState({ slugRef, pageModeRef, editModeRef, isParsingRef, imageKey }: PageRefs): PageState {
|
||||
const { activateNavigationWarning, deactivateNavigationWarning } = useNavigationWarning();
|
||||
|
||||
const toggleEditMode = () => {
|
||||
@@ -83,6 +89,14 @@ function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): P
|
||||
pageModeRef.value = PageMode.COOK;
|
||||
};
|
||||
|
||||
const toggleIsParsing = (v: boolean | null = null) => {
|
||||
if (v === null) {
|
||||
v = !isParsingRef.value;
|
||||
}
|
||||
|
||||
isParsingRef.value = v;
|
||||
};
|
||||
|
||||
const setEditMode = (v: EditorMode) => {
|
||||
editModeRef.value = v;
|
||||
};
|
||||
@@ -113,6 +127,7 @@ function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): P
|
||||
setMode,
|
||||
setEditMode,
|
||||
toggleCookMode,
|
||||
toggleIsParsing,
|
||||
|
||||
isEditForm: computed(() => {
|
||||
return pageModeRef.value === PageMode.EDIT && editModeRef.value === EditorMode.FORM;
|
||||
@@ -126,6 +141,9 @@ function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): P
|
||||
isCookMode: computed(() => {
|
||||
return pageModeRef.value === PageMode.COOK;
|
||||
}),
|
||||
isParsing: computed(() => {
|
||||
return isParsingRef.value;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ export const useCategoryData = function () {
|
||||
|
||||
export const useCategoryStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useStore<RecipeCategory>(store, loading, api.categories);
|
||||
return useStore<RecipeCategory>("category", store, loading, api.categories);
|
||||
};
|
||||
|
||||
export const usePublicCategoryStore = function (groupSlug: string, i18n?: Composer) {
|
||||
const api = usePublicExploreApi(groupSlug, i18n).explore;
|
||||
return useReadOnlyStore<RecipeCategory>(store, publicLoading, api.categories);
|
||||
return useReadOnlyStore<RecipeCategory>("category", store, publicLoading, api.categories);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ const publicLoading = ref(false);
|
||||
|
||||
export const useCookbookStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
const store = useStore<ReadCookBook>(cookbooks, loading, api.cookbooks);
|
||||
const store = useStore<ReadCookBook>("cookbook", cookbooks, loading, api.cookbooks);
|
||||
|
||||
const updateAll = async function (updateData: UpdateCookBook[]) {
|
||||
loading.value = true;
|
||||
@@ -25,5 +25,5 @@ export const useCookbookStore = function (i18n?: Composer) {
|
||||
|
||||
export const usePublicCookbookStore = function (groupSlug: string, i18n?: Composer) {
|
||||
const api = usePublicExploreApi(groupSlug, i18n).explore;
|
||||
return useReadOnlyStore<ReadCookBook>(cookbooks, publicLoading, api.cookbooks);
|
||||
return useReadOnlyStore<ReadCookBook>("cookbook", cookbooks, publicLoading, api.cookbooks);
|
||||
};
|
||||
|
||||
@@ -18,10 +18,10 @@ export const useFoodData = function () {
|
||||
|
||||
export const useFoodStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useStore<IngredientFood>(store, loading, api.foods);
|
||||
return useStore<IngredientFood>("food", store, loading, api.foods);
|
||||
};
|
||||
|
||||
export const usePublicFoodStore = function (groupSlug: string, i18n?: Composer) {
|
||||
const api = usePublicExploreApi(groupSlug, i18n).explore;
|
||||
return useReadOnlyStore<IngredientFood>(store, publicLoading, api.foods);
|
||||
return useReadOnlyStore<IngredientFood>("food", store, publicLoading, api.foods);
|
||||
};
|
||||
|
||||
@@ -9,10 +9,10 @@ const publicLoading = ref(false);
|
||||
|
||||
export const useHouseholdStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useReadOnlyStore<HouseholdSummary>(store, loading, api.households);
|
||||
return useReadOnlyStore<HouseholdSummary>("household", store, loading, api.households);
|
||||
};
|
||||
|
||||
export const usePublicHouseholdStore = function (groupSlug: string, i18n?: Composer) {
|
||||
const api = usePublicExploreApi(groupSlug, i18n).explore;
|
||||
return useReadOnlyStore<HouseholdSummary>(store, publicLoading, api.households);
|
||||
return useReadOnlyStore<HouseholdSummary>("household-public", store, publicLoading, api.households);
|
||||
};
|
||||
|
||||
@@ -17,5 +17,5 @@ export const useLabelData = function () {
|
||||
|
||||
export const useLabelStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useStore<MultiPurposeLabelOut>(store, loading, api.multiPurposeLabels);
|
||||
return useStore<MultiPurposeLabelOut>("label", store, loading, api.multiPurposeLabels);
|
||||
};
|
||||
|
||||
@@ -17,10 +17,10 @@ export const useTagData = function () {
|
||||
|
||||
export const useTagStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useStore<RecipeTag>(store, loading, api.tags);
|
||||
return useStore<RecipeTag>("tag", store, loading, api.tags);
|
||||
};
|
||||
|
||||
export const usePublicTagStore = function (groupSlug: string, i18n?: Composer) {
|
||||
const api = usePublicExploreApi(groupSlug, i18n).explore;
|
||||
return useReadOnlyStore<RecipeTag>(store, publicLoading, api.tags);
|
||||
return useReadOnlyStore<RecipeTag>("tag", store, publicLoading, api.tags);
|
||||
};
|
||||
|
||||
@@ -23,10 +23,10 @@ export const useToolData = function () {
|
||||
|
||||
export const useToolStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useStore<RecipeTool>(store, loading, api.tools);
|
||||
return useStore<RecipeTool>("tool", store, loading, api.tools);
|
||||
};
|
||||
|
||||
export const usePublicToolStore = function (groupSlug: string, i18n?: Composer) {
|
||||
const api = usePublicExploreApi(groupSlug, i18n).explore;
|
||||
return useReadOnlyStore<RecipeTool>(store, publicLoading, api.tools);
|
||||
return useReadOnlyStore<RecipeTool>("tool", store, publicLoading, api.tools);
|
||||
};
|
||||
|
||||
@@ -18,5 +18,5 @@ export const useUnitData = function () {
|
||||
|
||||
export const useUnitStore = function (i18n?: Composer) {
|
||||
const api = useUserApi(i18n);
|
||||
return useStore<IngredientUnit>(store, loading, api.units);
|
||||
return useStore<IngredientUnit>("unit", store, loading, api.units);
|
||||
};
|
||||
|
||||
@@ -16,5 +16,5 @@ export const useUserStore = function (i18n?: Composer) {
|
||||
const requests = useRequests(i18n);
|
||||
const api = new GroupUserAPIReadOnly(requests);
|
||||
|
||||
return useReadOnlyStore<UserSummary>(store, loading, api, { orderBy: "full_name" });
|
||||
return useReadOnlyStore<UserSummary>("user", store, loading, api, { orderBy: "full_name" });
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ export const useGroupRecipeActions = function (
|
||||
};
|
||||
|
||||
const actions = {
|
||||
...useStoreActions<GroupRecipeActionOut>(api.groupRecipeActions, groupRecipeActions, loading),
|
||||
...useStoreActions<GroupRecipeActionOut>("group-recipe-actions", api.groupRecipeActions, groupRecipeActions, loading),
|
||||
flushStore() {
|
||||
groupRecipeActions.value = [];
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Українська (Ukrainian)",
|
||||
value: "uk-UA",
|
||||
progress: 37,
|
||||
progress: 55,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -33,7 +33,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Svenska (Swedish)",
|
||||
value: "sv-SE",
|
||||
progress: 52,
|
||||
progress: 53,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -45,7 +45,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Slovenščina (Slovenian)",
|
||||
value: "sl-SI",
|
||||
progress: 39,
|
||||
progress: 40,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -57,7 +57,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Pусский (Russian)",
|
||||
value: "ru-RU",
|
||||
progress: 40,
|
||||
progress: 41,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -75,7 +75,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Português do Brasil (Brazilian Portuguese)",
|
||||
value: "pt-BR",
|
||||
progress: 41,
|
||||
progress: 46,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -87,13 +87,13 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Norsk (Norwegian)",
|
||||
value: "no-NO",
|
||||
progress: 39,
|
||||
progress: 40,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
name: "Nederlands (Dutch)",
|
||||
value: "nl-NL",
|
||||
progress: 49,
|
||||
progress: 52,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -105,7 +105,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Lietuvių (Lithuanian)",
|
||||
value: "lt-LT",
|
||||
progress: 26,
|
||||
progress: 27,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -123,19 +123,19 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Italiano (Italian)",
|
||||
value: "it-IT",
|
||||
progress: 40,
|
||||
progress: 43,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
name: "Íslenska (Icelandic)",
|
||||
value: "is-IS",
|
||||
progress: 3,
|
||||
progress: 10,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
name: "Magyar (Hungarian)",
|
||||
value: "hu-HU",
|
||||
progress: 44,
|
||||
progress: 45,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -159,7 +159,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Français (French)",
|
||||
value: "fr-FR",
|
||||
progress: 64,
|
||||
progress: 67,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -189,7 +189,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Español (Spanish)",
|
||||
value: "es-ES",
|
||||
progress: 42,
|
||||
progress: 45,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -207,19 +207,19 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Ελληνικά (Greek)",
|
||||
value: "el-GR",
|
||||
progress: 40,
|
||||
progress: 41,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
name: "Deutsch (German)",
|
||||
value: "de-DE",
|
||||
progress: 72,
|
||||
progress: 80,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
name: "Dansk (Danish)",
|
||||
value: "da-DK",
|
||||
progress: 40,
|
||||
progress: 43,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
@@ -231,13 +231,13 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Català (Catalan)",
|
||||
value: "ca-ES",
|
||||
progress: 38,
|
||||
progress: 37,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
name: "Български (Bulgarian)",
|
||||
value: "bg-BG",
|
||||
progress: 31,
|
||||
progress: 44,
|
||||
dir: "ltr",
|
||||
},
|
||||
{
|
||||
|
||||
85
frontend/composables/use-new-recipe-options.ts
Normal file
85
frontend/composables/use-new-recipe-options.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { useRecipeCreatePreferences } from "~/composables/use-users/preferences";
|
||||
|
||||
export interface UseNewRecipeOptionsProps {
|
||||
enableImportKeywords?: boolean;
|
||||
enableStayInEditMode?: boolean;
|
||||
enableParseRecipe?: boolean;
|
||||
}
|
||||
|
||||
export function useNewRecipeOptions(props: UseNewRecipeOptionsProps = {}) {
|
||||
const {
|
||||
enableImportKeywords = true,
|
||||
enableStayInEditMode = true,
|
||||
enableParseRecipe = true,
|
||||
} = props;
|
||||
|
||||
const router = useRouter();
|
||||
const recipeCreatePreferences = useRecipeCreatePreferences();
|
||||
|
||||
const importKeywordsAsTags = computed({
|
||||
get() {
|
||||
if (!enableImportKeywords) return false;
|
||||
return recipeCreatePreferences.value.importKeywordsAsTags;
|
||||
},
|
||||
set(v: boolean) {
|
||||
if (!enableImportKeywords) return;
|
||||
recipeCreatePreferences.value.importKeywordsAsTags = v;
|
||||
},
|
||||
});
|
||||
|
||||
const stayInEditMode = computed({
|
||||
get() {
|
||||
if (!enableStayInEditMode) return false;
|
||||
return recipeCreatePreferences.value.stayInEditMode;
|
||||
},
|
||||
set(v: boolean) {
|
||||
if (!enableStayInEditMode) return;
|
||||
recipeCreatePreferences.value.stayInEditMode = v;
|
||||
},
|
||||
});
|
||||
|
||||
const parseRecipe = computed({
|
||||
get() {
|
||||
if (!enableParseRecipe) return false;
|
||||
return recipeCreatePreferences.value.parseRecipe;
|
||||
},
|
||||
set(v: boolean) {
|
||||
if (!enableParseRecipe) return;
|
||||
recipeCreatePreferences.value.parseRecipe = v;
|
||||
},
|
||||
});
|
||||
|
||||
function navigateToRecipe(recipeSlug: string, groupSlug: string, createPagePath: string) {
|
||||
const editParam = enableStayInEditMode ? stayInEditMode.value : false;
|
||||
const parseParam = enableParseRecipe ? parseRecipe.value : false;
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
if (editParam) {
|
||||
queryParams.set("edit", "true");
|
||||
}
|
||||
if (parseParam) {
|
||||
queryParams.set("parse", "true");
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
const recipeUrl = `/g/${groupSlug}/r/${recipeSlug}${queryString ? `?${queryString}` : ""}`;
|
||||
|
||||
// Replace current entry to prevent re-import on back navigation
|
||||
router.replace(createPagePath).then(() => router.push(recipeUrl));
|
||||
}
|
||||
|
||||
return {
|
||||
// Computed properties for the checkboxes
|
||||
importKeywordsAsTags,
|
||||
stayInEditMode,
|
||||
parseRecipe,
|
||||
|
||||
// Helper functions
|
||||
navigateToRecipe,
|
||||
|
||||
// Props for conditional rendering
|
||||
enableImportKeywords,
|
||||
enableStayInEditMode,
|
||||
enableParseRecipe,
|
||||
};
|
||||
}
|
||||
@@ -17,15 +17,15 @@ export interface OrganizerBase {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type FieldType =
|
||||
| "string"
|
||||
export type FieldType
|
||||
= | "string"
|
||||
| "number"
|
||||
| "boolean"
|
||||
| "date"
|
||||
| RecipeOrganizer;
|
||||
|
||||
export type FieldValue =
|
||||
| string
|
||||
export type FieldValue
|
||||
= | string
|
||||
| number
|
||||
| boolean
|
||||
| Date
|
||||
|
||||
@@ -59,6 +59,12 @@ export interface UserRecipeFinderPreferences {
|
||||
includeToolsOnHand: boolean;
|
||||
}
|
||||
|
||||
export interface UserRecipeCreatePreferences {
|
||||
importKeywordsAsTags: boolean;
|
||||
stayInEditMode: boolean;
|
||||
parseRecipe: boolean;
|
||||
}
|
||||
|
||||
export function useUserMealPlanPreferences(): Ref<UserMealPlanPreferences> {
|
||||
const fromStorage = useLocalStorage(
|
||||
"meal-planner-preferences",
|
||||
@@ -200,3 +206,19 @@ export function useRecipeFinderPreferences(): Ref<UserRecipeFinderPreferences> {
|
||||
|
||||
return fromStorage;
|
||||
}
|
||||
|
||||
export function useRecipeCreatePreferences(): Ref<UserRecipeCreatePreferences> {
|
||||
const fromStorage = useLocalStorage(
|
||||
"recipe-create-preferences",
|
||||
{
|
||||
importKeywordsAsTags: false,
|
||||
stayInEditMode: false,
|
||||
parseRecipe: true,
|
||||
},
|
||||
{ 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<UserRecipeCreatePreferences>;
|
||||
|
||||
return fromStorage;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const useToggleDarkMode = () => {
|
||||
};
|
||||
|
||||
export const useAsyncKey = function () {
|
||||
return String(Date.now());
|
||||
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
};
|
||||
|
||||
export const titleCase = function (str: string) {
|
||||
|
||||
151
frontend/composables/useAuthBackend.ts
Normal file
151
frontend/composables/useAuthBackend.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { ref, computed } from "vue";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
|
||||
interface AuthData {
|
||||
value: UserOut | null;
|
||||
}
|
||||
|
||||
interface AuthStatus {
|
||||
value: "loading" | "authenticated" | "unauthenticated";
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
data: AuthData;
|
||||
status: AuthStatus;
|
||||
signIn: (credentials: FormData, options?: { redirect?: boolean }) => Promise<void>;
|
||||
signOut: (callbackUrl?: string) => Promise<void>;
|
||||
refresh: () => Promise<void>;
|
||||
getSession: () => Promise<void>;
|
||||
setToken: (token: string | null) => void;
|
||||
}
|
||||
|
||||
const authUser = ref<UserOut | null>(null);
|
||||
const authStatus = ref<"loading" | "authenticated" | "unauthenticated">("loading");
|
||||
|
||||
export const useAuthBackend = function (): AuthState {
|
||||
const { $axios } = useNuxtApp();
|
||||
const router = useRouter();
|
||||
const tokenName = useRuntimeConfig().public.AUTH_TOKEN;
|
||||
const tokenCookie = useCookie(tokenName);
|
||||
|
||||
function setToken(token: string | null) {
|
||||
tokenCookie.value = token;
|
||||
}
|
||||
|
||||
function handleAuthError(error: any, redirect = false) {
|
||||
// Only clear token on auth errors, not network errors
|
||||
if (error?.response?.status === 401) {
|
||||
setToken(null);
|
||||
authUser.value = null;
|
||||
authStatus.value = "unauthenticated";
|
||||
if (redirect) {
|
||||
router.push("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getSession(): Promise<void> {
|
||||
if (!tokenCookie.value) {
|
||||
authUser.value = null;
|
||||
authStatus.value = "unauthenticated";
|
||||
return;
|
||||
}
|
||||
|
||||
authStatus.value = "loading";
|
||||
try {
|
||||
const { data } = await $axios.get<UserOut>("/api/users/self");
|
||||
authUser.value = data;
|
||||
authStatus.value = "authenticated";
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error("Failed to fetch user session:", error);
|
||||
handleAuthError(error);
|
||||
authStatus.value = "unauthenticated";
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn(credentials: FormData): Promise<void> {
|
||||
authStatus.value = "loading";
|
||||
|
||||
try {
|
||||
const response = await $axios.post("/api/auth/token", credentials, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
|
||||
const { access_token } = response.data;
|
||||
setToken(access_token);
|
||||
await getSession();
|
||||
}
|
||||
catch (error) {
|
||||
authStatus.value = "unauthenticated";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut(callbackUrl: string = ""): Promise<void> {
|
||||
try {
|
||||
await $axios.post("/api/auth/logout");
|
||||
}
|
||||
catch (error) {
|
||||
// Continue with logout even if API call fails
|
||||
console.warn("Logout API call failed:", error);
|
||||
}
|
||||
finally {
|
||||
setToken(null);
|
||||
authUser.value = null;
|
||||
authStatus.value = "unauthenticated";
|
||||
await router.push(callbackUrl || "/login");
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
if (!tokenCookie.value) return;
|
||||
|
||||
try {
|
||||
const response = await $axios.get("/api/auth/refresh");
|
||||
const { access_token } = response.data;
|
||||
setToken(access_token);
|
||||
await getSession();
|
||||
}
|
||||
catch (error: any) {
|
||||
handleAuthError(error, true);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-refresh user data periodically when authenticated
|
||||
if (import.meta.client) {
|
||||
let refreshInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
watch(() => authStatus.value, (status) => {
|
||||
if (status === "authenticated") {
|
||||
refreshInterval = setInterval(() => {
|
||||
if (tokenCookie.value) {
|
||||
getSession().catch(() => {
|
||||
// Ignore errors in background refresh
|
||||
});
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 minutes
|
||||
}
|
||||
else {
|
||||
// Clear interval when not authenticated
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
}, { immediate: true });
|
||||
}
|
||||
|
||||
return {
|
||||
data: computed(() => authUser.value),
|
||||
status: computed(() => authStatus.value),
|
||||
signIn,
|
||||
signOut,
|
||||
refresh,
|
||||
getSession,
|
||||
setToken,
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ref, watch, computed } from "vue";
|
||||
import { useAuthBackend } from "~/composables/useAuthBackend";
|
||||
import type { UserOut } from "~/lib/api/types/user";
|
||||
|
||||
export const useMealieAuth = function () {
|
||||
const auth = useAuth();
|
||||
const { setToken } = useAuthState();
|
||||
const auth = useAuthBackend();
|
||||
const { $axios } = useNuxtApp();
|
||||
|
||||
// User Management
|
||||
@@ -37,24 +37,18 @@ export const useMealieAuth = function () {
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function signIn(...params: Parameters<typeof auth.signIn>) {
|
||||
await auth.signIn(...params);
|
||||
refreshCookie(useRuntimeConfig().public.AUTH_TOKEN);
|
||||
}
|
||||
|
||||
async function oauthSignIn() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const { data: token } = await $axios.get<{ access_token: string; token_type: "bearer" }>("/api/auth/oauth/callback", { params });
|
||||
setToken(token.access_token);
|
||||
auth.setToken(token.access_token);
|
||||
await auth.getSession();
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
loggedIn,
|
||||
signIn,
|
||||
signIn: auth.signIn,
|
||||
signOut: auth.signOut,
|
||||
signUp: auth.signUp,
|
||||
refresh: auth.refresh,
|
||||
oauthSignIn,
|
||||
};
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
// @ts-check
|
||||
import stylisticJs from "@stylistic/eslint-plugin-js";
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import withNuxt from "./.nuxt/eslint.config.mjs";
|
||||
|
||||
export default withNuxt({
|
||||
plugins: {
|
||||
"@stylistic/js": stylisticJs,
|
||||
"@stylistic": stylistic,
|
||||
},
|
||||
// Your custom configs here
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"vue/no-mutating-props": "warn",
|
||||
"vue/no-v-html": "warn",
|
||||
"object-curly-newline": "off",
|
||||
"consistent-list-newline": "off",
|
||||
"vue/first-attribute-linebreak": "off",
|
||||
"@stylistic/js/no-tabs": ["error", { allowIndentationTabs: true }],
|
||||
"@stylistic/no-tabs": ["error", { allowIndentationTabs: true }],
|
||||
"@stylistic/no-tabs": ["error"],
|
||||
"@stylistic/no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/html-indent": "off",
|
||||
"vue/html-closing-bracket-newline": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"vue/first-attribute-linebreak": "error",
|
||||
"vue/html-closing-bracket-newline": "error",
|
||||
"vue/max-attributes-per-line": [
|
||||
"error",
|
||||
{
|
||||
singleline: 5,
|
||||
multiline: 1,
|
||||
},
|
||||
],
|
||||
"vue/no-mutating-props": "error",
|
||||
"vue/no-v-html": "error",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Voer oorspronklike sleutelwoorde as merkers in",
|
||||
"stay-in-edit-mode": "Bly in redigeer modus",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Voer vanaf zip in",
|
||||
"import-from-zip-description": "Voer 'n enkele resep in wat vanaf 'n ander Mealie-instansie uitgevoer is.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "يمكنك الإضافة مباشرة باستخدام بيانات خام",
|
||||
"import-original-keywords-as-tags": "استيراد الكلمات المفتاحية الأصلية كوسوم",
|
||||
"stay-in-edit-mode": "البقاء في وضع التعديل",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "استيراد من ملف Zip",
|
||||
"import-from-zip-description": "استيراد وصفة واحدة تم تصديرها من حساب \"ميلي\" آخر",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "إنشاء طعام مفقود: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "لا يوجد طعام"
|
||||
"no-food": "لا يوجد طعام",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "إعادة تعيين عدد الحصص",
|
||||
"not-linked-ingredients": "مكونات إضافية",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"about-mealie": "Относно Mealie",
|
||||
"api-docs": "API Документация",
|
||||
"api-port": "API Порт",
|
||||
"application-mode": "Приложението",
|
||||
"application-mode": "Режим на приложение",
|
||||
"database-type": "Тип на база данни",
|
||||
"database-url": "URL адрес база данни",
|
||||
"default-group": "Група по подразбиране",
|
||||
@@ -69,7 +69,7 @@
|
||||
"new-notification": "Ново известие",
|
||||
"event-notifiers": "Известия за събитие",
|
||||
"apprise-url-skipped-if-blank": "URL за известяване (пропуска се ако е празно)",
|
||||
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
|
||||
"apprise-url-is-left-intentionally-blank": "Тъй като URL адресите на Apprise обикновено съдържат чувствителна информация, това поле е оставено умишлено празно по време на редактиране. Ако желаете да актуализирате URL адреса, моля, въведете новия тук, в противен случай го оставете празно, за да запазите текущия URL адрес.",
|
||||
"enable-notifier": "Включи известията",
|
||||
"what-events": "За кои събития трябва да се получават известия?",
|
||||
"user-events": "Потребителски събития",
|
||||
@@ -81,7 +81,7 @@
|
||||
"category-events": "Събития за категория",
|
||||
"when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група",
|
||||
"recipe-events": "Събития на рецептата",
|
||||
"label-events": "Label Events"
|
||||
"label-events": "Събития с етикети"
|
||||
},
|
||||
"general": {
|
||||
"add": "Добави",
|
||||
@@ -93,13 +93,13 @@
|
||||
"confirm-delete-generic": "Сигурни ли сте, че желаете да изтриете това?",
|
||||
"copied_message": "Копирано!",
|
||||
"create": "Добави",
|
||||
"created": "Създадено",
|
||||
"created": "Последно добавени",
|
||||
"custom": "Персонализиран",
|
||||
"dashboard": "Табло",
|
||||
"delete": "Изтриване",
|
||||
"disabled": "Деактивирано",
|
||||
"download": "Изтегли",
|
||||
"duplicate": "Дублирай",
|
||||
"duplicate": "Дублиране",
|
||||
"edit": "Редактирай",
|
||||
"enabled": "Активиран",
|
||||
"exception": "Грешка",
|
||||
@@ -135,15 +135,15 @@
|
||||
"ok": "Добре",
|
||||
"options": "Опции:",
|
||||
"plural-name": "Име в множествено число",
|
||||
"print": "Принтирай",
|
||||
"print-preferences": "Настройки на принтиране",
|
||||
"random": "Произволно",
|
||||
"print": "Отпечатване",
|
||||
"print-preferences": "Настройки на печата",
|
||||
"random": "Произволна рецепта",
|
||||
"rating": "Оценка",
|
||||
"recent": "Скорошни",
|
||||
"recipe": "Рецепта",
|
||||
"recipes": "Рецепти",
|
||||
"rename-object": "Преименувай {0}",
|
||||
"reset": "Нулирай",
|
||||
"reset": "По подразбиране",
|
||||
"saturday": "Събота",
|
||||
"save": "Запази",
|
||||
"settings": "Настройки",
|
||||
@@ -169,7 +169,7 @@
|
||||
"tuesday": "Вторник",
|
||||
"type": "Тип",
|
||||
"update": "Актуализация",
|
||||
"updated": "Обновено",
|
||||
"updated": "Последно обновени",
|
||||
"upload": "Качи",
|
||||
"url": "URL",
|
||||
"view": "Преглед",
|
||||
@@ -198,7 +198,7 @@
|
||||
"copy": "Копиране",
|
||||
"color": "Цвят",
|
||||
"timestamp": "Времева отметка",
|
||||
"last-made": "Последно приготвена на",
|
||||
"last-made": "Дата на последно приготвяне",
|
||||
"learn-more": "Научи повече",
|
||||
"this-feature-is-currently-inactive": "Тази функционалност в момента е неактивна",
|
||||
"clipboard-not-supported": "Не се поддържа клипборд",
|
||||
@@ -210,7 +210,7 @@
|
||||
"export-all": "Експортиране на всички",
|
||||
"refresh": "Опресняване",
|
||||
"upload-file": "Качване на файл",
|
||||
"created-on-date": "Създадено на {0}",
|
||||
"created-on-date": "Добавена на {0}",
|
||||
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
||||
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
|
||||
"confirm-delete-generic-items": "Сигурни ли сте, че желаете да изтриете следните елементи?",
|
||||
@@ -279,7 +279,7 @@
|
||||
"admin-group-management-text": "Промените по тази група ще бъдат отразени моментално.",
|
||||
"group-id-value": "ID на Групата: {0}",
|
||||
"total-households": "Общ брой домакинства",
|
||||
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
|
||||
"you-must-select-a-group-before-selecting-a-household": "Трябва да изберете група, преди да изберете домакинство"
|
||||
},
|
||||
"household": {
|
||||
"household": "Домакинство",
|
||||
@@ -300,8 +300,8 @@
|
||||
"household-recipe-preferences": "Предпочитания за рецептите на домакинството",
|
||||
"default-recipe-preferences-description": "Това са настройките по подразбиране когато нова рецепта е създадена в домакинството ви. Тези настройки могат да бъдат променени за всяка рецепта в менюто за настройки на рецептата.",
|
||||
"allow-users-outside-of-your-household-to-see-your-recipes": "Разреши на потребители от други домакинства да виждат рецептите ми",
|
||||
"allow-users-outside-of-your-household-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 household or with a pre-generated private link",
|
||||
"household-preferences": "Household Preferences"
|
||||
"allow-users-outside-of-your-household-to-see-your-recipes-description": "Когато е активирано, можете да използвате публичен линк за споделяне, за да споделяте конкретни рецепти, без да оторизирате потребителя. Когато е деактивирано, можете да споделяте рецепти само с потребители, които са във вашето домакинство, или с предварително генериран личен линк",
|
||||
"household-preferences": "Предпочитания на домакинството"
|
||||
},
|
||||
"meal-plan": {
|
||||
"create-a-new-meal-plan": "Създаване на нов хранителен план",
|
||||
@@ -323,15 +323,15 @@
|
||||
"mealplan-settings": "Настройки на менюто",
|
||||
"mealplan-update-failed": "Неуспешно обновяване на седмичното меню",
|
||||
"mealplan-updated": "Седмичното меню бе обновено",
|
||||
"mealplan-households-description": "If no household is selected, recipes can be added from any household",
|
||||
"mealplan-households-description": "Ако не е избрано домакинство, могат да се добавят рецепти от всяко домакинство",
|
||||
"any-category": "Всяка категория",
|
||||
"any-tag": "Всеки етикет",
|
||||
"any-household": "Any Household",
|
||||
"any-household": "Всяко домакинство",
|
||||
"no-meal-plan-defined-yet": "Все още няма създадено седмично меню",
|
||||
"no-meal-planned-for-today": "За днес няма планирано меню",
|
||||
"numberOfDays-hint": "Number of days on page load",
|
||||
"numberOfDays-label": "Default Days",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Само рецептите от тези категории ще бъдат използвани в хранителните планове",
|
||||
"numberOfDays-hint": "Брой дни за зареждане на страницата",
|
||||
"numberOfDays-label": "Дни по подразбиране",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Само рецепти от тези категории ще бъдат използвани в седмичното меню",
|
||||
"planner": "Планьор",
|
||||
"quick-week": "Бърза седмица",
|
||||
"side": "Предястие",
|
||||
@@ -359,7 +359,7 @@
|
||||
"for-type-meal-types": "за {0}",
|
||||
"meal-plan-rules": "Правила за съставяне на седмично меню",
|
||||
"new-rule": "Ново правило",
|
||||
"meal-plan-rules-description": "You can create rules for auto selecting recipes for your meal plans. These rules are used by the server to determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same day/type constraints then the rule filters will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.",
|
||||
"meal-plan-rules-description": "Можете да създавате правила за автоматично избиране на рецепти за вашите хранителни планове. Тези правила се използват от сървъра, за да определи произволния набор от рецепти, от които да се избира при създаването на хранителни планове. Обърнете внимание, че ако правилата имат едни и същи ограничения за ден/тип, филтрите на правилата ще бъдат обединени. На практика не е необходимо да се създават дублиращи се правила, но е възможно да се направи.",
|
||||
"new-rule-description": "Когато създавате ново правило за създаване на седмично меню, може да зададете ограничение правилото да бъде приложено за определен ден от седмицата и/или специфичен вид ястие. За да добавите правило за всички дни или всички типове ястия, Вие може да изберете \"Всички\", което ще го приложи за всички дни и/или видове ястия.",
|
||||
"recipe-rules": "Правила на рецептата",
|
||||
"applies-to-all-days": "Прилага се за всички дни",
|
||||
@@ -420,7 +420,7 @@
|
||||
},
|
||||
"recipekeeper": {
|
||||
"title": "Recipe Keeper",
|
||||
"description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below."
|
||||
"description-long": "Mealie може да импортира рецепти от Recipe Keeper. Експортирайте рецептите си в zip формат, след което качете .zip файла по-долу."
|
||||
}
|
||||
},
|
||||
"new-recipe": {
|
||||
@@ -434,7 +434,7 @@
|
||||
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Поставете в данните на рецептата си. Всеки ред ще бъде третиран като елемент от списъка.",
|
||||
"recipe-markup-specification": "Спецификации за маркиране на рецептата",
|
||||
"recipe-url": "URL на рецептата",
|
||||
"recipe-html-or-json": "Recipe HTML or JSON",
|
||||
"recipe-html-or-json": "HTML или JSON на рецептата",
|
||||
"upload-a-recipe": "Качи рецепта",
|
||||
"upload-individual-zip-file": "Качи като индивидуален .zip файлов формат от друга инстанция на Mealie.",
|
||||
"url-form-hint": "Копирай и постави линк от твоя любим сайт за рецепти",
|
||||
@@ -474,23 +474,23 @@
|
||||
"comment": "Коментар",
|
||||
"comments": "Коментари",
|
||||
"delete-confirmation": "Сигурни ли сте, че желаете да изтриете тази рецепта?",
|
||||
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?",
|
||||
"admin-delete-confirmation": "Ще изтриете рецепта, която не е ваша, използвайки администраторски права. Сигурни ли сте?",
|
||||
"delete-recipe": "Изтрий рецептата",
|
||||
"description": "Описание",
|
||||
"disable-amount": "Изключи количествата за съставките",
|
||||
"disable-comments": "Изключи коментарите",
|
||||
"duplicate": "Дублирай рецептата",
|
||||
"duplicate": "Дублиране на рецептата",
|
||||
"duplicate-name": "Име на новата рецепта",
|
||||
"edit-scale": "Редактиране на размера",
|
||||
"fat-content": "Мазнини",
|
||||
"fiber-content": "Влакна",
|
||||
"grams": "грама",
|
||||
"ingredient": "Съставка",
|
||||
"ingredients": "Съставки",
|
||||
"ingredients": "Необходими продукти",
|
||||
"insert-ingredient": "Въведете съставка",
|
||||
"insert-section": "Въведете раздел",
|
||||
"insert-above": "Insert Above",
|
||||
"insert-below": "Insert Below",
|
||||
"insert-above": "Вмъкни отгоре",
|
||||
"insert-below": "Вмъкни по-долу",
|
||||
"instructions": "Инструкции",
|
||||
"key-name-required": "Ключовото име е задължително",
|
||||
"landscape-view-coming-soon": "Пейзажен изглед",
|
||||
@@ -503,7 +503,7 @@
|
||||
"object-value": "Стойност на обект",
|
||||
"original-url": "Оригинален линк",
|
||||
"perform-time": "Време за готвене",
|
||||
"prep-time": "Време за приготвяне",
|
||||
"prep-time": "Време за подготовка",
|
||||
"protein-content": "Белтъци",
|
||||
"public-recipe": "Публична рецепта",
|
||||
"recipe-created": "Рецептата е създадена",
|
||||
@@ -511,27 +511,27 @@
|
||||
"recipe-deleted": "Рецептата е изтрита",
|
||||
"recipe-image": "Изображение на рецептата",
|
||||
"recipe-image-updated": "Изображението на рецептата беше обновено",
|
||||
"recipe-name": "Име на рецептата",
|
||||
"recipe-name": "Наименование",
|
||||
"recipe-settings": "Настройки на рецептата",
|
||||
"recipe-update-failed": "Обновяването на рецептата беше неуспешно",
|
||||
"recipe-updated": "Рецептата е обновена",
|
||||
"remove-from-favorites": "Премахни от любими",
|
||||
"remove-section": "Премахни раздел",
|
||||
"saturated-fat-content": "Saturated fat",
|
||||
"saturated-fat-content": "Наситени мазнини",
|
||||
"save-recipe-before-use": "Запази рецептата преди да я използваш",
|
||||
"section-title": "Заглавие на раздела",
|
||||
"servings": "Порция|порции",
|
||||
"serves-amount": "Serves {amount}",
|
||||
"servings": "Порции",
|
||||
"serves-amount": "Количествата са за {amount} порции",
|
||||
"share-recipe-message": "Искам да споделя моята рецепта {0} с теб.",
|
||||
"show-nutrition-values": "Покажи хранителните стойности",
|
||||
"sodium-content": "Натрий",
|
||||
"step-index": "Стъпка: {step}",
|
||||
"sugar-content": "Захар",
|
||||
"title": "Заглавие",
|
||||
"total-time": "Общо време",
|
||||
"trans-fat-content": "Trans-fat",
|
||||
"total-time": "Общо време за приготвяне",
|
||||
"trans-fat-content": "Транс мазнини",
|
||||
"unable-to-delete-recipe": "Изтриването на рецептата е невъзможно",
|
||||
"unsaturated-fat-content": "Unsaturated fat",
|
||||
"unsaturated-fat-content": "Ненаситени мазнини",
|
||||
"no-recipe": "Няма рецепта",
|
||||
"locked-by-owner": "Заключена от собственика",
|
||||
"join-the-conversation": "Присъедини се към разговора",
|
||||
@@ -539,9 +539,9 @@
|
||||
"entry-type": "Тип на записа",
|
||||
"date-format-hint": "MM/DD/YYYY формат",
|
||||
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD формат",
|
||||
"add-to-list": "Добави към списък",
|
||||
"add-to-plan": "Добави към план",
|
||||
"add-to-timeline": "Добави към историята на събитията",
|
||||
"add-to-list": "Добавяне към списък",
|
||||
"add-to-plan": "Добавяне към план",
|
||||
"add-to-timeline": "Добавяне към историята на събитията",
|
||||
"recipe-added-to-list": "Рецептата е добавена към списъка",
|
||||
"recipes-added-to-list": "Рецептите са добавени към списъка",
|
||||
"successfully-added-to-list": "Успешно добавено в списъка",
|
||||
@@ -549,9 +549,9 @@
|
||||
"failed-to-add-recipes-to-list": "Неуспешно добавяне на рецепта към списъка",
|
||||
"failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план",
|
||||
"failed-to-add-to-list": "Неуспешно добавяне към списъка",
|
||||
"yield": "Добив",
|
||||
"yields-amount-with-text": "Yields {amount} {text}",
|
||||
"yield-text": "Yield Text",
|
||||
"yield": "Количество",
|
||||
"yields-amount-with-text": "Порции {amount} {text}",
|
||||
"yield-text": "Забележка",
|
||||
"quantity": "Количество",
|
||||
"choose-unit": "Избери единица",
|
||||
"press-enter-to-create": "Натисните Enter за да създадете",
|
||||
@@ -561,10 +561,10 @@
|
||||
"see-original-text": "Виж оригиналния текст",
|
||||
"original-text-with-value": "Оригинален текст: {originalText}",
|
||||
"ingredient-linker": "Инструмент за свързване на съставки",
|
||||
"unlinked": "Not linked yet",
|
||||
"unlinked": "Все още не е свързано",
|
||||
"linked-to-other-step": "Свързано към друга стъпка",
|
||||
"auto": "Автоматично",
|
||||
"cook-mode": "Режим на готвене",
|
||||
"cook-mode": "Начин на приготвяне",
|
||||
"link-ingredients": "Свържи съставките",
|
||||
"merge-above": "Обедини с по-горната",
|
||||
"move-to-bottom": "Премести най-долу",
|
||||
@@ -579,18 +579,18 @@
|
||||
"timeline-is-empty": "Няма история на събитията. Опитайте да приготвите рецептата!",
|
||||
"timeline-no-events-found-try-adjusting-filters": "Няма намерени събития. Опитайте да промените филтрите си за търсене.",
|
||||
"group-global-timeline": "{groupName} История на събитията",
|
||||
"open-timeline": "Отвори историята на събитията",
|
||||
"open-timeline": "История на събитията",
|
||||
"made-this": "Сготвих рецептата",
|
||||
"how-did-it-turn-out": "Как се получи?",
|
||||
"user-made-this": "{user} направи това",
|
||||
"added-to-timeline": "Added to timeline",
|
||||
"failed-to-add-to-timeline": "Failed to add to timeline",
|
||||
"failed-to-update-recipe": "Failed to update recipe",
|
||||
"added-to-timeline-but-failed-to-add-image": "Added to timeline, but failed to add image",
|
||||
"added-to-timeline": "Добавено към историята на събитията",
|
||||
"failed-to-add-to-timeline": "Неуспешно добавяне към историята на събитията",
|
||||
"failed-to-update-recipe": "Неуспешно актуализиране на рецептата",
|
||||
"added-to-timeline-but-failed-to-add-image": "Добавено към хронологията, но добавянето на изображение бе неуспешно",
|
||||
"api-extras-description": "Екстрите за рецепти са ключова характеристика на Mealie API. Те Ви позволяват да създавате персонализирани JSON двойки ключ/стойност в рамките на рецепта, за да ги препращате към други приложения. Можете да използвате тези ключове, за да предоставите информация за задействане на автоматизация или персонализирани съобщения, за препращане към желаното от Вас устройство.",
|
||||
"message-key": "Ключ на съобщението",
|
||||
"parse": "Анализирай",
|
||||
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.",
|
||||
"ingredients-not-parsed-description": "Изглежда, че съставките ви все още не са анализирани. Кликнете върху бутона „{parse}“ по-долу, за да анализирате съставките си в структурирани храни.",
|
||||
"attach-images-hint": "Прикачете снимки като ги влачете и пуснете в редактора",
|
||||
"drop-image": "Премахване на изображение",
|
||||
"enable-ingredient-amounts-to-use-this-feature": "Пуснете количествата на съставките за да използвате функционалността",
|
||||
@@ -601,17 +601,17 @@
|
||||
"select-one-of-the-various-ways-to-create-a-recipe": "Изберете един от разнообразните начини за създаване на рецепта",
|
||||
"looking-for-migrations": "Миграция на данни",
|
||||
"import-with-url": "Импортирай от линк",
|
||||
"create-recipe": "Добави рецепта",
|
||||
"create-recipe": "Добавяне на рецепта",
|
||||
"create-recipe-description": "Създайте нова рецепта от чернова.",
|
||||
"create-recipes": "Създайте рецепти",
|
||||
"import-with-zip": "Импортирай от .zip",
|
||||
"create-recipe-from-an-image": "Create Recipe from an Image",
|
||||
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
|
||||
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
|
||||
"create-from-images": "Create from Images",
|
||||
"should-translate-description": "Translate the recipe into my language",
|
||||
"please-wait-image-procesing": "Please wait, the image is processing. This may take some time.",
|
||||
"please-wait-images-processing": "Please wait, the images are processing. This may take some time.",
|
||||
"crop-and-rotate-the-image": "Изрежете и завъртете изображението, така че да се вижда само текстът и той да е в правилната ориентация.",
|
||||
"create-from-images": "Създаване от изображения",
|
||||
"should-translate-description": "Преведете рецептата на моя език",
|
||||
"please-wait-image-procesing": "Моля, изчакайте, изображението се обработва. Това може да отнеме известно време.",
|
||||
"please-wait-images-processing": "Моля, изчакайте, изображенията се обработват. Това може да отнеме известно време.",
|
||||
"bulk-url-import": "Импортиране на рецепти от линк",
|
||||
"debug-scraper": "Отстраняване на грешки на скрейпъра",
|
||||
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Добави рецепта като предоставиш име. Всички рецепти трябва да имат уникални имена.",
|
||||
@@ -620,16 +620,17 @@
|
||||
"scrape-recipe-description": "Обходи рецепта по линк. Предоставете линк за сайт, който искате да бъде обходен. Mealie ще опита да обходи рецептата от този сайт и да я добави във Вашата колекция.",
|
||||
"scrape-recipe-have-a-lot-of-recipes": "Имате много рецепти, които искате да обходите наведнъж?",
|
||||
"scrape-recipe-suggest-bulk-importer": "Пробвайте масовото импорторане",
|
||||
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"scrape-recipe-have-raw-html-or-json-data": "Имате ли сурови HTML или JSON данни?",
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Можете да импортирате директно от сурови данни",
|
||||
"import-original-keywords-as-tags": "Добави оригиналните ключови думи като етикети",
|
||||
"stay-in-edit-mode": "Остани в режим на редакция",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Импортирай от Zip",
|
||||
"import-from-zip-description": "Импортирай рецепта, която е била експортирана от друга инстанция на Mealie.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
"import-from-html-or-json-description": "Import a single recipe from raw HTML or JSON. This is useful if you have a recipe from a site that Mealie can't scrape normally, or from some other external source.",
|
||||
"json-import-format-description-colon": "To import via JSON, it must be in valid format:",
|
||||
"json-editor": "JSON Editor",
|
||||
"import-from-html-or-json": "Импортиране от HTML или JSON",
|
||||
"import-from-html-or-json-description": "Импортирайте една рецепта от суров HTML или JSON. Това е полезно, ако имате рецепта от сайт, който Mealie не може да извлече нормално, или от друг външен източник.",
|
||||
"json-import-format-description-colon": "За да импортирате чрез JSON, той трябва да бъде във валиден формат:",
|
||||
"json-editor": "JSON редактор",
|
||||
"zip-files-must-have-been-exported-from-mealie": ".zip файловете трябва да бъдат експортирани от Mealie",
|
||||
"create-a-recipe-by-uploading-a-scan": "Добави рецепта като качиш сканирано копие.",
|
||||
"upload-a-png-image-from-a-recipe-book": "Качи png изображение от книга с рецепти",
|
||||
@@ -642,59 +643,65 @@
|
||||
"report-deletion-failed": "Неуспешно изтриване на доклад",
|
||||
"recipe-debugger": "Debugger на рецепти",
|
||||
"recipe-debugger-description": "Вземете URL на рецептата, която желаете да проверите за грешки и го поставете тук. URL ще бъде обходен и резултатите ще бъдат визуализирани. Ако не виждате върнати данни, сайтът който се опитвате да обходите не се поддържа от Mealie или библиотеката за обхождане.",
|
||||
"use-openai": "Use OpenAI",
|
||||
"recipe-debugger-use-openai-description": "Use OpenAI to parse the results instead of relying on the scraper library. When creating a recipe via URL, this is done automatically if the scraper library fails, but you may test it manually here.",
|
||||
"use-openai": "Използвайте OpenAI",
|
||||
"recipe-debugger-use-openai-description": "Използвайте OpenAI за анализиране на резултатите, вместо да разчитате на библиотеката за скрепер. Когато създавате рецепта чрез URL адрес, това се прави автоматично, ако библиотеката за скрепер се провали, но можете да я тествате ръчно тук.",
|
||||
"debug": "Отстраняване на грешки",
|
||||
"tree-view": "Дървовиден изглед",
|
||||
"recipe-servings": "Recipe Servings",
|
||||
"recipe-servings": "Порции рецепта",
|
||||
"recipe-yield": "Добиване от рецепта",
|
||||
"recipe-yield-text": "Recipe Yield Text",
|
||||
"recipe-yield-text": "Текст за порции на рецепта",
|
||||
"unit": "Единица",
|
||||
"upload-image": "Качване на изображение",
|
||||
"screen-awake": "Запази екрана активен",
|
||||
"remove-image": "Премахване на изображение",
|
||||
"nextStep": "Следваща стъпка",
|
||||
"recipe-actions": "Recipe Actions",
|
||||
"recipe-actions": "Действия с рецепти",
|
||||
"parser": {
|
||||
"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",
|
||||
"openai-parser": "OpenAI Parser",
|
||||
"parse-all": "Parse All",
|
||||
"no-unit": "No unit",
|
||||
"missing-unit": "Create missing unit: {unit}",
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"ingredient-parser": "Анализатор на съставки",
|
||||
"explanation": "За да използвате анализатора на съставките, щракнете върху бутона „Анализ на всички“, за да стартирате процеса. След като обработените съставки са налични, можете да прегледате елементите и да проверите дали са били анализирани правилно. Коефициентът на достоверност на модела се показва отдясно на заглавието на елемента. Този резултат е средна стойност на всички отделни оценки и не винаги може да бъде напълно точен.",
|
||||
"alerts-explainer": "Ще се показват предупреждения, ако бъдат намерени съответстващи храни или единици, но не съществуват в базата данни.",
|
||||
"select-parser": "Изберете парсер",
|
||||
"natural-language-processor": "Процесор за естествен език",
|
||||
"brute-parser": "Груб анализатор",
|
||||
"openai-parser": "OpenAI парсер",
|
||||
"parse-all": "Разбор на всички",
|
||||
"no-unit": "Няма зададена мерна единица",
|
||||
"missing-unit": "Създаване на липсваща мерна единица: {unit}",
|
||||
"missing-food": "Създаване на липсваща храна: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Тази мерна единица не може да бъде анализирана автоматично",
|
||||
"this-food-could-not-be-parsed-automatically": "Тази храна не може да бъде анализирана автоматично",
|
||||
"no-food": "Не е зададен вид храна",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
"upload-another-image": "Upload another image",
|
||||
"upload-images": "Upload images",
|
||||
"upload-more-images": "Upload more images",
|
||||
"set-as-cover-image": "Set as recipe cover image",
|
||||
"cover-image": "Cover image"
|
||||
"reset-servings-count": "Нулиране на броя на порциите",
|
||||
"not-linked-ingredients": "Допълнителни съставки",
|
||||
"upload-another-image": "Качете друго изображение",
|
||||
"upload-images": "Качване на изображения",
|
||||
"upload-more-images": "Качете още изображения",
|
||||
"set-as-cover-image": "Задай като изображение на корицата на рецептата",
|
||||
"cover-image": "Изображение на корицата"
|
||||
},
|
||||
"recipe-finder": {
|
||||
"recipe-finder": "Recipe Finder",
|
||||
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.",
|
||||
"selected-ingredients": "Selected Ingredients",
|
||||
"no-ingredients-selected": "No ingredients selected",
|
||||
"missing": "Missing",
|
||||
"no-recipes-found": "No recipes found",
|
||||
"no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters",
|
||||
"include-ingredients-on-hand": "Include Ingredients On Hand",
|
||||
"include-tools-on-hand": "Include Tools On Hand",
|
||||
"max-missing-ingredients": "Max Missing Ingredients",
|
||||
"max-missing-tools": "Max Missing Tools",
|
||||
"selected-tools": "Selected Tools",
|
||||
"other-filters": "Other Filters",
|
||||
"ready-to-make": "Ready to Make",
|
||||
"almost-ready-to-make": "Almost Ready to Make"
|
||||
"recipe-finder": "Търсачка на рецепти",
|
||||
"recipe-finder-description": "Търсете рецепти въз основа на съставките, които имате под ръка. Можете също да филтрирате по налични инструменти и да зададете максимален брой липсващи съставки или инструменти.",
|
||||
"selected-ingredients": "Избрани съставки",
|
||||
"no-ingredients-selected": "Няма избрани съставки",
|
||||
"missing": "Липсващ",
|
||||
"no-recipes-found": "Няма намерени рецепти",
|
||||
"no-recipes-found-description": "Опитайте да добавите още съставки към търсенето си или да коригирате филтрите си",
|
||||
"include-ingredients-on-hand": "Включете наличните съставки",
|
||||
"include-tools-on-hand": "Включете наличните инструменти",
|
||||
"max-missing-ingredients": "Максимален брой липсващи съставки",
|
||||
"max-missing-tools": "Максимален брой липсващи инструменти",
|
||||
"selected-tools": "Избрани инструменти",
|
||||
"other-filters": "Други филтри",
|
||||
"ready-to-make": "Готови за приготвяне",
|
||||
"almost-ready-to-make": "Почти готови за приготвяне"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Разширено търсене",
|
||||
@@ -705,7 +712,7 @@
|
||||
"or": "Или",
|
||||
"has-any": "Има някое",
|
||||
"has-all": "Има всички",
|
||||
"clear-selection": "Clear Selection",
|
||||
"clear-selection": "Изчистване на избора",
|
||||
"results": "Резултати",
|
||||
"search": "Търсене",
|
||||
"search-mealie": "Търсене в Mealie (Натисни /)",
|
||||
@@ -721,10 +728,10 @@
|
||||
"admin-settings": "Административни настройки",
|
||||
"backup": {
|
||||
"backup-created": "Архивът е създаден успешно",
|
||||
"backup-created-at-response-export_path": "Резервно копие е създадено на {path}",
|
||||
"backup-created-at-response-export_path": "Резервно копие е създадено в {path}",
|
||||
"backup-deleted": "Резервното копие е изтрито",
|
||||
"restore-success": "Успешно възстановяване",
|
||||
"restore-fail": "Restore failed. Check your server logs for more details",
|
||||
"restore-fail": "Възстановяването не бе успешно. Проверете лог файловете на сървъра си за повече подробности",
|
||||
"backup-tag": "Етикет на резервното копие",
|
||||
"create-heading": "Създай резервно копие",
|
||||
"delete-backup": "Изтрий резервно копие",
|
||||
@@ -737,7 +744,7 @@
|
||||
"backup-restore": "Възстановяване на резервно копие",
|
||||
"back-restore-description": "Възстановяването на това резервно копие ще презапише цялата текуща информация във Вашата база данни и директорията с данни, и ще ги замени със съдържанието от резервното копие. {cannot-be-undone} Ако възстановяването е успешно ще бъдете отписан от системата.",
|
||||
"cannot-be-undone": "Това действие не може да бъде отменено - използвайте с внимание.",
|
||||
"postgresql-note": "If you are using PostgreSQL, please review the {backup-restore-process} prior to restoring.",
|
||||
"postgresql-note": "Ако използвате PostgreSQL, моля, прегледайте {backup-restore-process} преди възстановяване.",
|
||||
"backup-restore-process-in-the-documentation": "процес за резервно копие/възстановяване в документацията",
|
||||
"irreversible-acknowledgment": "Разбирам, че това действие е невъзвращаемо, разрушително и може да доведе до загуба на данни",
|
||||
"restore-backup": "Възстановяване на резервно копие"
|
||||
@@ -841,7 +848,7 @@
|
||||
"email-configured": "Email е конфигуриран",
|
||||
"email-test-results": "Резултати от тест на email",
|
||||
"ready": "Готов",
|
||||
"not-ready": "Не е готово - Проверете променливите на средата",
|
||||
"not-ready": "Не е завършена - Проверете променливите на средата.",
|
||||
"succeeded": "Успешно",
|
||||
"failed": "Неуспешно",
|
||||
"general-about": "Основни настройки",
|
||||
@@ -862,9 +869,9 @@
|
||||
"oidc-ready": "Готов за OIDC",
|
||||
"oidc-ready-error-text": "Не всички OIDC стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате OIDC удостоверяване.",
|
||||
"oidc-ready-success-text": "Задължителните OIDC променливи са зададени.",
|
||||
"openai-ready": "OpenAI Ready",
|
||||
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
|
||||
"openai-ready-success-text": "Required OpenAI variables are all set."
|
||||
"openai-ready": "Готов за OpenAI",
|
||||
"openai-ready-error-text": "Не всички стойности на OpenAI са конфигурирани. Това може да се игнорира, ако не използвате функции на OpenAI.",
|
||||
"openai-ready-success-text": "Всички необходими променливи на OpenAI са зададени."
|
||||
},
|
||||
"shopping-list": {
|
||||
"all-lists": "Всички списъци",
|
||||
@@ -878,7 +885,7 @@
|
||||
"food": "Продукт",
|
||||
"note": "Бележка",
|
||||
"label": "Етикет",
|
||||
"save-label": "Save Label",
|
||||
"save-label": "Запазване на етикета",
|
||||
"linked-item-warning": "Елементът е добавен към една или повече рецепти. Редактиране на единиците или храните ще се отрази с непредвидими резултати когато добавяте или премахвате рецепта от списъка.",
|
||||
"toggle-food": "Превключване на храна",
|
||||
"manage-labels": "Управление на етикети",
|
||||
@@ -894,12 +901,12 @@
|
||||
"items-checked-count": "Няма отбелязани етикети|Един елемент е отбелязан|{count} елементи са отбелязани",
|
||||
"no-label": "Няма етикет",
|
||||
"completed-on": "Приключена на {date}",
|
||||
"you-are-offline": "You are offline",
|
||||
"you-are-offline-description": "Not all features are available while offline. You can still add, modify, and remove items, but you will not be able to sync your changes to the server until you are back online.",
|
||||
"are-you-sure-you-want-to-check-all-items": "Are you sure you want to check all items?",
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
|
||||
"no-shopping-lists-found": "No Shopping Lists Found"
|
||||
"you-are-offline": "Вие сте в офлайн режим",
|
||||
"you-are-offline-description": "Не всички функции са достъпни, докато сте офлайн. Все още можете да добавяте, променяте и премахвате елементи, но няма да можете да синхронизирате промените си със сървъра, докато не се свържете отново онлайн.",
|
||||
"are-you-sure-you-want-to-check-all-items": "Сигурни ли сте, че искате да изберете всички елементи?",
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Сигурни ли сте, че искате да премахнете отметката от всички елементи?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Сигурни ли сте, че искате да изтриете всички отметнати елементи?",
|
||||
"no-shopping-lists-found": "Не са намерени списъци за пазаруване"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Всички рецепти",
|
||||
@@ -1043,9 +1050,9 @@
|
||||
"authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie",
|
||||
"permissions": "Права",
|
||||
"administrator": "Администратор",
|
||||
"user-can-invite-other-to-group": "User can invite others to group",
|
||||
"user-can-invite-other-to-group": "Потребителя може да добавя други в групата",
|
||||
"user-can-manage-group": "Потребителя може да управлява групата",
|
||||
"user-can-manage-household": "User can manage household",
|
||||
"user-can-manage-household": "Потребителят може да управлява домакинството",
|
||||
"user-can-organize-group-data": "Потребителя може да организира данните на групата",
|
||||
"enable-advanced-features": "Включване на разширени функции",
|
||||
"it-looks-like-this-is-your-first-time-logging-in": "Изглежда това е първият път, в който влизате.",
|
||||
@@ -1076,8 +1083,8 @@
|
||||
"food-data": "Данни за храните",
|
||||
"example-food-singular": "пример: Домат",
|
||||
"example-food-plural": "пример: Домати",
|
||||
"label-overwrite-warning": "This will assign the chosen label to all selected foods and potentially overwrite your existing labels.",
|
||||
"on-hand-checkbox-label": "Setting this flag will make this food unchecked by default when adding a recipe to a shopping list."
|
||||
"label-overwrite-warning": "Това ще присвои избрания етикет на всички избрани храни и евентуално ще презапише съществуващите ви етикети.",
|
||||
"on-hand-checkbox-label": "Задаването на този флаг ще направи тази храна неотметната по подразбиране при добавяне на рецепта към списък за пазаруване."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Заредете базата данни с мерни единици на Вашия местен език.",
|
||||
@@ -1106,7 +1113,7 @@
|
||||
"edit-label": "Редактиране на етикет",
|
||||
"new-label": "Нов етикет",
|
||||
"labels": "Етикети",
|
||||
"assign-label": "Assign Label"
|
||||
"assign-label": "Присвояване на етикет"
|
||||
},
|
||||
"recipes": {
|
||||
"purge-exports": "Изчистване на експортите",
|
||||
@@ -1130,10 +1137,10 @@
|
||||
"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"
|
||||
"recipe-actions-data": "Данни за действия с рецепти",
|
||||
"new-recipe-action": "Ново действие с рецепта",
|
||||
"edit-recipe-action": "Редактиране на действието с рецепта",
|
||||
"action-type": "Вид действие"
|
||||
},
|
||||
"create-alias": "Създаване на псевдоним",
|
||||
"manage-aliases": "Управление на псевдоними",
|
||||
@@ -1170,7 +1177,7 @@
|
||||
"group-details": "Подробности за групата",
|
||||
"group-details-description": "Преди да създадете акаунт, ще трябва да създадете група. Вашата група ще съдържа само Вас, но ще можете да поканите други по-късно. Членовете във вашата група могат да споделят планове за хранене, списъци за пазаруване, рецепти и други!",
|
||||
"use-seed-data": "Използвай предварителни данни",
|
||||
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
|
||||
"use-seed-data-description": "Mealie се доставя с колекция от храни, единици и етикети, които могат да се използват за попълване на вашата група с полезни данни за организиране на вашите рецепти. Те са преведени на езика, който сте избрали в момента. Винаги можете да добавяте или променяте тези данни по-късно.",
|
||||
"account-details": "Подробни данни за акаунта"
|
||||
},
|
||||
"validation": {
|
||||
@@ -1301,25 +1308,25 @@
|
||||
"restore-from-v1-backup": "Имате резервно копие от предишна инстанция на Mealie v1? Можете да го възстановите тук.",
|
||||
"manage-profile-or-get-invite-link": "Управлявайте собствения си профил или вземете връзка за покана, която да споделите с други."
|
||||
},
|
||||
"debug-openai-services": "Debug OpenAI Services",
|
||||
"debug-openai-services-description": "Use this page to debug OpenAI services. You can test your OpenAI connection and see the results here. If you have image services enabled, you can also provide an image.",
|
||||
"run-test": "Run Test",
|
||||
"test-results": "Test Results",
|
||||
"debug-openai-services": "Отстраняване на грешки в услугите на OpenAI",
|
||||
"debug-openai-services-description": "Използвайте тази страница за отстраняване на грешки в услугите на OpenAI. Можете да тествате връзката си с OpenAI и да видите резултатите тук. Ако имате активирани услуги за изображения, можете също да предоставите изображение.",
|
||||
"run-test": "Изпълнение на теста",
|
||||
"test-results": "Резултати от теста",
|
||||
"group-delete-note": "Групите от потребители или домакинства немогат да бъдат изтривани",
|
||||
"household-delete-note": "Домакинства с потребители не могат да бъдат изтривани"
|
||||
},
|
||||
"profile": {
|
||||
"welcome-user": "👋 Добре дошъл(а), {0}!",
|
||||
"description": "Настройки на профил, рецепти и настройки на групата.",
|
||||
"invite-link": "Invite Link",
|
||||
"invite-link": "Линк за Покана",
|
||||
"get-invite-link": "Вземи линк за покана",
|
||||
"get-public-link": "Вземи публичен линк",
|
||||
"account-summary": "Обобщение на акаунта",
|
||||
"account-summary-description": "Обобщение на информацията за Вашата група.",
|
||||
"group-statistics": "Статистики на групата",
|
||||
"group-statistics-description": "Вашата статистика на групата дава известна представа как използвате Mealie.",
|
||||
"household-statistics": "Household Statistics",
|
||||
"household-statistics-description": "Your Household Statistics provide some insight how you're using Mealie.",
|
||||
"household-statistics": "Статистика на домакинствата",
|
||||
"household-statistics-description": "Статистиката на вашето домакинство дава известна представа за това как използвате Mealie.",
|
||||
"storage-capacity": "Капацитет за съхранение",
|
||||
"storage-capacity-description": "Вашият капацитет за съхранение е изчисление на изображенията и активите, които сте качили.",
|
||||
"personal": "Лични",
|
||||
@@ -1329,13 +1336,13 @@
|
||||
"api-tokens-description": "Управление на API токени за достъп от външни приложения.",
|
||||
"group-description": "Тези елементи се споделят във вашата група. Редактирането на един от тях ще го промени за цялата група!",
|
||||
"group-settings": "Настройки на групата",
|
||||
"group-settings-description": "Manage your common group settings, like privacy settings.",
|
||||
"household-description": "These items are shared within your household. Editing one of them will change it for the whole household!",
|
||||
"household-settings": "Household Settings",
|
||||
"household-settings-description": "Manage your household settings, like mealplan and privacy settings.",
|
||||
"group-settings-description": "Управлявайте общите настройки на групата си, като например настройките за поверителност.",
|
||||
"household-description": "Тези елементи се споделят в рамките на вашето домакинство. Редактирането на един от тях ще го промени за цялото домакинство!",
|
||||
"household-settings": "Настройки на домакинството",
|
||||
"household-settings-description": "Управлявайте настройките на домакинството си, като например план за хранене и настройки за поверителност.",
|
||||
"cookbooks-description": "Управление на категории на рецепти и генериране на съответните страници.",
|
||||
"members": "Участници",
|
||||
"members-description": "See who's in your household and manage their permissions.",
|
||||
"members-description": "Вижте кой е във вашето домакинство и управлявайте техните разрешения.",
|
||||
"webhooks-description": "Настройте webhooks, които се задействат в дните, в които имате планиран план за хранене.",
|
||||
"notifiers": "Уведомители",
|
||||
"notifiers-description": "Настройте имейл и push известия, които се задействат при конкретни събития.",
|
||||
@@ -1360,9 +1367,9 @@
|
||||
},
|
||||
"cookbook": {
|
||||
"cookbooks": "Готварски книги",
|
||||
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.",
|
||||
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households",
|
||||
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar",
|
||||
"description": "Готварските книги са друг начин за организиране на рецепти чрез създаване на напречни секции от рецепти, органайзери и други филтри. Създаването на готварска книга ще добави запис към страничната лента и всички рецепти с избраните филтри ще бъдат показани в готварската книга.",
|
||||
"hide-cookbooks-from-other-households": "Скриване на готварски книги от други домакинства",
|
||||
"hide-cookbooks-from-other-households-description": "Когато е активирано, в страничната лента ще се показват само готварски книги от вашето домакинство",
|
||||
"public-cookbook": "Публична книга с рецепти",
|
||||
"public-cookbook-description": "Публичните готварски книги могат да се споделят с потребители, които не са в Mealie, и ще се показват на страницата на вашите групи.",
|
||||
"filter-options": "Опции на филтъра",
|
||||
@@ -1372,31 +1379,31 @@
|
||||
"require-all-tools": "Изискване на всички инструменти",
|
||||
"cookbook-name": "Име на книгата с рецепти",
|
||||
"cookbook-with-name": "Книга с рецепти {0}",
|
||||
"household-cookbook-name": "{0} Cookbook {1}",
|
||||
"household-cookbook-name": "{0} Готварска книга {1}",
|
||||
"create-a-cookbook": "Създай Готварска книга",
|
||||
"cookbook": "Готварска книга"
|
||||
},
|
||||
"query-filter": {
|
||||
"logical-operators": {
|
||||
"and": "AND",
|
||||
"or": "OR"
|
||||
"and": "И",
|
||||
"or": "ИЛИ"
|
||||
},
|
||||
"relational-operators": {
|
||||
"equals": "equals",
|
||||
"does-not-equal": "does not equal",
|
||||
"is-greater-than": "is greater than",
|
||||
"is-greater-than-or-equal-to": "is greater than or equal to",
|
||||
"is-less-than": "is less than",
|
||||
"is-less-than-or-equal-to": "is less than or equal to"
|
||||
"equals": "е равно на",
|
||||
"does-not-equal": "не е равно на",
|
||||
"is-greater-than": "е по-голямо от",
|
||||
"is-greater-than-or-equal-to": "е по-голямо от или равно на",
|
||||
"is-less-than": "е по-малко от",
|
||||
"is-less-than-or-equal-to": "e по-малко или равно на"
|
||||
},
|
||||
"relational-keywords": {
|
||||
"is": "is",
|
||||
"is-not": "is not",
|
||||
"is-one-of": "is one of",
|
||||
"is-not-one-of": "is not one of",
|
||||
"contains-all-of": "contains all of",
|
||||
"is-like": "is like",
|
||||
"is-not-like": "is not like"
|
||||
"is": "е",
|
||||
"is-not": "не е",
|
||||
"is-one-of": "е едно от",
|
||||
"is-not-one-of": "не е едно от",
|
||||
"contains-all-of": "съдържа всички от",
|
||||
"is-like": "е като",
|
||||
"is-not-like": "не е като"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Podeu importar directament des de les dades planes",
|
||||
"import-original-keywords-as-tags": "Importa les paraules clau originals com a tags",
|
||||
"stay-in-edit-mode": "Segueix en el mode d'edició",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importa des d'un ZIP",
|
||||
"import-from-zip-description": "Importa una sola recepta que ha estat importada d'una altra instància de Mealie.",
|
||||
"import-from-html-or-json": "Importar des d'un HTML o JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Crear menjar que manca: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "Sense menjar"
|
||||
"no-food": "Sense menjar",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reiniciar racions servides",
|
||||
"not-linked-ingredients": "Ingredients addicionals",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Můžete importovat přímo ze surových dat",
|
||||
"import-original-keywords-as-tags": "Importovat původní klíčová slova jako štítky",
|
||||
"stay-in-edit-mode": "Zůstat v režimu úprav",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importovat ze zipu",
|
||||
"import-from-zip-description": "Importovat jeden recept, který byl exportován z jiné instance Mealie.",
|
||||
"import-from-html-or-json": "Importovat z HTML nebo JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Vytvořit chybějící jídlo: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Tuto jednotku nelze analyzovat automaticky",
|
||||
"this-food-could-not-be-parsed-automatically": "Toto jídlo nelze analyzovat automaticky",
|
||||
"no-food": "Žádné jídlo"
|
||||
"no-food": "Žádné jídlo",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Resetovat počet porcí",
|
||||
"not-linked-ingredients": "Další ingredience",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kan importere direkte fra rå data",
|
||||
"import-original-keywords-as-tags": "Importér originale nøgleord som mærker",
|
||||
"stay-in-edit-mode": "Bliv i redigeringstilstand",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importer fra zip-fil",
|
||||
"import-from-zip-description": "Importer en enkelt opskrift, der blev eksporteret fra en anden Mealie instans.",
|
||||
"import-from-html-or-json": "Importer fra HTML eller JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Opret manglende fødevare: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Denne enhed kunne ikke fortolkes automatisk",
|
||||
"this-food-could-not-be-parsed-automatically": "Denne fødevare kunne ikke fortolkes automatisk",
|
||||
"no-food": "Ingen fødevarer"
|
||||
"no-food": "Ingen fødevarer",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Nulstil antal serveringer",
|
||||
"not-linked-ingredients": "Yderligere ingredienser",
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
"organizers": "Organisieren",
|
||||
"caution": "Vorsicht",
|
||||
"show-advanced": "Erweiterte Optionen anzeigen",
|
||||
"add-field": "Bedingung hinzufügen",
|
||||
"add-field": "Feld Hinzufügen",
|
||||
"date-created": "Erstellungsdatum",
|
||||
"date-updated": "Aktualisiert am"
|
||||
},
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kannst direkt von Rohdaten importieren",
|
||||
"import-original-keywords-as-tags": "Importiere ursprüngliche Stichwörter als Schlagwörter",
|
||||
"stay-in-edit-mode": "Im Bearbeitungsmodus bleiben",
|
||||
"parse-recipe-ingredients-after-import": "Zutaten nach dem Import parsen",
|
||||
"import-from-zip": "Von Zip importieren",
|
||||
"import-from-zip-description": "Importiere ein einzelnes Rezept, das von einer anderen Mealie-Instanz exportiert wurde.",
|
||||
"import-from-html-or-json": "Aus HTML oder JSON importieren",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Fehlendes Lebensmittel erstellen: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Diese Einheit konnte nicht automatisch analysiert werden",
|
||||
"this-food-could-not-be-parsed-automatically": "Dieses Lebensmittel konnte nicht automatisch analysiert werden",
|
||||
"no-food": "Kein Lebensmittel"
|
||||
"no-food": "Kein Lebensmittel",
|
||||
"review-parsed-ingredients": "Geparste Zutaten überprüfen",
|
||||
"confidence-score": "Zuverlässigkeitswert",
|
||||
"ingredient-parser-description": "Deine Zutaten wurden erfolgreich geparst. Bitte überprüfe die Zutaten, bei denen wir uns nicht sicher sind.",
|
||||
"ingredient-parser-final-review-description": "Sobald alle Zutaten überprüft wurden, kannst du nochmal alle Zutaten kontrollieren, bevor die Änderungen ins Rezept übernommen werden.",
|
||||
"add-text-as-alias-for-item": "Füge \"{text}\" als Alias für {item} hinzu",
|
||||
"delete-item": "Element löschen"
|
||||
},
|
||||
"reset-servings-count": "Portionen zurücksetzen",
|
||||
"not-linked-ingredients": "Zusätzliche Zutaten",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Μπορείτε να κάνετε εισαγωγή απευθείας από ακατέργαστα δεδομένα",
|
||||
"import-original-keywords-as-tags": "Εισαγωγή αρχικών λέξεων-κλειδιών ως ετικέτες",
|
||||
"stay-in-edit-mode": "Παραμονή σε λειτουργία επεξεργασίας",
|
||||
"parse-recipe-ingredients-after-import": "Ανάλυση συστατικών συνταγής μετά την εισαγωγή",
|
||||
"import-from-zip": "Εισαγωγή μέσω zip",
|
||||
"import-from-zip-description": "Εισαγωγή μιας μόνο συνταγής που εξάχθηκε από μια άλλη υπόσταση Mealie.",
|
||||
"import-from-html-or-json": "Εισαγωγή από HTML ή JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Δημιουργία τροφίμου που λείπει: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Δεν ήταν δυνατή η αυτόματη ανάλυση αυτής της μονάδας",
|
||||
"this-food-could-not-be-parsed-automatically": "Δεν ήταν δυνατή η αυτόματη ανάλυση αυτού του φαγητού",
|
||||
"no-food": "Χωρίς Τρόφιμο"
|
||||
"no-food": "Χωρίς Τρόφιμο",
|
||||
"review-parsed-ingredients": "Επανεξέταση αναλυμένων συστατικών",
|
||||
"confidence-score": "Βαθμολογία εμπιστοσύνης",
|
||||
"ingredient-parser-description": "Τα συστατικά σας έχουν αναλυθεί επιτυχώς. Παρακαλούμε ελέγξτε τα συστατικά για τα οποία δεν είμαστε σίγουροι.",
|
||||
"ingredient-parser-final-review-description": "Μόλις εξεταστούν όλα τα συστατικά, θα έχετε μία ακόμη ευκαιρία να επανεξετάσετε όλα τα συστατικά πριν εφαρμόσετε τις αλλαγές στη συνταγή σας.",
|
||||
"add-text-as-alias-for-item": "Προσθήκη \"{text}\" ως ψευδώνυμο για το {item}",
|
||||
"delete-item": "Διαγραφή αντικειμένου"
|
||||
},
|
||||
"reset-servings-count": "Επαναφορά μέτρησης μερίδων",
|
||||
"not-linked-ingredients": "Πρόσθετα συστατικά",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Import original keywords as tags",
|
||||
"stay-in-edit-mode": "Stay in Edit mode",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Import from Zip",
|
||||
"import-from-zip-description": "Import a single recipe that was exported from another Mealie instance.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Import original keywords as tags",
|
||||
"stay-in-edit-mode": "Stay in Edit mode",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Import from Zip",
|
||||
"import-from-zip-description": "Import a single recipe that was exported from another Mealie instance.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"github": "GitHub",
|
||||
"log-lines": "Líneas de registro",
|
||||
"not-demo": "No Demo",
|
||||
"portfolio": "Portfolio",
|
||||
"portfolio": "Portafolio",
|
||||
"production": "Producción",
|
||||
"support": "Soporte",
|
||||
"version": "Versión",
|
||||
@@ -45,7 +45,7 @@
|
||||
"category-filter": "Filtros de Categorías",
|
||||
"category-update-failed": "Error al actualizar categoría",
|
||||
"category-updated": "Categoría actualizada",
|
||||
"uncategorized-count": "{count} no categorizado",
|
||||
"uncategorized-count": "{count} sin categorizar",
|
||||
"create-a-category": "Crear una categoría",
|
||||
"category-name": "Nombre de la categoría",
|
||||
"category": "Categoría"
|
||||
@@ -561,7 +561,7 @@
|
||||
"see-original-text": "Mostrar Texto Original",
|
||||
"original-text-with-value": "Texto original: {originalText}",
|
||||
"ingredient-linker": "Vincular ingredientes",
|
||||
"unlinked": "Not linked yet",
|
||||
"unlinked": "Aún no vinculado",
|
||||
"linked-to-other-step": "Enlazado a otro paso",
|
||||
"auto": "Auto",
|
||||
"cook-mode": "Modo Cocinar",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Puede importar directamente desde datos brutos",
|
||||
"import-original-keywords-as-tags": "Importar palabras clave originales como etiquetas",
|
||||
"stay-in-edit-mode": "Permanecer en modo edición",
|
||||
"parse-recipe-ingredients-after-import": "Analizar los ingredientes de la receta después de importarla",
|
||||
"import-from-zip": "Importar desde zip",
|
||||
"import-from-zip-description": "Importa una receta única que fue exportada desde otra instancia de Mealie.",
|
||||
"import-from-html-or-json": "Importar desde HTML o JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Crear comida faltante: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Esta unidad no pudo ser procesada automáticamente",
|
||||
"this-food-could-not-be-parsed-automatically": "Esta comida no pudo ser procesada automáticamente",
|
||||
"no-food": "Sin Comida"
|
||||
"no-food": "Sin Comida",
|
||||
"review-parsed-ingredients": "Revisar los ingredientes analizados",
|
||||
"confidence-score": "Puntuación de confianza",
|
||||
"ingredient-parser-description": "Tus ingredientes se han analizado correctamente. Revisa los ingredientes que no nos convencen.",
|
||||
"ingredient-parser-final-review-description": "Una vez que se hayan revisado todos los ingredientes, tendrás una oportunidad más de revisarlos todos antes de aplicar los cambios a tu receta.",
|
||||
"add-text-as-alias-for-item": "Añadir \"{text}\" como alias para {item}",
|
||||
"delete-item": "Borrar elemento"
|
||||
},
|
||||
"reset-servings-count": "Restablecer contador de porciones",
|
||||
"not-linked-ingredients": "Ingredientes adicionales",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Sa võid otse importida töötlemata andmetest",
|
||||
"import-original-keywords-as-tags": "Impordi originaal võtmesõnad siltidena",
|
||||
"stay-in-edit-mode": "Püsige redigeerimisrežiimis",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Impordi .zip-st",
|
||||
"import-from-zip-description": "Impordi üks retsept, mis oli eksporditud teisest Mealie paigaldusest.",
|
||||
"import-from-html-or-json": "Impordi HTMLst või JSONist",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Loo puuduv toit: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "Toit puudub"
|
||||
"no-food": "Toit puudub",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Lähtesta portsionite arv",
|
||||
"not-linked-ingredients": "Lisa-koostisosad",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Voit tuoda raakadatan suoraan",
|
||||
"import-original-keywords-as-tags": "Tuo alkuperäiset avainsanat tunnisteiksi",
|
||||
"stay-in-edit-mode": "Pysy muokkaustilassa",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Tuo zip-arkistosta",
|
||||
"import-from-zip-description": "Tuo yksi resepti, joka on viety toisesta Mealie-asennuksesta.",
|
||||
"import-from-html-or-json": "Tuo HTML- tai JSON-tiedostosta",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Luo puuttuva ruoka: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "Ei ruokaa"
|
||||
"no-food": "Ei ruokaa",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Palauta Annoksien Määrä",
|
||||
"not-linked-ingredients": "Muut ainesosat",
|
||||
|
||||
@@ -561,7 +561,7 @@
|
||||
"see-original-text": "Afficher le texte original",
|
||||
"original-text-with-value": "Texte original : {originalText}",
|
||||
"ingredient-linker": "Liaison d’ingrédients",
|
||||
"unlinked": "Not linked yet",
|
||||
"unlinked": "Pas encore associée",
|
||||
"linked-to-other-step": "Déjà associé à une autre étape",
|
||||
"auto": "Auto",
|
||||
"cook-mode": "Mode Cuisine",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Vous pouvez directement importer des données brutes",
|
||||
"import-original-keywords-as-tags": "Importer les mots-clés d'origine en tant que tags",
|
||||
"stay-in-edit-mode": "Rester en mode édition",
|
||||
"parse-recipe-ingredients-after-import": "Analyser les ingrédients de la recette après l'import",
|
||||
"import-from-zip": "Importer depuis un zip",
|
||||
"import-from-zip-description": "Importer une recette qui a été exportée depuis une autre instance de Mealie.",
|
||||
"import-from-html-or-json": "Importer depuis HTML ou JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Créer un aliment manquant : {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Cette unité n'a pas pu être analysée automatiquement",
|
||||
"this-food-could-not-be-parsed-automatically": "Cet aliment n'a pas pu être analysé automatiquement",
|
||||
"no-food": "Aucun aliment"
|
||||
"no-food": "Aucun aliment",
|
||||
"review-parsed-ingredients": "Vérifier les ingrédients analysés",
|
||||
"confidence-score": "Score de confiance",
|
||||
"ingredient-parser-description": "Vos ingrédients ont été analysés avec succès. Veuillez vérifier les ingrédients dont nous ne sommes pas certains.",
|
||||
"ingredient-parser-final-review-description": "Une fois que tous les ingrédients ont été analysés, vous aurez encore une chance de vérifier tous les ingrédients avant de les appliquer à votre recette.",
|
||||
"add-text-as-alias-for-item": "Ajouter \"{text}\" comme alias pour {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Réinitialiser le nombre de portions",
|
||||
"not-linked-ingredients": "Ingrédients supplémentaires",
|
||||
|
||||
@@ -561,7 +561,7 @@
|
||||
"see-original-text": "Afficher le texte original",
|
||||
"original-text-with-value": "Texte original: {originalText}",
|
||||
"ingredient-linker": "Association d’ingrédients",
|
||||
"unlinked": "Not linked yet",
|
||||
"unlinked": "Pas encore associée",
|
||||
"linked-to-other-step": "Lié à une autre étape",
|
||||
"auto": "Auto",
|
||||
"cook-mode": "Mode Cuisine",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Vous pouvez directement importer des données brutes",
|
||||
"import-original-keywords-as-tags": "Importer les mots-clés d'origine en tant que tags",
|
||||
"stay-in-edit-mode": "Rester en mode édition",
|
||||
"parse-recipe-ingredients-after-import": "Analyser les ingrédients de la recette après l'import",
|
||||
"import-from-zip": "Importer depuis un zip",
|
||||
"import-from-zip-description": "Importer une recette qui a été exportée depuis une autre instance de Mealie.",
|
||||
"import-from-html-or-json": "Importer depuis HTML ou JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Créer un aliment manquant : {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Cette unité n'a pas pu être analysée automatiquement",
|
||||
"this-food-could-not-be-parsed-automatically": "Cet aliment n'a pas pu être analysé automatiquement",
|
||||
"no-food": "Aucun aliment"
|
||||
"no-food": "Aucun aliment",
|
||||
"review-parsed-ingredients": "Vérifier les ingrédients analysés",
|
||||
"confidence-score": "Score de confiance",
|
||||
"ingredient-parser-description": "Vos ingrédients ont été analysés avec succès. Veuillez vérifier les ingrédients dont nous ne sommes pas certains.",
|
||||
"ingredient-parser-final-review-description": "Une fois que tous les ingrédients ont été analysés, vous aurez encore une chance de vérifier tous les ingrédients avant de les appliquer à votre recette.",
|
||||
"add-text-as-alias-for-item": "Ajouter \"{text}\" comme alias pour {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Réinitialiser le nombre de portions",
|
||||
"not-linked-ingredients": "Ingrédients supplémentaires",
|
||||
@@ -1170,7 +1177,7 @@
|
||||
"group-details": "Détails du groupe",
|
||||
"group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter d’autres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes d’achat, leurs recettes et plus encore !",
|
||||
"use-seed-data": "Utiliser l'initialisation de données",
|
||||
"use-seed-data-description": "Mealie est livrée avec une collection d'aliments, d'unités et d'étiquettes qui peuvent être utilisés pour remplir votre groupe avec des données utiles pour organiser vos recettes. Ceux-ci sont traduits dans la langue que vous avez sélectionnée. Vous pouvez toujours ajouter ou modifier ces données plus tard.",
|
||||
"use-seed-data-description": "Mealie est livrée avec une collection d'aliments, d'unités et d'étiquettes qui peuvent être utilisés pour peupler votre groupe avec des données utiles pour organiser vos recettes. Ceux-ci sont traduits dans la langue que vous avez sélectionnée. Vous pouvez toujours ajouter ou modifier ces données plus tard.",
|
||||
"account-details": "Détails du compte"
|
||||
},
|
||||
"validation": {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"support": "Soutenir",
|
||||
"version": "Version",
|
||||
"unknown-version": "inconnu",
|
||||
"sponsor": "Sponsoriser"
|
||||
"sponsor": "Soutenir"
|
||||
},
|
||||
"asset": {
|
||||
"assets": "Ressources",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Vous pouvez directement importer des données brutes",
|
||||
"import-original-keywords-as-tags": "Importer les mots-clés d'origine en tant que tags",
|
||||
"stay-in-edit-mode": "Rester en mode édition",
|
||||
"parse-recipe-ingredients-after-import": "Analyser les ingrédients de la recette après l'import",
|
||||
"import-from-zip": "Importer depuis un zip",
|
||||
"import-from-zip-description": "Importer une recette qui a été exportée depuis une autre instance de Mealie.",
|
||||
"import-from-html-or-json": "Importer depuis HTML ou JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Créer un aliment manquant : {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Cette unité n'a pas pu être analysée automatiquement",
|
||||
"this-food-could-not-be-parsed-automatically": "Cet aliment n'a pas pu être analysé automatiquement",
|
||||
"no-food": "Aucun aliment"
|
||||
"no-food": "Aucun aliment",
|
||||
"review-parsed-ingredients": "Vérifier les ingrédients analysés",
|
||||
"confidence-score": "Score de confiance",
|
||||
"ingredient-parser-description": "Vos ingrédients ont été analysés avec succès. Veuillez vérifier les ingrédients dont nous ne sommes pas certains.",
|
||||
"ingredient-parser-final-review-description": "Une fois que tous les ingrédients ont été analysés, vous aurez encore une chance de vérifier tous les ingrédients avant de les appliquer à votre recette.",
|
||||
"add-text-as-alias-for-item": "Ajouter \"{text}\" comme alias pour {item}",
|
||||
"delete-item": "Supprimer l'élément"
|
||||
},
|
||||
"reset-servings-count": "Réinitialiser le nombre de portions",
|
||||
"not-linked-ingredients": "Ingrédients supplémentaires",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "É posível importar diretamente a partir de datos en bruto",
|
||||
"import-original-keywords-as-tags": "Importar palavras-chave orixinais como etiquetas",
|
||||
"stay-in-edit-mode": "Permanecer no modo de edición",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importar de Zip",
|
||||
"import-from-zip-description": "Importar unha única receita exportada de outra instancia Mealie.",
|
||||
"import-from-html-or-json": "Importar a partir de HTML ou JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Crear a comida que falta: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Non foi posíbel procesar automaticamente esta unidade",
|
||||
"this-food-could-not-be-parsed-automatically": "Non foi posíbel procesar automaticamente este alimento",
|
||||
"no-food": "Sen Comida"
|
||||
"no-food": "Sen Comida",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reiniciar Contador de Porcións",
|
||||
"not-linked-ingredients": "Ingredientes Adicionais",
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"new-notification": "התראה חדשה",
|
||||
"event-notifiers": "מנגנוני התרעה על אירועים",
|
||||
"apprise-url-skipped-if-blank": "כתובת Apprise (דלג אם ריק)",
|
||||
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
|
||||
"apprise-url-is-left-intentionally-blank": "מאחר שכתובות URL של Apprise לרוב מכילות מידע רגיש, שזה זה נותר ריק במכוון בעת העריכה. אם ברצונך לעדכן את כתובת ה URL, אנא הזן כאן את ה-URL החדש, אחרת השאר אותו ריק כדי לשמור את כתובת ה-URL הנוכחית.",
|
||||
"enable-notifier": "הפעלת מתריע",
|
||||
"what-events": "לאילו אירועים לרשום את מתריע זה?",
|
||||
"user-events": "אירועי משתמש",
|
||||
@@ -81,7 +81,7 @@
|
||||
"category-events": "אירועי קטגוריות",
|
||||
"when-a-new-user-joins-your-group": "כאשר משתמש חדש מצטרף לקבוצה",
|
||||
"recipe-events": "אירועי מתכון",
|
||||
"label-events": "Label Events"
|
||||
"label-events": "הוסף תוויות לאירועים"
|
||||
},
|
||||
"general": {
|
||||
"add": "הוספה",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "ניתן לייבא ישירות ממידע גולמי",
|
||||
"import-original-keywords-as-tags": "ייבוא שמות מפתח מקוריות כתגיות",
|
||||
"stay-in-edit-mode": "השאר במצב עריכה",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "ייבא מקובץ",
|
||||
"import-from-zip-description": "ייבוא מתכון בודד שיוצא ממילי אחרת.",
|
||||
"import-from-html-or-json": "ייבוא מ-HTML או JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "יצירת אוכל חסר: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "אין אוכל"
|
||||
"no-food": "אין אוכל",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "איפוס מספר המנות",
|
||||
"not-linked-ingredients": "מרכיבים נוספים",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Uvezi originalne ključne riječi kao oznake",
|
||||
"stay-in-edit-mode": "Ostanite u načinu uređivanja",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Uvoz iz Zip-a",
|
||||
"import-from-zip-description": "Uvezi pojedinačni recept koji je izvezen iz druge instance Mealie aplikacije.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "A nyers adatokból közvetlenül is importálhat",
|
||||
"import-original-keywords-as-tags": "Eredeti kulcsszavak importálása címkeként",
|
||||
"stay-in-edit-mode": "Maradjon Szerkesztés módban",
|
||||
"parse-recipe-ingredients-after-import": "Recept összetevőinek elemzése importálás után",
|
||||
"import-from-zip": "Importálás ZIP-ből",
|
||||
"import-from-zip-description": "Egy másik Mealie-példányból kiexportált recept egyedi importálása.",
|
||||
"import-from-html-or-json": "Importálás HTML vagy JSON fájlból",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Hiányzó élelmiszer létrehozása: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Ez az egység nem tudja automatikusan értelmezni",
|
||||
"this-food-could-not-be-parsed-automatically": "Ezt az ételt nem lehetett automatikusan feldolgozni",
|
||||
"no-food": "Élelmiszer nélküli"
|
||||
"no-food": "Élelmiszer nélküli",
|
||||
"review-parsed-ingredients": "Kinyert hozzávalók ellenőrzése",
|
||||
"confidence-score": "Bizonyossági érték",
|
||||
"ingredient-parser-description": "Sikeresen feldolgoztuk a hozzávalókat. Kérljük, nézze át azokat, amelyekben nem vagyunk teljesen biztosak.",
|
||||
"ingredient-parser-final-review-description": "Miután az összes hozzávalót átnézte, még egyszer átnézheti az összes hozzávalót, mielőtt a változtatásokat átvezeti a receptbe.",
|
||||
"add-text-as-alias-for-item": "Adja hozzá a „{text}” nevet {item} aliasaként",
|
||||
"delete-item": "Elem törlése"
|
||||
},
|
||||
"reset-servings-count": "Adagok számának visszaállítása",
|
||||
"not-linked-ingredients": "Kiegészítő hozzávalók",
|
||||
@@ -1266,7 +1273,7 @@
|
||||
"action-clean-temporary-files-description": "Minden fájl és mappa eltávolítása a .temp könyvtárból",
|
||||
"action-clean-images-name": "Képek tisztítása",
|
||||
"action-clean-images-description": "Minden nem .webp kiterjesztésű kép eltávolítása",
|
||||
"actions-description": "A karbantartási műveletek {destructive_in_bold} és óvatosan használandók. Ezen műveletek bármelyikének végrehajtása {irreversible_in_bold}.",
|
||||
"actions-description": "A karbantartási műveletek {destructive_in_bold} jellegűek, ezért óvatosan kell alkalmazni őket. Ezen műveletek végrehajtása {irreversible_in_bold}.",
|
||||
"actions-description-destructive": "romboló",
|
||||
"actions-description-irreversible": "visszafordíthatatlan",
|
||||
"logs-action-refresh": "Logok frissítése",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"demo": "Kynning",
|
||||
"demo-status": "Kynningarstaða",
|
||||
"development": "Þróun",
|
||||
"docs": "Skjöl",
|
||||
"docs": "Upplýsingar",
|
||||
"download-log": "Sækja atvikaskrá",
|
||||
"download-recipe-json": "Síðast sótt JSON",
|
||||
"github": "GitHub",
|
||||
@@ -73,7 +73,7 @@
|
||||
"enable-notifier": "Virkja tilkynningar",
|
||||
"what-events": "Hvaða viðburði ætti þessi tilkynnir að vera áskrifandi að?",
|
||||
"user-events": "Notenda viðburðir",
|
||||
"mealplan-events": "Mataráætlunar viðburðir",
|
||||
"mealplan-events": "Matarplan viðburðir",
|
||||
"when-a-user-in-your-group-creates-a-new-mealplan": "Þegar notandi í þínum hópi býr til nýtt matarplan",
|
||||
"shopping-list-events": "Innkaupslista viðburðir",
|
||||
"cookbook-events": "Matreiðslubóks viðburðir",
|
||||
@@ -99,7 +99,7 @@
|
||||
"delete": "Eyða",
|
||||
"disabled": "Afvirkjað",
|
||||
"download": "Sækja",
|
||||
"duplicate": "Tvöfalda",
|
||||
"duplicate": "Afrita",
|
||||
"edit": "Breyta",
|
||||
"enabled": "Leyft",
|
||||
"exception": "Undantekningar",
|
||||
@@ -161,14 +161,14 @@
|
||||
"sunday": "Sunnudagur",
|
||||
"system": "Kerfi",
|
||||
"templates": "Sniðmót:",
|
||||
"test": "Próf",
|
||||
"test": "Prufa",
|
||||
"themes": "Þema",
|
||||
"thursday": "Fimmtudagur",
|
||||
"title": "Titill",
|
||||
"token": "Tóki",
|
||||
"tuesday": "Þriðjudagur",
|
||||
"type": "Tegund",
|
||||
"update": "Uppfærsla",
|
||||
"update": "Uppfæra",
|
||||
"updated": "Uppfært",
|
||||
"upload": "Hlaða upp",
|
||||
"url": "URL",
|
||||
@@ -192,7 +192,7 @@
|
||||
"a-name-is-required": "Nafn er krafist",
|
||||
"delete-with-name": "Eyða út {name}",
|
||||
"confirm-delete-generic-with-name": "Ertu viss um að þú viljir eyða út {name}?",
|
||||
"confirm-delete-own-admin-account": "Please note that you are trying to delete your own admin account! This action cannot be undone and will permanently delete your account?",
|
||||
"confirm-delete-own-admin-account": "Athugið! Þú ert að reyna að eyða eigin admin aðgangi! Þessa aðgerð er ekki hægt að draga til baka og aðganginum verður varanalega eytt.",
|
||||
"organizer": "Skipuleggjari",
|
||||
"transfer": "Færa",
|
||||
"copy": "Afrita",
|
||||
@@ -200,53 +200,53 @@
|
||||
"timestamp": "Tímastimpill",
|
||||
"last-made": "Síðast gert",
|
||||
"learn-more": "Læra meira",
|
||||
"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",
|
||||
"copied-items-to-clipboard": "No item copied to clipboard|One item copied to clipboard|Copied {count} items to clipboard",
|
||||
"this-feature-is-currently-inactive": "Þessi eiginleiki er nú óvirkur",
|
||||
"clipboard-not-supported": "Klippispjald er ekki stutt",
|
||||
"copied-to-clipboard": "Afritað á klippispjald",
|
||||
"your-browser-does-not-support-clipboard": "Vafrinn þinn styður ekki klippispjald",
|
||||
"copied-items-to-clipboard": "Ekkert afritað á klippispjaldið|Eitt atriði afritað á klippisjaldið|{count} atriði afrituð á klippispjaldið",
|
||||
"actions": "Aðgerðir",
|
||||
"selected-count": "Valið: {count}",
|
||||
"export-all": "Export All",
|
||||
"export-all": "Flytja allt út",
|
||||
"refresh": "Endurhlaða",
|
||||
"upload-file": "Hlaða upp skrá",
|
||||
"created-on-date": "Búið til: {0}",
|
||||
"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.",
|
||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
|
||||
"unsaved-changes": "Þú hefur ekki vistað breytingar. Viltu vista áður en þú ferð? Ýttu á \"Í lagi\" til að vista, \"Hætta við\" til að henda breytingum.",
|
||||
"clipboard-copy-failure": "Mistókst að afrita klippispjaldið.",
|
||||
"confirm-delete-generic-items": "Ertu viss um að þú viljir eyða eftirfylgjandi atriðum?",
|
||||
"organizers": "Skipuleggjarar",
|
||||
"caution": "Varúð",
|
||||
"show-advanced": "Show Advanced",
|
||||
"show-advanced": "Ítarlegar stillingar",
|
||||
"add-field": "Bæta við dálk",
|
||||
"date-created": "Date Created",
|
||||
"date-created": "Búið til",
|
||||
"date-updated": "Dagsetning uppfærð"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Ertu viss um að þú viljir eyða <b>{groupName}<b/>?",
|
||||
"cannot-delete-default-group": "Ekki hægt að eyða sjálfvöldum hóp",
|
||||
"cannot-delete-group-with-users": "Cannot delete group with users",
|
||||
"confirm-group-deletion": "Confirm Group Deletion",
|
||||
"cannot-delete-group-with-users": "Get ekki eytt hóp sem inniheldur notendur",
|
||||
"confirm-group-deletion": "Staðfestu að eyða hópnum",
|
||||
"create-group": "Búa til hóp",
|
||||
"error-updating-group": "Villa í að uppfæra hóp",
|
||||
"group": "Hópur",
|
||||
"group-deleted": "Hóp eytt",
|
||||
"group-deletion-failed": "Villa í að eyða hóp",
|
||||
"group-id-with-value": "Group ID: {groupID}",
|
||||
"group-id-with-value": "Hóp ID: {groupID}",
|
||||
"group-name": "Nafn hóps",
|
||||
"group-not-found": "Fann ekki hóp",
|
||||
"group-token": "Group Token",
|
||||
"group-with-value": "Group: {groupID}",
|
||||
"group-token": "Hóp token",
|
||||
"group-with-value": "Hópur: {groupID}",
|
||||
"groups": "Hópar",
|
||||
"manage-groups": "Umsjá hópa",
|
||||
"user-group": "Notendahópur",
|
||||
"user-group-created": "Notendahópur búinn til",
|
||||
"user-group-creation-failed": "User Group Creation Failed",
|
||||
"user-group-creation-failed": "Mistókst að stofna notenda hóp",
|
||||
"settings": {
|
||||
"keep-my-recipes-private": "Keep My Recipes Private",
|
||||
"keep-my-recipes-private-description": "Sets your group and all recipes defaults to private. You can always change this later."
|
||||
"keep-my-recipes-private": "Ekki deila mínum uppskriftum",
|
||||
"keep-my-recipes-private-description": "Hafa sjálfgefið að hópar og uppskriftir sé ekki deilt. Það er hægt að breyta þessu síðar."
|
||||
},
|
||||
"manage-members": "Umsjá meðlima",
|
||||
"manage-members-description": "Manage the permissions of the members in your household. {manage} allows the user to access the data-management page, and {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.",
|
||||
"manage-members-description": "Stjórnaðu aðgangsheimildum annarra í heimilinu. {manage} opnar gagnastjórnunarsíðu og {invite} býr til boðtengla fyrir aðra. Stjórnendur hópa geta ekki breytt eigin heimildum.",
|
||||
"manage": "Umsjá",
|
||||
"manage-household": "Umsjá heimilis",
|
||||
"invite": "Bjóða",
|
||||
@@ -257,109 +257,109 @@
|
||||
"private-group": "Lokaður hópur",
|
||||
"private-group-description": "Ef þú stillir hópinn þinn sem lokaðan hóp lokast á alla almennan aðgang. Þessi stilling hefur forgang fram yfir einstakar stillingar fyrir almenna sýn",
|
||||
"enable-public-access": "Virkja almennan aðgang",
|
||||
"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",
|
||||
"enable-public-access-description": "Gera uppskriftir innan hópa aðgengar öllum og leyfa gestum að skoða þær án þess að skrá sig inn",
|
||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Leyfa notendum utan þíns hóps að sjá þínar uppskriftir",
|
||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Sé þetta virkt getur þú deilt uppskriftum með tengli án þess notandi þurfi að stofna aðgang. Sé þetta óvirkt, er bara hægt að deila uppskriftum innan þíns hóps eða með tilbúnum tengli",
|
||||
"show-nutrition-information": "Sýna næringargildi innihalds",
|
||||
"show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown",
|
||||
"show-nutrition-information-description": "Sé þetta virkt eru næringargildi sýnileg. Ef það er engar upplýsingar um næringargildi þá koma þau ekki fram",
|
||||
"show-recipe-assets": "Sýna skrár og efni uppskriftar",
|
||||
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available",
|
||||
"default-to-landscape-view": "Default to landscape view",
|
||||
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
|
||||
"disable-users-from-commenting-on-recipes": "Disable users from commenting on recipes",
|
||||
"disable-users-from-commenting-on-recipes-description": "Hides the comment section on the recipe page and disables commenting",
|
||||
"disable-organizing-recipe-ingredients-by-units-and-food": "Disable organizing recipe ingredients by units and food",
|
||||
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields",
|
||||
"show-recipe-assets-description": "Ef virkt munu þær skrár sem hafa verið settar inn með uppskriftinni vera sýnilegar með uppskriftinni",
|
||||
"default-to-landscape-view": "Gera lárétt snið sjálfgefið",
|
||||
"default-to-landscape-view-description": "Ef virkt verður hausinn á uppskriftinni sýndur í láréttu sniði",
|
||||
"disable-users-from-commenting-on-recipes": "Slökkva á að notendur geti skrifað athugasemdir við uppskriftir",
|
||||
"disable-users-from-commenting-on-recipes-description": "Felur möguleikann á að skrifa og sjá athugasemdir við uppskriftir",
|
||||
"disable-organizing-recipe-ingredients-by-units-and-food": "Óvirkja röðun innihaldi uppskrifta eftir einingum og matvælum",
|
||||
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Felur matvörur, einingar og magn fyrir innihaldsefni og meðhöndlar innihald sem venjulegan texta reit",
|
||||
"general-preferences": "Almenni valmöguleikar",
|
||||
"group-recipe-preferences": "Group Recipe Preferences",
|
||||
"group-recipe-preferences": "Stillingar fyrir hóp uppskriftir",
|
||||
"report": "Skýrsla",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"group-management": "Group Management",
|
||||
"admin-group-management": "Admin Group Management",
|
||||
"admin-group-management-text": "Changes to this group will be reflected immediately.",
|
||||
"group-id-value": "Group Id: {0}",
|
||||
"total-households": "Total Households",
|
||||
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
|
||||
"report-with-id": "Skýrslu ID: {id}",
|
||||
"group-management": "Hópastjórnun",
|
||||
"admin-group-management": "Hópastjórnun",
|
||||
"admin-group-management-text": "Breytingar á þessum hóp koma strax fram.",
|
||||
"group-id-value": "Hóp ID: {0}",
|
||||
"total-households": "Fjöldi heimila",
|
||||
"you-must-select-a-group-before-selecting-a-household": "Þú verður að velja hóp áður en þú velur heimili"
|
||||
},
|
||||
"household": {
|
||||
"household": "Household",
|
||||
"households": "Households",
|
||||
"user-household": "User Household",
|
||||
"create-household": "Create Household",
|
||||
"household-name": "Household Name",
|
||||
"household-group": "Household Group",
|
||||
"household-management": "Household Management",
|
||||
"manage-households": "Manage Households",
|
||||
"admin-household-management": "Admin Household Management",
|
||||
"admin-household-management-text": "Changes to this household will be reflected immediately.",
|
||||
"household-id-value": "Household Id: {0}",
|
||||
"private-household": "Private Household",
|
||||
"private-household-description": "Setting your household to private will disable all public view options. This overrides any individual public view settings",
|
||||
"lock-recipe-edits-from-other-households": "Lock recipe edits from other households",
|
||||
"lock-recipe-edits-from-other-households-description": "When enabled only users in your household can edit recipes created by your household",
|
||||
"household-recipe-preferences": "Household Recipe Preferences",
|
||||
"default-recipe-preferences-description": "These are the default settings when a new recipe is created in your household. These can be changed for individual recipes in the recipe settings menu.",
|
||||
"allow-users-outside-of-your-household-to-see-your-recipes": "Allow users outside of your household to see your recipes",
|
||||
"allow-users-outside-of-your-household-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 household or with a pre-generated private link",
|
||||
"household-preferences": "Household Preferences"
|
||||
"household": "Heimili",
|
||||
"households": "Heimili",
|
||||
"user-household": "Heimilishópur notanda",
|
||||
"create-household": "Stofna heimili",
|
||||
"household-name": "Heiti heimilis",
|
||||
"household-group": "Hópur heimilis",
|
||||
"household-management": "Umsjá heimilis",
|
||||
"manage-households": "Umsjá heimilis",
|
||||
"admin-household-management": "Umsjá heimilis",
|
||||
"admin-household-management-text": "Breytingar á þessu heimili koma strax fram.",
|
||||
"household-id-value": "Heimilis ID: {0}",
|
||||
"private-household": "Einka heimili",
|
||||
"private-household-description": "Ef þú stillir hópinn þinn sem lokaðan hóp lokast á alla almennan aðgang. Þessi stilling hefur forgang fram yfir einstakar stillingar fyrir almenna sýn",
|
||||
"lock-recipe-edits-from-other-households": "Loka fyrir að önnur heimili geti breytt uppskriftum",
|
||||
"lock-recipe-edits-from-other-households-description": "Þegar virkt þá geta bara notendur í þínu heimili breytt uppskriftum sem er settar inn af þínu heimili",
|
||||
"household-recipe-preferences": "Stillingar fyrir heimilis uppskriftir",
|
||||
"default-recipe-preferences-description": "Þetta eru sjálfgefnar stillingar þegar nýjar uppskriftir eru settar inn í þínu heimili. Þeim er hægt að breyta fyrir hverja uppskrift í stillingum fyrir uppskriftina.",
|
||||
"allow-users-outside-of-your-household-to-see-your-recipes": "Leyfa notendum utan þíns heimilis að sjá ykkar uppskriftir",
|
||||
"allow-users-outside-of-your-household-to-see-your-recipes-description": "Sé þetta virkt getur þú deilt uppskriftum með tengli án þess notandi þurfi að stofna aðgang. Sé þetta óvirkt, er bara hægt að deila uppskriftum innan þíns hóps eða með tilbúnum tengli",
|
||||
"household-preferences": "Stillingar heimilis"
|
||||
},
|
||||
"meal-plan": {
|
||||
"create-a-new-meal-plan": "Create a New Meal Plan",
|
||||
"update-this-meal-plan": "Update this Meal Plan",
|
||||
"dinner-this-week": "Dinner This Week",
|
||||
"dinner-today": "Dinner Today",
|
||||
"dinner-tonight": "DINNER TONIGHT",
|
||||
"edit-meal-plan": "Edit Meal Plan",
|
||||
"end-date": "End Date",
|
||||
"group": "Group (Beta)",
|
||||
"create-a-new-meal-plan": "Búa til nýtt matarplan",
|
||||
"update-this-meal-plan": "Uppfæra matarplan",
|
||||
"dinner-this-week": "Kvöldmaturinn í þessari viku",
|
||||
"dinner-today": "Kvöldmaturinn í dag",
|
||||
"dinner-tonight": "KVÖLDMATUR Í KVÖLD",
|
||||
"edit-meal-plan": "Breyta matarplani",
|
||||
"end-date": "Loka dagsetning",
|
||||
"group": "Hópur (Beta)",
|
||||
"main": "Main",
|
||||
"meal-planner": "Meal Planner",
|
||||
"meal-plans": "Meal Plans",
|
||||
"mealplan-categories": "MEALPLAN CATEGORIES",
|
||||
"mealplan-created": "Mealplan created",
|
||||
"mealplan-creation-failed": "Mealplan creation failed",
|
||||
"mealplan-deleted": "Mealplan Deleted",
|
||||
"mealplan-deletion-failed": "Mealplan deletion failed",
|
||||
"mealplan-settings": "Mealplan Settings",
|
||||
"mealplan-update-failed": "Mealplan update failed",
|
||||
"mealplan-updated": "Mealplan Updated",
|
||||
"mealplan-households-description": "If no household is selected, recipes can be added from any household",
|
||||
"any-category": "Any Category",
|
||||
"any-tag": "Any Tag",
|
||||
"any-household": "Any Household",
|
||||
"no-meal-plan-defined-yet": "No meal plan defined yet",
|
||||
"no-meal-planned-for-today": "No meal planned for today",
|
||||
"numberOfDays-hint": "Number of days on page load",
|
||||
"numberOfDays-label": "Default Days",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans",
|
||||
"planner": "Planner",
|
||||
"meal-planner": "Matarplan",
|
||||
"meal-plans": "Matarplön",
|
||||
"mealplan-categories": "MATARPLANS HÓPAR",
|
||||
"mealplan-created": "Matarplan útbúið",
|
||||
"mealplan-creation-failed": "Mistókst að búa til matarplan",
|
||||
"mealplan-deleted": "Matarplani eytt",
|
||||
"mealplan-deletion-failed": "Mistókst að eyða matarplani",
|
||||
"mealplan-settings": "Stillingar matarplans",
|
||||
"mealplan-update-failed": "Mistókst að uppfæra matarplan",
|
||||
"mealplan-updated": "Matarplan uppfært",
|
||||
"mealplan-households-description": "Ef ekkert heimili er valið er hægt að bæta við uppskriftum frá öllum heimilimum",
|
||||
"any-category": "Allir flokkar",
|
||||
"any-tag": "Allir flokkar",
|
||||
"any-household": "Öll heimili",
|
||||
"no-meal-plan-defined-yet": "Ekkert matarplan hefur verið skilgreint",
|
||||
"no-meal-planned-for-today": "Ekkert matarplan skipulagt í dag",
|
||||
"numberOfDays-hint": "Fjöldi daga við síðuhleðslu",
|
||||
"numberOfDays-label": "Sjálfgefnir dagar",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Aðeins uppskriftir í þessum flokkum verða notaðir í matarplan",
|
||||
"planner": "Matarplan",
|
||||
"quick-week": "Quick Week",
|
||||
"side": "Side",
|
||||
"sides": "Sides",
|
||||
"start-date": "Start Date",
|
||||
"rule-day": "Rule Day",
|
||||
"meal-type": "Meal Type",
|
||||
"breakfast": "Breakfast",
|
||||
"lunch": "Lunch",
|
||||
"dinner": "Dinner",
|
||||
"type-any": "Any",
|
||||
"day-any": "Any",
|
||||
"editor": "Editor",
|
||||
"meal-recipe": "Meal Recipe",
|
||||
"meal-title": "Meal Title",
|
||||
"meal-note": "Meal Note",
|
||||
"note-only": "Note Only",
|
||||
"random-meal": "Random Meal",
|
||||
"random-dinner": "Random Dinner",
|
||||
"random-side": "Random Side",
|
||||
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
|
||||
"to-all-days": "to all days",
|
||||
"on-days": "on {0}s",
|
||||
"for-all-meal-types": "for all meal types",
|
||||
"for-type-meal-types": "for {0} meal types",
|
||||
"meal-plan-rules": "Meal Plan Rules",
|
||||
"new-rule": "New Rule",
|
||||
"meal-plan-rules-description": "You can create rules for auto selecting recipes for your meal plans. These rules are used by the server to determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same day/type constraints then the rule filters will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.",
|
||||
"side": "Meðlæti",
|
||||
"sides": "Meðlæti",
|
||||
"start-date": "Upphafsdagsetning",
|
||||
"rule-day": "Regla fyrir dag",
|
||||
"meal-type": "Hvernig matur",
|
||||
"breakfast": "Morgunverður",
|
||||
"lunch": "Hádegisverður",
|
||||
"dinner": "Kvöldverður",
|
||||
"type-any": "Allir",
|
||||
"day-any": "Alla",
|
||||
"editor": "Ritill",
|
||||
"meal-recipe": "Uppskrift",
|
||||
"meal-title": "Heiti máltíðar",
|
||||
"meal-note": "Athugasemdir",
|
||||
"note-only": "Bara athugasemd",
|
||||
"random-meal": "Handahófskennd máltíð",
|
||||
"random-dinner": "Handahófskenndur kvöldverður",
|
||||
"random-side": "Handahófskennt meðlæti",
|
||||
"this-rule-will-apply": "Þessi regla gildir {dayCriteria} {mealTypeCriteria}.",
|
||||
"to-all-days": "alla daga",
|
||||
"on-days": "á {0}",
|
||||
"for-all-meal-types": "um allar tegundir máltíða",
|
||||
"for-type-meal-types": "um {0} ",
|
||||
"meal-plan-rules": "Reglur fyrir máltíðar skipulag",
|
||||
"new-rule": "Ný regla",
|
||||
"meal-plan-rules-description": "Þú getur búið til reglur til að velja sjálfvirkt uppskriftir fyrir máltíðar skipulagið. Þessar reglur eru notaðar af kerfinu til að ákvarða hvaða uppskriftir koma til greina þegar máltíðaplön eru búin til af handahófi. Athugaðu að ef reglur hafa sömu skilyrði fyrir dag eða tegund máltíðar, verða síurnar í reglunum sameinaðar. Í reynd er óþarfi að búa til tvítekna reglur, þó það sé mögulegt.",
|
||||
"new-rule-description": "When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to \"Any\" which will apply it to all the possible values for the day and/or meal type.",
|
||||
"recipe-rules": "Recipe Rules",
|
||||
"applies-to-all-days": "Applies to all days",
|
||||
@@ -468,7 +468,7 @@
|
||||
"calories": "Calories",
|
||||
"calories-suffix": "calories",
|
||||
"carbohydrate-content": "Carbohydrate",
|
||||
"categories": "Categories",
|
||||
"categories": "Flokkar",
|
||||
"cholesterol-content": "Cholesterol",
|
||||
"comment-action": "Comment",
|
||||
"comment": "Comment",
|
||||
@@ -479,22 +479,22 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"duplicate": "Duplicate recipe",
|
||||
"duplicate-name": "Name of the new recipe",
|
||||
"duplicate": "Afrita uppskrift",
|
||||
"duplicate-name": "Nafn á nýju uppskriftinni",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
"ingredient": "Ingredient",
|
||||
"ingredients": "Ingredients",
|
||||
"grams": "grömm",
|
||||
"ingredient": "Innihald",
|
||||
"ingredients": "Innihald",
|
||||
"insert-ingredient": "Insert Ingredient",
|
||||
"insert-section": "Insert Section",
|
||||
"insert-above": "Insert Above",
|
||||
"insert-below": "Insert Below",
|
||||
"instructions": "Instructions",
|
||||
"instructions": "Aðferð",
|
||||
"key-name-required": "Key Name Required",
|
||||
"landscape-view-coming-soon": "Landscape View",
|
||||
"milligrams": "milligrams",
|
||||
"milligrams": "milligrömm",
|
||||
"new-key-name": "New Key Name",
|
||||
"no-white-space-allowed": "No White Space Allowed",
|
||||
"note": "Note",
|
||||
@@ -525,8 +525,8 @@
|
||||
"share-recipe-message": "I wanted to share my {0} recipe with you.",
|
||||
"show-nutrition-values": "Show Nutrition Values",
|
||||
"sodium-content": "Sodium",
|
||||
"step-index": "Step: {step}",
|
||||
"sugar-content": "Sugar",
|
||||
"step-index": "Skref {step}",
|
||||
"sugar-content": "Sykur",
|
||||
"title": "Title",
|
||||
"total-time": "Total Time",
|
||||
"trans-fat-content": "Trans-fat",
|
||||
@@ -535,7 +535,7 @@
|
||||
"no-recipe": "No Recipe",
|
||||
"locked-by-owner": "Locked by Owner",
|
||||
"join-the-conversation": "Join the Conversation",
|
||||
"add-recipe-to-mealplan": "Add Recipe to Mealplan",
|
||||
"add-recipe-to-mealplan": "Bæta uppskrift við matarplan",
|
||||
"entry-type": "Entry Type",
|
||||
"date-format-hint": "MM/DD/YYYY format",
|
||||
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD format",
|
||||
@@ -545,9 +545,9 @@
|
||||
"recipe-added-to-list": "Recipe added to list",
|
||||
"recipes-added-to-list": "Recipes added to list",
|
||||
"successfully-added-to-list": "Successfully added to list",
|
||||
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
||||
"recipe-added-to-mealplan": "Uppskrift bætt við matarplan",
|
||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
||||
"failed-to-add-recipe-to-mealplan": "Mistókst að bæta uppskrift við matarplan",
|
||||
"failed-to-add-to-list": "Failed to add to list",
|
||||
"yield": "Yield",
|
||||
"yields-amount-with-text": "Yields {amount} {text}",
|
||||
@@ -560,12 +560,12 @@
|
||||
"toggle-section": "Toggle Section",
|
||||
"see-original-text": "See Original Text",
|
||||
"original-text-with-value": "Original Text: {originalText}",
|
||||
"ingredient-linker": "Ingredient Linker",
|
||||
"ingredient-linker": "Tengja innihald",
|
||||
"unlinked": "Not linked yet",
|
||||
"linked-to-other-step": "Linked to other step",
|
||||
"linked-to-other-step": "Tengt við önnur skref",
|
||||
"auto": "Auto",
|
||||
"cook-mode": "Cook Mode",
|
||||
"link-ingredients": "Link Ingredients",
|
||||
"link-ingredients": "Tengja við innihald",
|
||||
"merge-above": "Merge Above",
|
||||
"move-to-bottom": "Move To Bottom",
|
||||
"move-to-top": "Move To Top",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Import original keywords as tags",
|
||||
"stay-in-edit-mode": "Stay in Edit mode",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Import from Zip",
|
||||
"import-from-zip-description": "Import a single recipe that was exported from another Mealie instance.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -653,7 +654,7 @@
|
||||
"upload-image": "Upload image",
|
||||
"screen-awake": "Keep Screen Awake",
|
||||
"remove-image": "Remove image",
|
||||
"nextStep": "Next step",
|
||||
"nextStep": "Næsta skref",
|
||||
"recipe-actions": "Recipe Actions",
|
||||
"parser": {
|
||||
"ingredient-parser": "Ingredient Parser",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
@@ -718,7 +725,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Add a New Theme",
|
||||
"admin-settings": "Admin Settings",
|
||||
"admin-settings": "Stjórnanda stillingar",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created-at-response-export_path": "Backup Created at {path}",
|
||||
@@ -751,7 +758,7 @@
|
||||
"first-day-of-week": "First day of the week",
|
||||
"group-settings-updated": "Group Settings Updated",
|
||||
"homepage": {
|
||||
"all-categories": "All Categories",
|
||||
"all-categories": "Allir flokkar",
|
||||
"card-per-section": "Card Per Section",
|
||||
"home-page": "Home Page",
|
||||
"home-page-sections": "Home Page Sections",
|
||||
@@ -767,11 +774,11 @@
|
||||
"organize": "Organize",
|
||||
"page-name": "Page Name",
|
||||
"pages": "Pages",
|
||||
"profile": "Profile",
|
||||
"profile": "Prófíll",
|
||||
"remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries",
|
||||
"set-new-time": "Set New Time",
|
||||
"settings-update-failed": "Settings update failed",
|
||||
"settings-updated": "Settings updated",
|
||||
"settings-updated": "Stillingar uppfærðar",
|
||||
"site-settings": "Site Settings",
|
||||
"theme": {
|
||||
"accent": "Accent",
|
||||
@@ -792,7 +799,7 @@
|
||||
"theme-name": "Theme Name",
|
||||
"theme-name-is-required": "Theme Name is required.",
|
||||
"theme-saved": "Theme Saved",
|
||||
"theme-updated": "Theme updated",
|
||||
"theme-updated": "Þema uppfært",
|
||||
"warning": "Warning",
|
||||
"light-mode": "Light Mode",
|
||||
"dark-mode": "Dark Mode"
|
||||
@@ -844,7 +851,7 @@
|
||||
"not-ready": "Not Ready - Check Environmental Variables",
|
||||
"succeeded": "Succeeded",
|
||||
"failed": "Failed",
|
||||
"general-about": "General About",
|
||||
"general-about": "Upplýsingar",
|
||||
"application-version": "Application Version",
|
||||
"application-version-error-text": "Your current version ({0}) does not match the latest release. Considering updating to the latest version ({1}).",
|
||||
"mealie-is-up-to-date": "Mealie is up to date",
|
||||
@@ -874,10 +881,10 @@
|
||||
"new-list": "New List",
|
||||
"quantity": "Quantity: {0}",
|
||||
"shopping-list": "Shopping List",
|
||||
"shopping-lists": "Shopping Lists",
|
||||
"shopping-lists": "Innkaupalisti",
|
||||
"food": "Food",
|
||||
"note": "Note",
|
||||
"label": "Label",
|
||||
"label": "Merkingar",
|
||||
"save-label": "Save Label",
|
||||
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
|
||||
"toggle-food": "Toggle Food",
|
||||
@@ -904,13 +911,13 @@
|
||||
"sidebar": {
|
||||
"all-recipes": "All Recipes",
|
||||
"backups": "Backups",
|
||||
"categories": "Categories",
|
||||
"categories": "Flokkar",
|
||||
"cookbooks": "Cookbooks",
|
||||
"dashboard": "Dashboard",
|
||||
"home-page": "Home Page",
|
||||
"manage-users": "Manage Users",
|
||||
"migrations": "Migrations",
|
||||
"profile": "Profile",
|
||||
"profile": "Prófíll",
|
||||
"search": "Search",
|
||||
"site-settings": "Site Settings",
|
||||
"tags": "Tags",
|
||||
@@ -957,7 +964,7 @@
|
||||
"tool": "Tool"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Admin",
|
||||
"admin": "Stjórnandi",
|
||||
"are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link <b>{link}<b/>?",
|
||||
"are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user <b>{activeName} ID: {activeId}<b/>?",
|
||||
"auth-method": "Auth Method",
|
||||
@@ -989,14 +996,14 @@
|
||||
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
|
||||
"password-must-match": "Password must match",
|
||||
"password-reset-failed": "Password reset failed",
|
||||
"password-updated": "Password updated",
|
||||
"password-updated": "Lykilorð uppfært",
|
||||
"password": "Password",
|
||||
"password-strength": "Password is {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"register": "Register",
|
||||
"reset-password": "Reset Password",
|
||||
"sign-in": "Sign in",
|
||||
"total-mealplans": "Total MealPlans",
|
||||
"total-mealplans": "Fjöldi matarplana",
|
||||
"total-users": "Total Users",
|
||||
"upload-photo": "Upload Photo",
|
||||
"use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password",
|
||||
@@ -1008,9 +1015,9 @@
|
||||
"user-password": "User Password",
|
||||
"user-successfully-logged-in": "User Successfully Logged In",
|
||||
"user-update-failed": "User update failed",
|
||||
"user-updated": "User updated",
|
||||
"user-updated": "Notandaupplýsingar uppfærðar",
|
||||
"user": "User",
|
||||
"username": "Username",
|
||||
"username": "Notandanafn",
|
||||
"users-header": "USERS",
|
||||
"users": "Users",
|
||||
"user-not-found": "User not found",
|
||||
@@ -1021,7 +1028,7 @@
|
||||
"enable-advanced-content": "Enable Advanced Content",
|
||||
"enable-advanced-content-description": "Enables advanced features like Recipe Scaling, API keys, Webhooks, and Data Management. Don't worry, you can always change this later",
|
||||
"favorite-recipes": "Favorite Recipes",
|
||||
"email-or-username": "Email or Username",
|
||||
"email-or-username": "Tölvupóstur eða notandanafn",
|
||||
"remember-me": "Remember Me",
|
||||
"please-enter-your-email-and-password": "Please enter your email and password",
|
||||
"invalid-credentials": "Invalid Credentials",
|
||||
@@ -1035,14 +1042,14 @@
|
||||
},
|
||||
"user-management": "User Management",
|
||||
"reset-locked-users": "Reset Locked Users",
|
||||
"admin-user-creation": "Admin User Creation",
|
||||
"admin-user-management": "Admin User Management",
|
||||
"admin-user-creation": "Búa til stjórnandaaðgang",
|
||||
"admin-user-management": "Stjórna notendum",
|
||||
"user-details": "User Details",
|
||||
"user-name": "User Name",
|
||||
"authentication-method": "Authentication Method",
|
||||
"authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie'",
|
||||
"permissions": "Permissions",
|
||||
"administrator": "Administrator",
|
||||
"administrator": "Stjórnandi",
|
||||
"user-can-invite-other-to-group": "User can invite others to group",
|
||||
"user-can-manage-group": "User can manage group",
|
||||
"user-can-manage-household": "User can manage household",
|
||||
@@ -1121,10 +1128,10 @@
|
||||
"data-exports-description": "This section provides links to available exports that are ready to download. These exports do expire, so be sure to grab them while they're still available.",
|
||||
"data-exports": "Data Exports",
|
||||
"tag": "Tag",
|
||||
"categorize": "Categorize",
|
||||
"update-settings": "Update Settings",
|
||||
"categorize": "Flokka",
|
||||
"update-settings": "Uppfæra stillingar",
|
||||
"tag-recipes": "Tag Recipes",
|
||||
"categorize-recipes": "Categorize Recipes",
|
||||
"categorize-recipes": "Flokka uppskriftir",
|
||||
"export-recipes": "Export Recipes",
|
||||
"delete-recipes": "Delete Recipes",
|
||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||
@@ -1146,8 +1153,8 @@
|
||||
"columns": "Columns",
|
||||
"combine": "Combine",
|
||||
"categories": {
|
||||
"edit-category": "Edit Category",
|
||||
"new-category": "New Category",
|
||||
"edit-category": "Breyta flokkum",
|
||||
"new-category": "Nýr flokkur",
|
||||
"category-data": "Category Data"
|
||||
},
|
||||
"tags": {
|
||||
@@ -1175,7 +1182,7 @@
|
||||
},
|
||||
"validation": {
|
||||
"group-name-is-taken": "Group name is taken",
|
||||
"username-is-taken": "Username is taken",
|
||||
"username-is-taken": "Notandanafn er þegar í notkun",
|
||||
"email-is-taken": "Email is taken",
|
||||
"this-field-is-required": "This Field is Required"
|
||||
},
|
||||
@@ -1205,7 +1212,7 @@
|
||||
},
|
||||
"demo": {
|
||||
"info_message_with_version": "This is a Demo for version: {version}",
|
||||
"demo_username": "Username: {username}",
|
||||
"demo_username": "Notandi: {username}",
|
||||
"demo_password": "Password: {password}"
|
||||
},
|
||||
"ocr-editor": {
|
||||
@@ -1284,13 +1291,13 @@
|
||||
"openai": "OpenAI",
|
||||
"show-individual-confidence": "Show individual confidence",
|
||||
"ingredient-text": "Ingredient Text",
|
||||
"average-confident": "{0} Confident",
|
||||
"average-confident": "{0} öryggi",
|
||||
"try-an-example": "Try an example",
|
||||
"parser": "Parser",
|
||||
"background-tasks": "Background Tasks",
|
||||
"background-tasks-description": "Here you can view all the running background tasks and their status",
|
||||
"no-logs-found": "No Logs Found",
|
||||
"tasks": "Tasks",
|
||||
"tasks": "Verkefni",
|
||||
"setup": {
|
||||
"first-time-setup": "First Time Setup",
|
||||
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
|
||||
@@ -1325,14 +1332,14 @@
|
||||
"personal": "Personal",
|
||||
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
|
||||
"user-settings": "User Settings",
|
||||
"user-settings-description": "Manage your preferences, change your password, and update your email.",
|
||||
"user-settings-description": "Breyta þínum stillingum, breyta lykilorði og breyta tölvupósti.",
|
||||
"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-settings": "Group Settings",
|
||||
"group-settings-description": "Manage your common group settings, like privacy settings.",
|
||||
"household-description": "These items are shared within your household. Editing one of them will change it for the whole household!",
|
||||
"household-settings": "Household Settings",
|
||||
"household-settings-description": "Manage your household settings, like mealplan and privacy settings.",
|
||||
"household-settings-description": "Stjórnaðu stillingum heimilis, eins og matarplani og persónuverndarstillingum.",
|
||||
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
|
||||
"members": "Members",
|
||||
"members-description": "See who's in your household and manage their permissions.",
|
||||
@@ -1348,7 +1355,7 @@
|
||||
"personal-information": "Personal Information",
|
||||
"preferences": "Preferences",
|
||||
"show-advanced-description": "Show advanced features (API Keys, Webhooks, and Data Management)",
|
||||
"back-to-profile": "Back to Profile",
|
||||
"back-to-profile": "Til baka í prófíl",
|
||||
"looking-for-privacy-settings": "Looking for Privacy Settings?",
|
||||
"manage-your-api-tokens": "Manage Your API Tokens",
|
||||
"manage-user-profile": "Manage User Profile",
|
||||
@@ -1359,7 +1366,7 @@
|
||||
"manage-data-migrations": "Manage Data Migrations"
|
||||
},
|
||||
"cookbook": {
|
||||
"cookbooks": "Cookbooks",
|
||||
"cookbooks": "Uppskriftabók",
|
||||
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.",
|
||||
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households",
|
||||
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar",
|
||||
@@ -1367,7 +1374,7 @@
|
||||
"public-cookbook-description": "Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.",
|
||||
"filter-options": "Filter Options",
|
||||
"filter-options-description": "When require all is selected the cookbook will only include recipes that have all of the items selected. This applies to each subset of selectors and not a cross section of the selected items.",
|
||||
"require-all-categories": "Require All Categories",
|
||||
"require-all-categories": "Þarf alla flokka",
|
||||
"require-all-tags": "Require All Tags",
|
||||
"require-all-tools": "Require All Tools",
|
||||
"cookbook-name": "Cookbook Name",
|
||||
|
||||
@@ -561,7 +561,7 @@
|
||||
"see-original-text": "Vedi Testo Originale",
|
||||
"original-text-with-value": "Testo originale: {originalText}",
|
||||
"ingredient-linker": "Linker degli Ingredienti",
|
||||
"unlinked": "Not linked yet",
|
||||
"unlinked": "Non ancora collegato",
|
||||
"linked-to-other-step": "Collegato ad un altro passaggio",
|
||||
"auto": "Automatico",
|
||||
"cook-mode": "Modalità di Cottura",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "È possibile importare direttamente dai dati grezzi",
|
||||
"import-original-keywords-as-tags": "Importa parole chiave originali come tag",
|
||||
"stay-in-edit-mode": "Rimani in modalità Modifica",
|
||||
"parse-recipe-ingredients-after-import": "Analizza gli ingredienti della ricetta dopo l'importazione",
|
||||
"import-from-zip": "Importa da Zip",
|
||||
"import-from-zip-description": "Importa una singola ricetta esportata da un'altra istanza di Mealie.",
|
||||
"import-from-html-or-json": "Importa da HTML o JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Crea cibo mancante: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Questa unità non può essere analizzata automaticamente",
|
||||
"this-food-could-not-be-parsed-automatically": "Questo alimento non può essere analizzato automaticamente",
|
||||
"no-food": "Nessun Alimento"
|
||||
"no-food": "Nessun Alimento",
|
||||
"review-parsed-ingredients": "Controlla ingredienti analizzati",
|
||||
"confidence-score": "Punteggio Di Confidenza",
|
||||
"ingredient-parser-description": "I tuoi ingredienti sono stati analizzati con successo. Controlla gli ingredienti di cui non siamo sicuri.",
|
||||
"ingredient-parser-final-review-description": "Una volta che tutti gli ingredienti sono stati controllati, avrai ancora una possibilità di rivederli tutti prima di applicare le modifiche alla ricetta.",
|
||||
"add-text-as-alias-for-item": "Aggiungi \"{text}\" come alias per {item}",
|
||||
"delete-item": "Elimina elemento"
|
||||
},
|
||||
"reset-servings-count": "Reimposta conteggio porzioni",
|
||||
"not-linked-ingredients": "Ingredienti Aggiuntivi",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "生データから直接インポートできます",
|
||||
"import-original-keywords-as-tags": "元のキーワードをタグとしてインポート",
|
||||
"stay-in-edit-mode": "編集モードを維持",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Zipからインポート",
|
||||
"import-from-zip-description": "別のMealieインスタンスからエクスポートされた1つのレシピをインポートします。",
|
||||
"import-from-html-or-json": "HTML または JSON からインポート",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "欠けている食材を作成: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "食材はありません"
|
||||
"no-food": "食材はありません",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "サービング数をリセット",
|
||||
"not-linked-ingredients": "追加の材料",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Import original keywords as tags",
|
||||
"stay-in-edit-mode": "Stay in Edit mode",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Import from Zip",
|
||||
"import-from-zip-description": "Import a single recipe that was exported from another Mealie instance.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
|
||||
"import-original-keywords-as-tags": "Įkelti pradinius raktažodžius kaip žymas",
|
||||
"stay-in-edit-mode": "Toliau redaguoti",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Įkelti iš .ZIP archyvo",
|
||||
"import-from-zip-description": "Įkelti atskirą receptą, išsaugotą iš Mealie.",
|
||||
"import-from-html-or-json": "Import from HTML or JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Create missing food: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "No Food"
|
||||
"no-food": "No Food",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reset Servings Count",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Jūs varat importēt no neapstrādātiem datiem tieši",
|
||||
"import-original-keywords-as-tags": "Importējiet oriģinālos atslēgvārdus kā tagus",
|
||||
"stay-in-edit-mode": "Palieciet rediģēšanas režīmā",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importēt no Zip",
|
||||
"import-from-zip-description": "Importējiet vienu recepti, kas tika eksportēta no cita Mealie gadījuma.",
|
||||
"import-from-html-or-json": "Importēt no HTML vai JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Izveidojiet trūkstošo ēdienu: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "Nav pārtikas"
|
||||
"no-food": "Nav pārtikas",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Atiestatīt porciju skaitu",
|
||||
"not-linked-ingredients": "Additional Ingredients",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "U kunt direct importeren uit onbewerkte gegevens",
|
||||
"import-original-keywords-as-tags": "Importeer oorspronkelijke trefwoorden als labels",
|
||||
"stay-in-edit-mode": "Blijf in bewerkingsmodus",
|
||||
"parse-recipe-ingredients-after-import": "Ontleed de ingrediënten van het recept na importeren",
|
||||
"import-from-zip": "Importeren uit zip",
|
||||
"import-from-zip-description": "Importeer een recept dat geëxporteerd was uit een andere Mealie instantie.",
|
||||
"import-from-html-or-json": "Importeer van HTML of JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Ontbrekend levensmiddel maken: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Mealie kon deze meeteenheid niet automatisch verwerken",
|
||||
"this-food-could-not-be-parsed-automatically": "Mealie kon dit soort voedsel niet automatisch verwerken",
|
||||
"no-food": "Geen levensmiddel"
|
||||
"no-food": "Geen levensmiddel",
|
||||
"review-parsed-ingredients": "Controleer ingrediënten na de ontleding",
|
||||
"confidence-score": "Betrouwbaarheids-score",
|
||||
"ingredient-parser-description": "Uw ingrediëntenlijst is succesvol ontleed. Controleer de ingrediënten waar we niet zeker van zijn.",
|
||||
"ingredient-parser-final-review-description": "Als alle ingrediënten gecontroleerd zijn heb je, voor het toepassen van wijzigingen in je recept, nog één kans om alle ingrediënten te controleren.",
|
||||
"add-text-as-alias-for-item": "Voeg \"{text}\" toe als alias voor {item}",
|
||||
"delete-item": "Verwijderen item"
|
||||
},
|
||||
"reset-servings-count": "Porties resetten",
|
||||
"not-linked-ingredients": "Extra ingrediënten",
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"new-notification": "Nytt varsel",
|
||||
"event-notifiers": "Hendelsesvarsler",
|
||||
"apprise-url-skipped-if-blank": "Apprise-URL (hoppes over hvis tom)",
|
||||
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
|
||||
"apprise-url-is-left-intentionally-blank": "Siden Apprise URL-er vanligvis inneholder sensitiv informasjon, må dette feltet ikke være tomt mens du redigerer. Hvis du vil oppdatere URL, skriv inn den nye her, ellers la den være tom for å beholde den gjeldende URL.",
|
||||
"enable-notifier": "Aktiver varslingsagenten",
|
||||
"what-events": "Hvilke hendelser skal denne varslingsagenten abonnere på?",
|
||||
"user-events": "Brukerhendelser",
|
||||
@@ -81,7 +81,7 @@
|
||||
"category-events": "Kategorihendelser",
|
||||
"when-a-new-user-joins-your-group": "Når en ny bruker blir med i gruppen din",
|
||||
"recipe-events": "Oppskriftshendelser",
|
||||
"label-events": "Label Events"
|
||||
"label-events": "Merk hendelser"
|
||||
},
|
||||
"general": {
|
||||
"add": "Legg til",
|
||||
@@ -561,7 +561,7 @@
|
||||
"see-original-text": "Se opprinnelig tekst",
|
||||
"original-text-with-value": "Opprinnelig tekst: {originalText}",
|
||||
"ingredient-linker": "Tilknytt ingredienser",
|
||||
"unlinked": "Not linked yet",
|
||||
"unlinked": "Ingen lenke enda",
|
||||
"linked-to-other-step": "Tilknyttet et annet steg",
|
||||
"auto": "Automatisk",
|
||||
"cook-mode": "Tilberedelsesmodus",
|
||||
@@ -590,7 +590,7 @@
|
||||
"api-extras-description": "Ekstramaterialer til oppskrifter er en viktig funksjon i Mealie API-en. De lar deg opprette egendefinerte JSON-nøkkel/verdi-par innenfor en oppskrift for å referere fra tredjepartsapplikasjoner. Du kan bruke disse nøklene til å gi informasjon for eksempel for å utløse automatiseringer eller egendefinerte meldinger som skal videreformidles til ønsket enhet.",
|
||||
"message-key": "Meldingsnøkkel",
|
||||
"parse": "Analyser",
|
||||
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.",
|
||||
"ingredients-not-parsed-description": "Det ser ut som ingridensene ikke har blitt analysert enda. Klikk\"{parse}\"-knappen under for å analysere ingrediensese som strukturert matvarer.",
|
||||
"attach-images-hint": "Fest bilder ved å dra og slippe dem inn i redigereringsverktøyet",
|
||||
"drop-image": "Slipp bilde",
|
||||
"enable-ingredient-amounts-to-use-this-feature": "Aktiver ingrediensmengder for å bruke denne funksjonen",
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kan importere fra rådata direkte",
|
||||
"import-original-keywords-as-tags": "Importer originale søkeord som emneord",
|
||||
"stay-in-edit-mode": "Forbli i redigeringsmodus",
|
||||
"parse-recipe-ingredients-after-import": "Analyser oppskriftens ingredienser etter at importen er fullført",
|
||||
"import-from-zip": "Importer fra zip-fil",
|
||||
"import-from-zip-description": "Importer en enkelt oppskrift som ble eksportert fra en annen Mealie-instans.",
|
||||
"import-from-html-or-json": "Importer fra HTML eller JSON",
|
||||
@@ -667,9 +668,15 @@
|
||||
"no-unit": "Ingen enhet",
|
||||
"missing-unit": "Opprett manglende enhet: {unit}",
|
||||
"missing-food": "Opprett manglende mat: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-unit-could-not-be-parsed-automatically": "Denne enheten kunne ikke analyseres automatisk",
|
||||
"this-food-could-not-be-parsed-automatically": "Denne maten kunne ikke leses automatisk",
|
||||
"no-food": "Ingen matvarer"
|
||||
"no-food": "Ingen matvarer",
|
||||
"review-parsed-ingredients": "Gjennomgå de analyserte ingrediensene",
|
||||
"confidence-score": "Tillit score",
|
||||
"ingredient-parser-description": "Ingrediensene har blitt analysert. Venligst gjennomgå ingrediensene vi ikke er sikkre på.",
|
||||
"ingredient-parser-final-review-description": "Når alle ingredienser er gjennomgått, har du en siste sjanse til å gjennomgå alle ingrediensene før du tar i bruk endringene på oppskriften din.",
|
||||
"add-text-as-alias-for-item": "Legg til \"{text}\" som kallenavn for {item}",
|
||||
"delete-item": "Slett gjenstanden"
|
||||
},
|
||||
"reset-servings-count": "Nullstill antall porsjoner",
|
||||
"not-linked-ingredients": "Tilleggsingredienser",
|
||||
@@ -1170,7 +1177,7 @@
|
||||
"group-details": "Gruppedetaljer",
|
||||
"group-details-description": "Før du oppretter en konto må du opprette en gruppe. Gruppen din vil bare inneholde deg, men du vil kunne invitere andre senere. Medlemmer i gruppen din kan dele måltider, handlelister, oppskrifter med mer!",
|
||||
"use-seed-data": "Bruk tilføringsdata",
|
||||
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
|
||||
"use-seed-data-description": "Mealie leveres med en kolleksjon av matvarer, enheter og etiketter, som can bli brukt til å utfylle gruppen med hjelpsom data for organisering av oppskrifter. Disse er oversatt over til språket du har valgt. Du kan alltid legge til eller modifisere denne dataen senere.",
|
||||
"account-details": "Kontodetaljer"
|
||||
},
|
||||
"validation": {
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Możesz zaimportować bezpośrednio z surowych danych",
|
||||
"import-original-keywords-as-tags": "Importuj oryginalne słowa kluczowe jako tagi",
|
||||
"stay-in-edit-mode": "Pozostań w trybie edycji",
|
||||
"parse-recipe-ingredients-after-import": "Analizuj składniki receptury po zaimportowaniu",
|
||||
"import-from-zip": "Importuj z pliku Zip",
|
||||
"import-from-zip-description": "Importuj pojedynczy przepis, który został wyeksportowany z innej instancji Mealie.",
|
||||
"import-from-html-or-json": "Importuj z HTML lub JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Utwórz brakującą potrawę: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Nie można przetworzyć tej jednostki automatycznie",
|
||||
"this-food-could-not-be-parsed-automatically": "Nie można przetworzyć tego jedzenia automatycznie",
|
||||
"no-food": "Brak potrawy"
|
||||
"no-food": "Brak potrawy",
|
||||
"review-parsed-ingredients": "Przejrzyj przeanalizowane składniki",
|
||||
"confidence-score": "Wskaźnik pewności",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Usuń przedmiot"
|
||||
},
|
||||
"reset-servings-count": "Zresetuj liczbę porcji",
|
||||
"not-linked-ingredients": "Dodatkowe składniki",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Você pode importar diretamente de dados brutos",
|
||||
"import-original-keywords-as-tags": "Importar palavras-chave originais como marcadores",
|
||||
"stay-in-edit-mode": "Permanecer no modo de edição",
|
||||
"parse-recipe-ingredients-after-import": "Interpretar os ingredientes da receita após importar",
|
||||
"import-from-zip": "Importar do .zip",
|
||||
"import-from-zip-description": "Importar uma única receita exportada de outra instância Mealie.",
|
||||
"import-from-html-or-json": "Importar de HTML ou JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Criar comida ausente: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Esta unidade não pôde ser analisada automaticamente",
|
||||
"this-food-could-not-be-parsed-automatically": "Este alimento não pôde ser analisado automaticamente",
|
||||
"no-food": "Sem Comida"
|
||||
"no-food": "Sem Comida",
|
||||
"review-parsed-ingredients": "Revisar ingredientes analisados",
|
||||
"confidence-score": "Nota de Confiança",
|
||||
"ingredient-parser-description": "Seus ingredientes foram analisados com sucesso. Por favor, verifique os ingredientes que não temos certeza.",
|
||||
"ingredient-parser-final-review-description": "Depois que todos os ingredientes forem revisados, você terá mais uma chance de revisar todos os ingredientes antes de aplicar as mudanças em sua receita.",
|
||||
"add-text-as-alias-for-item": "Adicionar \"{text}\" como apelido para {item}",
|
||||
"delete-item": "Excluir item"
|
||||
},
|
||||
"reset-servings-count": "Redefinir Contagem de Porções",
|
||||
"not-linked-ingredients": "Ingredientes adicionais",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "É possível importar diretamente a partir de dados em bruto",
|
||||
"import-original-keywords-as-tags": "Importar palavras-chave originais como etiquetas",
|
||||
"stay-in-edit-mode": "Permanecer no modo de edição",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importar de Zip",
|
||||
"import-from-zip-description": "Importar uma única receita que foi exportada de outra instância Mealie.",
|
||||
"import-from-html-or-json": "Importar a partir de HTML ou JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Criar ingrediente em falta: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "Não foi possível processar automaticamente esta unidade",
|
||||
"this-food-could-not-be-parsed-automatically": "Não foi possível processar automaticamente este alimento",
|
||||
"no-food": "Nenhum Ingrediente"
|
||||
"no-food": "Nenhum Ingrediente",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Reiniciar Contador de Doses",
|
||||
"not-linked-ingredients": "Ingredientes Adicionais",
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"scrape-recipe-you-can-import-from-raw-data-directly": "Poți importa datele direct",
|
||||
"import-original-keywords-as-tags": "Importă cuvintele cheie originale ca tag-uri",
|
||||
"stay-in-edit-mode": "Rămâi în modul Editare",
|
||||
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
|
||||
"import-from-zip": "Importă din zip",
|
||||
"import-from-zip-description": "Importă o rețetă care a fost exportată dintr-o altă instanță a Mealie.",
|
||||
"import-from-html-or-json": "Importă din HTML sau JSON",
|
||||
@@ -669,7 +670,13 @@
|
||||
"missing-food": "Creează mâncare lipsă: {food}",
|
||||
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
|
||||
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
|
||||
"no-food": "Niciun aliment"
|
||||
"no-food": "Niciun aliment",
|
||||
"review-parsed-ingredients": "Review parsed ingredients",
|
||||
"confidence-score": "Confidence Score",
|
||||
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
|
||||
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Delete Item"
|
||||
},
|
||||
"reset-servings-count": "Resetează numărul de serviri",
|
||||
"not-linked-ingredients": "Ingrediente suplimentare",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user