mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-25 01:03:13 -05:00
Compare commits
56 Commits
v1.0.0beta
...
v1.0.0beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13850cda1f | ||
|
|
483f789b8e | ||
|
|
1b83c82997 | ||
|
|
34f52c06a6 | ||
|
|
07fef8af9f | ||
|
|
703ee32653 | ||
|
|
3d4e5441dd | ||
|
|
f00280e32b | ||
|
|
9e6a720cf1 | ||
|
|
7f50071312 | ||
|
|
c64da1fdb7 | ||
|
|
2809cef3b1 | ||
|
|
2f7ff6d178 | ||
|
|
c05e048b65 | ||
|
|
157bad0e29 | ||
|
|
f96a584a5d | ||
|
|
151e20489a | ||
|
|
7dbb0858bd | ||
|
|
b921e95163 | ||
|
|
cb15db2d27 | ||
|
|
c158672d12 | ||
|
|
292bf7068a | ||
|
|
5db4dedc3f | ||
|
|
f122c382e9 | ||
|
|
c865bc7769 | ||
|
|
efffe26a19 | ||
|
|
8b054fd945 | ||
|
|
bb1fa52d10 | ||
|
|
d4b92a8ade | ||
|
|
85d514eb1a | ||
|
|
8878f78ab1 | ||
|
|
d315ad63d2 | ||
|
|
48053b55b9 | ||
|
|
78c7399ff7 | ||
|
|
f70fc18222 | ||
|
|
6f83b0f522 | ||
|
|
5a053cdcd6 | ||
|
|
b1256f4ad2 | ||
|
|
525842e9a1 | ||
|
|
9e261f5235 | ||
|
|
3f808f8f00 | ||
|
|
394df6c210 | ||
|
|
754e77c9cb | ||
|
|
3030e3e7f4 | ||
|
|
f6c18ec73d | ||
|
|
84dc60d7bf | ||
|
|
7541175b75 | ||
|
|
932f4a72df | ||
|
|
b904b161eb | ||
|
|
504bf41b9c | ||
|
|
92ccbae657 | ||
|
|
c0d59db83d | ||
|
|
511ce91630 | ||
|
|
5f5eb2c46d | ||
|
|
4662253d0e | ||
|
|
8836a258bd |
41
.github/workflows/frontend-lint.yml
vendored
41
.github/workflows/frontend-lint.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- mealie-next
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
lint:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
@@ -47,3 +47,42 @@ jobs:
|
||||
- name: Run linter 👀
|
||||
run: yarn lint
|
||||
working-directory: "frontend"
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [16]
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Setup node env 🏗
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
check-latest: true
|
||||
|
||||
- name: Get yarn cache directory path 🛠
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Cache node_modules 📦
|
||||
uses: actions/cache@v2.1.4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies 👨🏻💻
|
||||
run: yarn
|
||||
working-directory: "frontend"
|
||||
|
||||
- name: Run Build 🚚
|
||||
run: yarn build
|
||||
working-directory: "frontend"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and Mealie will automatically import the relevant data or add a family recipe with the UI editor. Mealie also provides an API for interactions from 3rd party applications.
|
||||
|
||||
- [Remember to join the Discord](https://discord.gg/QuStdQGSGK)!
|
||||
- [Documentation](https://docs.mealie.io)
|
||||
- [Documentation](https://nightly.mealie.io)
|
||||
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
@@ -87,7 +87,7 @@ Thanks to Linode for providing Hosting for the Demo, Beta, and Documentation sit
|
||||
[issues-shield]: https://img.shields.io/github/issues/hay-kot/mealie.svg?style=flat-square
|
||||
[issues-url]: https://github.com/hay-kot/mealie/issues
|
||||
[license-shield]: https://img.shields.io/github/license/hay-kot/mealie.svg?style=flat-square
|
||||
[license-url]: https://github.com/hay-kot/mealie/blob/master/LICENSE.txt
|
||||
[license-url]: https://github.com/hay-kot/mealie/blob/mealie-next/LICENSE
|
||||
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
|
||||
[linkedin-url]: https://linkedin.com/in/hay-kot
|
||||
[product-screenshot]: docs/docs/assets/img/home_screenshot.png
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""add new webhook fields
|
||||
|
||||
|
||||
Revision ID: f30cf048c228
|
||||
Revises: ab0bae02578f
|
||||
Create Date: 2022-06-15 21:05:34.851857
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f30cf048c228"
|
||||
down_revision = "ab0bae02578f"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("webhook_urls", sa.Column("webhook_type", sa.String(), nullable=True))
|
||||
op.add_column("webhook_urls", sa.Column("scheduled_time", sa.Time(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("webhook_urls", "scheduled_time")
|
||||
op.drop_column("webhook_urls", "webhook_type")
|
||||
# ### end Alembic commands ###
|
||||
@@ -2,8 +2,6 @@ preserve_hierarchy: false
|
||||
files:
|
||||
- source: /frontend/lang/messages/en-US.json
|
||||
translation: /frontend/lang/messages/%locale%.json
|
||||
- source: /frontend/lang/dateTimeFormats/en-US.json
|
||||
translation: /frontend/lang/dateTimeFormats/%locale%.json
|
||||
- source: /mealie/lang/messages/en-US.json
|
||||
translation: /mealie/lang/messages/%locale%.json
|
||||
- source: /mealie/repos/seed/resources/foods/locales/en-US.json
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# vx.x.x COOL TITLE GOES HERE
|
||||
|
||||
**App Version: vx.x.x**
|
||||
|
||||
**Database Version: vx.x.x**
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
!!! error "Breaking Changes"
|
||||
|
||||
#### Database
|
||||
|
||||
#### ENV Variables
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- Fixed ...
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
### General
|
||||
- New Thing 1
|
||||
|
||||
|
||||
### UI Improvements
|
||||
-
|
||||
|
||||
|
||||
### Behind the Scenes
|
||||
- Refactoring...
|
||||
126
docs/docs/changelog/v1.0.0beta-4.md
Normal file
126
docs/docs/changelog/v1.0.0beta-4.md
Normal file
@@ -0,0 +1,126 @@
|
||||
### Security
|
||||
|
||||
#### v1.0.0beta-3 and Under - Recipe Scraper: Server Side Request Forgery Lead To Denial Of Service
|
||||
|
||||
!!! error "CWE-918: Server-Side Request Forgery (SSRF)"
|
||||
In this case if a attacker try to load a huge file then server will try to load the file and eventually server use its all memory which will dos the server
|
||||
|
||||
##### Mitigation
|
||||
|
||||
HTML is now scraped via a Stream and canceled after a 15 second timeout to prevent arbitrary data from being loaded into the server.
|
||||
|
||||
#### v1.0.0beta-3 and Under - Recipe Assets: Remote Code Execution
|
||||
|
||||
!!! error "CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine"
|
||||
As a low privileged user, Create a new recipe and click on the "+" to add a New Asset.
|
||||
Select a file, then proxy the request that will create the asset.
|
||||
|
||||
Since mealie/routes/recipe/recipe_crud_routes.py:306 is calling slugify on the name POST parameter, we use $ which slugify() will remove completely.
|
||||
|
||||
Since mealie/routes/recipe/recipe_crud_routes.py:306 is concatenating raw user input from the extension POST parameter into the variable file_name, which ultimately gets used when writing to disk, we can use a directory traversal attack in the extension (e.g. ./../../../tmp/pwn.txt) to write the file to arbitrary location on the server.
|
||||
|
||||
As an attacker, now that we have a strong attack primitive, we can start getting creative to get RCE. Since the files were being created by root, we could add an entry to /etc/passwd, create a crontab, etc. but since there was templating functionality in the application that peaked my interest. The PoC in the HTTP request above creates a Jinja2 template at /app/data/template/pwn.html. Since Jinja2 templates execute Python code when rendered, all we have to do now to get code execution is render the malicious template. This was easy enough.
|
||||
|
||||
##### Mitigation
|
||||
|
||||
We've added proper path sanitization to ensure that the user is not allowed to write to arbitrary locations on the server.
|
||||
|
||||
!!! warning "Breaking Change Incoming"
|
||||
As this has shown a significant area of exposure in the templates that Mealie was provided for exporting recipes, we'll be removing this feature in the next Beta release and will instead rely on the community to provide tooling around transforming recipes using templates. This will significantly limit the possible exposure of users injecting malicious templates into the application. The template functionality will be completely removed in the next beta release v1.0.0beta-5
|
||||
|
||||
#### All version Markdown Editor: Cross Site Scripting
|
||||
|
||||
!!! error "CWE-79: Cross-site Scripting (XSS) - Stored"
|
||||
A low privilege user can insert malicious JavaScript code into the Recipe Instructions which will execute in another person's browser that visits the recipe.
|
||||
|
||||
`<img src=x onerror=alert(document.domain)>`
|
||||
|
||||
##### Mitigation
|
||||
|
||||
This issues is present on all pages that allow markdown input. This error has been mitigated by wrapping the 3rd Party Markdown component and using the `domPurify` library to strip out the dangerous HTML.
|
||||
|
||||
#### v1.0.0beta-3 and Under - Image Scraper: Server-Side Request Forgery
|
||||
|
||||
!!! error "CWE-918: Server-Side Request Forgery (SSRF)"
|
||||
In the recipe edit page, is possible to upload an image directly or via an URL provided by the user. The function that handles the fetching and saving of the image via the URL doesn't have any URL verification, which allows to fetch internal services.
|
||||
|
||||
Furthermore, after the resource is fetch, there is no MIME type validation, which would ensure that the resource is indeed an image. After this, because there is no extension in the provided URL, the application will fallback to jpg, and original for the image name.
|
||||
|
||||
Then the result is saved to disk with the original.jpg name, that can be retrieved from the following URL: http://<domain>/api/media/recipes/<recipe-uid>/images/original.jpg. This file will contain the full response of the provided URL.
|
||||
|
||||
**Impact**
|
||||
|
||||
An attacker can get sensitive information of any internal-only services running. For example, if the application is hosted on Amazon Web Services (AWS) platform, its possible to fetch the AWS API endpoint, https://169.254.169.254, which returns API keys and other sensitive metadata.
|
||||
|
||||
##### Mitigation
|
||||
|
||||
Two actions were taken to reduce exposure to SSRF in this case.
|
||||
|
||||
1. The application will not prevent requests being made to local resources by checking for localhost or 127.0.0.1 domain names.
|
||||
2. The mime-type of the response is now checked prior to writing to disk.
|
||||
|
||||
If either of the above actions prevent the user from uploading images, the application will alert the user of what error occurred.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- For erroneously-translated datetime config ([#1362](https://github.com/hay-kot/mealie/issues/1362))
|
||||
- Fixed text color on RecipeCard in RecipePrintView and implemented ingredient sections ([#1351](https://github.com/hay-kot/mealie/issues/1351))
|
||||
- Ingredient sections lost after parsing ([#1368](https://github.com/hay-kot/mealie/issues/1368))
|
||||
- Increased float rounding precision for CRF parser ([#1369](https://github.com/hay-kot/mealie/issues/1369))
|
||||
- Infinite scroll bug on all recipes page ([#1393](https://github.com/hay-kot/mealie/issues/1393))
|
||||
- Fast fail of bulk importer ([#1394](https://github.com/hay-kot/mealie/issues/1394))
|
||||
- Bump @mdi/js from 5.9.55 to 6.7.96 in /frontend ([#1279](https://github.com/hay-kot/mealie/issues/1279))
|
||||
- Bump @nuxtjs/i18n from 7.0.3 to 7.2.2 in /frontend ([#1288](https://github.com/hay-kot/mealie/issues/1288))
|
||||
- Bump date-fns from 2.23.0 to 2.28.0 in /frontend ([#1293](https://github.com/hay-kot/mealie/issues/1293))
|
||||
- Bump fuse.js from 6.5.3 to 6.6.2 in /frontend ([#1325](https://github.com/hay-kot/mealie/issues/1325))
|
||||
- Bump core-js from 3.17.2 to 3.23.1 in /frontend ([#1383](https://github.com/hay-kot/mealie/issues/1383))
|
||||
- All-recipes page now sorts alphabetically ([#1405](https://github.com/hay-kot/mealie/issues/1405))
|
||||
- Sort recent recipes by created_at instead of date_added ([#1417](https://github.com/hay-kot/mealie/issues/1417))
|
||||
- Only show scaler when ingredients amounts enabled ([#1426](https://github.com/hay-kot/mealie/issues/1426))
|
||||
- Add missing types for API token deletion ([#1428](https://github.com/hay-kot/mealie/issues/1428))
|
||||
- Entry nutrition checker ([#1448](https://github.com/hay-kot/mealie/issues/1448))
|
||||
- Use == operator instead of is_ for sql queries ([#1453](https://github.com/hay-kot/mealie/issues/1453))
|
||||
- Use `mtime` instead of `ctime` for backup dates ([#1461](https://github.com/hay-kot/mealie/issues/1461))
|
||||
- Mealplan pagination ([#1464](https://github.com/hay-kot/mealie/issues/1464))
|
||||
- Properly use pagination for group event notifies ([#1512](https://github.com/hay-kot/mealie/pull/1512))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add go bulk import example ([#1388](https://github.com/hay-kot/mealie/issues/1388))
|
||||
- Fix old link
|
||||
- Pagination and filtering, and fixed a few broken links ([#1488](https://github.com/hay-kot/mealie/issues/1488))
|
||||
|
||||
### Features
|
||||
|
||||
- Toggle display of ingredient references in recipe instructions ([#1268](https://github.com/hay-kot/mealie/issues/1268))
|
||||
- Add custom scaling option ([#1345](https://github.com/hay-kot/mealie/issues/1345))
|
||||
- Implemented "order by" API parameters for recipe, food, and unit queries ([#1356](https://github.com/hay-kot/mealie/issues/1356))
|
||||
- Implement user favorites page ([#1376](https://github.com/hay-kot/mealie/issues/1376))
|
||||
- Extend Apprise JSON notification functionality with programmatic data ([#1355](https://github.com/hay-kot/mealie/issues/1355))
|
||||
- Mealplan-webhooks ([#1403](https://github.com/hay-kot/mealie/issues/1403))
|
||||
- Added "last-modified" header to supported record types ([#1379](https://github.com/hay-kot/mealie/issues/1379))
|
||||
- Re-write get all routes to use pagination ([#1424](https://github.com/hay-kot/mealie/issues/1424))
|
||||
- Advanced filtering API ([#1468](https://github.com/hay-kot/mealie/issues/1468))
|
||||
- Restore frontend sorting for all recipes ([#1497](https://github.com/hay-kot/mealie/issues/1497))
|
||||
- Implemented local storage for sorting and dynamic sort icons on the new recipe sort card ([1506](https://github.com/hay-kot/mealie/pull/1506))
|
||||
- create new foods and units from their Data Management pages ([#1511](https://github.com/hay-kot/mealie/pull/1511))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Bump dev deps ([#1418](https://github.com/hay-kot/mealie/issues/1418))
|
||||
- Bump @vue/runtime-dom in /frontend ([#1423](https://github.com/hay-kot/mealie/issues/1423))
|
||||
- Backend page_all route cleanup ([#1483](https://github.com/hay-kot/mealie/issues/1483))
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove depreciated repo call ([#1370](https://github.com/hay-kot/mealie/issues/1370))
|
||||
|
||||
### Hotfix
|
||||
|
||||
- Tame typescript beast
|
||||
|
||||
### UI
|
||||
|
||||
- Improve parser ui text display ([#1437](https://github.com/hay-kot/mealie/issues/1437))
|
||||
|
||||
<!-- generated by git-cliff -->
|
||||
@@ -43,6 +43,9 @@ import_from_file $input $token $mealie_url
|
||||
|
||||
```
|
||||
|
||||
#### Go
|
||||
See <a href="https://github.com/Jleagle/mealie-importer" target="_blank">Jleagle/mealie-importer</a>.
|
||||
|
||||
#### Python
|
||||
```python
|
||||
import requests
|
||||
|
||||
@@ -11,7 +11,67 @@ Mealie supports long-live api tokens in the user frontend. See [user settings pa
|
||||
On your local installation you can access interactive API documentation that provides `curl` examples and expected results. This allows you to easily test and interact with your API to identify places to include your own functionality. You can visit the documentation at `http://mealie.yourdomain.com/docs` or see the example at the [Demo Site](https://mealie-demo.hay-kot.dev/docs)
|
||||
|
||||
### Recipe Extras
|
||||
Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.
|
||||
Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.
|
||||
|
||||
For example you could add `{"message": "Remember to thaw the chicken"}` to a recipe and use the webhooks built into mealie to send that message payload to a destination to be processed.
|
||||
|
||||
### Pagination and Filtering
|
||||
Most document types share a uniform pagination and filtering API (e.g. `GET /api/recipes`). These allow you to filter by an arbitrary combination of criteria and return only a certain number of documents (i.e. a single "page" of documents).
|
||||
|
||||
#### Pagination
|
||||
The pagination API allows you to limit how many documents you return in each call. This is important when serving data to an application, as you don't want to wait for a huge payload every time you load a page. You may also not want to render all documents at once, opting to render only a few at a time.
|
||||
|
||||
The `perPage` parameter tells Mealie how many documents to return (this is similar to `LIMIT` in SQL). If you want to keep fetching more data in batches, first determine your batch size (in other words: how many documents you want per-page), then make additional calls by changing the `page` parameter. If your `perPage` size is 30, then page 1 will return the first 30 documents, page 2 will return the next 30 documents, etc.
|
||||
|
||||
Many applications will keep track of the query and adjust the page parameter appropriately, but some applications can't do this, or a particular implementation may make this difficult. The response includes pagination guides to help you find the next page and previous page. Here is a sample response:
|
||||
```json
|
||||
{
|
||||
"page": 2,
|
||||
"per_page": 5,
|
||||
"total": 23,
|
||||
"total_pages": 5,
|
||||
"data": [...],
|
||||
"next": "/recipes?page=3&per_page=5&order_by=name&order_direction=asc",
|
||||
"previous": "/recipes?page=1&per_page=5&order_by=name&order_direction=asc"
|
||||
}
|
||||
```
|
||||
Notice that the route does not contain the baseurl (e.g. `https://mymealieapplication.com/api`).
|
||||
|
||||
There are a few shorthands available to reduce the number of calls for certain common requests:
|
||||
- if you want to return _all_ results, effectively disabling pagination, set `perPage = -1` (and fetch the first page)
|
||||
- if you want to fetch the _last_ page, set `page = -1`
|
||||
|
||||
#### Filtering
|
||||
The `queryFilter` parameter enables fine-grained control over your query. You can filter by any combination of attributes connected by logical operators (`AND`, `OR`). You can also group attributes together using parenthesis. For string, date, or datetime literals, you should surround them in double quotes (e.g. `"Pasta Fagioli"`). If there are no spaces in your literal (such as dates) the API will probably parse it correctly, but it's recommended that you use quotes anyway.
|
||||
|
||||
Here are several examples of filters. These filter strings are not surrounded in quotes for ease of reading, but they are _strings_, so they will probably be in quotes in your language.
|
||||
|
||||
##### Simple Filters
|
||||
Here is an example of a filter to find a recipe with the name "Pasta Fagioli": <br>
|
||||
`name = "Pasta Fagioli"`
|
||||
|
||||
This filter will find all recipes created on or after a particular date: <br>
|
||||
`createdAt >= "2021-02-22"`
|
||||
|
||||
> **_NOTE:_** The API uses Python's [dateutil parser](https://dateutil.readthedocs.io/en/stable/parser.html), which parses many different date/datetime formats.
|
||||
|
||||
This filter will find all units that have `useAbbreviation` disabled: <br>
|
||||
`useAbbreviation = false`
|
||||
|
||||
##### Compound Filters
|
||||
You can combine multiple filter statements using logical operators (`AND`, `OR`).
|
||||
|
||||
This filter will only return recipes named "Pasta Fagioli" or "Grandma's Brisket": <br>
|
||||
`name = "Pasta Fagioli" OR name = "Grandma's Brisket"`
|
||||
|
||||
This filter will return all recipes created before a particular date, except for the one named "Ultimate Vegan Ramen Recipe With Miso Broth": <br>
|
||||
`createdAt < "January 2nd, 2014" AND name <> "Ultimate Vegan Ramen Recipe With Miso Broth"`
|
||||
|
||||
This filter will return three particular recipes: <br>
|
||||
`name = "Pasta Fagioli" OR name = "Grandma's Brisket" OR name = "Ultimate Vegan Ramen Recipe With Miso Broth"`
|
||||
|
||||
##### Advanced Filters
|
||||
You can have multiple filter groups combined by logical operators. You can define a filter group with parenthesis.
|
||||
|
||||
Here's a filter that will find all recipes updated between two particular times, but exclude the "Pasta Fagioli" recipe: <br>
|
||||
`(updatedAt > "2022-07-17T15:47:00Z" AND updatedAt < "2022-07-17T15:50:00Z") AND name <> "Pasta Fagioli"`
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
**For Environmental Variable Configuration See:**
|
||||
|
||||
- [Frontend Configuration](/mealie/documentation/getting-started/installation/frontend-config/)
|
||||
- [Backend Configuration](/mealie/documentation/getting-started/installation/backend-config/)
|
||||
- [Frontend Configuration](./frontend-config.md)
|
||||
- [Backend Configuration](./backend-config.md)
|
||||
|
||||
```yaml
|
||||
---
|
||||
version: "3.7"
|
||||
services:
|
||||
mealie-frontend:
|
||||
image: hkotel/mealie:frontend-v1.0.0beta-3
|
||||
image: hkotel/mealie:frontend-v1.0.0beta-4
|
||||
container_name: mealie-frontend
|
||||
depends_on:
|
||||
- mealie-api
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
volumes:
|
||||
- mealie-data:/app/data/ # (3)
|
||||
mealie-api:
|
||||
image: hkotel/mealie:api-v1.0.0beta-3
|
||||
image: hkotel/mealie:api-v1.0.0beta-4
|
||||
container_name: mealie-api
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
@@ -4,15 +4,15 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
|
||||
|
||||
**For Environmental Variable Configuration See:**
|
||||
|
||||
- [Frontend Configuration](/mealie/documentation/getting-started/installation/frontend-config/)
|
||||
- [Backend Configuration](/mealie/documentation/getting-started/installation/backend-config/)
|
||||
- [Frontend Configuration](./frontend-config.md)
|
||||
- [Backend Configuration](./backend-config.md)
|
||||
|
||||
```yaml
|
||||
---
|
||||
version: "3.7"
|
||||
services:
|
||||
mealie-frontend:
|
||||
image: hkotel/mealie:frontend-v1.0.0beta-3
|
||||
image: hkotel/mealie:frontend-v1.0.0beta-4
|
||||
container_name: mealie-frontend
|
||||
environment:
|
||||
# Set Frontend ENV Variables Here
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
volumes:
|
||||
- mealie-data:/app/data/ # (3)
|
||||
mealie-api:
|
||||
image: hkotel/mealie:api-v1.0.0beta-3
|
||||
image: hkotel/mealie:api-v1.0.0beta-4
|
||||
container_name: mealie-api
|
||||
volumes:
|
||||
- mealie-data:/app/data/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -88,6 +88,7 @@ nav:
|
||||
- Improving Ingredient Parser: "contributors/guides/ingredient-parser.md"
|
||||
|
||||
- Change Log:
|
||||
- v1.0.0beta-4: "changelog/v1.0.0beta-4.md"
|
||||
- v1.0.0beta-3: "changelog/v1.0.0beta-3.md"
|
||||
- v1.0.0beta-2: "changelog/v1.0.0beta-2.md"
|
||||
- v1.0.0 Beta: "changelog/v1.0.0.md"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
import { ApiRequestInstance, PaginationData } from "~/types/api";
|
||||
|
||||
export interface CrudAPIInterface {
|
||||
requests: ApiRequestInstance;
|
||||
@@ -18,13 +18,13 @@ export abstract class BaseAPI {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseCRUDAPI<CreateType, ReadType, UpdateType=CreateType> extends BaseAPI implements CrudAPIInterface {
|
||||
export abstract class BaseCRUDAPI<CreateType, ReadType, UpdateType = CreateType> extends BaseAPI implements CrudAPIInterface {
|
||||
abstract baseRoute: string;
|
||||
abstract itemRoute(itemId: string | number): string;
|
||||
|
||||
async getAll(start = 0, limit = 9999, params = {} as any) {
|
||||
return await this.requests.get<ReadType[]>(this.baseRoute, {
|
||||
params: { start, limit, ...params },
|
||||
async getAll(page = 1, perPage = -1, params = {} as any) {
|
||||
return await this.requests.get<PaginationData<ReadType>>(this.baseRoute, {
|
||||
params: { page, perPage, ...params },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { BaseCRUDAPI } from "../_base";
|
||||
import { ChangePassword, DeleteTokenResponse, LongLiveTokenIn, LongLiveTokenOut, ResetPassword, UserBase, UserIn, UserOut } from "~/types/api-types/user";
|
||||
import {
|
||||
ChangePassword,
|
||||
DeleteTokenResponse,
|
||||
LongLiveTokenIn,
|
||||
LongLiveTokenOut,
|
||||
ResetPassword,
|
||||
UserBase,
|
||||
UserFavorites,
|
||||
UserIn,
|
||||
UserOut,
|
||||
} from "~/types/api-types/user";
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
@@ -32,7 +42,7 @@ export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> {
|
||||
}
|
||||
|
||||
async getFavorites(id: string) {
|
||||
await this.requests.get(routes.usersIdFavorites(id));
|
||||
return await this.requests.get<UserFavorites>(routes.usersIdFavorites(id));
|
||||
}
|
||||
|
||||
async changePassword(id: string, changePassword: ChangePassword) {
|
||||
|
||||
84
frontend/components/Domain/Group/GroupWebhookEditor.vue
Normal file
84
frontend/components/Domain/Group/GroupWebhookEditor.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card-text>
|
||||
<v-switch v-model="webhookCopy.enabled" label="Enabled"></v-switch>
|
||||
<v-text-field v-model="webhookCopy.name" label="Webhook Name"></v-text-field>
|
||||
<v-text-field v-model="webhookCopy.url" label="Webhook Url"></v-text-field>
|
||||
<v-time-picker v-model="scheduledTime" class="elevation-2" ampm-in-title format="ampm"></v-time-picker>
|
||||
</v-card-text>
|
||||
<v-card-actions class="py-0 justify-end">
|
||||
<BaseButtonGroup
|
||||
:buttons="[
|
||||
{
|
||||
icon: $globals.icons.delete,
|
||||
text: $tc('general.delete'),
|
||||
event: 'delete',
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.testTube,
|
||||
text: $tc('general.test'),
|
||||
event: 'test',
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.save,
|
||||
text: $tc('general.save'),
|
||||
event: 'save',
|
||||
},
|
||||
]"
|
||||
@delete="$emit('delete', webhookCopy.id)"
|
||||
@save="handleSave"
|
||||
@test="$emit('test', webhookCopy.id)"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
|
||||
import { ReadWebhook } from "~/types/api-types/group";
|
||||
import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
webhook: {
|
||||
type: Object as () => ReadWebhook,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ["delete", "save", "test"],
|
||||
setup(props, { emit }) {
|
||||
const itemUTC = ref<string>(props.webhook.scheduledTime);
|
||||
const itemLocal = ref<string>(timeUTCToLocal(props.webhook.scheduledTime));
|
||||
|
||||
const scheduledTime = computed({
|
||||
get() {
|
||||
return itemLocal.value;
|
||||
},
|
||||
set(v: string) {
|
||||
itemUTC.value = timeLocalToUTC(v);
|
||||
itemLocal.value = v;
|
||||
},
|
||||
});
|
||||
|
||||
const webhookCopy = ref({ ...props.webhook });
|
||||
|
||||
function handleSave() {
|
||||
webhookCopy.value.scheduledTime = itemLocal.value;
|
||||
emit("save", webhookCopy.value);
|
||||
}
|
||||
|
||||
return {
|
||||
webhookCopy,
|
||||
scheduledTime,
|
||||
handleSave,
|
||||
itemUTC,
|
||||
itemLocal,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.$tc("settings.webhooks.webhooks"),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -24,7 +24,7 @@
|
||||
<RecipeFavoriteBadge v-if="loggedIn" class="mx-1" color="info" button-style :slug="slug" show-always />
|
||||
<v-tooltip v-if="!locked" bottom color="info">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on" @click="$emit('input', true)">
|
||||
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on" @click="$emit('edit', true)">
|
||||
<v-icon> {{ $globals.icons.edit }} </v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</v-icon>
|
||||
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.random") }}
|
||||
</v-btn>
|
||||
|
||||
<v-menu v-if="$listeners.sort" offset-y left>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn text :icon="$vuetify.breakpoint.xsOnly" v-bind="attrs" :loading="sortLoading" v-on="on">
|
||||
@@ -23,6 +24,48 @@
|
||||
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.sort") }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item @click="sortRecipesFrontend(EVENTS.az)">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.orderAlphabeticalAscending }}
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.sort-alphabetically") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sortRecipesFrontend(EVENTS.rating)">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.star }}
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.rating") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sortRecipesFrontend(EVENTS.created)">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.newBox }}
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.created") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sortRecipesFrontend(EVENTS.updated)">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.update }}
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.updated") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sortRecipesFrontend(EVENTS.shuffle)">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.shuffleVariant }}
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.shuffle") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-menu v-if="$listeners.sortRecipes" offset-y left>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn text :icon="$vuetify.breakpoint.xsOnly" v-bind="attrs" :loading="sortLoading" v-on="on">
|
||||
<v-icon :left="!$vuetify.breakpoint.xsOnly">
|
||||
{{ preferences.sortIcon }}
|
||||
</v-icon>
|
||||
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.sort") }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item @click="sortRecipes(EVENTS.az)">
|
||||
<v-icon left>
|
||||
@@ -48,17 +91,22 @@
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.updated") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sortRecipes(EVENTS.shuffle)">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.shuffleVariant }}
|
||||
</v-icon>
|
||||
<v-list-item-title>{{ $t("general.shuffle") }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<ContextMenu
|
||||
v-if="!$vuetify.breakpoint.xsOnly"
|
||||
:items="[
|
||||
{
|
||||
title: 'Toggle View',
|
||||
icon: $globals.icons.eye,
|
||||
event: 'toggle-dense-view',
|
||||
},
|
||||
]"
|
||||
@toggle-dense-view="toggleMobileCards()"
|
||||
/>
|
||||
</v-app-bar>
|
||||
<div v-if="recipes" class="mt-2">
|
||||
<v-row v-if="!viewScale">
|
||||
<v-row v-if="!useMobileCards">
|
||||
<v-col v-for="(recipe, index) in recipes" :key="recipe.slug + index" :sm="6" :md="6" :lg="4" :xl="3">
|
||||
<v-lazy>
|
||||
<RecipeCard
|
||||
@@ -99,17 +147,38 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
<div v-if="usePagination">
|
||||
<v-card v-intersect="infiniteScroll"></v-card>
|
||||
<v-fade-transition>
|
||||
<AppLoader v-if="loading" :loading="loading" />
|
||||
</v-fade-transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
useAsync,
|
||||
useContext,
|
||||
useRouter,
|
||||
} from "@nuxtjs/composition-api";
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
import RecipeCard from "./RecipeCard.vue";
|
||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||
import { useSorter } from "~/composables/recipes";
|
||||
import {Recipe} from "~/types/api-types/recipe";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import { useLazyRecipes, useSorter } from "~/composables/recipes";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { useUserSortPreferences } from "~/composables/use-users/preferences";
|
||||
|
||||
const SORT_EVENT = "sort";
|
||||
const REPLACE_RECIPES_EVENT = "replaceRecipes";
|
||||
const APPEND_RECIPES_EVENT = "appendRecipes";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -129,10 +198,6 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
mobileCards: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
singleColumn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -141,8 +206,14 @@ export default defineComponent({
|
||||
type: Array as () => Recipe[],
|
||||
default: () => [],
|
||||
},
|
||||
usePagination: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
const preferences = useUserSortPreferences();
|
||||
|
||||
const utils = useSorter();
|
||||
|
||||
const EVENTS = {
|
||||
@@ -154,8 +225,8 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const { $globals, $vuetify } = useContext();
|
||||
const viewScale = computed(() => {
|
||||
return props.mobileCards || $vuetify.breakpoint.smAndDown;
|
||||
const useMobileCards = computed(() => {
|
||||
return $vuetify.breakpoint.smAndDown || preferences.value.useMobileCards;
|
||||
});
|
||||
|
||||
const displayTitleIcon = computed(() => {
|
||||
@@ -164,7 +235,7 @@ export default defineComponent({
|
||||
|
||||
const state = reactive({
|
||||
sortLoading: false,
|
||||
})
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
function navigateRandom() {
|
||||
@@ -176,7 +247,114 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const page = ref(1);
|
||||
const perPage = ref(30);
|
||||
const hasMore = ref(true);
|
||||
const ready = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const { fetchMore } = useLazyRecipes();
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.usePagination) {
|
||||
const newRecipes = await fetchMore(
|
||||
page.value,
|
||||
perPage.value,
|
||||
preferences.value.orderBy,
|
||||
preferences.value.orderDirection
|
||||
);
|
||||
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
|
||||
ready.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
const infiniteScroll = useThrottleFn(() => {
|
||||
useAsync(async () => {
|
||||
if (!ready.value || !hasMore.value || loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
page.value = page.value + 1;
|
||||
|
||||
const newRecipes = await fetchMore(
|
||||
page.value,
|
||||
perPage.value,
|
||||
preferences.value.orderBy,
|
||||
preferences.value.orderDirection
|
||||
);
|
||||
if (!newRecipes.length) {
|
||||
hasMore.value = false;
|
||||
} else {
|
||||
context.emit(APPEND_RECIPES_EVENT, newRecipes);
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}, useAsyncKey());
|
||||
}, 500);
|
||||
|
||||
/**
|
||||
* sortRecipes helps filter using the API. This will eventually replace the sortRecipesFrontend function which pulls all recipes
|
||||
* (without pagination) and does the sorting in the frontend.
|
||||
* TODO: remove sortRecipesFrontend and remove duplicate "sortRecipes" section in the template (above)
|
||||
* @param sortType
|
||||
*/
|
||||
function sortRecipes(sortType: string) {
|
||||
if (state.sortLoading || loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
function setter(orderBy: string, ascIcon: string, descIcon: string) {
|
||||
if (preferences.value.orderBy !== orderBy) {
|
||||
preferences.value.orderBy = orderBy;
|
||||
preferences.value.orderDirection = "asc";
|
||||
} else {
|
||||
preferences.value.orderDirection = preferences.value.orderDirection === "asc" ? "desc" : "asc";
|
||||
}
|
||||
preferences.value.sortIcon = preferences.value.orderDirection === "asc" ? ascIcon : descIcon;
|
||||
}
|
||||
|
||||
switch (sortType) {
|
||||
case EVENTS.az:
|
||||
setter("name", $globals.icons.sortAlphabeticalAscending, $globals.icons.sortAlphabeticalDescending);
|
||||
break;
|
||||
case EVENTS.rating:
|
||||
setter("rating", $globals.icons.sortAscending, $globals.icons.sortDescending);
|
||||
break;
|
||||
case EVENTS.created:
|
||||
setter("created_at", $globals.icons.sortCalendarAscending, $globals.icons.sortCalendarDescending);
|
||||
break;
|
||||
case EVENTS.updated:
|
||||
setter("updated_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown Event", sortType);
|
||||
return;
|
||||
}
|
||||
|
||||
useAsync(async () => {
|
||||
// reset pagination
|
||||
page.value = 1;
|
||||
hasMore.value = true;
|
||||
|
||||
state.sortLoading = true;
|
||||
loading.value = true;
|
||||
|
||||
// fetch new recipes
|
||||
const newRecipes = await fetchMore(
|
||||
page.value,
|
||||
perPage.value,
|
||||
preferences.value.orderBy,
|
||||
preferences.value.orderDirection
|
||||
);
|
||||
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
|
||||
|
||||
state.sortLoading = false;
|
||||
loading.value = false;
|
||||
}, useAsyncKey());
|
||||
}
|
||||
|
||||
function sortRecipesFrontend(sortType: string) {
|
||||
state.sortLoading = true;
|
||||
const sortTarget = [...props.recipes];
|
||||
switch (sortType) {
|
||||
@@ -203,13 +381,22 @@ export default defineComponent({
|
||||
state.sortLoading = false;
|
||||
}
|
||||
|
||||
function toggleMobileCards() {
|
||||
preferences.value.useMobileCards = !preferences.value.useMobileCards;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
EVENTS,
|
||||
viewScale,
|
||||
displayTitleIcon,
|
||||
EVENTS,
|
||||
infiniteScroll,
|
||||
loading,
|
||||
navigateRandom,
|
||||
preferences,
|
||||
sortRecipes,
|
||||
sortRecipesFrontend,
|
||||
toggleMobileCards,
|
||||
useMobileCards,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -261,7 +261,7 @@ export default defineComponent({
|
||||
async function getShoppingLists() {
|
||||
const { data } = await api.shopping.lists.getAll();
|
||||
if (data) {
|
||||
shoppingLists.value = data;
|
||||
shoppingLists.value = data.items ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,10 +131,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function refreshTokens() {
|
||||
const { data } = await userApi.recipes.share.getAll(0, 999, { recipe_id: props.recipeId });
|
||||
const { data } = await userApi.recipes.share.getAll(1, -1, { recipe_id: props.recipeId });
|
||||
|
||||
if (data) {
|
||||
state.tokens = data;
|
||||
state.tokens = data.items ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<v-list-item dense @click="toggleChecked(index)">
|
||||
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary" />
|
||||
<v-list-item-content :key="ingredient.quantity">
|
||||
<VueMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredientDisplay[index]" />
|
||||
<SafeMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredientDisplay[index]" />
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</div>
|
||||
@@ -22,14 +22,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
type: Array as () => RecipeIngredient[],
|
||||
|
||||
@@ -58,13 +58,13 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<div class="d-flex justify-space-between justify-start">
|
||||
<div v-if="showCookMode" class="d-flex justify-space-between justify-start">
|
||||
<h2 class="mb-4 mt-1">{{ $t("recipe.instructions") }}</h2>
|
||||
<BaseButton v-if="!public" minor :to="$router.currentRoute.path + '/cook'" cancel color="primary">
|
||||
<BaseButton v-if="!public && !edit" minor cancel color="primary" @click="toggleCookMode()">
|
||||
<template #icon>
|
||||
{{ $globals.icons.primary }}
|
||||
</template>
|
||||
Cook
|
||||
Cook Mode
|
||||
</BaseButton>
|
||||
</div>
|
||||
<draggable
|
||||
@@ -197,7 +197,15 @@
|
||||
<v-expand-transition>
|
||||
<div v-show="!isChecked(index) && !edit" class="m-0 p-0">
|
||||
<v-card-text class="markdown">
|
||||
<VueMarkdown class="markdown" :source="step.text"> </VueMarkdown>
|
||||
<SafeMarkdown class="markdown" :source="step.text" />
|
||||
<div v-if="cookMode && step.ingredientReferences && step.ingredientReferences.length > 0">
|
||||
<v-divider class="mb-2"></v-divider>
|
||||
<div
|
||||
v-for="ing in step.ingredientReferences"
|
||||
:key="ing.referenceId"
|
||||
v-html="getIngredientByRefId(ing.referenceId)"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
@@ -211,9 +219,16 @@
|
||||
|
||||
<script lang="ts">
|
||||
import draggable from "vuedraggable";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { ref, toRefs, reactive, defineComponent, watch, onMounted, useContext } from "@nuxtjs/composition-api";
|
||||
import {
|
||||
ref,
|
||||
toRefs,
|
||||
reactive,
|
||||
defineComponent,
|
||||
watch,
|
||||
onMounted,
|
||||
useContext,
|
||||
computed,
|
||||
} from "@nuxtjs/composition-api";
|
||||
import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset } from "~/types/api-types/recipe";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
|
||||
@@ -228,7 +243,6 @@ interface MergerHistory {
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
@@ -264,6 +278,14 @@ export default defineComponent({
|
||||
type: Array as () => RecipeAsset[],
|
||||
required: true,
|
||||
},
|
||||
cookMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
@@ -313,13 +335,20 @@ export default defineComponent({
|
||||
});
|
||||
});
|
||||
|
||||
const showCookMode = ref(false);
|
||||
|
||||
// Eliminate state with an eager call to watcher?
|
||||
onMounted(() => {
|
||||
props.value.forEach((element) => {
|
||||
props.value.forEach((element: RecipeStep) => {
|
||||
if (element.id !== undefined) {
|
||||
showTitleEditor.value[element.id] = validateTitle(element.title);
|
||||
}
|
||||
|
||||
// showCookMode.value = false;
|
||||
if (showCookMode.value === false && element.ingredientReferences && element.ingredientReferences.length > 0) {
|
||||
showCookMode.value = true;
|
||||
}
|
||||
|
||||
showTitleEditor.value = { ...showTitleEditor.value };
|
||||
});
|
||||
});
|
||||
@@ -376,6 +405,14 @@ export default defineComponent({
|
||||
referenceId: ref,
|
||||
};
|
||||
});
|
||||
|
||||
// Update the visibility of the cook mode button
|
||||
showCookMode.value = false;
|
||||
props.value.forEach((element) => {
|
||||
if (showCookMode.value === false && element.ingredientReferences && element.ingredientReferences.length > 0) {
|
||||
showCookMode.value = true;
|
||||
}
|
||||
});
|
||||
state.dialog = false;
|
||||
}
|
||||
|
||||
@@ -446,12 +483,27 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
function getIngredientByRefId(refId: string) {
|
||||
const ing = props.ingredients.find((ing) => ing.referenceId === refId) || "";
|
||||
const ingredientLookup = computed(() => {
|
||||
const results: { [key: string]: RecipeIngredient } = {};
|
||||
return props.ingredients.reduce((prev, ing) => {
|
||||
if (ing.referenceId === undefined) {
|
||||
return prev;
|
||||
}
|
||||
prev[ing.referenceId] = ing;
|
||||
return prev;
|
||||
}, results);
|
||||
});
|
||||
|
||||
function getIngredientByRefId(refId: string | undefined) {
|
||||
if (refId === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const ing = ingredientLookup.value[refId] ?? "";
|
||||
if (ing === "") {
|
||||
return "";
|
||||
}
|
||||
return parseIngredientText(ing, props.disableAmount);
|
||||
return parseIngredientText(ing, props.disableAmount, props.scale);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
@@ -571,6 +623,10 @@ export default defineComponent({
|
||||
props.value[index].text += text;
|
||||
}
|
||||
|
||||
function toggleCookMode() {
|
||||
context.emit("cookModeToggle");
|
||||
}
|
||||
|
||||
return {
|
||||
// Image Uploader
|
||||
toggleDragMode,
|
||||
@@ -598,6 +654,8 @@ export default defineComponent({
|
||||
updateIndex,
|
||||
autoSetReferences,
|
||||
parseIngredientText,
|
||||
toggleCookMode,
|
||||
showCookMode,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{{ note.title }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<VueMarkdown :source="note.text"> </VueMarkdown>
|
||||
<SafeMarkdown :source="note.text" />
|
||||
</v-card-text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,15 +30,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { RecipeNote } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array as () => RecipeNote[],
|
||||
|
||||
@@ -81,9 +81,12 @@ export default defineComponent({
|
||||
},
|
||||
};
|
||||
const valueNotNull = computed(() => {
|
||||
Object.values(props.value).forEach((valueProperty) => {
|
||||
if (valueProperty && valueProperty !== "") return true;
|
||||
});
|
||||
let key: keyof Nutrition;
|
||||
for (key in props.value) {
|
||||
if (props.value[key] !== null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -97,8 +100,8 @@ export default defineComponent({
|
||||
labels,
|
||||
valueNotNull,
|
||||
showViewer,
|
||||
updateValue
|
||||
}
|
||||
updateValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -84,7 +84,7 @@ export default defineComponent({
|
||||
|
||||
if (!props.items) return byLetter;
|
||||
|
||||
props.items.forEach((item) => {
|
||||
props.items.sort((a, b) => a.name.localeCompare(b.name)).forEach((item) => {
|
||||
const letter = item.name[0].toUpperCase();
|
||||
if (!byLetter[letter]) {
|
||||
byLetter[letter] = [];
|
||||
@@ -92,12 +92,6 @@ export default defineComponent({
|
||||
byLetter[letter].push(item);
|
||||
});
|
||||
|
||||
for (const key in byLetter) {
|
||||
byLetter[key] = byLetter[key].sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
|
||||
return byLetter;
|
||||
});
|
||||
|
||||
|
||||
@@ -11,42 +11,57 @@
|
||||
</section>
|
||||
|
||||
<v-card-text class="px-0">
|
||||
<VueMarkdown :source="recipe.description" />
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
</v-card-text>
|
||||
|
||||
<!-- Ingredients -->
|
||||
<section>
|
||||
<v-card-title class="headline pl-0"> {{ $t("recipe.ingredients") }} </v-card-title>
|
||||
<div class="ingredient-grid">
|
||||
<div class="ingredient-col-1">
|
||||
<ul>
|
||||
<li v-for="(text, index) in splitIngredients.firstHalf" :key="index" v-html="text" />
|
||||
</ul>
|
||||
</div>
|
||||
<div class="ingredient-col-2">
|
||||
<ul>
|
||||
<li v-for="(text, index) in splitIngredients.secondHalf" :key="index" v-html="text" />
|
||||
</ul>
|
||||
<div
|
||||
v-for="(ingredientSection, sectionIndex) in ingredientSections"
|
||||
:key="`ingredient-section-${sectionIndex}`"
|
||||
class="print-section"
|
||||
>
|
||||
<div class="ingredient-grid">
|
||||
<template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients">
|
||||
<h4 v-if="ingredient.title" :key="`ingredient-title-${ingredientIndex}`" class="ingredient-title mt-2">
|
||||
{{ ingredient.title }}
|
||||
</h4>
|
||||
<p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Instructions -->
|
||||
<section>
|
||||
<v-card-title class="headline pl-0">{{ $t("recipe.instructions") }}</v-card-title>
|
||||
<div v-for="(step, index) in recipe.recipeInstructions" :key="index">
|
||||
<h3 v-if="step.title" class="mb-2">{{ step.title }}</h3>
|
||||
<div class="ml-5">
|
||||
<h4>{{ $t("recipe.step-index", { step: index + 1 }) }}</h4>
|
||||
<VueMarkdown :source="step.text" />
|
||||
<div
|
||||
v-for="(instructionSection, sectionIndex) in instructionSections"
|
||||
:key="`instruction-section-${sectionIndex}`"
|
||||
:class="{ 'print-section': instructionSection.sectionName }"
|
||||
>
|
||||
<div v-for="(step, stepIndex) in instructionSection.instructions" :key="`instruction-${stepIndex}`">
|
||||
<div class="print-section">
|
||||
<h4 v-if="step.title" :key="`instruction-title-${stepIndex}`" class="instruction-title mb-2">
|
||||
{{ step.title }}
|
||||
</h4>
|
||||
<h5>{{ $t("recipe.step-index", { step: stepIndex + instructionSection.stepOffset + 1 }) }}</h5>
|
||||
<SafeMarkdown :source="step.text" class="recipe-step-body" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Notes -->
|
||||
<v-divider v-if="hasNotes" class="grey my-4"></v-divider>
|
||||
|
||||
<section>
|
||||
<div v-for="(note, index) in recipe.notes" :key="index + 'note'">
|
||||
<h4>{{ note.title }}</h4>
|
||||
<VueMarkdown :source="note.text" />
|
||||
<div class="print-section">
|
||||
<h4>{{ note.title }}</h4>
|
||||
<SafeMarkdown :source="note.text" class="note-body" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -54,21 +69,24 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { Recipe, RecipeIngredient, RecipeStep } from "~/types/api-types/recipe";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
|
||||
type SplitIngredients = {
|
||||
firstHalf: string[];
|
||||
secondHalf: string[];
|
||||
type IngredientSection = {
|
||||
sectionName: string;
|
||||
ingredients: RecipeIngredient[];
|
||||
};
|
||||
|
||||
type InstructionSection = {
|
||||
sectionName: string;
|
||||
stepOffset: number;
|
||||
instructions: RecipeStep[];
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
RecipeTimeCard,
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
recipe: {
|
||||
@@ -77,33 +95,98 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const splitIngredients = computed<SplitIngredients>(() => {
|
||||
const firstHalf = props.recipe.recipeIngredient
|
||||
?.slice(0, Math.ceil(props.recipe.recipeIngredient.length / 2))
|
||||
.map((ingredient) => {
|
||||
return parseIngredientText(ingredient, props.recipe?.settings?.disableAmount || false);
|
||||
});
|
||||
// Group ingredients by section so we can style them independently
|
||||
const ingredientSections = computed<IngredientSection[]>(() => {
|
||||
if (!props.recipe.recipeIngredient) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const secondHalf = props.recipe.recipeIngredient
|
||||
?.slice(Math.ceil(props.recipe.recipeIngredient.length / 2))
|
||||
.map((ingredient) => {
|
||||
return parseIngredientText(ingredient, props.recipe?.settings?.disableAmount || false);
|
||||
});
|
||||
return props.recipe.recipeIngredient.reduce((sections, ingredient) => {
|
||||
// if title append new section to the end of the array
|
||||
if (ingredient.title) {
|
||||
sections.push({
|
||||
sectionName: ingredient.title,
|
||||
ingredients: [ingredient],
|
||||
});
|
||||
|
||||
return {
|
||||
firstHalf: firstHalf || [],
|
||||
secondHalf: secondHalf || [],
|
||||
};
|
||||
return sections;
|
||||
}
|
||||
|
||||
// append new section if first
|
||||
if (sections.length === 0) {
|
||||
sections.push({
|
||||
sectionName: "",
|
||||
ingredients: [ingredient],
|
||||
});
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
// otherwise add ingredient to last section in the array
|
||||
sections[sections.length - 1].ingredients.push(ingredient);
|
||||
return sections;
|
||||
}, [] as IngredientSection[]);
|
||||
});
|
||||
|
||||
// Group instructions by section so we can style them independently
|
||||
const instructionSections = computed<InstructionSection[]>(() => {
|
||||
if (!props.recipe.recipeInstructions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return props.recipe.recipeInstructions.reduce((sections, step) => {
|
||||
const offset = (() => {
|
||||
if (sections.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const lastOffset = sections[sections.length - 1].stepOffset;
|
||||
const lastNumSteps = sections[sections.length - 1].instructions.length;
|
||||
return lastOffset + lastNumSteps;
|
||||
})();
|
||||
|
||||
// if title append new section to the end of the array
|
||||
if (step.title) {
|
||||
sections.push({
|
||||
sectionName: step.title,
|
||||
stepOffset: offset,
|
||||
instructions: [step],
|
||||
});
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
// append if first element
|
||||
if (sections.length === 0) {
|
||||
sections.push({
|
||||
sectionName: "",
|
||||
stepOffset: offset,
|
||||
instructions: [step],
|
||||
});
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
// otherwise add step to last section in the array
|
||||
sections[sections.length - 1].instructions.push(step);
|
||||
return sections;
|
||||
}, [] as InstructionSection[]);
|
||||
});
|
||||
|
||||
const hasNotes = computed(() => {
|
||||
return props.recipe.notes && props.recipe.notes.length > 0;
|
||||
});
|
||||
|
||||
function parseText(ingredient: RecipeIngredient) {
|
||||
return parseIngredientText(ingredient, props.recipe.settings?.disableAmount || false);
|
||||
}
|
||||
|
||||
return {
|
||||
hasNotes,
|
||||
splitIngredients,
|
||||
parseText,
|
||||
parseIngredientText,
|
||||
ingredientSections,
|
||||
instructionSections,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -133,12 +216,23 @@ export default defineComponent({
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
/* Makes all text solid black */
|
||||
.print-container {
|
||||
display: none;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.print-container,
|
||||
.print-container >>> * {
|
||||
opacity: 1 !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
/* Prevents sections from being broken up between pages */
|
||||
.print-section {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-bottom: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
@@ -152,7 +246,20 @@ p {
|
||||
.ingredient-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 1rem;
|
||||
grid-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.ingredient-title,
|
||||
.instruction-title {
|
||||
grid-column: 1 / span 2;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.ingredient-body,
|
||||
.recipe-step-body,
|
||||
.note-body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
103
frontend/components/Domain/Recipe/RecipeScaleEditButton.vue
Normal file
103
frontend/components/Domain/Recipe/RecipeScaleEditButton.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-center d-flex align-center">
|
||||
<div>
|
||||
<v-menu v-model="menu" :disabled="!editScale" offset-y top nudge-top="6" :close-on-content-click="false">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-card class="pa-1 px-2" dark color="secondary darken-1" small v-bind="attrs" v-on="on">
|
||||
<span v-if="recipeYield"> {{ scaledYield }} </span>
|
||||
<span v-if="!recipeYield"> x {{ scale }} </span>
|
||||
</v-card>
|
||||
</template>
|
||||
<v-card min-width="300px">
|
||||
<v-card-title class="mb-0">
|
||||
{{ $t("recipe.edit-scale") }}
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-n5">
|
||||
<div class="mt-4 d-flex align-center">
|
||||
<v-text-field v-model.number="scale" type="number" :min="0" :label="$t('recipe.edit-scale')" />
|
||||
<v-tooltip right color="secondary darken-1">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn v-bind="attrs" icon class="mx-1" small v-on="on" @click="scale = 1">
|
||||
<v-icon>
|
||||
{{ $globals.icons.undo }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span> Reset Scale </span>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</div>
|
||||
<BaseButtonGroup
|
||||
v-if="editScale"
|
||||
class="pl-2"
|
||||
:large="false"
|
||||
:buttons="[
|
||||
{
|
||||
icon: $globals.icons.minus,
|
||||
text: 'Decrease Scale by 1',
|
||||
event: 'decrement',
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.createAlt,
|
||||
text: 'Increase Scale by 1',
|
||||
event: 'increment',
|
||||
},
|
||||
]"
|
||||
@decrement="scale > 1 ? scale-- : null"
|
||||
@increment="scale++"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, computed } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
recipeYield: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
basicYield: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
scaledYield: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
editScale: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
tempScale: 1,
|
||||
menu: false,
|
||||
});
|
||||
|
||||
const scale = computed({
|
||||
get: () => props.value,
|
||||
set: (value) => {
|
||||
const newScaleNumber = parseFloat(`${value}`);
|
||||
emit("input", isNaN(newScaleNumber) ? 0 : newScaleNumber);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
scale,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title> {{ $auth.user.fullName }}</v-list-item-title>
|
||||
<v-list-item-subtitle> {{ $auth.user.admin ? $t("user.admin") : $t("user.user") }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>
|
||||
<NuxtLink class="favorites-link" :to="`/user/${$auth.user.id}/favorites`"> Favorite Recipes </NuxtLink>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
@@ -200,4 +202,12 @@ export default defineComponent({
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.favorites-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.favorites-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-card-actions>
|
||||
<v-menu v-if="tableConfig.hideColumns" offset-y bottom nudge-bottom="6" :close-on-content-click="false">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn color="accent" class="mr-1" dark v-bind="attrs" v-on="on">
|
||||
<v-btn color="accent" class="mr-2" dark v-bind="attrs" v-on="on">
|
||||
<v-icon>
|
||||
{{ $globals.icons.cog }}
|
||||
</v-icon>
|
||||
|
||||
@@ -22,21 +22,15 @@
|
||||
dense
|
||||
rows="4"
|
||||
/>
|
||||
<VueMarkdown v-else :source="value" />
|
||||
<SafeMarkdown v-else :source="value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
|
||||
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MarkdownEditor",
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
||||
42
frontend/components/global/SafeMarkdown.vue
Normal file
42
frontend/components/global/SafeMarkdown.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<VueMarkdown :source="sanitizeMarkdown(source)"></VueMarkdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
source: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
function sanitizeMarkdown(rawHtml: string | null | undefined): string {
|
||||
if (!rawHtml) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const sanitized = DOMPurify.sanitize(rawHtml, {
|
||||
USE_PROFILES: { html: true },
|
||||
// TODO: some more thought could be put into what is allowed and what isn't
|
||||
ALLOWED_TAGS: ["img", "div", "p"],
|
||||
ADD_ATTR: ["src", "alt", "height", "width", "class"],
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizeMarkdown,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -30,11 +30,15 @@ export function useStoreActions<T extends BoundT>(
|
||||
const allItems = useAsync(async () => {
|
||||
const { data } = await api.getAll();
|
||||
|
||||
if (allRef) {
|
||||
allRef.value = data;
|
||||
if (data && allRef) {
|
||||
allRef.value = data.items;
|
||||
}
|
||||
|
||||
return data ?? [];
|
||||
if (data) {
|
||||
return data.items ?? [];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, useAsyncKey());
|
||||
|
||||
loading.value = false;
|
||||
@@ -45,8 +49,8 @@ export function useStoreActions<T extends BoundT>(
|
||||
loading.value = true;
|
||||
const { data } = await api.getAll();
|
||||
|
||||
if (data && allRef) {
|
||||
allRef.value = data;
|
||||
if (data && data.items && allRef) {
|
||||
allRef.value = data.items;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
|
||||
@@ -23,7 +23,6 @@ export function parseIngredientText(ingredient: RecipeIngredient, disableAmount:
|
||||
|
||||
// casting to number is required as sometimes quantity is a string
|
||||
if (quantity && Number(quantity) !== 0) {
|
||||
console.log("Using Quantity", quantity, typeof quantity);
|
||||
if (unit?.fraction) {
|
||||
const fraction = frac(quantity * scale, 10, true);
|
||||
if (fraction[0] !== undefined && fraction[0] > 0) {
|
||||
|
||||
@@ -21,7 +21,12 @@ export const useTools = function (eager = true) {
|
||||
loading.value = true;
|
||||
const units = useAsync(async () => {
|
||||
const { data } = await api.tools.getAll();
|
||||
return data;
|
||||
|
||||
if (data) {
|
||||
return data.items;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, useAsyncKey());
|
||||
|
||||
loading.value = false;
|
||||
@@ -33,7 +38,7 @@ export const useTools = function (eager = true) {
|
||||
const { data } = await api.tools.getAll();
|
||||
|
||||
if (data) {
|
||||
tools.value = data;
|
||||
tools.value = data.items;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
|
||||
@@ -18,8 +18,8 @@ function swap(t: Array<unknown>, i: number, j: number) {
|
||||
export const useSorter = () => {
|
||||
function sortAToZ(list: Array<Recipe>) {
|
||||
list.sort((a, b) => {
|
||||
const textA = a.name?.toUpperCase() ?? "";
|
||||
const textB = b.name?.toUpperCase() ?? "";
|
||||
const textA: string = a.name?.toUpperCase() ?? "";
|
||||
const textB: string = b.name?.toUpperCase() ?? "";
|
||||
return textA < textB ? -1 : textA > textB ? 1 : 0;
|
||||
});
|
||||
}
|
||||
@@ -61,13 +61,9 @@ export const useLazyRecipes = function () {
|
||||
|
||||
const recipes = ref<Recipe[]>([]);
|
||||
|
||||
async function fetchMore(start: number, limit: number) {
|
||||
const { data } = await api.recipes.getAll(start, limit);
|
||||
if (data) {
|
||||
data.forEach((recipe) => {
|
||||
recipes.value?.push(recipe);
|
||||
});
|
||||
}
|
||||
async function fetchMore(page: number, perPage: number, orderBy: string | null = null, orderDirection = "desc") {
|
||||
const { data } = await api.recipes.getAll(page, perPage, { orderBy, orderDirection });
|
||||
return data ? data.items : [];
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -80,26 +76,26 @@ export const useRecipes = (all = false, fetchRecipes = true) => {
|
||||
const api = useUserApi();
|
||||
|
||||
// recipes is non-reactive!!
|
||||
const { recipes, start, end } = (() => {
|
||||
const { recipes, page, perPage } = (() => {
|
||||
if (all) {
|
||||
return {
|
||||
recipes: allRecipes,
|
||||
start: 0,
|
||||
end: 9999,
|
||||
page: 1,
|
||||
perPage: -1,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
recipes: recentRecipes,
|
||||
start: 0,
|
||||
end: 30,
|
||||
page: 1,
|
||||
perPage: 30,
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
async function refreshRecipes() {
|
||||
const { data } = await api.recipes.getAll(start, end, { loadFood: true });
|
||||
const { data } = await api.recipes.getAll(page, perPage, { loadFood: true, orderBy: "created_at" });
|
||||
if (data) {
|
||||
recipes.value = data;
|
||||
recipes.value = data.items;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,11 @@ export const useCookbooks = function () {
|
||||
const units = useAsync(async () => {
|
||||
const { data } = await api.cookbooks.getAll();
|
||||
|
||||
return data;
|
||||
if (data) {
|
||||
return data.items;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, useAsyncKey());
|
||||
|
||||
loading.value = false;
|
||||
@@ -41,8 +45,8 @@ export const useCookbooks = function () {
|
||||
loading.value = true;
|
||||
const { data } = await api.cookbooks.getAll();
|
||||
|
||||
if (data && cookbookStore) {
|
||||
cookbookStore.value = data;
|
||||
if (data && data.items && cookbookStore) {
|
||||
cookbookStore.value = data.items;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
|
||||
@@ -26,13 +26,17 @@ export const useMealplans = function (range: Ref<DateRange>) {
|
||||
loading.value = true;
|
||||
const units = useAsync(async () => {
|
||||
const query = {
|
||||
start: format(range.value.start, "yyyy-MM-dd"),
|
||||
limit: format(range.value.end, "yyyy-MM-dd"),
|
||||
start_date: format(range.value.start, "yyyy-MM-dd"),
|
||||
end_date: format(range.value.end, "yyyy-MM-dd"),
|
||||
};
|
||||
// @ts-ignore TODO Modify typing to allow for string start+limit for mealplans
|
||||
const { data } = await api.mealplans.getAll(query.start, query.limit);
|
||||
const { data } = await api.mealplans.getAll(1, -1, { start_date: query.start_date, end_date: query.end_date });
|
||||
|
||||
return data;
|
||||
if (data) {
|
||||
return data.items;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, useAsyncKey());
|
||||
|
||||
loading.value = false;
|
||||
@@ -41,14 +45,14 @@ export const useMealplans = function (range: Ref<DateRange>) {
|
||||
async refreshAll(this: void) {
|
||||
loading.value = true;
|
||||
const query = {
|
||||
start: format(range.value.start, "yyyy-MM-dd"),
|
||||
limit: format(range.value.end, "yyyy-MM-dd"),
|
||||
start_date: format(range.value.start, "yyyy-MM-dd"),
|
||||
end_date: format(range.value.end, "yyyy-MM-dd"),
|
||||
};
|
||||
// @ts-ignore TODO Modify typing to allow for string start+limit for mealplans
|
||||
const { data } = await api.mealplans.getAll(query.start, query.limit);
|
||||
const { data } = await api.mealplans.getAll(1, -1, { start_date: query.start_date, end_date: query.end_date });
|
||||
|
||||
if (data) {
|
||||
mealplans.value = data;
|
||||
if (data && data.items) {
|
||||
mealplans.value = data.items;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
|
||||
@@ -14,7 +14,11 @@ export const useGroupWebhooks = function () {
|
||||
const units = useAsync(async () => {
|
||||
const { data } = await api.groupWebhooks.getAll();
|
||||
|
||||
return data;
|
||||
if (data) {
|
||||
return data.items;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, useAsyncKey());
|
||||
|
||||
loading.value = false;
|
||||
@@ -24,8 +28,8 @@ export const useGroupWebhooks = function () {
|
||||
loading.value = true;
|
||||
const { data } = await api.groupWebhooks.getAll();
|
||||
|
||||
if (data) {
|
||||
webhooks.value = data;
|
||||
if (data && data.items) {
|
||||
webhooks.value = data.items;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
@@ -37,7 +41,7 @@ export const useGroupWebhooks = function () {
|
||||
enabled: false,
|
||||
name: "New Webhook",
|
||||
url: "",
|
||||
time: "00:00",
|
||||
scheduledTime: "00:00",
|
||||
};
|
||||
|
||||
const { data } = await api.groupWebhooks.createOne(payload);
|
||||
@@ -52,8 +56,23 @@ export const useGroupWebhooks = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to UTC time
|
||||
const [hours, minutes] = updateData.scheduledTime.split(":");
|
||||
|
||||
const newDt = new Date();
|
||||
newDt.setHours(Number(hours));
|
||||
newDt.setMinutes(Number(minutes));
|
||||
|
||||
updateData.scheduledTime = `${pad(newDt.getUTCHours(), 2)}:${pad(newDt.getUTCMinutes(), 2)}`;
|
||||
console.log(updateData.scheduledTime);
|
||||
|
||||
const payload = {
|
||||
...updateData,
|
||||
scheduledTime: updateData.scheduledTime,
|
||||
};
|
||||
|
||||
loading.value = true;
|
||||
const { data } = await api.groupWebhooks.updateOne(updateData.id, updateData);
|
||||
const { data } = await api.groupWebhooks.updateOne(updateData.id, payload);
|
||||
if (data) {
|
||||
this.refreshAll();
|
||||
}
|
||||
@@ -73,3 +92,25 @@ export const useGroupWebhooks = function () {
|
||||
|
||||
return { webhooks, actions, validForm };
|
||||
};
|
||||
|
||||
function pad(num: number, size: number) {
|
||||
let numStr = num.toString();
|
||||
while (numStr.length < size) numStr = "0" + numStr;
|
||||
return numStr;
|
||||
}
|
||||
|
||||
export function timeUTCToLocal(time: string): string {
|
||||
const [hours, minutes] = time.split(":");
|
||||
const dt = new Date();
|
||||
dt.setUTCMinutes(Number(minutes));
|
||||
dt.setUTCHours(Number(hours));
|
||||
return `${pad(dt.getHours(), 2)}:${pad(dt.getMinutes(), 2)}`;
|
||||
}
|
||||
|
||||
export function timeLocalToUTC(time: string) {
|
||||
const [hours, minutes] = time.split(":");
|
||||
const dt = new Date();
|
||||
dt.setHours(Number(hours));
|
||||
dt.setMinutes(Number(minutes));
|
||||
return `${pad(dt.getUTCHours(), 2)}:${pad(dt.getUTCMinutes(), 2)}`;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,12 @@ export const useGroups = function () {
|
||||
const asyncKey = String(Date.now());
|
||||
const groups = useAsync(async () => {
|
||||
const { data } = await api.groups.getAll();
|
||||
return data;
|
||||
|
||||
if (data) {
|
||||
return data.items;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, asyncKey);
|
||||
|
||||
loading.value = false;
|
||||
@@ -53,7 +58,13 @@ export const useGroups = function () {
|
||||
async function refreshAllGroups() {
|
||||
loading.value = true;
|
||||
const { data } = await api.groups.getAll();
|
||||
groups.value = data;
|
||||
|
||||
if (data) {
|
||||
groups.value = data.items;
|
||||
} else {
|
||||
groups.value = null;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@ export const useAllUsers = function () {
|
||||
const asyncKey = String(Date.now());
|
||||
const allUsers = useAsync(async () => {
|
||||
const { data } = await api.users.getAll();
|
||||
return data;
|
||||
if (data) {
|
||||
return data.items;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, asyncKey);
|
||||
|
||||
loading.value = false;
|
||||
@@ -27,7 +31,13 @@ export const useAllUsers = function () {
|
||||
async function refreshAllUsers() {
|
||||
loading.value = true;
|
||||
const { data } = await api.users.getAll();
|
||||
users.value = data;
|
||||
|
||||
if (data) {
|
||||
users.value = data.items;
|
||||
} else {
|
||||
users.value = null;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
|
||||
28
frontend/composables/use-users/preferences.ts
Normal file
28
frontend/composables/use-users/preferences.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
|
||||
export interface UserRecipePreferences {
|
||||
orderBy: string;
|
||||
orderDirection: string;
|
||||
sortIcon: string;
|
||||
useMobileCards: boolean;
|
||||
}
|
||||
|
||||
export function useUserSortPreferences(): Ref<UserRecipePreferences> {
|
||||
const { $globals } = useContext();
|
||||
|
||||
const fromStorage = useLocalStorage(
|
||||
"recipe-section-preferences",
|
||||
{
|
||||
orderBy: "name",
|
||||
orderDirection: "asc",
|
||||
sortIcon: $globals.icons.sortAlphabeticalAscending,
|
||||
useMobileCards: false,
|
||||
},
|
||||
{ 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 Ref<UserRecipePreferences>;
|
||||
|
||||
return fromStorage;
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"short": {
|
||||
"month": "kort",
|
||||
"day": "numerisk",
|
||||
"weekday": "lang"
|
||||
"month": "short",
|
||||
"day": "numeric",
|
||||
"weekday": "long"
|
||||
},
|
||||
"medium": {
|
||||
"month": "lang",
|
||||
"day": "numerisk",
|
||||
"weekday": "lang",
|
||||
"year": "numerisk"
|
||||
"month": "long",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"year": "numeric"
|
||||
},
|
||||
"long": {
|
||||
"year": "numerisk",
|
||||
"month": "lang",
|
||||
"day": "numerisk",
|
||||
"weekday": "lang",
|
||||
"hour": "numerisk",
|
||||
"minute": "numerisk"
|
||||
"year": "numeric",
|
||||
"month": "long",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"hour": "numeric",
|
||||
"minute": "numeric"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"short": {
|
||||
"month": "kurz",
|
||||
"day": "numerisch",
|
||||
"weekday": "lang"
|
||||
"month": "short",
|
||||
"day": "numeric",
|
||||
"weekday": "long"
|
||||
},
|
||||
"medium": {
|
||||
"month": "lang",
|
||||
"day": "numerisch",
|
||||
"weekday": "lang",
|
||||
"year": "numerisch"
|
||||
"month": "long",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"year": "numeric"
|
||||
},
|
||||
"long": {
|
||||
"year": "numerisch",
|
||||
"month": "lang",
|
||||
"day": "numerisch",
|
||||
"weekday": "lang",
|
||||
"hour": "numerisch",
|
||||
"minute": "numerisch"
|
||||
"year": "numeric",
|
||||
"month": "long",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"hour": "numeric",
|
||||
"minute": "numeric"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"short": {
|
||||
"month": "breve",
|
||||
"day": "numerico",
|
||||
"weekday": "lungo"
|
||||
"month": "short",
|
||||
"day": "numeric",
|
||||
"weekday": "long"
|
||||
},
|
||||
"medium": {
|
||||
"month": "lungo",
|
||||
"day": "numerico",
|
||||
"weekday": "lungo",
|
||||
"year": "numerico"
|
||||
"month": "long",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"year": "numeric"
|
||||
},
|
||||
"long": {
|
||||
"year": "numeric",
|
||||
"month": "lungo",
|
||||
"day": "numerico",
|
||||
"weekday": "lungo",
|
||||
"hour": "numerico",
|
||||
"minute": "numerico"
|
||||
"month": "long",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"hour": "numeric",
|
||||
"minute": "numeric"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"short": {
|
||||
"month": "short",
|
||||
"day": "числовий",
|
||||
"day": "numeric",
|
||||
"weekday": "long"
|
||||
},
|
||||
"medium": {
|
||||
"month": "long",
|
||||
"day": "числовий",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"year": "числовий"
|
||||
"year": "numeric"
|
||||
},
|
||||
"long": {
|
||||
"year": "числовий",
|
||||
"year": "numeric",
|
||||
"month": "long",
|
||||
"day": "числовий",
|
||||
"day": "numeric",
|
||||
"weekday": "long",
|
||||
"hour": "числовий",
|
||||
"minute": "числовий"
|
||||
"hour": "numeric",
|
||||
"minute": "numeric"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Descripció",
|
||||
"disable-amount": "Oculta les quantitats",
|
||||
"disable-comments": "Oculta els comentaris",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Greixos",
|
||||
"fiber-content": "Fibra",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
{
|
||||
"about": {
|
||||
"about": "About",
|
||||
"about-mealie": "About Mealie",
|
||||
"api-docs": "API Docs",
|
||||
"api-port": "API Port",
|
||||
"application-mode": "Application Mode",
|
||||
"database-type": "Database Type",
|
||||
"database-url": "Database URL",
|
||||
"default-group": "Default Group",
|
||||
"about": "O aplikaci",
|
||||
"about-mealie": "O Mealie",
|
||||
"api-docs": "Dokumentace API",
|
||||
"api-port": "Dokumentace portu",
|
||||
"application-mode": "Režim aplikace",
|
||||
"database-type": "Typ databáze",
|
||||
"database-url": "URL databáze",
|
||||
"default-group": "Výchozí skupina",
|
||||
"demo": "Demo",
|
||||
"demo-status": "Demo Status",
|
||||
"development": "Development",
|
||||
"docs": "Docs",
|
||||
"download-log": "Download Log",
|
||||
"download-recipe-json": "Last Scraped JSON",
|
||||
"demo-status": "Stav dema",
|
||||
"development": "Vývoj",
|
||||
"docs": "Dokumentace",
|
||||
"download-log": "Stáhnout log",
|
||||
"download-recipe-json": "Poslední scrapovaný JSON",
|
||||
"github": "Github",
|
||||
"log-lines": "Log Lines",
|
||||
"not-demo": "Not Demo",
|
||||
"log-lines": "Řádky logů",
|
||||
"not-demo": "Není demo",
|
||||
"portfolio": "Portfolio",
|
||||
"production": "Production",
|
||||
"support": "Support",
|
||||
"version": "Version"
|
||||
"production": "Produkce",
|
||||
"support": "Podpora",
|
||||
"version": "Verze"
|
||||
},
|
||||
"asset": {
|
||||
"assets": "Assets",
|
||||
"code": "Code",
|
||||
"file": "File",
|
||||
"image": "Image",
|
||||
"new-asset": "New Asset",
|
||||
"assets": "Zdroje",
|
||||
"code": "Kód",
|
||||
"file": "Soubor",
|
||||
"image": "Obrázek",
|
||||
"new-asset": "Nový zdroj",
|
||||
"pdf": "PDF",
|
||||
"recipe": "Recipe",
|
||||
"show-assets": "Show Assets"
|
||||
"recipe": "Recept",
|
||||
"show-assets": "Zobrazit zdroje"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Categories",
|
||||
"category-created": "Category created",
|
||||
"category-creation-failed": "Category creation failed",
|
||||
"category-deleted": "Category Deleted",
|
||||
"category-deletion-failed": "Category deletion failed",
|
||||
"category-filter": "Category Filter",
|
||||
"category-update-failed": "Category update failed",
|
||||
"category-updated": "Category updated",
|
||||
"uncategorized-count": "Uncategorized {count}"
|
||||
"categories": "Kategorie",
|
||||
"category-created": "Kategorie vytvořena",
|
||||
"category-creation-failed": "Vytvoření kategorie selhalo",
|
||||
"category-deleted": "Kategorie smazána",
|
||||
"category-deletion-failed": "Smazání kategorie se nezdařilo",
|
||||
"category-filter": "Filtr kategorií",
|
||||
"category-update-failed": "Aktualizace kategorie selhala",
|
||||
"category-updated": "Kategorie byla aktualizována",
|
||||
"uncategorized-count": "Nezařazené {count}"
|
||||
},
|
||||
"events": {
|
||||
"apprise-url": "Apprise URL",
|
||||
"database": "Database",
|
||||
"delete-event": "Delete Event",
|
||||
"new-notification-form-description": "Mealie uses the Apprise library to generate notifications. They offer many options for services to use for notifications. Refer to their wiki for a comprehensive guide on how to create the URL for your service. If available, selecting the type of your notification may include extra features.",
|
||||
"new-version": "New version available!",
|
||||
"notification": "Notification",
|
||||
"refresh": "Refresh",
|
||||
"scheduled": "Scheduled",
|
||||
"something-went-wrong": "Something Went Wrong!",
|
||||
"subscribed-events": "Subscribed Events",
|
||||
"test-message-sent": "Test Message Sent"
|
||||
"database": "Databáze",
|
||||
"delete-event": "Smazat Událost",
|
||||
"new-notification-form-description": "Mealie používá knihovnu Apprise pro generování notifikací. Nabízí spousty služeb pro zasílání oznámení. Podívejte se do jejich wiki pro komplexní návod jak vytvářet URL pro vaši službu. Výběr typu oznámení může obsahovat další extra funkce.",
|
||||
"new-version": "Je dostupná nová verze!",
|
||||
"notification": "Oznámení",
|
||||
"refresh": "Obnovit",
|
||||
"scheduled": "Naplánováno",
|
||||
"something-went-wrong": "Něco se nepovedlo!",
|
||||
"subscribed-events": "Odebírané události",
|
||||
"test-message-sent": "Testovací zpráva odeslána"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Cancel",
|
||||
"cancel": "Zrušit",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
@@ -108,62 +108,62 @@
|
||||
"reset": "Reset",
|
||||
"saturday": "Saturday",
|
||||
"save": "Save",
|
||||
"settings": "Settings",
|
||||
"share": "Share",
|
||||
"shuffle": "Shuffle",
|
||||
"sort": "Sort",
|
||||
"sort-alphabetically": "Alphabetical",
|
||||
"status": "Status",
|
||||
"submit": "Submit",
|
||||
"success-count": "Success: {count}",
|
||||
"sunday": "Sunday",
|
||||
"templates": "Templates:",
|
||||
"settings": "Nastavení",
|
||||
"share": "Sdílet",
|
||||
"shuffle": "Náhodně",
|
||||
"sort": "Seřadit",
|
||||
"sort-alphabetically": "Abecedně",
|
||||
"status": "Stav",
|
||||
"submit": "Odeslat",
|
||||
"success-count": "Úspěšné: {count}",
|
||||
"sunday": "Neděle",
|
||||
"templates": "Šablony:",
|
||||
"test": "Test",
|
||||
"themes": "Themes",
|
||||
"thursday": "Thursday",
|
||||
"themes": "Motivy",
|
||||
"thursday": "Čtvrtek",
|
||||
"token": "Token",
|
||||
"tuesday": "Tuesday",
|
||||
"type": "Type",
|
||||
"update": "Update",
|
||||
"updated": "Updated",
|
||||
"upload": "Upload",
|
||||
"tuesday": "Úterý",
|
||||
"type": "Typ",
|
||||
"update": "Aktualizace",
|
||||
"updated": "Aktualizováno",
|
||||
"upload": "Nahrát",
|
||||
"url": "URL",
|
||||
"view": "View",
|
||||
"wednesday": "Wednesday",
|
||||
"yes": "Yes",
|
||||
"foods": "Foods",
|
||||
"units": "Units",
|
||||
"back": "Back",
|
||||
"next": "Next"
|
||||
"view": "Zobrazit",
|
||||
"wednesday": "Středa",
|
||||
"yes": "Ano",
|
||||
"foods": "Potraviny",
|
||||
"units": "Jednotky",
|
||||
"back": "Zpět",
|
||||
"next": "Další"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
|
||||
"cannot-delete-default-group": "Cannot delete default group",
|
||||
"cannot-delete-group-with-users": "Cannot delete group with users",
|
||||
"confirm-group-deletion": "Confirm Group Deletion",
|
||||
"create-group": "Create Group",
|
||||
"error-updating-group": "Error updating group",
|
||||
"group": "Group",
|
||||
"group-deleted": "Group deleted",
|
||||
"group-deletion-failed": "Group deletion failed",
|
||||
"group-id-with-value": "Group ID: {groupID}",
|
||||
"group-name": "Group Name",
|
||||
"group-not-found": "Group not found",
|
||||
"group-with-value": "Group: {groupID}",
|
||||
"groups": "Groups",
|
||||
"manage-groups": "Manage Groups",
|
||||
"user-group": "User Group",
|
||||
"user-group-created": "User Group Created",
|
||||
"user-group-creation-failed": "User Group Creation Failed",
|
||||
"are-you-sure-you-want-to-delete-the-group": "Jste si jisti, že chcete smazat <b>{groupName}<b/>?",
|
||||
"cannot-delete-default-group": "Nelze smazat výchozí skupinu",
|
||||
"cannot-delete-group-with-users": "Nelze smazat skupinu obsahující uživatele",
|
||||
"confirm-group-deletion": "Potvrdit smazání skupiny",
|
||||
"create-group": "Vytvořit skupinu",
|
||||
"error-updating-group": "Chyba při aktualizaci skupiny",
|
||||
"group": "Skupina",
|
||||
"group-deleted": "Skupina smazána",
|
||||
"group-deletion-failed": "Smazání skupiny se nezdařilo",
|
||||
"group-id-with-value": "ID skupiny: {groupID}",
|
||||
"group-name": "Název skupiny",
|
||||
"group-not-found": "Skupina nenalezena",
|
||||
"group-with-value": "Skupina: {groupID}",
|
||||
"groups": "Skupiny",
|
||||
"manage-groups": "Spravovat skupiny",
|
||||
"user-group": "Skupina uživatelů",
|
||||
"user-group-created": "Uživatelská skupina vytvořena",
|
||||
"user-group-creation-failed": "Vytvoření uživatelské skupiny se nezdařilo",
|
||||
"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": "Ponechat mé recepty soukromé",
|
||||
"keep-my-recipes-private-description": "Nastaví vaši skupinu a všechny recepty jako soukromé. Později to můžete změnit."
|
||||
}
|
||||
},
|
||||
"meal-plan": {
|
||||
"create-a-new-meal-plan": "Create a New Meal Plan",
|
||||
"dinner-this-week": "Dinner This Week",
|
||||
"dinner-today": "Dinner Today",
|
||||
"create-a-new-meal-plan": "Vytvořit nový jídelníček",
|
||||
"dinner-this-week": "Večeře na tento týden",
|
||||
"dinner-today": "Dnešní večeře",
|
||||
"dinner-tonight": "DINNER TONIGHT",
|
||||
"edit-meal-plan": "Edit Meal Plan",
|
||||
"end-date": "End Date",
|
||||
@@ -224,58 +224,59 @@
|
||||
"404-page-not-found": "404 Page not found",
|
||||
"all-recipes": "All Recipes",
|
||||
"new-page-created": "New page created",
|
||||
"page": "Page",
|
||||
"page-creation-failed": "Page creation failed",
|
||||
"page-deleted": "Page deleted",
|
||||
"page-deletion-failed": "Page deletion failed",
|
||||
"page-update-failed": "Page update failed",
|
||||
"page-updated": "Page updated",
|
||||
"pages-update-failed": "Pages update failed",
|
||||
"pages-updated": "Pages updated"
|
||||
"page": "Stránka",
|
||||
"page-creation-failed": "Vytvoření stránky se nezdařilo",
|
||||
"page-deleted": "Stránka smazána",
|
||||
"page-deletion-failed": "Smazání stránky se nezdařilo",
|
||||
"page-update-failed": "Aktualizace stránky se nezdařila",
|
||||
"page-updated": "Stránka aktualizována",
|
||||
"pages-update-failed": "Aktualizace stránek se nezdařila",
|
||||
"pages-updated": "Stránky aktualizovány"
|
||||
},
|
||||
"recipe": {
|
||||
"add-key": "Add Key",
|
||||
"add-to-favorites": "Add to Favorites",
|
||||
"add-key": "Přidat klíč",
|
||||
"add-to-favorites": "Přidat do oblíbených",
|
||||
"api-extras": "API Extras",
|
||||
"calories": "Calories",
|
||||
"calories-suffix": "calories",
|
||||
"carbohydrate-content": "Carbohydrate",
|
||||
"categories": "Categories",
|
||||
"comment-action": "Comment",
|
||||
"comments": "Comments",
|
||||
"delete-confirmation": "Are you sure you want to delete this recipe?",
|
||||
"delete-recipe": "Delete Recipe",
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
"ingredient": "Ingredient",
|
||||
"ingredients": "Ingredients",
|
||||
"insert-section": "Insert Section",
|
||||
"instructions": "Instructions",
|
||||
"key-name-required": "Key Name Required",
|
||||
"calories": "Kalorie",
|
||||
"calories-suffix": "kalorie",
|
||||
"carbohydrate-content": "Sacharidy",
|
||||
"categories": "Kategorie",
|
||||
"comment-action": "Komentář",
|
||||
"comments": "Komentáře",
|
||||
"delete-confirmation": "Opravdu chcete smazat tento recept?",
|
||||
"delete-recipe": "Smazat recept",
|
||||
"description": "Popis",
|
||||
"disable-amount": "Nezobrazovat množství ingrediencí",
|
||||
"disable-comments": "Zakázat komentáře",
|
||||
"edit-scale": "Upravit měřítko",
|
||||
"fat-content": "Tuky",
|
||||
"fiber-content": "Vláknina",
|
||||
"grams": "gramy",
|
||||
"ingredient": "Ingredience",
|
||||
"ingredients": "Ingredience",
|
||||
"insert-section": "Vložit sekci",
|
||||
"instructions": "Postup",
|
||||
"key-name-required": "Je vyžadován název klíče",
|
||||
"landscape-view-coming-soon": "Landscape View (Coming Soon)",
|
||||
"milligrams": "milligrams",
|
||||
"new-key-name": "New Key Name",
|
||||
"no-white-space-allowed": "No White Space Allowed",
|
||||
"note": "Note",
|
||||
"nutrition": "Nutrition",
|
||||
"object-key": "Object Key",
|
||||
"object-value": "Object Value",
|
||||
"original-url": "Original URL",
|
||||
"perform-time": "Cook Time",
|
||||
"prep-time": "Prep Time",
|
||||
"protein-content": "Protein",
|
||||
"public-recipe": "Public Recipe",
|
||||
"recipe-created": "Recipe created",
|
||||
"recipe-creation-failed": "Recipe creation failed",
|
||||
"recipe-deleted": "Recipe deleted",
|
||||
"recipe-image": "Recipe Image",
|
||||
"recipe-image-updated": "Recipe image updated",
|
||||
"recipe-name": "Recipe Name",
|
||||
"recipe-settings": "Recipe Settings",
|
||||
"milligrams": "miligramy",
|
||||
"new-key-name": "Nový název klíče",
|
||||
"no-white-space-allowed": "Prázdná místa nejsou povolena",
|
||||
"note": "Poznámka",
|
||||
"nutrition": "Výživové hodnoty",
|
||||
"object-key": "Klíč objektu",
|
||||
"object-value": "Hodnota objektu",
|
||||
"original-url": "Původní URL",
|
||||
"perform-time": "Doba vaření",
|
||||
"prep-time": "Doba přípravy",
|
||||
"protein-content": "Bílkoviny",
|
||||
"public-recipe": "Veřejný recept",
|
||||
"recipe-created": "Recept vytvořen",
|
||||
"recipe-creation-failed": "Vytvoření receptu selhalo",
|
||||
"recipe-deleted": "Recept smazán",
|
||||
"recipe-image": "Obrázek receptu",
|
||||
"recipe-image-updated": "Obrázek receptu aktualizován",
|
||||
"recipe-name": "Název receptu",
|
||||
"recipe-settings": "Nastavení receptu",
|
||||
"recipe-update-failed": "Recipe update failed",
|
||||
"recipe-updated": "Recipe updated",
|
||||
"remove-from-favorites": "Remove from Favorites",
|
||||
@@ -334,62 +335,62 @@
|
||||
"card-per-section": "Card Per Section",
|
||||
"home-page": "Home Page",
|
||||
"home-page-sections": "Home Page Sections",
|
||||
"show-recent": "Show Recent"
|
||||
"show-recent": "Zobrazit poslední"
|
||||
},
|
||||
"language": "Language",
|
||||
"latest": "Latest",
|
||||
"local-api": "Local API",
|
||||
"locale-settings": "Locale settings",
|
||||
"migrations": "Migrations",
|
||||
"new-page": "New Page",
|
||||
"notify": "Notify",
|
||||
"organize": "Organize",
|
||||
"page-name": "Page Name",
|
||||
"pages": "Pages",
|
||||
"profile": "Profile",
|
||||
"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",
|
||||
"site-settings": "Site Settings",
|
||||
"language": "Jazyk",
|
||||
"latest": "Poslední",
|
||||
"local-api": "Lokální API",
|
||||
"locale-settings": "Nastavení locale",
|
||||
"migrations": "Migrace",
|
||||
"new-page": "Nová stránka",
|
||||
"notify": "Upozornit",
|
||||
"organize": "Organizovat",
|
||||
"page-name": "Název stránky",
|
||||
"pages": "Stránky",
|
||||
"profile": "Profil",
|
||||
"remove-existing-entries-matching-imported-entries": "Odstranit existující položky odpovídající importovaným položkám",
|
||||
"set-new-time": "Nastavit nový čas",
|
||||
"settings-update-failed": "Aktualizace nastavení se nezdařila",
|
||||
"settings-updated": "Nastavení aktualizováno",
|
||||
"site-settings": "Nastavení webu",
|
||||
"theme": {
|
||||
"accent": "Accent",
|
||||
"dark": "Dark",
|
||||
"default-to-system": "Default to system",
|
||||
"error": "Error",
|
||||
"error-creating-theme-see-log-file": "Error creating theme. See log file.",
|
||||
"error-deleting-theme": "Error deleting theme",
|
||||
"error-updating-theme": "Error updating theme",
|
||||
"info": "Info",
|
||||
"light": "Light",
|
||||
"primary": "Primary",
|
||||
"secondary": "Secondary",
|
||||
"success": "Success",
|
||||
"switch-to-dark-mode": "Switch to dark mode",
|
||||
"switch-to-light-mode": "Switch to light mode",
|
||||
"theme-deleted": "Theme deleted",
|
||||
"theme-name": "Theme Name",
|
||||
"theme-name-is-required": "Theme Name is required.",
|
||||
"theme-saved": "Theme Saved",
|
||||
"theme-updated": "Theme updated",
|
||||
"warning": "Warning"
|
||||
"accent": "Odstín",
|
||||
"dark": "Tmavý",
|
||||
"default-to-system": "Výchozí nastavení systému",
|
||||
"error": "Chyba",
|
||||
"error-creating-theme-see-log-file": "Chyba při vytváření motivu. Viz log soubor.",
|
||||
"error-deleting-theme": "Chyba při mazání motivu",
|
||||
"error-updating-theme": "Chyba při aktualizaci motivu",
|
||||
"info": "Informace",
|
||||
"light": "Světlý",
|
||||
"primary": "Primární",
|
||||
"secondary": "Sekundární",
|
||||
"success": "Úspěšně dokončeno",
|
||||
"switch-to-dark-mode": "Přepnout do tmavého režimu",
|
||||
"switch-to-light-mode": "Přepnout do světlého režimu",
|
||||
"theme-deleted": "Motiv odstraněn",
|
||||
"theme-name": "Název motivu",
|
||||
"theme-name-is-required": "Název motivu je povinný.",
|
||||
"theme-saved": "Motiv uložen",
|
||||
"theme-updated": "Motiv aktualizován",
|
||||
"warning": "Upozornění"
|
||||
},
|
||||
"token": {
|
||||
"active-tokens": "ACTIVE TOKENS",
|
||||
"active-tokens": "AKTIVNÍ TOKENY",
|
||||
"api-token": "API Token",
|
||||
"api-tokens": "API Tokens",
|
||||
"copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Copy this token for use with an external application. This token will not be viewable again.",
|
||||
"create-an-api-token": "Create an API Token",
|
||||
"token-name": "Token Name"
|
||||
"api-tokens": "API Tokeny",
|
||||
"copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Zkopírujte tento token pro použití v externí aplikaci. Tento token nebude znovu zobrazen.",
|
||||
"create-an-api-token": "Vytvořit nový API token",
|
||||
"token-name": "Název tokenu"
|
||||
},
|
||||
"toolbox": {
|
||||
"assign-all": "Assign All",
|
||||
"bulk-assign": "Bulk Assign",
|
||||
"new-name": "New Name",
|
||||
"no-unused-items": "No Unused Items",
|
||||
"recipes-affected": "No Recipes Affected|One Recipe Affected|{count} Recipes Affected",
|
||||
"remove-unused": "Remove Unused",
|
||||
"title-case-all": "Title Case All",
|
||||
"assign-all": "Přiřadit vše",
|
||||
"bulk-assign": "Hromadné přiřazení",
|
||||
"new-name": "Nový název",
|
||||
"no-unused-items": "Žádné nepoužité položky",
|
||||
"recipes-affected": "Žádné recepty neovlivněny|Jeden recept ovlivněn|{count} receptů ovlivněno",
|
||||
"remove-unused": "Odstranit nepoužívané",
|
||||
"title-case-all": "Změnit první písmena slov na kapitálky",
|
||||
"toolbox": "Toolbox",
|
||||
"unorganized": "Unorganized"
|
||||
},
|
||||
@@ -455,26 +456,26 @@
|
||||
"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/>?",
|
||||
"confirm-link-deletion": "Confirm Link Deletion",
|
||||
"confirm-password": "Confirm Password",
|
||||
"confirm-user-deletion": "Confirm User Deletion",
|
||||
"could-not-validate-credentials": "Could Not Validate Credentials",
|
||||
"create-link": "Create Link",
|
||||
"create-user": "Create User",
|
||||
"current-password": "Current Password",
|
||||
"e-mail-must-be-valid": "E-mail must be valid",
|
||||
"edit-user": "Edit User",
|
||||
"confirm-password": "Potvrdit heslo",
|
||||
"confirm-user-deletion": "Potvrdit smazání uživatele",
|
||||
"could-not-validate-credentials": "Nelze ověřit přihlašovací údaje",
|
||||
"create-link": "Vytvořit odkaz",
|
||||
"create-user": "Vytvořit uživatele",
|
||||
"current-password": "Současné heslo",
|
||||
"e-mail-must-be-valid": "E-mail musí být platný",
|
||||
"edit-user": "Upravit uživatele",
|
||||
"email": "Email",
|
||||
"error-cannot-delete-super-user": "Error! Cannot Delete Super User",
|
||||
"existing-password-does-not-match": "Existing password does not match",
|
||||
"full-name": "Full Name",
|
||||
"invite-only": "Invite Only",
|
||||
"link-id": "Link ID",
|
||||
"link-name": "Link Name",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"manage-users": "Manage Users",
|
||||
"new-password": "New Password",
|
||||
"new-user": "New User",
|
||||
"error-cannot-delete-super-user": "Chyba! Nelze odstranit superuživatele",
|
||||
"existing-password-does-not-match": "Hesla se neshodují",
|
||||
"full-name": "Jméno a příjmení",
|
||||
"invite-only": "Jen na pozvání",
|
||||
"link-id": "ID odkazu",
|
||||
"link-name": "Název odkazu",
|
||||
"login": "Přihlášení",
|
||||
"logout": "Odhlášení",
|
||||
"manage-users": "Spravovat uživatele",
|
||||
"new-password": "Nové heslo",
|
||||
"new-user": "Nový uživatel",
|
||||
"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",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Beskrivelse",
|
||||
"disable-amount": "Slå ingrediensmængder fra",
|
||||
"disable-comments": "Slå kommentarer fra",
|
||||
"edit-scale": "Rediger skalering",
|
||||
"fat-content": "Fedt",
|
||||
"fiber-content": "Kostfibre",
|
||||
"grams": "gram",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Beschreibung",
|
||||
"disable-amount": "Zutatenmenge deaktivieren",
|
||||
"disable-comments": "Kommentare deaktivieren",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fett",
|
||||
"fiber-content": "Ballaststoffe",
|
||||
"grams": "g",
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
"about": {
|
||||
"about": "Σχετικά με",
|
||||
"about-mealie": "Σχετικά με το Mealie",
|
||||
"api-docs": "API Docs",
|
||||
"api-port": "API Port",
|
||||
"api-docs": "Έγγραφα API",
|
||||
"api-port": "Θύρα API",
|
||||
"application-mode": "Κατάσταση εφαρμογής",
|
||||
"database-type": "Τύπος βάσης δεδομένων",
|
||||
"database-url": "Database URL",
|
||||
"database-url": "URL Βάσης Δεδομένων",
|
||||
"default-group": "Προεπιλεγμένη ομάδα",
|
||||
"demo": "Επίδειξη",
|
||||
"demo-status": "Κατάσταση επίδειξης",
|
||||
"development": "Ανάπτυξη",
|
||||
"docs": "Docs",
|
||||
"docs": "Έγγραφα",
|
||||
"download-log": "Λήψη αρχείου καταγραφής",
|
||||
"download-recipe-json": "Τελευταίο Scraped JSON",
|
||||
"github": "Github",
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"asset": {
|
||||
"assets": "Στοιχεία",
|
||||
"code": "Code",
|
||||
"code": "Κώδικας",
|
||||
"file": "Αρχείο",
|
||||
"image": "Εικόνα",
|
||||
"new-asset": "Νέο Στοιχείο",
|
||||
@@ -33,7 +33,7 @@
|
||||
"show-assets": "Εμφάνιση Στοιχείων"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Categories",
|
||||
"categories": "Κατηγορίες",
|
||||
"category-created": "Δημιουργήθηκε η κατηγορία",
|
||||
"category-creation-failed": "Η δημιουργία κατηγορίας απέτυχε",
|
||||
"category-deleted": "Κατηγορία Διαγράφηκε",
|
||||
@@ -69,7 +69,7 @@
|
||||
"dashboard": "Ταμπλό",
|
||||
"delete": "Διαγραφή",
|
||||
"disabled": "Ανενεργό",
|
||||
"download": "Download",
|
||||
"download": "Λήψη",
|
||||
"edit": "Επεξεργασία",
|
||||
"enabled": "Ενεργό",
|
||||
"exception": "Εξαίρεση",
|
||||
@@ -131,10 +131,10 @@
|
||||
"view": "Προβολη",
|
||||
"wednesday": "Τετάρτη",
|
||||
"yes": "Ναι",
|
||||
"foods": "Foods",
|
||||
"units": "Units",
|
||||
"back": "Back",
|
||||
"next": "Next"
|
||||
"foods": "Φαγητά",
|
||||
"units": "Μονάδες",
|
||||
"back": "Πίσω",
|
||||
"next": "Επόμενο"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό τον ασφαλή σύνδεσμο <b>{groupName}<b/>;",
|
||||
@@ -156,8 +156,8 @@
|
||||
"user-group-created": "Η Ομάδα Χρηστών Δημιουργήθηκε",
|
||||
"user-group-creation-failed": "Αποτυχία Δημιουργίας Ομάδας Χρηστών",
|
||||
"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": "Κρατήστε Τις Συνταγές Μου Ιδιωτικές",
|
||||
"keep-my-recipes-private-description": "Ορίζει την ομάδα σας και όλες τις συνταγές ιδιωτικές από προεπιλογή. Μπορείτε πάντα να το αλλάξετε αργότερα."
|
||||
}
|
||||
},
|
||||
"meal-plan": {
|
||||
@@ -206,7 +206,7 @@
|
||||
"error-details": "Μόνο ιστοσελίδες που περιέχουν ld+json ή μικροδεδομένα μπορούν να εισαχθούν από την Mealie. Οι πιο σημαντικές ιστοσελίδες συνταγών υποστηρίζουν αυτή τη δομή δεδομένων. Αν το site σας δεν μπορεί να εισαχθεί, αλλά υπάρχουν δεδομένα json στο αρχείο καταγραφής, παρακαλούμε να υποβάλετε ένα github πρόβλημα με το URL και τα δεδομένα.",
|
||||
"error-title": "Φαίνεται Όπως Δεν Μπορούσαμε Να βρούμε Οτιδήποτε",
|
||||
"from-url": "Εισαγωγή συνταγής",
|
||||
"github-issues": "GitHub Issues",
|
||||
"github-issues": "Σφάλματα GitHub",
|
||||
"google-ld-json-info": "Google ld+json Info",
|
||||
"must-be-a-valid-url": "Πρέπει να είναι ένα έγκυρο URL",
|
||||
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Επικόλληση δεδομένων συνταγών σας. Κάθε γραμμή θα αντιμετωπίζεται ως αντικείμενο σε μια λίστα",
|
||||
@@ -216,9 +216,9 @@
|
||||
"upload-individual-zip-file": "Ανεβάστε ένα μεμονωμένο αρχείο .zip που εξάγεται από μια άλλη περίπτωση Mealie.",
|
||||
"url-form-hint": "Αντιγράψτε και επικολλήστε έναν σύνδεσμο από την αγαπημένη σας ιστοσελίδα συνταγών",
|
||||
"view-scraped-data": "Προβολή Παραγόμενων Δεδομένων",
|
||||
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
|
||||
"trim-prefix-description": "Trim first character from each line",
|
||||
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns"
|
||||
"trim-whitespace-description": "Περικοπή αιχμής και διαδρομής κενών καθώς και κενών γραμμών",
|
||||
"trim-prefix-description": "Περικοπή πρώτου χαρακτήρα από κάθε γραμμή",
|
||||
"split-by-numbered-line-description": "Προσπάθεια για χωρισμό μιας παραγράφου ταιριάζοντας μοτίβα '1)' ή '1'"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404. η σελίδα δεν βρέθηκε",
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Περιγραφή",
|
||||
"disable-amount": "Απενεργοποίηση Ποσών Συστατικών",
|
||||
"disable-comments": "Απενεργοποιηση σχολιων",
|
||||
"edit-scale": "Επεξεργασία Κλίμακας",
|
||||
"fat-content": "Λιπαρά",
|
||||
"fiber-content": "Ίνα",
|
||||
"grams": "γραμμάρια",
|
||||
@@ -291,7 +292,7 @@
|
||||
"title": "Τίτλος",
|
||||
"total-time": "Συνολικός Χρόνος",
|
||||
"unable-to-delete-recipe": "Αδυναμία διαγραφής συνταγής",
|
||||
"no-recipe": "No Recipe"
|
||||
"no-recipe": "Καμία Συνταγή"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Σύνθετη Αναζήτηση",
|
||||
@@ -413,9 +414,9 @@
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Συνταγές όλες",
|
||||
"backups": "Backups",
|
||||
"backups": "Αντίγραφα ασφαλείας",
|
||||
"categories": "Κατηγορίες",
|
||||
"cookbooks": "Cookbooks",
|
||||
"cookbooks": "Μαγειρικά Βιβλία",
|
||||
"dashboard": "Ταμπλό",
|
||||
"home-page": "Αρχική Σελίδα",
|
||||
"manage-users": "Διαχ. χρηστών",
|
||||
@@ -425,7 +426,7 @@
|
||||
"site-settings": "Ρυθμ. site",
|
||||
"tags": "Ετικέτα",
|
||||
"toolbox": "Εργαλειοθήκη",
|
||||
"language": "Language"
|
||||
"language": "Γλώσσα"
|
||||
},
|
||||
"signup": {
|
||||
"error-signing-up": "Σφάλμα Στην Υπογραφή",
|
||||
@@ -448,7 +449,7 @@
|
||||
"untagged-count": "Χωρίς ετικέτα {count}"
|
||||
},
|
||||
"tool": {
|
||||
"tools": "Tools"
|
||||
"tools": "Εργαλεία"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Διαχειριστής",
|
||||
@@ -467,7 +468,7 @@
|
||||
"error-cannot-delete-super-user": "Σφάλμα! Αδυναμία Διαγραφής Υπερχρήστη",
|
||||
"existing-password-does-not-match": "Ο υπάρχων κωδικός πρόσβασης δεν ταιριάζει",
|
||||
"full-name": "Πλήρες όνομα",
|
||||
"invite-only": "Invite Only",
|
||||
"invite-only": "Μόνο με πρόσκληση",
|
||||
"link-id": "Σύνδεσμος ID",
|
||||
"link-name": "Όνομα συνδέσμου",
|
||||
"login": "Σύνδεση",
|
||||
@@ -480,8 +481,8 @@
|
||||
"password-reset-failed": "Αποτυχία επαναφοράς κωδικού πρόσβασης",
|
||||
"password-updated": "Ο κωδικός πρόσβασης ενημερώθηκε",
|
||||
"password": "Κωδικός",
|
||||
"password-strength": "Password is {strength}",
|
||||
"register": "Register",
|
||||
"password-strength": "Ο κωδικός είναι {strength}",
|
||||
"register": "Εγγραφή",
|
||||
"reset-password": "Επαναφορά Κωδικού",
|
||||
"sign-in": "Είσοδος",
|
||||
"total-mealplans": "Σύνολο Σχεδίων Γεύματος",
|
||||
@@ -505,21 +506,21 @@
|
||||
"webhooks-enabled": "Το webhook είναι ενεργό",
|
||||
"you-are-not-allowed-to-create-a-user": "Δεν επιτρέπεται να δημιουργήσετε ένα χρήστη",
|
||||
"you-are-not-allowed-to-delete-this-user": "Δεν επιτρέπεται να διαγράψετε αυτόν τον χρήστη",
|
||||
"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"
|
||||
"enable-advanced-content": "Ενεργοποίηση Προηγμένου Περιεχομένου",
|
||||
"enable-advanced-content-description": "Ενεργοποιεί προηγμένες λειτουργίες όπως κλιμάκωση συνταγής, κλειδιά API, Webhooks και διαχείριση δεδομένων. Μην ανησυχείτε, μπορείτε πάντα να το αλλάξετε αργότερα"
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "translated",
|
||||
"choose-language": "Choose Language",
|
||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||
"read-the-docs": "Read the docs"
|
||||
"translated": "μεταφρασμένο",
|
||||
"choose-language": "Επιλογή γλώσσας",
|
||||
"select-description": "Επιλέξτε τη γλώσσα για το περιβάλλον εργασίας εργασίας Mealie. Η ρύθμιση ισχύει μόνο για εσάς, όχι για άλλους χρήστες.",
|
||||
"how-to-contribute-description": "Δεν είναι κάτι μεταφρασμένο ακόμα, λανθασμένο, ή η γλώσσα σας λείπει από τη λίστα? {read-the-docs-link} για το πώς να συνεισφέρει!",
|
||||
"read-the-docs": "Διαβάστε τα έγγραφα"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-food-example": "Merging {food1} into {food2}",
|
||||
"merge-dialog-text": "Ο συνδυασμός των επιλεγμένων τροφίμων θα συγχωνεύσει την πηγή τροφίμων και θα στοχεύσει τα τρόφιμα σε ένα μόνο φαγητό. Η πηγή τροφίμων θα διαγραφεί και όλες οι αναφορές στην πηγή τροφίμων θα ενημερωθούν ώστε να υποδείξουν το τρόφιμο-στόχο.",
|
||||
"merge-food-example": "Συγχώνευση {food1} στο {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
},
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fibre",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Descripción",
|
||||
"disable-amount": "Desactivar cantidades de ingredientes",
|
||||
"disable-comments": "Desactivar comentarios",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Grasa",
|
||||
"fiber-content": "Fibra",
|
||||
"grams": "gramos",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -216,9 +216,9 @@
|
||||
"upload-individual-zip-file": "Chargez un fichier .zip exporté depuis une autre instance Mealie.",
|
||||
"url-form-hint": "Copiez et collez un lien depuis votre site de recettes favori",
|
||||
"view-scraped-data": "Voir les données récupérées",
|
||||
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
|
||||
"trim-prefix-description": "Trim first character from each line",
|
||||
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns"
|
||||
"trim-whitespace-description": "Ajuster les espaces de début et de fin ainsi que les lignes vides",
|
||||
"trim-prefix-description": "Couper le premier caractère de chaque ligne",
|
||||
"split-by-numbered-line-description": "Tenter de découper un paragraphe par correspondance de motifs : '1) ou '1.'"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404 Page introuvable",
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Désactiver les quantités d'ingrédients",
|
||||
"disable-comments": "Désactiver les commentaires",
|
||||
"edit-scale": "Modifier l'échelle",
|
||||
"fat-content": "Matières grasses",
|
||||
"fiber-content": "Fibres",
|
||||
"grams": "grammes",
|
||||
@@ -415,7 +416,7 @@
|
||||
"all-recipes": "Les recettes",
|
||||
"backups": "Sauvegardes",
|
||||
"categories": "Catégories",
|
||||
"cookbooks": "Cookbooks",
|
||||
"cookbooks": "Livre de recettes",
|
||||
"dashboard": "Console",
|
||||
"home-page": "Accueil",
|
||||
"manage-users": "Utilisateurs",
|
||||
@@ -467,7 +468,7 @@
|
||||
"error-cannot-delete-super-user": "Erreur! Impossible de supprimer le super utilisateur",
|
||||
"existing-password-does-not-match": "Le mot de passe actuel ne correspond pas",
|
||||
"full-name": "Nom",
|
||||
"invite-only": "Invite Only",
|
||||
"invite-only": "Invités uniquement",
|
||||
"link-id": "ID du lien",
|
||||
"link-name": "Nom du lien",
|
||||
"login": "Connexion",
|
||||
@@ -480,7 +481,7 @@
|
||||
"password-reset-failed": "Échec de la réinitialisation du mot de passe",
|
||||
"password-updated": "Mot de passe mis à jour",
|
||||
"password": "Mot de passe",
|
||||
"password-strength": "Password is {strength}",
|
||||
"password-strength": "Robustesse du mot de passe : {strength}",
|
||||
"register": "S'inscrire",
|
||||
"reset-password": "Réinitialiser le mot de passe",
|
||||
"sign-in": "Se connecter",
|
||||
@@ -505,45 +506,45 @@
|
||||
"webhooks-enabled": "Webhooks activés",
|
||||
"you-are-not-allowed-to-create-a-user": "Vous n'avez pas le droit de créer un utilisateur",
|
||||
"you-are-not-allowed-to-delete-this-user": "Vous n'avez pas le droit de supprimer cet utilisateur",
|
||||
"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"
|
||||
"enable-advanced-content": "Activer le contenu avancé",
|
||||
"enable-advanced-content-description": "Active les fonctionnalités avancées comme la mise à l'échelle des recettes, les clés API, les Webhooks, et la gestion des données. Ne vous inquiétez pas, vous pouvez toujours modifier cela plus tard"
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "translated",
|
||||
"choose-language": "Choose Language",
|
||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||
"read-the-docs": "Read the docs"
|
||||
"translated": "traduit",
|
||||
"choose-language": "Choisir la langue",
|
||||
"select-description": "Choisissez la langue de l'interface utilisateur de Mealie. Ce paramètre s'applique uniquement à vous, pas aux autres utilisateurs.",
|
||||
"how-to-contribute-description": "Quelque chose n'est pas encore traduit, mal traduit, ou votre langue est manquante dans la liste ? {read-the-docs-link} sur la façon de contribuer !",
|
||||
"read-the-docs": "Lire la documentation"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"seed-data": "Génération de données",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-food-example": "Merging {food1} into {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
"merge-dialog-text": "La combinaison des aliments sélectionnés fusionnera l'aliment source et l'aliment cible en un seul aliment. L'aliment source sera supprimé et toutes les références à l'aliment source seront mises à jour pour pointer vers l'aliment cible.",
|
||||
"merge-food-example": "Fusion de {food1} dans {food2}",
|
||||
"seed-dialog-text": "Ensemencez la base de données avec des aliments basés sur votre langue locale. Cela permettra de créer plus de 200 aliments communs qui pourront être utilisés pour organiser votre base de données. Les aliments sont traduits grâce à un effort communautaire.",
|
||||
"seed-dialog-warning": "Vous avez déjà des éléments dans votre base de données. Cette action ne réconciliera pas les doublons, vous devrez les gérer manuellement."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Seed the database with common units based on your local language."
|
||||
"seed-dialog-text": "Introduisez dans la base de données des unités communes basées sur votre langue locale."
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Seed the database with common labels based on your local language."
|
||||
"seed-dialog-text": "Introduisez dans la base de données des unités communes basées sur votre langue locale."
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "User Registration",
|
||||
"join-a-group": "Join a Group",
|
||||
"create-a-new-group": "Create a New Group",
|
||||
"provide-registration-token-description": "Please provide the registration token associated with the group that you'd like to join. You'll need to obtain this from an existing group member.",
|
||||
"group-details": "Group Details",
|
||||
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
|
||||
"use-seed-data": "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.",
|
||||
"account-details": "Account Details"
|
||||
"user-registration": "Inscription d'utilisateur",
|
||||
"join-a-group": "Rejoindre un groupe",
|
||||
"create-a-new-group": "Créer un nouveau groupe",
|
||||
"provide-registration-token-description": "Veuillez fournir le jeton d'enregistrement associé au groupe que vous souhaitez rejoindre. Vous devrez l'obtenir auprès d'un membre existant du groupe.",
|
||||
"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 planification de repas, leurs listes d'achat, leurs recettes et plus encore !",
|
||||
"use-seed-data": "Utiliser la génération de données",
|
||||
"use-seed-data-description": "Mealie est livré avec une collection d'aliments, d'unités et d'étiquettes qui peuvent être utilisés pour alimenter votre groupe avec des données utiles pour organiser vos recettes.",
|
||||
"account-details": "Détails du compte"
|
||||
},
|
||||
"validation": {
|
||||
"group-name-is-taken": "Group name is taken",
|
||||
"username-is-taken": "Username is taken",
|
||||
"email-is-taken": "Email is taken"
|
||||
"group-name-is-taken": "Le nom du groupe est déjà pris",
|
||||
"username-is-taken": "Nom d'utilisateur déjà utilisé",
|
||||
"email-is-taken": "Cet e-mail est déjà pris"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"log-lines": "Lignes de log",
|
||||
"not-demo": "Non",
|
||||
"portfolio": "Portfolio",
|
||||
"production": "Production",
|
||||
"production": "Réalisation",
|
||||
"support": "Soutenir",
|
||||
"version": "Version"
|
||||
},
|
||||
@@ -216,9 +216,9 @@
|
||||
"upload-individual-zip-file": "Chargez un fichier .zip exporté depuis une autre instance Mealie.",
|
||||
"url-form-hint": "Copiez et collez un lien depuis votre site de recettes favori",
|
||||
"view-scraped-data": "Voir les données récupérées",
|
||||
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
|
||||
"trim-prefix-description": "Trim first character from each line",
|
||||
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns"
|
||||
"trim-whitespace-description": "Ajuster les espaces de début et de fin ainsi que les lignes vides",
|
||||
"trim-prefix-description": "Couper le premier caractère de chaque ligne",
|
||||
"split-by-numbered-line-description": "Tente de découper un paragraphe par correspondance de motifs : '1) ou '1.'"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404 Page introuvable",
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Désactiver les quantités des ingrédients",
|
||||
"disable-comments": "Désactiver les commentaires",
|
||||
"edit-scale": "Modifier l'échelle",
|
||||
"fat-content": "Matières grasses",
|
||||
"fiber-content": "Fibres",
|
||||
"grams": "grammes",
|
||||
@@ -260,7 +261,7 @@
|
||||
"milligrams": "milligrammes",
|
||||
"new-key-name": "Nouveau nom de clé",
|
||||
"no-white-space-allowed": "Aucun espace blanc autorisé",
|
||||
"note": "Note",
|
||||
"note": "Remarque",
|
||||
"nutrition": "Valeurs nutritionnelles",
|
||||
"object-key": "Clé d'objet",
|
||||
"object-value": "Valeur d'objet",
|
||||
@@ -415,7 +416,7 @@
|
||||
"all-recipes": "Les recettes",
|
||||
"backups": "Sauvegardes",
|
||||
"categories": "Catégories",
|
||||
"cookbooks": "Cookbooks",
|
||||
"cookbooks": "Livre de recettes",
|
||||
"dashboard": "Tableau de bord",
|
||||
"home-page": "Accueil",
|
||||
"manage-users": "Utilisateurs",
|
||||
@@ -451,7 +452,7 @@
|
||||
"tools": "Outils"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Admin",
|
||||
"admin": "Administrateur",
|
||||
"are-you-sure-you-want-to-delete-the-link": "Êtes-vous sûr de vouloir supprimer le lien <b>{link}<b/> ?",
|
||||
"are-you-sure-you-want-to-delete-the-user": "Êtes-vous sûr de vouloir supprimer l'utilisateur <b>{activeName} ID : {activeId}<b/> ?",
|
||||
"confirm-link-deletion": "Confirmer la suppression du lien",
|
||||
@@ -467,7 +468,7 @@
|
||||
"error-cannot-delete-super-user": "Erreur ! Impossible de supprimer le super utilisateur",
|
||||
"existing-password-does-not-match": "Le mot de passe actuel ne correspond pas",
|
||||
"full-name": "Nom",
|
||||
"invite-only": "Invite Only",
|
||||
"invite-only": "Invités uniquement",
|
||||
"link-id": "ID du lien",
|
||||
"link-name": "Nom du lien",
|
||||
"login": "Connexion",
|
||||
@@ -480,7 +481,7 @@
|
||||
"password-reset-failed": "Échec de la réinitialisation du mot de passe",
|
||||
"password-updated": "Mot de passe mis à jour",
|
||||
"password": "Mot de passe",
|
||||
"password-strength": "Password is {strength}",
|
||||
"password-strength": "Robustesse du mot de passe : {strength}",
|
||||
"register": "S'inscrire",
|
||||
"reset-password": "Réinitialiser le mot de passe",
|
||||
"sign-in": "Se connecter",
|
||||
@@ -505,45 +506,45 @@
|
||||
"webhooks-enabled": "Webhooks activés",
|
||||
"you-are-not-allowed-to-create-a-user": "Vous n'avez pas le droit de créer un utilisateur",
|
||||
"you-are-not-allowed-to-delete-this-user": "Vous n'avez pas le droit de supprimer cet utilisateur",
|
||||
"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"
|
||||
"enable-advanced-content": "Activer le contenu avancé",
|
||||
"enable-advanced-content-description": "Active les fonctionnalités avancées comme la mise à l'échelle des recettes, les clés API, les Webhooks, et la gestion des données. Ne vous inquiétez pas, vous pouvez toujours modifier cela plus tard"
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "traduit",
|
||||
"choose-language": "Choisir la langue",
|
||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||
"read-the-docs": "Read the docs"
|
||||
"select-description": "Choisissez la langue de l'interface utilisateur de Mealie. Ce paramètre s'applique uniquement à vous, pas aux autres utilisateurs.",
|
||||
"how-to-contribute-description": "Quelque chose n'est pas encore traduit, mal traduit, ou votre langue est manquante dans la liste ? {read-the-docs-link} sur la façon de contribuer !",
|
||||
"read-the-docs": "Lire la documentation"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"seed-data": "Génération de données",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-food-example": "Merging {food1} into {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
"merge-dialog-text": "La combinaison des aliments sélectionnés fusionnera l'aliment source et l'aliment cible en un seul aliment. L'aliment source sera supprimé et toutes les références à l'aliment source seront mises à jour pour pointer vers l'aliment cible.",
|
||||
"merge-food-example": "Fusion de {food1} dans {food2}",
|
||||
"seed-dialog-text": "Ensemencez la base de données avec des aliments basés sur votre langue locale. Cela permettra de créer plus de 200 aliments communs qui pourront être utilisés pour organiser votre base de données. Les aliments sont traduits grâce à un effort communautaire.",
|
||||
"seed-dialog-warning": "Vous avez déjà des éléments dans votre base de données. Cette action ne réconciliera pas les doublons, vous devrez les gérer manuellement."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Seed the database with common units based on your local language."
|
||||
"seed-dialog-text": "Introduisez dans la base de données des unités communes basées sur votre langue locale."
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Seed the database with common labels based on your local language."
|
||||
"seed-dialog-text": "Introduisez dans la base de données des unités communes basées sur votre langue locale."
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "User Registration",
|
||||
"user-registration": "Inscription d'utilisateur",
|
||||
"join-a-group": "Rejoindre un groupe",
|
||||
"create-a-new-group": "Créer un nouveau groupe",
|
||||
"provide-registration-token-description": "Please provide the registration token associated with the group that you'd like to join. You'll need to obtain this from an existing group member.",
|
||||
"provide-registration-token-description": "Veuillez fournir le jeton d'enregistrement associé au groupe que vous souhaitez rejoindre. Vous devrez l'obtenir auprès d'un membre existant du groupe.",
|
||||
"group-details": "Détails du groupe",
|
||||
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
|
||||
"use-seed-data": "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.",
|
||||
"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 planification de repas, leurs listes d'achat, leurs recettes et plus encore !",
|
||||
"use-seed-data": "Utiliser la génération de données",
|
||||
"use-seed-data-description": "Mealie est livré avec une collection d'aliments, d'unités et d'étiquettes qui peuvent être utilisés pour alimenter votre groupe avec des données utiles pour organiser vos recettes.",
|
||||
"account-details": "Détails du compte"
|
||||
},
|
||||
"validation": {
|
||||
"group-name-is-taken": "Group name is taken",
|
||||
"group-name-is-taken": "Le nom du groupe est déjà pris",
|
||||
"username-is-taken": "Nom d'utilisateur déjà utilisé",
|
||||
"email-is-taken": "Email is taken"
|
||||
"email-is-taken": "Cet e-mail est déjà pris"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Leírás",
|
||||
"disable-amount": "Hozzávalók mennyiségének letiltása",
|
||||
"disable-comments": "Megjegyzések letiltása",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Zsír",
|
||||
"fiber-content": "Rostok",
|
||||
"grams": "gramm",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Descrizione",
|
||||
"disable-amount": "Disabilita Quantità Ingredienti",
|
||||
"disable-comments": "Disattiva Commenti",
|
||||
"edit-scale": "Modifica Scala",
|
||||
"fat-content": "Grassi",
|
||||
"fiber-content": "Fibre",
|
||||
"grams": "grammi",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
550
frontend/lang/messages/lt-LT.json
Normal file
550
frontend/lang/messages/lt-LT.json
Normal file
@@ -0,0 +1,550 @@
|
||||
{
|
||||
"about": {
|
||||
"about": "About",
|
||||
"about-mealie": "About Mealie",
|
||||
"api-docs": "API Docs",
|
||||
"api-port": "API Port",
|
||||
"application-mode": "Application Mode",
|
||||
"database-type": "Database Type",
|
||||
"database-url": "Database URL",
|
||||
"default-group": "Default Group",
|
||||
"demo": "Demo",
|
||||
"demo-status": "Demo Status",
|
||||
"development": "Development",
|
||||
"docs": "Docs",
|
||||
"download-log": "Download Log",
|
||||
"download-recipe-json": "Last Scraped JSON",
|
||||
"github": "Github",
|
||||
"log-lines": "Log Lines",
|
||||
"not-demo": "Not Demo",
|
||||
"portfolio": "Portfolio",
|
||||
"production": "Production",
|
||||
"support": "Support",
|
||||
"version": "Version"
|
||||
},
|
||||
"asset": {
|
||||
"assets": "Assets",
|
||||
"code": "Code",
|
||||
"file": "File",
|
||||
"image": "Image",
|
||||
"new-asset": "New Asset",
|
||||
"pdf": "PDF",
|
||||
"recipe": "Recipe",
|
||||
"show-assets": "Show Assets"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Categories",
|
||||
"category-created": "Category created",
|
||||
"category-creation-failed": "Category creation failed",
|
||||
"category-deleted": "Category Deleted",
|
||||
"category-deletion-failed": "Category deletion failed",
|
||||
"category-filter": "Category Filter",
|
||||
"category-update-failed": "Category update failed",
|
||||
"category-updated": "Category updated",
|
||||
"uncategorized-count": "Uncategorized {count}"
|
||||
},
|
||||
"events": {
|
||||
"apprise-url": "Apprise URL",
|
||||
"database": "Database",
|
||||
"delete-event": "Delete Event",
|
||||
"new-notification-form-description": "Mealie uses the Apprise library to generate notifications. They offer many options for services to use for notifications. Refer to their wiki for a comprehensive guide on how to create the URL for your service. If available, selecting the type of your notification may include extra features.",
|
||||
"new-version": "New version available!",
|
||||
"notification": "Notification",
|
||||
"refresh": "Refresh",
|
||||
"scheduled": "Scheduled",
|
||||
"something-went-wrong": "Something Went Wrong!",
|
||||
"subscribed-events": "Subscribed Events",
|
||||
"test-message-sent": "Test Message Sent"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Cancel",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"confirm-delete-generic": "Are you sure you want to delete this?",
|
||||
"copied": "Copied",
|
||||
"create": "Create",
|
||||
"created": "Created",
|
||||
"custom": "Custom",
|
||||
"dashboard": "Dashboard",
|
||||
"delete": "Delete",
|
||||
"disabled": "Disabled",
|
||||
"download": "Download",
|
||||
"edit": "Edit",
|
||||
"enabled": "Enabled",
|
||||
"exception": "Exception",
|
||||
"failed-count": "Failed: {count}",
|
||||
"failure-uploading-file": "Failure uploading file",
|
||||
"favorites": "Favorites",
|
||||
"field-required": "Field Required",
|
||||
"file-folder-not-found": "File/folder not found",
|
||||
"file-uploaded": "File uploaded",
|
||||
"filter": "Filter",
|
||||
"friday": "Friday",
|
||||
"general": "General",
|
||||
"get": "Get",
|
||||
"home": "Home",
|
||||
"image": "Image",
|
||||
"image-upload-failed": "Image upload failed",
|
||||
"import": "Import",
|
||||
"json": "JSON",
|
||||
"keyword": "Keyword",
|
||||
"link-copied": "Link Copied",
|
||||
"loading-recipes": "Loading Recipes",
|
||||
"monday": "Monday",
|
||||
"name": "Name",
|
||||
"new": "New",
|
||||
"no": "No",
|
||||
"no-recipe-found": "No Recipe Found",
|
||||
"ok": "OK",
|
||||
"options": "Options:",
|
||||
"print": "Print",
|
||||
"random": "Random",
|
||||
"rating": "Rating",
|
||||
"recent": "Recent",
|
||||
"recipe": "Recipe",
|
||||
"recipes": "Recipes",
|
||||
"rename-object": "Rename {0}",
|
||||
"reset": "Reset",
|
||||
"saturday": "Saturday",
|
||||
"save": "Save",
|
||||
"settings": "Settings",
|
||||
"share": "Share",
|
||||
"shuffle": "Shuffle",
|
||||
"sort": "Sort",
|
||||
"sort-alphabetically": "Alphabetical",
|
||||
"status": "Status",
|
||||
"submit": "Submit",
|
||||
"success-count": "Success: {count}",
|
||||
"sunday": "Sunday",
|
||||
"templates": "Templates:",
|
||||
"test": "Test",
|
||||
"themes": "Themes",
|
||||
"thursday": "Thursday",
|
||||
"token": "Token",
|
||||
"tuesday": "Tuesday",
|
||||
"type": "Type",
|
||||
"update": "Update",
|
||||
"updated": "Updated",
|
||||
"upload": "Upload",
|
||||
"url": "URL",
|
||||
"view": "View",
|
||||
"wednesday": "Wednesday",
|
||||
"yes": "Yes",
|
||||
"foods": "Foods",
|
||||
"units": "Units",
|
||||
"back": "Back",
|
||||
"next": "Next"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
|
||||
"cannot-delete-default-group": "Cannot delete default group",
|
||||
"cannot-delete-group-with-users": "Cannot delete group with users",
|
||||
"confirm-group-deletion": "Confirm Group Deletion",
|
||||
"create-group": "Create Group",
|
||||
"error-updating-group": "Error updating group",
|
||||
"group": "Group",
|
||||
"group-deleted": "Group deleted",
|
||||
"group-deletion-failed": "Group deletion failed",
|
||||
"group-id-with-value": "Group ID: {groupID}",
|
||||
"group-name": "Group Name",
|
||||
"group-not-found": "Group not found",
|
||||
"group-with-value": "Group: {groupID}",
|
||||
"groups": "Groups",
|
||||
"manage-groups": "Manage Groups",
|
||||
"user-group": "User Group",
|
||||
"user-group-created": "User Group Created",
|
||||
"user-group-creation-failed": "User Group Creation Failed",
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"meal-plan": {
|
||||
"create-a-new-meal-plan": "Create a New 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)",
|
||||
"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",
|
||||
"no-meal-plan-defined-yet": "No meal plan defined yet",
|
||||
"no-meal-planned-for-today": "No meal planned for today",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans",
|
||||
"planner": "Planner",
|
||||
"quick-week": "Quick Week",
|
||||
"side": "Side",
|
||||
"sides": "Sides",
|
||||
"start-date": "Start Date"
|
||||
},
|
||||
"migration": {
|
||||
"chowdown": {
|
||||
"description": "Migrate data from Chowdown",
|
||||
"title": "Chowdown"
|
||||
},
|
||||
"migration-data-removed": "Migration data removed",
|
||||
"nextcloud": {
|
||||
"description": "Migrate data from a Nextcloud Cookbook instance",
|
||||
"title": "Nextcloud Cookbook"
|
||||
},
|
||||
"no-migration-data-available": "No Migration Data Available",
|
||||
"recipe-migration": "Recipe Migration"
|
||||
},
|
||||
"new-recipe": {
|
||||
"bulk-add": "Bulk Add",
|
||||
"error-details": "Only websites containing ld+json or microdata can be imported by Mealie. Most major recipe websites support this data structure. If your site cannot be imported but there is json data in the log, please submit a github issue with the URL and data.",
|
||||
"error-title": "Looks Like We Couldn't Find Anything",
|
||||
"from-url": "Import a Recipe",
|
||||
"github-issues": "GitHub Issues",
|
||||
"google-ld-json-info": "Google ld+json Info",
|
||||
"must-be-a-valid-url": "Must be a Valid URL",
|
||||
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list",
|
||||
"recipe-markup-specification": "Recipe Markup Specification",
|
||||
"recipe-url": "Recipe URL",
|
||||
"upload-a-recipe": "Upload a Recipe",
|
||||
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.",
|
||||
"url-form-hint": "Copy and paste a link from your favorite recipe website",
|
||||
"view-scraped-data": "View Scraped Data",
|
||||
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
|
||||
"trim-prefix-description": "Trim first character from each line",
|
||||
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404 Page not found",
|
||||
"all-recipes": "All Recipes",
|
||||
"new-page-created": "New page created",
|
||||
"page": "Page",
|
||||
"page-creation-failed": "Page creation failed",
|
||||
"page-deleted": "Page deleted",
|
||||
"page-deletion-failed": "Page deletion failed",
|
||||
"page-update-failed": "Page update failed",
|
||||
"page-updated": "Page updated",
|
||||
"pages-update-failed": "Pages update failed",
|
||||
"pages-updated": "Pages updated"
|
||||
},
|
||||
"recipe": {
|
||||
"add-key": "Add Key",
|
||||
"add-to-favorites": "Add to Favorites",
|
||||
"api-extras": "API Extras",
|
||||
"calories": "Calories",
|
||||
"calories-suffix": "calories",
|
||||
"carbohydrate-content": "Carbohydrate",
|
||||
"categories": "Categories",
|
||||
"comment-action": "Comment",
|
||||
"comments": "Comments",
|
||||
"delete-confirmation": "Are you sure you want to delete this recipe?",
|
||||
"delete-recipe": "Delete Recipe",
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
"ingredient": "Ingredient",
|
||||
"ingredients": "Ingredients",
|
||||
"insert-section": "Insert Section",
|
||||
"instructions": "Instructions",
|
||||
"key-name-required": "Key Name Required",
|
||||
"landscape-view-coming-soon": "Landscape View",
|
||||
"milligrams": "milligrams",
|
||||
"new-key-name": "New Key Name",
|
||||
"no-white-space-allowed": "No White Space Allowed",
|
||||
"note": "Note",
|
||||
"nutrition": "Nutrition",
|
||||
"object-key": "Object Key",
|
||||
"object-value": "Object Value",
|
||||
"original-url": "Original URL",
|
||||
"perform-time": "Cook Time",
|
||||
"prep-time": "Prep Time",
|
||||
"protein-content": "Protein",
|
||||
"public-recipe": "Public Recipe",
|
||||
"recipe-created": "Recipe created",
|
||||
"recipe-creation-failed": "Recipe creation failed",
|
||||
"recipe-deleted": "Recipe deleted",
|
||||
"recipe-image": "Recipe Image",
|
||||
"recipe-image-updated": "Recipe image updated",
|
||||
"recipe-name": "Recipe Name",
|
||||
"recipe-settings": "Recipe Settings",
|
||||
"recipe-update-failed": "Recipe update failed",
|
||||
"recipe-updated": "Recipe updated",
|
||||
"remove-from-favorites": "Remove from Favorites",
|
||||
"remove-section": "Remove Section",
|
||||
"save-recipe-before-use": "Save recipe before use",
|
||||
"section-title": "Section Title",
|
||||
"servings": "Servings",
|
||||
"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",
|
||||
"title": "Title",
|
||||
"total-time": "Total Time",
|
||||
"unable-to-delete-recipe": "Unable to Delete Recipe",
|
||||
"no-recipe": "No Recipe"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Advanced Search",
|
||||
"and": "and",
|
||||
"exclude": "Exclude",
|
||||
"include": "Include",
|
||||
"max-results": "Max Results",
|
||||
"or": "Or",
|
||||
"results": "Results",
|
||||
"search": "Search",
|
||||
"search-mealie": "Search Mealie (press /)",
|
||||
"search-placeholder": "Search...",
|
||||
"tag-filter": "Tag Filter"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Add a New Theme",
|
||||
"admin-settings": "Admin Settings",
|
||||
"backup": {
|
||||
"backup-created-at-response-export_path": "Backup Created at {path}",
|
||||
"backup-deleted": "Backup deleted",
|
||||
"backup-tag": "Backup Tag",
|
||||
"create-heading": "Create A Backup",
|
||||
"delete-backup": "Delete Backup",
|
||||
"error-creating-backup-see-log-file": "Error Creating Backup. See Log File",
|
||||
"full-backup": "Full Backup",
|
||||
"import-summary": "Import Summary",
|
||||
"partial-backup": "Partial Backup",
|
||||
"unable-to-delete-backup": "Unable to Delete Backup."
|
||||
},
|
||||
"backup-and-exports": "Backups",
|
||||
"change-password": "Change Password",
|
||||
"current": "Version:",
|
||||
"custom-pages": "Custom Pages",
|
||||
"edit-page": "Edit Page",
|
||||
"events": "Events",
|
||||
"first-day-of-week": "First day of the week",
|
||||
"group-settings-updated": "Group Settings Updated",
|
||||
"homepage": {
|
||||
"all-categories": "All Categories",
|
||||
"card-per-section": "Card Per Section",
|
||||
"home-page": "Home Page",
|
||||
"home-page-sections": "Home Page Sections",
|
||||
"show-recent": "Show Recent"
|
||||
},
|
||||
"language": "Language",
|
||||
"latest": "Latest",
|
||||
"local-api": "Local API",
|
||||
"locale-settings": "Locale settings",
|
||||
"migrations": "Migrations",
|
||||
"new-page": "New Page",
|
||||
"notify": "Notify",
|
||||
"organize": "Organize",
|
||||
"page-name": "Page Name",
|
||||
"pages": "Pages",
|
||||
"profile": "Profile",
|
||||
"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",
|
||||
"site-settings": "Site Settings",
|
||||
"theme": {
|
||||
"accent": "Accent",
|
||||
"dark": "Dark",
|
||||
"default-to-system": "Default to system",
|
||||
"error": "Error",
|
||||
"error-creating-theme-see-log-file": "Error creating theme. See log file.",
|
||||
"error-deleting-theme": "Error deleting theme",
|
||||
"error-updating-theme": "Error updating theme",
|
||||
"info": "Info",
|
||||
"light": "Light",
|
||||
"primary": "Primary",
|
||||
"secondary": "Secondary",
|
||||
"success": "Success",
|
||||
"switch-to-dark-mode": "Switch to dark mode",
|
||||
"switch-to-light-mode": "Switch to light mode",
|
||||
"theme-deleted": "Theme deleted",
|
||||
"theme-name": "Theme Name",
|
||||
"theme-name-is-required": "Theme Name is required.",
|
||||
"theme-saved": "Theme Saved",
|
||||
"theme-updated": "Theme updated",
|
||||
"warning": "Warning"
|
||||
},
|
||||
"token": {
|
||||
"active-tokens": "ACTIVE TOKENS",
|
||||
"api-token": "API Token",
|
||||
"api-tokens": "API Tokens",
|
||||
"copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Copy this token for use with an external application. This token will not be viewable again.",
|
||||
"create-an-api-token": "Create an API Token",
|
||||
"token-name": "Token Name"
|
||||
},
|
||||
"toolbox": {
|
||||
"assign-all": "Assign All",
|
||||
"bulk-assign": "Bulk Assign",
|
||||
"new-name": "New Name",
|
||||
"no-unused-items": "No Unused Items",
|
||||
"recipes-affected": "No Recipes Affected|One Recipe Affected|{count} Recipes Affected",
|
||||
"remove-unused": "Remove Unused",
|
||||
"title-case-all": "Title Case All",
|
||||
"toolbox": "Toolbox",
|
||||
"unorganized": "Unorganized"
|
||||
},
|
||||
"webhooks": {
|
||||
"test-webhooks": "Test Webhooks",
|
||||
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at",
|
||||
"webhook-url": "Webhook URL",
|
||||
"webhooks-caps": "WEBHOOKS",
|
||||
"webhooks": "Webhooks"
|
||||
}
|
||||
},
|
||||
"shopping-list": {
|
||||
"all-lists": "All Lists",
|
||||
"create-shopping-list": "Create Shopping List",
|
||||
"from-recipe": "From Recipe",
|
||||
"list-name": "List Name",
|
||||
"new-list": "New List",
|
||||
"quantity": "Quantity: {0}",
|
||||
"shopping-list": "Shopping List",
|
||||
"shopping-lists": "Shopping Lists"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "All Recipes",
|
||||
"backups": "Backups",
|
||||
"categories": "Categories",
|
||||
"cookbooks": "Cookbooks",
|
||||
"dashboard": "Dashboard",
|
||||
"home-page": "Home Page",
|
||||
"manage-users": "Manage Users",
|
||||
"migrations": "Migrations",
|
||||
"profile": "Profile",
|
||||
"search": "Search",
|
||||
"site-settings": "Site Settings",
|
||||
"tags": "Tags",
|
||||
"toolbox": "Toolbox",
|
||||
"language": "Language"
|
||||
},
|
||||
"signup": {
|
||||
"error-signing-up": "Error Signing Up",
|
||||
"sign-up": "Sign Up",
|
||||
"sign-up-link-created": "Sign up link created",
|
||||
"sign-up-link-creation-failed": "Sign up link creation failed",
|
||||
"sign-up-links": "Sign Up Links",
|
||||
"sign-up-token-deleted": "Sign Up Token Deleted",
|
||||
"sign-up-token-deletion-failed": "Sign up token deletion failed",
|
||||
"welcome-to-mealie": "Welcome to Mealie! To become a user of this instance you are required to have a valid invitation link. If you haven't recieved an invitation you are unable to sign-up. To recieve a link, contact the sites administrator."
|
||||
},
|
||||
"tag": {
|
||||
"tag-created": "Tag created",
|
||||
"tag-creation-failed": "Tag creation failed",
|
||||
"tag-deleted": "Tag deleted",
|
||||
"tag-deletion-failed": "Tag deletion failed",
|
||||
"tag-update-failed": "Tag update failed",
|
||||
"tag-updated": "Tag updated",
|
||||
"tags": "Tags",
|
||||
"untagged-count": "Untagged {count}"
|
||||
},
|
||||
"tool": {
|
||||
"tools": "Tools"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Admin",
|
||||
"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/>?",
|
||||
"confirm-link-deletion": "Confirm Link Deletion",
|
||||
"confirm-password": "Confirm Password",
|
||||
"confirm-user-deletion": "Confirm User Deletion",
|
||||
"could-not-validate-credentials": "Could Not Validate Credentials",
|
||||
"create-link": "Create Link",
|
||||
"create-user": "Create User",
|
||||
"current-password": "Current Password",
|
||||
"e-mail-must-be-valid": "E-mail must be valid",
|
||||
"edit-user": "Edit User",
|
||||
"email": "Email",
|
||||
"error-cannot-delete-super-user": "Error! Cannot Delete Super User",
|
||||
"existing-password-does-not-match": "Existing password does not match",
|
||||
"full-name": "Full Name",
|
||||
"invite-only": "Invite Only",
|
||||
"link-id": "Link ID",
|
||||
"link-name": "Link Name",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"manage-users": "Manage Users",
|
||||
"new-password": "New Password",
|
||||
"new-user": "New User",
|
||||
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
|
||||
"password-must-match": "Password must match",
|
||||
"password-reset-failed": "Password reset failed",
|
||||
"password-updated": "Password updated",
|
||||
"password": "Password",
|
||||
"password-strength": "Password is {strength}",
|
||||
"register": "Register",
|
||||
"reset-password": "Reset Password",
|
||||
"sign-in": "Sign in",
|
||||
"total-mealplans": "Total MealPlans",
|
||||
"total-users": "Total Users",
|
||||
"upload-photo": "Upload Photo",
|
||||
"use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password",
|
||||
"user-created": "User created",
|
||||
"user-creation-failed": "User creation failed",
|
||||
"user-deleted": "User deleted",
|
||||
"user-id-with-value": "User ID: {id}",
|
||||
"user-id": "User ID",
|
||||
"user-password": "User Password",
|
||||
"user-successfully-logged-in": "User Successfully Logged In",
|
||||
"user-update-failed": "User update failed",
|
||||
"user-updated": "User updated",
|
||||
"user": "User",
|
||||
"username": "Username",
|
||||
"users-header": "USERS",
|
||||
"users": "Users",
|
||||
"webhook-time": "Webhook Time",
|
||||
"webhooks-enabled": "Webhooks Enabled",
|
||||
"you-are-not-allowed-to-create-a-user": "You are not allowed to create a user",
|
||||
"you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user",
|
||||
"enable-advanced-content": "Enable Advanced Content",
|
||||
"enable-advanced-content-description": "Enables advanced features like Recipe Scaling, API keys, Webhooks, and Data Management. Don't worry, you can always change this later"
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "translated",
|
||||
"choose-language": "Choose Language",
|
||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||
"read-the-docs": "Read the docs"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-food-example": "Merging {food1} into {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Seed the database with common units based on your local language."
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Seed the database with common labels based on your local language."
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "User Registration",
|
||||
"join-a-group": "Join a Group",
|
||||
"create-a-new-group": "Create a New Group",
|
||||
"provide-registration-token-description": "Please provide the registration token associated with the group that you'd like to join. You'll need to obtain this from an existing group member.",
|
||||
"group-details": "Group Details",
|
||||
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
|
||||
"use-seed-data": "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.",
|
||||
"account-details": "Account Details"
|
||||
},
|
||||
"validation": {
|
||||
"group-name-is-taken": "Group name is taken",
|
||||
"username-is-taken": "Username is taken",
|
||||
"email-is-taken": "Email is taken"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"database-type": "Databasetype",
|
||||
"database-url": "Database URL",
|
||||
"default-group": "Standaardgroep",
|
||||
"demo": "Demo",
|
||||
"demo": "Voorbeeld",
|
||||
"demo-status": "Demo status",
|
||||
"development": "Versies in ontwikkeling",
|
||||
"docs": "Documentatie",
|
||||
@@ -33,7 +33,7 @@
|
||||
"show-assets": "Toon Bijlagen"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Categories",
|
||||
"categories": "Categorieën",
|
||||
"category-created": "Categorie aangemaakt",
|
||||
"category-creation-failed": "Categorie aanmaken mislukt",
|
||||
"category-deleted": "Categorie Verwijderd",
|
||||
@@ -69,7 +69,7 @@
|
||||
"dashboard": "Dashboard",
|
||||
"delete": "Verwijderen",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"download": "Download",
|
||||
"download": "Downloaden",
|
||||
"edit": "Bewerken",
|
||||
"enabled": "Ingeschakeld",
|
||||
"exception": "Uitzondering",
|
||||
@@ -83,7 +83,7 @@
|
||||
"friday": "Vrijdag",
|
||||
"general": "Algemeen",
|
||||
"get": "Haal op",
|
||||
"home": "Home",
|
||||
"home": "Startpagina",
|
||||
"image": "Afbeelding",
|
||||
"image-upload-failed": "Afbeelding uploaden mislukt",
|
||||
"import": "Importeren",
|
||||
@@ -131,10 +131,10 @@
|
||||
"view": "Weergave",
|
||||
"wednesday": "Woensdag",
|
||||
"yes": "Ja",
|
||||
"foods": "Foods",
|
||||
"units": "Units",
|
||||
"back": "Back",
|
||||
"next": "Next"
|
||||
"foods": "Voedsel",
|
||||
"units": "Eenheden",
|
||||
"back": "Terug",
|
||||
"next": "Volgende"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Weet je zeker dat je <b>{groupName}<b/> wilt verwijderen?",
|
||||
@@ -156,8 +156,8 @@
|
||||
"user-group-created": "Gebruikersgroep aangemaakt",
|
||||
"user-group-creation-failed": "Aanmaken gebruikersgroep is mislukt",
|
||||
"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": "Houd mijn recepten privé",
|
||||
"keep-my-recipes-private-description": "Stelt je groep en alle recepten standaard privé in. Je kunt dit later nog wijzigen."
|
||||
}
|
||||
},
|
||||
"meal-plan": {
|
||||
@@ -217,8 +217,8 @@
|
||||
"url-form-hint": "Kopieer en plak een link vanuit jouw favoriete receptwebsite",
|
||||
"view-scraped-data": "Bekijk Opgehaalde Data",
|
||||
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
|
||||
"trim-prefix-description": "Trim first character from each line",
|
||||
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns"
|
||||
"trim-prefix-description": "Knip het eerste teken van elke regel bij",
|
||||
"split-by-numbered-line-description": "Pogingen om een alinea te splitsen door de '1)' of '1.' patronen te gebruiken"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404 Pagina niet gevonden",
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Omschrijving",
|
||||
"disable-amount": "Ingrediënt Hoeveelheden Uitschakelen",
|
||||
"disable-comments": "Reacties Uitschakelen",
|
||||
"edit-scale": "Bewerk Schaal",
|
||||
"fat-content": "Vet",
|
||||
"fiber-content": "Vezels",
|
||||
"grams": "gram",
|
||||
@@ -291,7 +292,7 @@
|
||||
"title": "Titel",
|
||||
"total-time": "Totale Tijd",
|
||||
"unable-to-delete-recipe": "Kan recept niet verwijderen",
|
||||
"no-recipe": "No Recipe"
|
||||
"no-recipe": "Geen Recept"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Geavanceerd Zoeken",
|
||||
@@ -415,7 +416,7 @@
|
||||
"all-recipes": "Recepten",
|
||||
"backups": "Backups",
|
||||
"categories": "Categorieën",
|
||||
"cookbooks": "Cookbooks",
|
||||
"cookbooks": "Koekboeken",
|
||||
"dashboard": "Dashboard",
|
||||
"home-page": "Home Pagina",
|
||||
"manage-users": "Gebruikers",
|
||||
@@ -424,8 +425,8 @@
|
||||
"search": "Zoeken",
|
||||
"site-settings": "Instellingen",
|
||||
"tags": "Labels",
|
||||
"toolbox": "Toolbox",
|
||||
"language": "Language"
|
||||
"toolbox": "Gereedschapskist",
|
||||
"language": "Taal"
|
||||
},
|
||||
"signup": {
|
||||
"error-signing-up": "Fout bij registreren",
|
||||
@@ -448,7 +449,7 @@
|
||||
"untagged-count": "Niet gelabeld {count}"
|
||||
},
|
||||
"tool": {
|
||||
"tools": "Tools"
|
||||
"tools": "Hulpmiddelen"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Beheerder",
|
||||
@@ -467,7 +468,7 @@
|
||||
"error-cannot-delete-super-user": "Fout! Kan supergebruiker niet verwijderen",
|
||||
"existing-password-does-not-match": "Bestaande wachtwoord komt niet overeen",
|
||||
"full-name": "Voor- en achternaam",
|
||||
"invite-only": "Invite Only",
|
||||
"invite-only": "Alleen op uitnodiging",
|
||||
"link-id": "Koppeling ID",
|
||||
"link-name": "Koppeling Naam",
|
||||
"login": "Inloggen",
|
||||
@@ -480,8 +481,8 @@
|
||||
"password-reset-failed": "Wachtwoord resetten is mislukt",
|
||||
"password-updated": "Wachtwoord bijgewerkt",
|
||||
"password": "Wachtwoord",
|
||||
"password-strength": "Password is {strength}",
|
||||
"register": "Register",
|
||||
"password-strength": "Wachtwoord is {strength}",
|
||||
"register": "Registreren",
|
||||
"reset-password": "Wachtwoord Herstellen",
|
||||
"sign-in": "Inloggen",
|
||||
"total-mealplans": "Totaal maaltijdplannen",
|
||||
@@ -505,7 +506,7 @@
|
||||
"webhooks-enabled": "Webhooks ingeschakeld",
|
||||
"you-are-not-allowed-to-create-a-user": "Je hebt geen toestemming om een gebruiker aan te maken",
|
||||
"you-are-not-allowed-to-delete-this-user": "Je hebt geen toestemming om deze gebruiker te verwijderen",
|
||||
"enable-advanced-content": "Enable Advanced Content",
|
||||
"enable-advanced-content": "Schakel geavanceerde instellingen in",
|
||||
"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"
|
||||
},
|
||||
"language-dialog": {
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Beskrivelse",
|
||||
"disable-amount": "Deaktiver Ingrediens-mengde",
|
||||
"disable-comments": "Deaktiver kommentarer",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fett",
|
||||
"fiber-content": "Kostfiber",
|
||||
"grams": "gram",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Opis",
|
||||
"disable-amount": "Wyłącz ilości składników",
|
||||
"disable-comments": "Wyłącz komentarze",
|
||||
"edit-scale": "Edytuj skalę",
|
||||
"fat-content": "Tłuszcz",
|
||||
"fiber-content": "Błonnik",
|
||||
"grams": "gram",
|
||||
@@ -516,18 +517,18 @@
|
||||
"read-the-docs": "Przeczytaj dokumentację"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"seed-data": "Dane przykładowe",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-dialog-text": "Połączenie wybranej żywności połączy źródło żywności i żywność docelową w pojedynczą żywność. Źródło żywności zostanie usunięte, a wszystkie odniesienia do źródłowej żywności zostaną zaktualizowane tak, aby wskazywały na docelową żywność.",
|
||||
"merge-food-example": "Scalanie {food1} do {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
"seed-dialog-text": "Wypełnij bazę daniami na podstawie wybranego lokalnego języka. Akcja ta stworzy ponad 200 zwyczajowych potraw które mogą zostać użyte do organizacji Twojej bazy. Potrawy tłumaczone są przez wysiłek społeczeństwa.",
|
||||
"seed-dialog-warning": "Posiadasz już wartości w bazie. Rozwiązanie problemu z duplikatami leżeć będzie w gestii użytkownika."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Seed the database with common units based on your local language."
|
||||
"seed-dialog-text": "Wypełnij bazę zwyczajowymi jednostkami dla wybranego języka."
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Seed the database with common labels based on your local language."
|
||||
"seed-dialog-text": "Wypełnij bazę zwyczajowymi etytkietami dla wybranego języka."
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
@@ -537,7 +538,7 @@
|
||||
"provide-registration-token-description": "Podaj kod rejestracyjny powiązany z grupą do której chcesz dołączyć. Taki kod uzyskać możesz od użytkownika który przynależy już do owej grupy.",
|
||||
"group-details": "Szczegóły grupy",
|
||||
"group-details-description": "Zanim utworzysz konto musisz stworzyć grupę. Twoja grupa zawierać będzie tylko Ciebie, ale będziesz istniała możlwiość zaproszenia do niej innych. Użytkownicy Twojej grupy mogą współdzielić plany posiłków, listy zakupów, przepisy i więcej!",
|
||||
"use-seed-data": "Use Seed Data",
|
||||
"use-seed-data": "Użyj przykładowych danych",
|
||||
"use-seed-data-description": "Mealie dostarcza zestaw posiłków, jednostek i opisów które mogą zostać użyte do zapełnienia Twojej grupy przydatnymi danymi do ogranizacji Twoich przepisów.",
|
||||
"account-details": "Szczegóły konta"
|
||||
},
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Descrição",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Desativar Comentários",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Gordura",
|
||||
"fiber-content": "Fibras",
|
||||
"grams": "gramas",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Descrição",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Описание",
|
||||
"disable-amount": "Не показывать кол-во ингредиентов",
|
||||
"disable-comments": "Отключить комментарии",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Жиры",
|
||||
"fiber-content": "Клетчатка",
|
||||
"grams": "гр.",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Popis",
|
||||
"disable-amount": "Vypnúť množstvá surovín",
|
||||
"disable-comments": "Vypnúť komentáre",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Tuky",
|
||||
"fiber-content": "Vlákniny",
|
||||
"grams": "gramov",
|
||||
|
||||
550
frontend/lang/messages/sl-SI.json
Normal file
550
frontend/lang/messages/sl-SI.json
Normal file
@@ -0,0 +1,550 @@
|
||||
{
|
||||
"about": {
|
||||
"about": "O programu",
|
||||
"about-mealie": "O programu Mealie",
|
||||
"api-docs": "API dokumentacija",
|
||||
"api-port": "API vrata",
|
||||
"application-mode": "Način aplikacije",
|
||||
"database-type": "Tip podatkovne baze",
|
||||
"database-url": "URL naslov podatkovne baze",
|
||||
"default-group": "Privzeta skupina",
|
||||
"demo": "Testno",
|
||||
"demo-status": "Status testa",
|
||||
"development": "Razvoj",
|
||||
"docs": "Dokumentacija",
|
||||
"download-log": "Prenesi dnevniške zapise",
|
||||
"download-recipe-json": "Zadnji prebran JSON",
|
||||
"github": "Github",
|
||||
"log-lines": "Vrstice",
|
||||
"not-demo": "Ni testno",
|
||||
"portfolio": "Portfelj",
|
||||
"production": "Produkcija",
|
||||
"support": "Podpora",
|
||||
"version": "Verzija"
|
||||
},
|
||||
"asset": {
|
||||
"assets": "Viri",
|
||||
"code": "Koda",
|
||||
"file": "Datoteka",
|
||||
"image": "Slika",
|
||||
"new-asset": "Nov vir",
|
||||
"pdf": "PDF",
|
||||
"recipe": "Recept",
|
||||
"show-assets": "Prikaži vire"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Kategorije",
|
||||
"category-created": "Kategorija kreirana",
|
||||
"category-creation-failed": "Napaka pri kreiranju kategorije",
|
||||
"category-deleted": "Kategorija izbrisana",
|
||||
"category-deletion-failed": "Napaka pri brisanju kategorije",
|
||||
"category-filter": "Filter kategorije",
|
||||
"category-update-failed": "Napaka pri posodobitvi kategorije",
|
||||
"category-updated": "Kategorija posodobljena",
|
||||
"uncategorized-count": "Nekategorizirano {count}"
|
||||
},
|
||||
"events": {
|
||||
"apprise-url": "Apprise URL",
|
||||
"database": "Baza podatkov",
|
||||
"delete-event": "Zbriši dogodek",
|
||||
"new-notification-form-description": "Mealie uporablja Apprise knjižnico za kreiranje obvestil. Omogoča več različnih servisov za uporabo obvestil. Preglejte njihovo wiki stran, za bolj natančen vodič, kako izdelati URL za vaš servis. Če je na voljo, so za vaš izbran servis obvestil, na voljo tudi dodane možnosti.",
|
||||
"new-version": "Na voljo je nova verzija!",
|
||||
"notification": "Obvestila",
|
||||
"refresh": "Osveži",
|
||||
"scheduled": "Načrtovano",
|
||||
"something-went-wrong": "Nekaj je šlo narobe!",
|
||||
"subscribed-events": "Naročeni dogodki",
|
||||
"test-message-sent": "Testno sporočilo je bilo poslano"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Prekliči",
|
||||
"clear": "Počisti",
|
||||
"close": "Zapri",
|
||||
"confirm": "Potrdi",
|
||||
"confirm-delete-generic": "Ali ste prepričani, da želite to izbrisati?",
|
||||
"copied": "Kopirano",
|
||||
"create": "Ustvari",
|
||||
"created": "Ustvarjeno",
|
||||
"custom": "Po meri",
|
||||
"dashboard": "Nadzorna plošča",
|
||||
"delete": "Izbriši",
|
||||
"disabled": "Onemogočeno",
|
||||
"download": "Prenesi",
|
||||
"edit": "Uredi",
|
||||
"enabled": "Omogočeno",
|
||||
"exception": "Izjema",
|
||||
"failed-count": "Spodletelo: {count}",
|
||||
"failure-uploading-file": "Napaka pri nalaganju datoteke",
|
||||
"favorites": "Priljubljene",
|
||||
"field-required": "Obvezno polje",
|
||||
"file-folder-not-found": "Ne najdem datoteke/mape",
|
||||
"file-uploaded": "Datoteka naložena",
|
||||
"filter": "Filter",
|
||||
"friday": "Petek",
|
||||
"general": "Splošno",
|
||||
"get": "Pridobi",
|
||||
"home": "Doma",
|
||||
"image": "Slika",
|
||||
"image-upload-failed": "Nalaganje slike ni uspelo",
|
||||
"import": "Uvozi",
|
||||
"json": "JSON",
|
||||
"keyword": "Ključna beseda",
|
||||
"link-copied": "Povezava kopirana",
|
||||
"loading-recipes": "Nalagam recepte",
|
||||
"monday": "Ponedeljek",
|
||||
"name": "Ime",
|
||||
"new": "Novo",
|
||||
"no": "Ne",
|
||||
"no-recipe-found": "Ne najdem recepta",
|
||||
"ok": "V redu",
|
||||
"options": "Možnosti:",
|
||||
"print": "Natisni",
|
||||
"random": "Naključno",
|
||||
"rating": "Ocena",
|
||||
"recent": "Nedavno",
|
||||
"recipe": "Recept",
|
||||
"recipes": "Recepti",
|
||||
"rename-object": "Preimenuj {0}",
|
||||
"reset": "Ponastavi",
|
||||
"saturday": "Sobota",
|
||||
"save": "Shrani",
|
||||
"settings": "Nastavitve",
|
||||
"share": "Deli",
|
||||
"shuffle": "Naključno",
|
||||
"sort": "Razvrsti",
|
||||
"sort-alphabetically": "Po abecedi",
|
||||
"status": "Stanje",
|
||||
"submit": "Pošlji",
|
||||
"success-count": "Uspešno: {count}",
|
||||
"sunday": "Nedelja",
|
||||
"templates": "Predloge:",
|
||||
"test": "Test",
|
||||
"themes": "Teme",
|
||||
"thursday": "Četrtek",
|
||||
"token": "Žeton",
|
||||
"tuesday": "Torek",
|
||||
"type": "Tip",
|
||||
"update": "Posodobi",
|
||||
"updated": "Posodobljen",
|
||||
"upload": "Naloži",
|
||||
"url": "URL",
|
||||
"view": "Poglej",
|
||||
"wednesday": "Sreda",
|
||||
"yes": "Da",
|
||||
"foods": "Hrana",
|
||||
"units": "Enote",
|
||||
"back": "Nazaj",
|
||||
"next": "Naprej"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Ste prepričani, da želite izbrisati <b>{groupName}<b/>?",
|
||||
"cannot-delete-default-group": "Privzete skupine ni mogoče izbrisati",
|
||||
"cannot-delete-group-with-users": "Ne morem izbrisati skupine z uporabniki",
|
||||
"confirm-group-deletion": "Potrdi brisanje skupine",
|
||||
"create-group": "Ustvari skupino",
|
||||
"error-updating-group": "Napaka pri posodobitvi skupine",
|
||||
"group": "Skupina",
|
||||
"group-deleted": "Skupina izbrisana",
|
||||
"group-deletion-failed": "Napaka pri brisanju skupine",
|
||||
"group-id-with-value": "ID skupine: {groupID}",
|
||||
"group-name": "Ime skupine",
|
||||
"group-not-found": "Ne najdem skupine",
|
||||
"group-with-value": "Skupina: {groupID}",
|
||||
"groups": "Skupine",
|
||||
"manage-groups": "Upravljanje skupin",
|
||||
"user-group": "Uporabniška skupina",
|
||||
"user-group-created": "Uporabniška skupina je ustvarjena",
|
||||
"user-group-creation-failed": "Napaka pri ustvarjanju skupine",
|
||||
"settings": {
|
||||
"keep-my-recipes-private": "Moji recepti naj bodo privatni",
|
||||
"keep-my-recipes-private-description": "Nastavi vaše skupine in vse recepte privzeto kot privatni. Kasneje lahko to vedno spremenite."
|
||||
}
|
||||
},
|
||||
"meal-plan": {
|
||||
"create-a-new-meal-plan": "Izdelaj nov načrt obrokov",
|
||||
"dinner-this-week": "Večerje tega tedna",
|
||||
"dinner-today": "Današnja večerja",
|
||||
"dinner-tonight": "DANAŠNJA VEČERJA",
|
||||
"edit-meal-plan": "Uredi planer obrokov",
|
||||
"end-date": "Končni datum",
|
||||
"group": "Skupina (beta)",
|
||||
"main": "Glavna jed",
|
||||
"meal-planner": "Načrtovanje obrokov",
|
||||
"meal-plans": "Načrti obrokov",
|
||||
"mealplan-categories": "KATEGORIJE NAČRTA OBROKA",
|
||||
"mealplan-created": "Načrt obroka je ustvarjen",
|
||||
"mealplan-creation-failed": "Napaka pri ustvarjanju načrta obroka",
|
||||
"mealplan-deleted": "Načrt obroka je izbrisan",
|
||||
"mealplan-deletion-failed": "Napaka pri izbrisu načrta obroka",
|
||||
"mealplan-settings": "Nastavitve načrta obroka",
|
||||
"mealplan-update-failed": "Napaka pri posodobitvi načrta obroka",
|
||||
"mealplan-updated": "Načrt obroka je posodobljen",
|
||||
"no-meal-plan-defined-yet": "Za danes ni definiranjega načrta obroka",
|
||||
"no-meal-planned-for-today": "Za danes ni planiranega načrta obroka",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Samo recepti v teh kategorija bodo uporabljeni v načrtu obroka",
|
||||
"planner": "Načrtovalec",
|
||||
"quick-week": "Hiter pogled tedna",
|
||||
"side": "Priloga",
|
||||
"sides": "Priloge",
|
||||
"start-date": "Datum začetka"
|
||||
},
|
||||
"migration": {
|
||||
"chowdown": {
|
||||
"description": "Migriraj podatke iz Chowdown-a",
|
||||
"title": "Chowdown"
|
||||
},
|
||||
"migration-data-removed": "Migrirani podatki so odstranjeni",
|
||||
"nextcloud": {
|
||||
"description": "Migriraj podatke iz Nextcloud Cookbook",
|
||||
"title": "Nextcloud Cookbook"
|
||||
},
|
||||
"no-migration-data-available": "Na volji ni migracijskih podatkov",
|
||||
"recipe-migration": "Migracija recepta"
|
||||
},
|
||||
"new-recipe": {
|
||||
"bulk-add": "Množično dodajanje",
|
||||
"error-details": "Samo spletne strani, ki vsebujejo Id+json ali mikro podatke, se lahko uvozijo v Mealie. Večina večjih spletnih strani z recepti že podpirajo to podatkovno strukturo. Če se vaša stran ne uvozi, v log datoteki pa so json podatki, prosim odprite težavo na github-u z URL naslovom in podatki.",
|
||||
"error-title": "Kot kaže nisem ničesar našel",
|
||||
"from-url": "Uvozi recept",
|
||||
"github-issues": "GitHub težave",
|
||||
"google-ld-json-info": "Google Id+json podatki",
|
||||
"must-be-a-valid-url": "Mora biti veljaven URL",
|
||||
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Prilepi podatke recepta. Vsaka vrsta bo obravnavana kot element v seznamu",
|
||||
"recipe-markup-specification": "Markup specifikacija recepta",
|
||||
"recipe-url": "URL recepta",
|
||||
"upload-a-recipe": "Naloži recept",
|
||||
"upload-individual-zip-file": "Naloži posamezno .zip datoteko, izvoženo iz druge Mealie namestitve.",
|
||||
"url-form-hint": "Kopiraj in prilepi povezavo iz vaše priljubljene strani z recepti",
|
||||
"view-scraped-data": "Poglej postrgane podatke",
|
||||
"trim-whitespace-description": "Poreži začetne in končne presledke, kot tudi prazne vrstice",
|
||||
"trim-prefix-description": "Poreži prvi znak v vsaki vrstici",
|
||||
"split-by-numbered-line-description": "Poskušaj razdeliti odstavek z ujemanjem '1)' ali '1.' vzorcev"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404 strani ni mogoče najti",
|
||||
"all-recipes": "Vsi recepti",
|
||||
"new-page-created": "Ustvarjena nova stran",
|
||||
"page": "Stran",
|
||||
"page-creation-failed": "Ustvarjanje strani ni uspelo",
|
||||
"page-deleted": "Stran izbirsana",
|
||||
"page-deletion-failed": "Napaka pri brisanju strani",
|
||||
"page-update-failed": "Napaka pri posodobitvi strani",
|
||||
"page-updated": "Stran je posodobljena",
|
||||
"pages-update-failed": "Napaka pri posodobitvi strani",
|
||||
"pages-updated": "Strani posodobljene"
|
||||
},
|
||||
"recipe": {
|
||||
"add-key": "Dodajte ključ",
|
||||
"add-to-favorites": "Dodajte med priljubljene",
|
||||
"api-extras": "API dodatno",
|
||||
"calories": "Kalorije",
|
||||
"calories-suffix": "kalorije",
|
||||
"carbohydrate-content": "Ogljikovi hidrati",
|
||||
"categories": "Kategorije",
|
||||
"comment-action": "Pripomba",
|
||||
"comments": "Pripombe",
|
||||
"delete-confirmation": "Ali želite izbrisati ta recept?",
|
||||
"delete-recipe": "Izbriši recept",
|
||||
"description": "Opis",
|
||||
"disable-amount": "Onemogoči prikaz količin sestavin",
|
||||
"disable-comments": "Onemogoči komentarje",
|
||||
"edit-scale": "Uredi stran",
|
||||
"fat-content": "Maščoba",
|
||||
"fiber-content": "Vlakna",
|
||||
"grams": "grami",
|
||||
"ingredient": "Sestavina",
|
||||
"ingredients": "Sestavine",
|
||||
"insert-section": "Vstavi odsek",
|
||||
"instructions": "Navodila",
|
||||
"key-name-required": "Obvezen vnos imena ključa",
|
||||
"landscape-view-coming-soon": "Ležeči pogled",
|
||||
"milligrams": "miligrami",
|
||||
"new-key-name": "Novo ime ključa",
|
||||
"no-white-space-allowed": "Presledki niso dovoljeni",
|
||||
"note": "Zapisek",
|
||||
"nutrition": "Prehrana",
|
||||
"object-key": "Ključ objekta",
|
||||
"object-value": "Vrednost objekta",
|
||||
"original-url": "Izvorni URL",
|
||||
"perform-time": "Čas kuhanja",
|
||||
"prep-time": "Čas priprav",
|
||||
"protein-content": "Beljakovine",
|
||||
"public-recipe": "Javen recept",
|
||||
"recipe-created": "Recept je ustvarjen",
|
||||
"recipe-creation-failed": "Napaka pri ustvarjanju recepta",
|
||||
"recipe-deleted": "Recept je izbrisan",
|
||||
"recipe-image": "Slika recepta",
|
||||
"recipe-image-updated": "Slika recepta je posodobljena",
|
||||
"recipe-name": "Ime recepta",
|
||||
"recipe-settings": "Nastavitve recepta",
|
||||
"recipe-update-failed": "Napaka pri posodobitvi recepta",
|
||||
"recipe-updated": "Recept je posodobljen",
|
||||
"remove-from-favorites": "Odstrani iz priljubljenih",
|
||||
"remove-section": "Odstrani odsek",
|
||||
"save-recipe-before-use": "Shrani recept pred uporabo",
|
||||
"section-title": "Naslov odseka",
|
||||
"servings": "Porcija",
|
||||
"share-recipe-message": "Rad bi delil moj {0} recept z vami.",
|
||||
"show-nutrition-values": "Prikaži vrednosti prehrane",
|
||||
"sodium-content": "Natrij",
|
||||
"step-index": "Korak: {step}",
|
||||
"sugar-content": "Sladkor",
|
||||
"title": "Naslov",
|
||||
"total-time": "Skupni čas",
|
||||
"unable-to-delete-recipe": "Ne morem izbrisati recepta",
|
||||
"no-recipe": "Ni recepta"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Napredno iskanje",
|
||||
"and": "in",
|
||||
"exclude": "Izvzemi",
|
||||
"include": "Vključi",
|
||||
"max-results": "Največ rezultatov",
|
||||
"or": "Ali",
|
||||
"results": "Rezultati",
|
||||
"search": "Iskanje",
|
||||
"search-mealie": "Išči po Mealie (pritisni /)",
|
||||
"search-placeholder": "Išči...",
|
||||
"tag-filter": "Filter oznak"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Dodaj novo temo",
|
||||
"admin-settings": "Administratorske nastavitve",
|
||||
"backup": {
|
||||
"backup-created-at-response-export_path": "Varnostna kopija ustvarjena v {path}",
|
||||
"backup-deleted": "Varnostna kopija je izbrisana",
|
||||
"backup-tag": "Oznaka varnostne kopije",
|
||||
"create-heading": "Izdelaj varnostno kopijo",
|
||||
"delete-backup": "Izbriši varnostno kopijo",
|
||||
"error-creating-backup-see-log-file": "Napaka pri izdelovanju varnostni kopije. Preveri strežniške datoteke",
|
||||
"full-backup": "Popolna varnostna kopija",
|
||||
"import-summary": "Povzetek uvoza",
|
||||
"partial-backup": "Delna varnostna kopija",
|
||||
"unable-to-delete-backup": "Napaka pri izbrisu varnostne kopije."
|
||||
},
|
||||
"backup-and-exports": "Varnostne kopije",
|
||||
"change-password": "Spremeni geslo",
|
||||
"current": "Verzija:",
|
||||
"custom-pages": "Strani po meri",
|
||||
"edit-page": "Uredi stran",
|
||||
"events": "Dogodki",
|
||||
"first-day-of-week": "Prvi dan v tednu",
|
||||
"group-settings-updated": "Nastavitve skupine so bile posodobljene",
|
||||
"homepage": {
|
||||
"all-categories": "Vse kategorije",
|
||||
"card-per-section": "Kartica po odsekih",
|
||||
"home-page": "Domača stran",
|
||||
"home-page-sections": "Odseki domače strani",
|
||||
"show-recent": "Pokaži nedavne"
|
||||
},
|
||||
"language": "Jezik",
|
||||
"latest": "Najnovejše",
|
||||
"local-api": "Lokalni API",
|
||||
"locale-settings": "Področne nastavitve",
|
||||
"migrations": "Migracija",
|
||||
"new-page": "Nova stran",
|
||||
"notify": "Obvesti",
|
||||
"organize": "Organiziraj",
|
||||
"page-name": "Ime strani",
|
||||
"pages": "Strani",
|
||||
"profile": "Profil",
|
||||
"remove-existing-entries-matching-imported-entries": "Odstrani vrednosti, ki se ujemajo z vnesenimi vrednostmi",
|
||||
"set-new-time": "Nastavi nov čas",
|
||||
"settings-update-failed": "Napaka pri posodobitvi nastavitev",
|
||||
"settings-updated": "Nastavitve so posodobljene",
|
||||
"site-settings": "Nastavitve strani",
|
||||
"theme": {
|
||||
"accent": "Naglas",
|
||||
"dark": "Temno",
|
||||
"default-to-system": "Privzeto glede na sistem",
|
||||
"error": "Napaka",
|
||||
"error-creating-theme-see-log-file": "Napaka pri izdelavi teme. Preveri dnevniško datoteko.",
|
||||
"error-deleting-theme": "Napaka pri brisanju teme",
|
||||
"error-updating-theme": "Napaka pri posodobitvi teme",
|
||||
"info": "Informacije",
|
||||
"light": "Svetlo",
|
||||
"primary": "Primarno",
|
||||
"secondary": "Sekundarno",
|
||||
"success": "Uspešno",
|
||||
"switch-to-dark-mode": "Preklopi na temni način",
|
||||
"switch-to-light-mode": "Preklopi na svetli način",
|
||||
"theme-deleted": "Tema je bila izbrisana",
|
||||
"theme-name": "Ime teme",
|
||||
"theme-name-is-required": "Ime teme je obvezen podatek.",
|
||||
"theme-saved": "Tema je shranjena",
|
||||
"theme-updated": "Tema posodobljena",
|
||||
"warning": "Opozorilo"
|
||||
},
|
||||
"token": {
|
||||
"active-tokens": "AKTIVNI ŽETONI",
|
||||
"api-token": "API žeton",
|
||||
"api-tokens": "API žetoni",
|
||||
"copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Kopiraj žeton za uporabo v zunanji aplikaciji. Ta žeton kasneje ne bo več viden.",
|
||||
"create-an-api-token": "Ustvari nov API žeton",
|
||||
"token-name": "Ime žetona"
|
||||
},
|
||||
"toolbox": {
|
||||
"assign-all": "Dodeli vse",
|
||||
"bulk-assign": "Množično dodeljevanje",
|
||||
"new-name": "Novo ime",
|
||||
"no-unused-items": "Ni neuporabljeni elementov",
|
||||
"recipes-affected": "Ne vpliva na recepte|Vpliva na en recept|Vpliva na {count} recepte",
|
||||
"remove-unused": "Odstrani neuporabljene",
|
||||
"title-case-all": "Naziv z veliko",
|
||||
"toolbox": "Orodjarna",
|
||||
"unorganized": "Neorganizirano"
|
||||
},
|
||||
"webhooks": {
|
||||
"test-webhooks": "Test Webhook-ov",
|
||||
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Spodnji URL naslovi bodo prejeli webhook-e s podatki receptov za načrt obrokov na načrtovani dan. Trenutno se bodo webhooki-i izvedli ob",
|
||||
"webhook-url": "Webhook URL",
|
||||
"webhooks-caps": "WEBHOOKS",
|
||||
"webhooks": "Webbhook-i"
|
||||
}
|
||||
},
|
||||
"shopping-list": {
|
||||
"all-lists": "Vsi seznami",
|
||||
"create-shopping-list": "Ustvarite nakupovalni seznam",
|
||||
"from-recipe": "Iz recepta",
|
||||
"list-name": "Ime seznama",
|
||||
"new-list": "Nov seznam",
|
||||
"quantity": "Količina: {0}",
|
||||
"shopping-list": "Nakupovalni seznam",
|
||||
"shopping-lists": "Nakupovalni seznami"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Vsi recepti",
|
||||
"backups": "Varnostne kopije",
|
||||
"categories": "Kategorije",
|
||||
"cookbooks": "Kuharska knjiga",
|
||||
"dashboard": "Nadzorna plošča",
|
||||
"home-page": "Domača stran",
|
||||
"manage-users": "Uporabniki",
|
||||
"migrations": "Migracija",
|
||||
"profile": "Profil",
|
||||
"search": "Iskanje",
|
||||
"site-settings": "Nastavitve strani",
|
||||
"tags": "Značke",
|
||||
"toolbox": "Orodjarna",
|
||||
"language": "Jezik"
|
||||
},
|
||||
"signup": {
|
||||
"error-signing-up": "Napaka pri vpisu",
|
||||
"sign-up": "Vpis",
|
||||
"sign-up-link-created": "Povezava vpisa je ustvarjena",
|
||||
"sign-up-link-creation-failed": "Napaka pri kreiranju povezave vpisa",
|
||||
"sign-up-links": "Vpisne povezave",
|
||||
"sign-up-token-deleted": "Vpisni ključ je izbrisan",
|
||||
"sign-up-token-deletion-failed": "Napaka pri izbrisu vpisnega ključa",
|
||||
"welcome-to-mealie": "Dobrodobšli v Meali! Če želite postati uporabnik te namestitve, potrebujete imeti veljavno povezavo povabila. Če še niste prejeli povabila se ne boste mogli vpisati. Če želite prejeti povabilo, kontaktiraje administratorja strani."
|
||||
},
|
||||
"tag": {
|
||||
"tag-created": "Značka kreirana",
|
||||
"tag-creation-failed": "Napaka pri kreiranju značke",
|
||||
"tag-deleted": "Značna izbrisana",
|
||||
"tag-deletion-failed": "Napaka pri izbrisu značke",
|
||||
"tag-update-failed": "Napaka pri posodobitve značke",
|
||||
"tag-updated": "Značka posodobljena",
|
||||
"tags": "Značke",
|
||||
"untagged-count": "Brez značke {count}"
|
||||
},
|
||||
"tool": {
|
||||
"tools": "Orodja"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Administrator",
|
||||
"are-you-sure-you-want-to-delete-the-link": "Ste prepričani, da želite izbrisati povezavo <b>{link}<b/>?",
|
||||
"are-you-sure-you-want-to-delete-the-user": "Ste prepričani, da želite izbrisati uporabnika <b>{activeName}<b/> ID: {activeId}<b/>?",
|
||||
"confirm-link-deletion": "Potrdite izbris povezave",
|
||||
"confirm-password": "Potrdite geslo",
|
||||
"confirm-user-deletion": "Potrdite izbris uporabnika",
|
||||
"could-not-validate-credentials": "Ne morem potrditi poverilnic",
|
||||
"create-link": "Ustvarite povezavo",
|
||||
"create-user": "Ustvarite uporabnika",
|
||||
"current-password": "Trenutno geslo",
|
||||
"e-mail-must-be-valid": "E-mail mora biti veljaven",
|
||||
"edit-user": "Uredi uporabnika",
|
||||
"email": "E-mail",
|
||||
"error-cannot-delete-super-user": "Napaka! Ne morem izbrisati super uporabnika",
|
||||
"existing-password-does-not-match": "Ponovljeno geslo ne ustreza prvemu",
|
||||
"full-name": "Polno ime",
|
||||
"invite-only": "Samo na povabilo",
|
||||
"link-id": "ID povezave",
|
||||
"link-name": "Ime povezave",
|
||||
"login": "Prijava",
|
||||
"logout": "Odjava",
|
||||
"manage-users": "Upravljanje uporabnikov",
|
||||
"new-password": "Novo geslo",
|
||||
"new-user": "Nov uporabnik",
|
||||
"password-has-been-reset-to-the-default-password": "Geslo je bila nastavljeno na privzeto geslo",
|
||||
"password-must-match": "Gesli se morata ujemati",
|
||||
"password-reset-failed": "Napaka pri ponastavitvi gesla",
|
||||
"password-updated": "Geslo posodobljeno",
|
||||
"password": "Geslo",
|
||||
"password-strength": "Moč gesla {strength}",
|
||||
"register": "Registriraj se",
|
||||
"reset-password": "Ponastavi geslo",
|
||||
"sign-in": "Vpis",
|
||||
"total-mealplans": "Skupaj načrtov obrokov",
|
||||
"total-users": "Skupaj uporabnikov",
|
||||
"upload-photo": "Naloži fotografijo",
|
||||
"use-8-characters-or-more-for-your-password": "Uporabi vsaj 8 znakov ali več za vaše geslo",
|
||||
"user-created": "Uporabnik ustvarjen",
|
||||
"user-creation-failed": "Napaka pri ustvarjanju uporabnika",
|
||||
"user-deleted": "Uporabnik izbrisan",
|
||||
"user-id-with-value": "ID uporabnika: {id}",
|
||||
"user-id": "ID uporabnika",
|
||||
"user-password": "Uporabniško geslo",
|
||||
"user-successfully-logged-in": "Uporabnik uspešno prijavljen",
|
||||
"user-update-failed": "Napaka pri posodobitvi uporabnika",
|
||||
"user-updated": "Uporabnik posodobljen",
|
||||
"user": "Uporabnik",
|
||||
"username": "Uporabniško ime",
|
||||
"users-header": "UPORABNIKI",
|
||||
"users": "Uporabniki",
|
||||
"webhook-time": "Webhook čas",
|
||||
"webhooks-enabled": "Webhook-i omogočeni",
|
||||
"you-are-not-allowed-to-create-a-user": "Nimate pravic za ustvarjanje uporabnika",
|
||||
"you-are-not-allowed-to-delete-this-user": "Nimate pravic za izbris tega uporabnika",
|
||||
"enable-advanced-content": "Omogoči napredne nastavitve",
|
||||
"enable-advanced-content-description": "Omogoči napredne nastavitve kot so spreminjanje merila receptov, API ključi, Webhook in upravljanje s podatki. Ne skrbite, vse to lahko spremenite kasneje"
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "prevedeno",
|
||||
"choose-language": "Izberite jezik",
|
||||
"select-description": "Izberite jezik Meali uporabniškega vmesnika. Nastavitve so v uporabi samo za vas in ne za ostale uporabnike.",
|
||||
"how-to-contribute-description": "Če kaj ni prevedeno, napačno prevedeno ali vaš jezik v celoti manjka iz seznama? Poglejte si {read-the-docs-link} kako lahko pomagate!",
|
||||
"read-the-docs": "Preberite dokumentacijo"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Napolni podatke",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Združitev izbranih jedi bo združila izvorno jed in ciljno jed v eno samo jed. Izvorna jed bo izbrisana in vse povezave na izvorno jed, bodo po novem kazale na ciljno jed.",
|
||||
"merge-food-example": "Združujem {food1} v {food2}",
|
||||
"seed-dialog-text": "Napolni podatkovno bazo s jedmi, ki izvirajo iz vašega lokalnega jezika. To bo kreiralo 200+ običajnih jedi, ki se lahko uporabijo za organizacijo vaše podatkovne baze. Jedi so prevedene s pomočjo skupnosti.",
|
||||
"seed-dialog-warning": "Nekatere elemente že imate v podatkovni bazi. To opravilo ne bo upoštevalo dvojnikov in jih boste morali sami ročno upravljati."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Napolni podatkovno bazo z običajnimi enotami, glede na vaš lokalni jezik."
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Napolni podatkovno bazi s običajnimi oznakami, glede na vaš lokalni jezik."
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "Registracija uporabnika",
|
||||
"join-a-group": "Pridruži se skupini",
|
||||
"create-a-new-group": "Ustvarite novo skupino",
|
||||
"provide-registration-token-description": "Prosim pridobite registracijski žeton povezan s skupino, ki se ji želite pridružiti. Pridobiti ga boste morali od obstoječega člana skupine.",
|
||||
"group-details": "Detajli skupine",
|
||||
"group-details-description": "Preden kreirate račun, morate kreirati skupino. V skupini boste sprva samo vi, vendar imate možnost povabiti še ostale člane. Člani v vaši skupini lahko delijo načrte obrokov, nakupovalne sezname, recepte in še več!",
|
||||
"use-seed-data": "Uporabi privzete podatke",
|
||||
"use-seed-data-description": "Meali vključuje zbirko jedi, enot in oznak, ki se lahko uporabno uporabijo v vaši skupini za organizacijo receptov.",
|
||||
"account-details": "Podatki o računu"
|
||||
},
|
||||
"validation": {
|
||||
"group-name-is-taken": "Ime skupine je že zasedeno",
|
||||
"username-is-taken": "Uporabniško ime zasedeno",
|
||||
"email-is-taken": "E-mail je zaseden"
|
||||
}
|
||||
}
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"database-url": "Databas URL",
|
||||
"default-group": "Standardgrupp",
|
||||
"demo": "Demo",
|
||||
"demo-status": "Demo Status",
|
||||
"demo-status": "Status för demo",
|
||||
"development": "Utveckling",
|
||||
"docs": "Dokumentation",
|
||||
"download-log": "Ladda ner logg",
|
||||
@@ -33,7 +33,7 @@
|
||||
"show-assets": "Visa tillgångar"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Categories",
|
||||
"categories": "Kategorier",
|
||||
"category-created": "Kategori skapad",
|
||||
"category-creation-failed": "Kategori gick inte att skapa",
|
||||
"category-deleted": "Kategori raderad",
|
||||
@@ -66,7 +66,7 @@
|
||||
"create": "Skapa",
|
||||
"created": "Skapad",
|
||||
"custom": "Anpassad",
|
||||
"dashboard": "Dashboard",
|
||||
"dashboard": "Startsida",
|
||||
"delete": "Ta bort",
|
||||
"disabled": "Inaktiverad",
|
||||
"download": "Ladda ner",
|
||||
@@ -131,10 +131,10 @@
|
||||
"view": "Visa",
|
||||
"wednesday": "Onsdag",
|
||||
"yes": "Ja",
|
||||
"foods": "Foods",
|
||||
"units": "Units",
|
||||
"back": "Back",
|
||||
"next": "Next"
|
||||
"foods": "Mat",
|
||||
"units": "Enheter",
|
||||
"back": "Tillbaka",
|
||||
"next": "Nästa"
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Är du säker på att du vill radera <b>{groupName}<b/>?",
|
||||
@@ -156,8 +156,8 @@
|
||||
"user-group-created": "Användargrupp skapad",
|
||||
"user-group-creation-failed": "Gruppen gick inte att skapa",
|
||||
"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": "Behåll mina recept privata",
|
||||
"keep-my-recipes-private-description": "Sätter din grupp och alla recept till privata som förval. Du kan alltid ändra detta senare."
|
||||
}
|
||||
},
|
||||
"meal-plan": {
|
||||
@@ -216,9 +216,9 @@
|
||||
"upload-individual-zip-file": "Ladda upp en individuell .zip-fil som exporteras från en annan Mealie-instans.",
|
||||
"url-form-hint": "Kopiera och klistra in en länk från din favorit recept webbplats",
|
||||
"view-scraped-data": "Visa skrotade data",
|
||||
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
|
||||
"trim-prefix-description": "Trim first character from each line",
|
||||
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns"
|
||||
"trim-whitespace-description": "Ta bort inledande och avslutande blanksteg samt tomma rader",
|
||||
"trim-prefix-description": "Ta bort första tecknet från varje rad",
|
||||
"split-by-numbered-line-description": "Försök att dela ett stycke genom att matcha mönstret '1)' eller '1.'"
|
||||
},
|
||||
"page": {
|
||||
"404-page-not-found": "404 sidan hittades inte",
|
||||
@@ -236,7 +236,7 @@
|
||||
"recipe": {
|
||||
"add-key": "Lägg till nyckel",
|
||||
"add-to-favorites": "Lägg till i favoriter",
|
||||
"api-extras": "API Extras",
|
||||
"api-extras": "API-tillägg",
|
||||
"calories": "Kalorier",
|
||||
"calories-suffix": "kalorier",
|
||||
"carbohydrate-content": "Kolhydrat",
|
||||
@@ -248,8 +248,9 @@
|
||||
"description": "Beskrivning",
|
||||
"disable-amount": "Inaktivera ingredienser mängder",
|
||||
"disable-comments": "Inaktivera kommentarer",
|
||||
"edit-scale": "Ändra skala",
|
||||
"fat-content": "Fett",
|
||||
"fiber-content": "Fiber",
|
||||
"fiber-content": "Fibrer",
|
||||
"grams": "gram",
|
||||
"ingredient": "Ingrediens",
|
||||
"ingredients": "Ingredienser",
|
||||
@@ -291,7 +292,7 @@
|
||||
"title": "Titel",
|
||||
"total-time": "Total tid",
|
||||
"unable-to-delete-recipe": "Gick inte att radera receptet",
|
||||
"no-recipe": "No Recipe"
|
||||
"no-recipe": "Inget recept"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Avancerad sökning",
|
||||
@@ -360,7 +361,7 @@
|
||||
"error-creating-theme-see-log-file": "Fel vid skapande av tema. Se loggfil.",
|
||||
"error-deleting-theme": "Fel vid borttagning av tema",
|
||||
"error-updating-theme": "Gick inte att uppdatera tema",
|
||||
"info": "Info",
|
||||
"info": "Information",
|
||||
"light": "Ljust",
|
||||
"primary": "Primär",
|
||||
"secondary": "Sekundär",
|
||||
@@ -396,7 +397,7 @@
|
||||
"webhooks": {
|
||||
"test-webhooks": "Testa Webhooks",
|
||||
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Följande webbadresser kommer att mottaga webhooks med receptdata för dagens planerade måltid. För närvarande körs webhooks klockan",
|
||||
"webhook-url": "Webhook URL",
|
||||
"webhook-url": "Webhook-URL",
|
||||
"webhooks-caps": "WEBHOOKS",
|
||||
"webhooks": "Webhooks"
|
||||
}
|
||||
@@ -413,9 +414,9 @@
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Alla recept",
|
||||
"backups": "Backups",
|
||||
"backups": "Säkerhetskopior",
|
||||
"categories": "Kategorier",
|
||||
"cookbooks": "Cookbooks",
|
||||
"cookbooks": "Kokböcker",
|
||||
"dashboard": "Startsida",
|
||||
"home-page": "Startsida",
|
||||
"manage-users": "Användare",
|
||||
@@ -425,7 +426,7 @@
|
||||
"site-settings": "Inställningar",
|
||||
"tags": "Taggar",
|
||||
"toolbox": "Verktygslåda",
|
||||
"language": "Language"
|
||||
"language": "Språk"
|
||||
},
|
||||
"signup": {
|
||||
"error-signing-up": "Fel vid registreringen",
|
||||
@@ -448,7 +449,7 @@
|
||||
"untagged-count": "Otaggad {count}"
|
||||
},
|
||||
"tool": {
|
||||
"tools": "Tools"
|
||||
"tools": "Verktyg"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Administratör",
|
||||
@@ -467,10 +468,10 @@
|
||||
"error-cannot-delete-super-user": "Fel! Det går inte att ta bort superanvändare",
|
||||
"existing-password-does-not-match": "Befintligt lösenord matchar inte",
|
||||
"full-name": "Fullständigt namn",
|
||||
"invite-only": "Invite Only",
|
||||
"invite-only": "Endast inbjudna",
|
||||
"link-id": "Länk ID",
|
||||
"link-name": "Länk namn",
|
||||
"login": "Login",
|
||||
"login": "Logga in",
|
||||
"logout": "Logga ut",
|
||||
"manage-users": "Hantera användare",
|
||||
"new-password": "Nytt lösenord",
|
||||
@@ -480,8 +481,8 @@
|
||||
"password-reset-failed": "Återställningen av lösenordet misslyckades",
|
||||
"password-updated": "Lösenord uppdaterat",
|
||||
"password": "Lösenord",
|
||||
"password-strength": "Password is {strength}",
|
||||
"register": "Register",
|
||||
"password-strength": "Lösenordsstyrka {strength}",
|
||||
"register": "Registrering",
|
||||
"reset-password": "Ändra lösenord",
|
||||
"sign-in": "Logga in",
|
||||
"total-mealplans": "Antal måltidsplaner",
|
||||
@@ -505,45 +506,45 @@
|
||||
"webhooks-enabled": "Webhooks aktiverat",
|
||||
"you-are-not-allowed-to-create-a-user": "Du har inte behörighet att skapa en användare",
|
||||
"you-are-not-allowed-to-delete-this-user": "Du har inte behörighet att radera denna användare",
|
||||
"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"
|
||||
"enable-advanced-content": "Aktivera avancerade funktioner",
|
||||
"enable-advanced-content-description": "Aktiverar avancerade funktioner som receptskalning, API-nycklar, Webhooks och datahantering. Oroa dig inte, du kan alltid ändra detta senare"
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "translated",
|
||||
"choose-language": "Choose Language",
|
||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||
"read-the-docs": "Read the docs"
|
||||
"translated": "översatt",
|
||||
"choose-language": "Välj språk",
|
||||
"select-description": "Välj språk för Mealie användargränssnitt. Inställningen gäller endast för dig, inte andra användare.",
|
||||
"how-to-contribute-description": "Är något inte översatt ännu, felöversatt eller saknas ditt språk i listan? {read-the-docs-link} om hur man bidrar!",
|
||||
"read-the-docs": "Läs dokumentationen"
|
||||
},
|
||||
"data-pages": {
|
||||
"seed-data": "Seed Data",
|
||||
"seed-data": "Exempeldata",
|
||||
"foods": {
|
||||
"merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.",
|
||||
"merge-food-example": "Merging {food1} into {food2}",
|
||||
"seed-dialog-text": "Seed the database with foods based on your local language. This will create 200+ common foods that can be used to organize your database. Foods are translated via a community effort.",
|
||||
"seed-dialog-warning": "You have already have some items in your database. This action will not reconcile duplicates, you will have to manage them manually."
|
||||
"merge-dialog-text": "Kombinera valda livsmedel kommer att slå samman de valda livsmedlen till ett livsmedel. Ursprungslivsmedlet kommer att raderas och alla hänvisningar till detta kommer att uppdateras för att peka på det kombinerade livsmedlet.",
|
||||
"merge-food-example": "Slå ihop {food1} till {food2}",
|
||||
"seed-dialog-text": "Fyll databasen med livsmedel baserade på ditt språk. Detta kommer att skapa 200+ vanliga livsmedel som kan användas för att organisera din databas. Livsmedlen översätts via ett gemenskapsinsats.",
|
||||
"seed-dialog-warning": "Du har redan några objekt i din databas. Denna åtgärd kommer inte att förena dubbletter, du kommer att behöva hantera dem manuellt."
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Seed the database with common units based on your local language."
|
||||
"seed-dialog-text": "Fyll databasen med vanliga enheter baserade på ditt språk."
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Seed the database with common labels based on your local language."
|
||||
"seed-dialog-text": "Fyll databasen med vanliga etiketter baserade på ditt språk."
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "User Registration",
|
||||
"join-a-group": "Join a Group",
|
||||
"create-a-new-group": "Create a New Group",
|
||||
"provide-registration-token-description": "Please provide the registration token associated with the group that you'd like to join. You'll need to obtain this from an existing group member.",
|
||||
"group-details": "Group Details",
|
||||
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
|
||||
"use-seed-data": "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.",
|
||||
"account-details": "Account Details"
|
||||
"user-registration": "Användarregistrering",
|
||||
"join-a-group": "Gå med i en grupp",
|
||||
"create-a-new-group": "Skapa en ny grupp",
|
||||
"provide-registration-token-description": "Ange registreringstoken som är kopplad till den grupp som du vill gå med. Du måste få detta från en befintlig gruppmedlem.",
|
||||
"group-details": "Gruppuppgifter",
|
||||
"group-details-description": "Innan du skapar ett konto måste du skapa en grupp. Din grupp kommer bara att innehålla dig, men du kommer att kunna bjuda in andra senare. Medlemmarna i din grupp kan dela måltidsplaner, inköpslistor, recept och mycket mer!",
|
||||
"use-seed-data": "Använd exempeldata",
|
||||
"use-seed-data-description": "Mealie innehåller en samling av livsmedel, enheter och etiketter som kan användas för att fylla din grupp med användbara data för att organisera dina recept.",
|
||||
"account-details": "Kontouppgifter"
|
||||
},
|
||||
"validation": {
|
||||
"group-name-is-taken": "Group name is taken",
|
||||
"username-is-taken": "Username is taken",
|
||||
"email-is-taken": "Email is taken"
|
||||
"group-name-is-taken": "Gruppnamnet är upptaget",
|
||||
"username-is-taken": "Användarnamnet är upptaget",
|
||||
"email-is-taken": "E-postadressen är upptagen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Опис",
|
||||
"disable-amount": "Сховати кількість інгредієнтів",
|
||||
"disable-comments": "Вимкнути коментарі",
|
||||
"edit-scale": "Maßeinheiten bearbeiten",
|
||||
"fat-content": "Жири",
|
||||
"fiber-content": "Волокна",
|
||||
"grams": "грами",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "Description",
|
||||
"disable-amount": "Disable Ingredient Amounts",
|
||||
"disable-comments": "Disable Comments",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "Fat",
|
||||
"fiber-content": "Fiber",
|
||||
"grams": "grams",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "描述",
|
||||
"disable-amount": "关闭显示成分数量",
|
||||
"disable-comments": "禁用评论",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "脂肪",
|
||||
"fiber-content": "纤维",
|
||||
"grams": "克",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"description": "描述",
|
||||
"disable-amount": "停用成分數量",
|
||||
"disable-comments": "關閉留言",
|
||||
"edit-scale": "Edit Scale",
|
||||
"fat-content": "脂肪",
|
||||
"fiber-content": "纖維",
|
||||
"grams": "克",
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@adapttive/vue-markdown": "^4.0.1",
|
||||
"@mdi/js": "^5.9.55",
|
||||
"@mdi/js": "^6.7.96",
|
||||
"@nuxtjs/auth-next": "5.0.0-1624817847.21691f1",
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
"@nuxtjs/i18n": "^7.0.3",
|
||||
"@nuxtjs/i18n": "7.0.3",
|
||||
"@nuxtjs/proxy": "^2.1.0",
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"@vue/composition-api": "^1.6.2",
|
||||
"@vueuse/core": "^8.5.0",
|
||||
"core-js": "^3.15.1",
|
||||
"date-fns": "^2.23.0",
|
||||
"fuse.js": "^6.5.3",
|
||||
"@vueuse/core": "^9.0.2",
|
||||
"core-js": "^3.23.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"isomorphic-dompurify": "^0.19.0",
|
||||
"nuxt": "^2.15.8",
|
||||
"v-jsoneditor": "^1.4.5",
|
||||
@@ -42,15 +42,15 @@
|
||||
"@nuxtjs/google-fonts": "^1.3.0",
|
||||
"@nuxtjs/vuetify": "^1.12.1",
|
||||
"@types/sortablejs": "^1.13.0",
|
||||
"@vue/runtime-dom": "^3.2.36",
|
||||
"@vue/runtime-dom": "^3.2.37",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-nuxt": "^3.2.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^9.0.1",
|
||||
"lint-staged": "^12.4.2",
|
||||
"lint-staged": "^13.0.2",
|
||||
"nuxt-vite": "0.2.3",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier": "^2.7.1",
|
||||
"vue2-script-setup-transform": "^0.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,35 @@
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Create Dialog -->
|
||||
<BaseDialog
|
||||
v-model="createDialog"
|
||||
:icon="$globals.icons.foods"
|
||||
title="Create Food"
|
||||
:submit-text="$tc('general.save')"
|
||||
@submit="createFood"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-form ref="domNewFoodForm">
|
||||
<v-text-field
|
||||
v-model="createTarget.name"
|
||||
autofocus
|
||||
label="Name"
|
||||
:rules="[validators.required]"
|
||||
></v-text-field>
|
||||
<v-text-field v-model="createTarget.description" label="Description"></v-text-field>
|
||||
<v-autocomplete
|
||||
v-model="createTarget.labelId"
|
||||
clearable
|
||||
:items="allLabels"
|
||||
item-value="id"
|
||||
item-text="name"
|
||||
label="Food Label"
|
||||
>
|
||||
</v-autocomplete>
|
||||
</v-form> </v-card-text
|
||||
></BaseDialog>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<BaseDialog
|
||||
v-model="editDialog"
|
||||
@@ -63,7 +92,7 @@
|
||||
@submit="editSaveFood"
|
||||
>
|
||||
<v-card-text v-if="editTarget">
|
||||
<v-form ref="domCreateFoodForm">
|
||||
<v-form ref="domNewFoodForm">
|
||||
<v-text-field v-model="editTarget.name" label="Name" :rules="[validators.required]"></v-text-field>
|
||||
<v-text-field v-model="editTarget.description" label="Description"></v-text-field>
|
||||
<v-autocomplete
|
||||
@@ -100,8 +129,10 @@
|
||||
:bulk-actions="[]"
|
||||
@delete-one="deleteEventHandler"
|
||||
@edit-one="editEventHandler"
|
||||
@create-one="createEventHandler"
|
||||
>
|
||||
<template #button-row>
|
||||
<BaseButton create @click="createDialog = true" />
|
||||
<BaseButton @click="mergeDialog = true">
|
||||
<template #icon> {{ $globals.icons.foods }} </template>
|
||||
Combine
|
||||
@@ -128,10 +159,11 @@ import { computed } from "vue-demi";
|
||||
import type { LocaleObject } from "@nuxtjs/i18n";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { IngredientFood } from "~/types/api-types/recipe";
|
||||
import { CreateIngredientFood, IngredientFood } from "~/types/api-types/recipe";
|
||||
import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue";
|
||||
import { useLocales } from "~/composables/use-locales";
|
||||
import { useFoodStore, useLabelStore } from "~/composables/store";
|
||||
import { VForm } from "~/types/vuetify";
|
||||
|
||||
export default defineComponent({
|
||||
components: { MultiPurposeLabel },
|
||||
@@ -166,6 +198,34 @@ export default defineComponent({
|
||||
|
||||
const foodStore = useFoodStore();
|
||||
|
||||
// ===============================================================
|
||||
// Food Creator
|
||||
|
||||
const domNewFoodForm = ref<VForm>();
|
||||
const createDialog = ref(false);
|
||||
const createTarget = ref<CreateIngredientFood>({
|
||||
name: "",
|
||||
});
|
||||
|
||||
function createEventHandler() {
|
||||
createDialog.value = true;
|
||||
}
|
||||
|
||||
async function createFood() {
|
||||
if (!createTarget.value || !createTarget.value.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error the createOne function erroneously expects an id because it uses the IngredientFood type
|
||||
await foodStore.actions.createOne(createTarget.value);
|
||||
createDialog.value = false;
|
||||
|
||||
domNewFoodForm.value?.reset();
|
||||
createTarget.value = {
|
||||
name: "",
|
||||
};
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// Food Editor
|
||||
|
||||
@@ -262,6 +322,11 @@ export default defineComponent({
|
||||
foods: foodStore.foods,
|
||||
allLabels,
|
||||
validators,
|
||||
// Create
|
||||
createDialog,
|
||||
createEventHandler,
|
||||
createFood,
|
||||
createTarget,
|
||||
// Edit
|
||||
editDialog,
|
||||
editEventHandler,
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<v-card-actions class="mt-n5 mb-1">
|
||||
<v-menu offset-y bottom nudge-bottom="6" :close-on-content-click="false">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn color="accent" class="mr-1" dark v-bind="attrs" v-on="on">
|
||||
<v-btn color="accent" class="mr-2" dark v-bind="attrs" v-on="on">
|
||||
<v-icon left>
|
||||
{{ $globals.icons.cog }}
|
||||
</v-icon>
|
||||
|
||||
@@ -22,6 +22,30 @@
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Create Dialog -->
|
||||
<BaseDialog
|
||||
v-model="createDialog"
|
||||
:icon="$globals.icons.units"
|
||||
title="Create Unit"
|
||||
:submit-text="$tc('general.save')"
|
||||
@submit="createUnit"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-form ref="domNewUnitForm">
|
||||
<v-text-field
|
||||
v-model="createTarget.name"
|
||||
autofocus
|
||||
label="Name"
|
||||
:rules="[validators.required]"
|
||||
></v-text-field>
|
||||
<v-text-field v-model="createTarget.abbreviation" label="Abbreviation"></v-text-field>
|
||||
<v-text-field v-model="createTarget.description" label="Description"></v-text-field>
|
||||
<v-checkbox v-model="createTarget.fraction" hide-details label="Display as Fraction"></v-checkbox>
|
||||
<v-checkbox v-model="createTarget.useAbbreviation" hide-details label="Use Abbreviation"></v-checkbox>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<BaseDialog
|
||||
v-model="editDialog"
|
||||
@@ -100,8 +124,11 @@
|
||||
:bulk-actions="[]"
|
||||
@delete-one="deleteEventHandler"
|
||||
@edit-one="editEventHandler"
|
||||
@create-one="createEventHandler"
|
||||
>
|
||||
<template #button-row>
|
||||
<BaseButton create @click="createDialog = true" />
|
||||
|
||||
<BaseButton @click="mergeDialog = true">
|
||||
<template #icon> {{ $globals.icons.units }} </template>
|
||||
Combine
|
||||
@@ -132,9 +159,10 @@ import { computed, defineComponent, onMounted, ref } from "@nuxtjs/composition-a
|
||||
import type { LocaleObject } from "@nuxtjs/i18n";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { IngredientUnit } from "~/types/api-types/recipe";
|
||||
import { CreateIngredientUnit, IngredientUnit } from "~/types/api-types/recipe";
|
||||
import { useLocales } from "~/composables/use-locales";
|
||||
import { useUnitStore } from "~/composables/store";
|
||||
import { VForm } from "~/types/vuetify";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
@@ -178,6 +206,41 @@ export default defineComponent({
|
||||
|
||||
const { units, actions: unitActions } = useUnitStore();
|
||||
|
||||
// ============================================================
|
||||
// Create Units
|
||||
|
||||
const createDialog = ref(false);
|
||||
const domNewUnitForm = ref<VForm>();
|
||||
|
||||
// we explicitly set booleans to false since forms don't POST unchecked boxes
|
||||
const createTarget = ref<CreateIngredientUnit>({
|
||||
name: "",
|
||||
fraction: false,
|
||||
useAbbreviation: false,
|
||||
});
|
||||
|
||||
function createEventHandler() {
|
||||
createDialog.value = true;
|
||||
}
|
||||
|
||||
async function createUnit() {
|
||||
if (!createTarget.value || !createTarget.value.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error the createOne function erroneously expects an id because it uses the IngredientUnit type
|
||||
await unitActions.createOne(createTarget.value);
|
||||
createDialog.value = false;
|
||||
|
||||
domNewUnitForm.value?.reset();
|
||||
createTarget.value = {
|
||||
name: "",
|
||||
fraction: false,
|
||||
useAbbreviation: false,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Edit Units
|
||||
const editDialog = ref(false);
|
||||
const editTarget = ref<IngredientUnit | null>(null);
|
||||
@@ -195,6 +258,7 @@ export default defineComponent({
|
||||
editDialog.value = false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Delete Units
|
||||
const deleteDialog = ref(false);
|
||||
const deleteTarget = ref<IngredientUnit | null>(null);
|
||||
@@ -263,6 +327,11 @@ export default defineComponent({
|
||||
tableHeaders,
|
||||
units,
|
||||
validators,
|
||||
// Create
|
||||
createDialog,
|
||||
createEventHandler,
|
||||
createUnit,
|
||||
createTarget,
|
||||
// Edit
|
||||
editDialog,
|
||||
editEventHandler,
|
||||
|
||||
@@ -126,7 +126,7 @@ export default defineComponent({
|
||||
const { data } = await api.mealplanRules.getAll();
|
||||
|
||||
if (data) {
|
||||
allRules.value = data;
|
||||
allRules.value = data.items ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-text-field v-model="notifiers[index].name" label="Name"></v-text-field>
|
||||
<v-text-field v-model="notifiers[index].appriseUrl" label="Apprise URL (skipped in blank)"></v-text-field>
|
||||
<v-text-field v-model="notifiers[index].appriseUrl" label="Apprise URL (skipped if blank)"></v-text-field>
|
||||
<v-checkbox v-model="notifiers[index].enabled" label="Enable Notifier" dense></v-checkbox>
|
||||
|
||||
<v-divider></v-divider>
|
||||
@@ -130,12 +130,12 @@ export default defineComponent({
|
||||
|
||||
const notifiers = useAsync(async () => {
|
||||
const { data } = await api.groupEventNotifier.getAll();
|
||||
return data ?? [];
|
||||
return data?.items;
|
||||
}, useAsyncKey());
|
||||
|
||||
async function refreshNotifiers() {
|
||||
const { data } = await api.groupEventNotifier.getAll();
|
||||
notifiers.value = data ?? [];
|
||||
notifiers.value = data?.items;
|
||||
}
|
||||
|
||||
const createNotifierData: GroupEventNotifierCreate = reactive({
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-webhooks.svg')"></v-img>
|
||||
</template>
|
||||
<template #title> Webhooks </template>
|
||||
The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the webhooks
|
||||
will be sent with the data from the recipe that is scheduled for the day
|
||||
<v-card-text class="pb-0">
|
||||
The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the
|
||||
webhooks will be sent with the data from the recipe that is scheduled for the day. Note that webhook execution
|
||||
is not exact. The webhooks are executed on a 5 minutes interval so the webhooks will be executed within 5 +/-
|
||||
minutes of the scheduled.
|
||||
</v-card-text>
|
||||
</BasePageTitle>
|
||||
|
||||
<BannerExperimental />
|
||||
|
||||
<BaseButton create @click="actions.createOne()" />
|
||||
<v-expansion-panels class="mt-2">
|
||||
<v-expansion-panel v-for="(webhook, index) in webhooks" :key="index" class="my-2 left-border rounded">
|
||||
@@ -17,7 +23,7 @@
|
||||
<v-icon large left :color="webhook.enabled ? 'info' : null">
|
||||
{{ $globals.icons.webhook }}
|
||||
</v-icon>
|
||||
{{ webhook.name }} - {{ webhook.time }}
|
||||
{{ webhook.name }} - {{ timeDisplay(timeUTCToLocal(webhook.scheduledTime)) }}
|
||||
</div>
|
||||
<template #actions>
|
||||
<v-btn small icon class="ml-2">
|
||||
@@ -28,35 +34,12 @@
|
||||
</template>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-card-text>
|
||||
<v-switch v-model="webhook.enabled" label="Enabled"></v-switch>
|
||||
<v-text-field v-model="webhook.name" label="Webhook Name"></v-text-field>
|
||||
<v-text-field v-model="webhook.url" label="Webhook Url"></v-text-field>
|
||||
<v-time-picker v-model="webhook.time" class="elevation-2" ampm-in-title format="ampm"></v-time-picker>
|
||||
</v-card-text>
|
||||
<v-card-actions class="py-0 justify-end">
|
||||
<BaseButtonGroup
|
||||
:buttons="[
|
||||
{
|
||||
icon: $globals.icons.delete,
|
||||
text: $t('general.delete'),
|
||||
event: 'delete',
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.testTube,
|
||||
text: $t('general.test'),
|
||||
event: 'test',
|
||||
},
|
||||
{
|
||||
icon: $globals.icons.save,
|
||||
text: $t('general.save'),
|
||||
event: 'save',
|
||||
},
|
||||
]"
|
||||
@delete="actions.deleteOne(webhook.id)"
|
||||
@save="actions.updateOne(webhook)"
|
||||
/>
|
||||
</v-card-actions>
|
||||
<GroupWebhookEditor
|
||||
:key="webhook.id"
|
||||
:webhook="webhook"
|
||||
@save="actions.updateOne($event)"
|
||||
@delete="actions.deleteOne($event)"
|
||||
/>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
@@ -65,15 +48,28 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { useGroupWebhooks } from "~/composables/use-group-webhooks";
|
||||
import { useGroupWebhooks, timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
|
||||
import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: { GroupWebhookEditor },
|
||||
setup() {
|
||||
const { actions, webhooks } = useGroupWebhooks();
|
||||
function timeDisplay(time: string): string {
|
||||
// returns the time in the format HH:MM AM/PM
|
||||
const [hours, minutes] = time.split(":");
|
||||
const ampm = Number(hours) < 12 ? "AM" : "PM";
|
||||
const hour = Number(hours) % 12 || 12;
|
||||
const minute = minutes.padStart(2, "0");
|
||||
return `${hour}:${minute} ${ampm}`;
|
||||
}
|
||||
|
||||
return {
|
||||
webhooks,
|
||||
actions,
|
||||
timeLocalToUTC,
|
||||
timeUTCToLocal,
|
||||
timeDisplay,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<v-container
|
||||
v-if="recipe"
|
||||
:class="{
|
||||
'pa-0': $vuetify.breakpoint.smAndDown,
|
||||
}"
|
||||
>
|
||||
<v-card-title>
|
||||
<h1 class="headline">{{ recipe.name }}</h1>
|
||||
</v-card-title>
|
||||
|
||||
<v-stepper v-model="activeStep" flat>
|
||||
<v-toolbar class="ma-1 elevation-2 rounded">
|
||||
<v-toolbar-title class="headline">
|
||||
Step {{ activeStep }} of {{ recipe.recipeInstructions.length }}</v-toolbar-title
|
||||
>
|
||||
</v-toolbar>
|
||||
<div class="d-flex mt-3 px-2">
|
||||
<BaseButton color="primary" @click="$router.go(-1)">
|
||||
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
|
||||
To Recipe
|
||||
</BaseButton>
|
||||
<v-btn rounded icon color="primary" class="ml-auto" small @click="scale > 1 ? scale-- : null">
|
||||
<v-icon>
|
||||
{{ $globals.icons.minus }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn rounded color="primary" small> Scale: {{ scale }} </v-btn>
|
||||
<v-btn rounded icon color="primary" small @click="scale++">
|
||||
<v-icon>
|
||||
{{ $globals.icons.createAlt }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-stepper-items>
|
||||
<template v-for="(step, index) in recipe.recipeInstructions">
|
||||
<v-stepper-content :key="index + 1 + '-content'" :step="index + 1" class="pa-0 mt-2 elevation-0">
|
||||
<v-card class="ma-2">
|
||||
<v-card-text>
|
||||
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
|
||||
<VueMarkdown :source="step.text"> </VueMarkdown>
|
||||
<template v-if="step.ingredientReferences.length > 0">
|
||||
<v-divider></v-divider>
|
||||
<div>
|
||||
<h2 class="mb-4 mt-4">{{ $t("recipe.ingredients") }}</h2>
|
||||
<div
|
||||
v-for="ing in step.ingredientReferences"
|
||||
:key="ing.referenceId"
|
||||
v-html="getIngredientByRefId(ing.referenceId)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card-actions class="justify-center">
|
||||
<BaseButton color="primary" :disabled="index == 0" @click="activeStep = activeStep - 1">
|
||||
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
|
||||
Back
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
icon-right
|
||||
:disabled="index + 1 == recipe.recipeInstructions.length"
|
||||
color="primary"
|
||||
@click="activeStep = activeStep + 1"
|
||||
>
|
||||
<template #icon> {{ $globals.icons.arrowRightBold }}</template>
|
||||
Next
|
||||
</BaseButton>
|
||||
</v-card-actions>
|
||||
</v-stepper-content>
|
||||
</template>
|
||||
</v-stepper-items>
|
||||
</v-stepper>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, useRoute, ref } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { useStaticRoutes } from "~/composables/api";
|
||||
import { parseIngredientText, useRecipe } from "~/composables/recipes";
|
||||
|
||||
export default defineComponent({
|
||||
components: { VueMarkdown },
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const slug = route.value.params.slug;
|
||||
const activeStep = ref(1);
|
||||
const scale = ref(1);
|
||||
|
||||
const { recipe } = useRecipe(slug);
|
||||
|
||||
const { recipeImage } = useStaticRoutes();
|
||||
|
||||
function getIngredientByRefId(refId: string) {
|
||||
if (!recipe.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ing = recipe?.value.recipeIngredient?.find((ing) => ing.referenceId === refId) || "";
|
||||
if (ing === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return parseIngredientText(ing, recipe?.value?.settings?.disableAmount || false, scale.value);
|
||||
}
|
||||
|
||||
return {
|
||||
scale,
|
||||
getIngredientByRefId,
|
||||
activeStep,
|
||||
slug,
|
||||
recipe,
|
||||
recipeImage,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -17,7 +17,7 @@
|
||||
<RecipeRating :key="recipe.slug" :value="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
|
||||
</v-card-title>
|
||||
<v-divider class="my-2"></v-divider>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
<v-divider></v-divider>
|
||||
<div class="d-flex justify-center mt-5">
|
||||
<RecipeTimeCard
|
||||
@@ -35,7 +35,7 @@
|
||||
:max-width="enableLandscape ? null : '50%'"
|
||||
min-height="50"
|
||||
:height="hideImage ? undefined : imageHeight"
|
||||
:src="recipeImage(recipe.id, imageKey)"
|
||||
:src="recipeImage(recipe.id, recipe.image, imageKey)"
|
||||
class="d-print-none"
|
||||
@error="hideImage = true"
|
||||
>
|
||||
@@ -53,10 +53,7 @@
|
||||
class="ml-auto mt-n8 pb-4"
|
||||
@close="closeEditor"
|
||||
@json="toggleJson"
|
||||
@edit="
|
||||
jsonEditor = false;
|
||||
form = true;
|
||||
"
|
||||
@edit="toggleEdit"
|
||||
@save="updateRecipe(recipe.slug, recipe)"
|
||||
@delete="deleteRecipe(recipe.slug)"
|
||||
@print="printRecipe"
|
||||
@@ -84,7 +81,7 @@
|
||||
<v-card-title class="px-0 py-2 ma-0 headline">
|
||||
{{ recipe.name }}
|
||||
</v-card-title>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
|
||||
<div class="pb-2 d-flex justify-center flex-wrap">
|
||||
<RecipeTimeCard
|
||||
@@ -176,40 +173,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between align-center pt-2 pb-3">
|
||||
<v-tooltip v-if="!form" small top color="secondary darken-1">
|
||||
<v-tooltip v-if="!form && recipe.recipeYield" small top color="secondary darken-1">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn
|
||||
v-if="recipe.recipeYield"
|
||||
dense
|
||||
small
|
||||
:hover="false"
|
||||
type="label"
|
||||
:ripple="false"
|
||||
elevation="0"
|
||||
color="secondary darken-1"
|
||||
class="rounded-sm static"
|
||||
<RecipeScaleEditButton
|
||||
v-model.number="scale"
|
||||
v-bind="attrs"
|
||||
@click="scale = 1"
|
||||
:recipe-yield="recipe.recipeYield"
|
||||
:basic-yield="basicYield"
|
||||
:scaled-yield="scaledYield"
|
||||
:edit-scale="!recipe.settings.disableAmount && !form"
|
||||
v-on="on"
|
||||
>
|
||||
{{ scaledYield }}
|
||||
</v-btn>
|
||||
/>
|
||||
</template>
|
||||
<span> Reset Scale </span>
|
||||
<span> {{ $t("recipe.edit-scale") }} </span>
|
||||
</v-tooltip>
|
||||
|
||||
<template v-if="!recipe.settings.disableAmount && !form">
|
||||
<v-btn color="secondary darken-1" class="mx-1" small @click="scale > 1 ? scale-- : null">
|
||||
<v-icon>
|
||||
{{ $globals.icons.minus }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn color="secondary darken-1" small @click="scale++">
|
||||
<v-icon>
|
||||
{{ $globals.icons.createAlt }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<RecipeRating
|
||||
@@ -222,7 +199,7 @@
|
||||
</div>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="4" lg="4">
|
||||
<v-col v-if="!cookModeToggle || form" cols="12" sm="12" md="4" lg="4">
|
||||
<RecipeIngredients
|
||||
v-if="!form"
|
||||
:value="recipe.recipeIngredient"
|
||||
@@ -311,17 +288,24 @@
|
||||
</client-only>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-divider v-if="$vuetify.breakpoint.mdAndUp" class="my-divider" :vertical="true"></v-divider>
|
||||
<v-divider
|
||||
v-if="$vuetify.breakpoint.mdAndUp && !cookModeToggle"
|
||||
class="my-divider"
|
||||
:vertical="true"
|
||||
></v-divider>
|
||||
|
||||
<v-col cols="12" sm="12" md="8" lg="8">
|
||||
<v-col cols="12" sm="12" :md="8 + (cookModeToggle ? 1 : 0) * 4" :lg="8 + (cookModeToggle ? 1 : 0) * 4">
|
||||
<RecipeInstructions
|
||||
v-model="recipe.recipeInstructions"
|
||||
:assets.sync="recipe.assets"
|
||||
:ingredients="recipe.recipeIngredient"
|
||||
:disable-amount="recipe.settings.disableAmount"
|
||||
:edit="form"
|
||||
:recipe-id="recipe.id"
|
||||
:recipe-slug="recipe.slug"
|
||||
:assets.sync="recipe.assets"
|
||||
:cook-mode="cookModeToggle"
|
||||
:scale="scale"
|
||||
@cookModeToggle="cookModeToggle = !cookModeToggle"
|
||||
/>
|
||||
<div v-if="form" class="d-flex">
|
||||
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
||||
@@ -377,14 +361,14 @@
|
||||
</v-card>
|
||||
|
||||
<RecipeNutrition
|
||||
v-if="recipe.settings.showNutrition"
|
||||
v-if="recipe.settings.showNutrition && !cookModeToggle"
|
||||
v-model="recipe.nutrition"
|
||||
class="mt-10"
|
||||
:edit="form"
|
||||
/>
|
||||
<client-only>
|
||||
<RecipeAssets
|
||||
v-if="recipe.settings.showAssets"
|
||||
v-if="recipe.settings.showAssets && !cookModeToggle"
|
||||
v-model="recipe.assets"
|
||||
:edit="form"
|
||||
:slug="recipe.slug"
|
||||
@@ -393,7 +377,7 @@
|
||||
</client-only>
|
||||
</div>
|
||||
|
||||
<RecipeNotes v-model="recipe.notes" :edit="form" />
|
||||
<RecipeNotes v-if="!cookModeToggle" v-model="recipe.notes" :edit="form" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -405,7 +389,7 @@
|
||||
:label="$t('recipe.original-url')"
|
||||
></v-text-field>
|
||||
<v-btn
|
||||
v-else-if="recipe.orgURL"
|
||||
v-else-if="recipe.orgURL && !cookModeToggle"
|
||||
dense
|
||||
small
|
||||
:hover="false"
|
||||
@@ -458,7 +442,7 @@
|
||||
</div>
|
||||
|
||||
<RecipeComments
|
||||
v-if="recipe && !recipe.settings.disableComments && !form"
|
||||
v-if="recipe && !recipe.settings.disableComments && !form && !cookModeToggle"
|
||||
v-model="recipe.comments"
|
||||
:slug="recipe.slug"
|
||||
:recipe-id="recipe.id"
|
||||
@@ -481,8 +465,6 @@ import {
|
||||
useRouter,
|
||||
onMounted,
|
||||
} from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import draggable from "vuedraggable";
|
||||
import { invoke, until, useWakeLock } from "@vueuse/core";
|
||||
import { onUnmounted } from "vue-demi";
|
||||
@@ -500,6 +482,7 @@ import RecipeNutrition from "~/components/Domain/Recipe/RecipeNutrition.vue";
|
||||
import RecipeInstructions from "~/components/Domain/Recipe/RecipeInstructions.vue";
|
||||
import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue";
|
||||
import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue";
|
||||
import RecipeScaleEditButton from "~/components/Domain/Recipe/RecipeScaleEditButton.vue";
|
||||
import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue";
|
||||
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
|
||||
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
||||
@@ -509,7 +492,6 @@ import { Recipe } from "~/types/api-types/recipe";
|
||||
import { uuid4, deepCopy } from "~/composables/use-utils";
|
||||
import { useRouteQuery } from "~/composables/use-router";
|
||||
import { useToolStore } from "~/composables/store";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
draggable,
|
||||
@@ -534,7 +516,7 @@ export default defineComponent({
|
||||
RecipeSettingsMenu,
|
||||
RecipeTimeCard,
|
||||
RecipeTools,
|
||||
VueMarkdown,
|
||||
RecipeScaleEditButton,
|
||||
},
|
||||
async beforeRouteLeave(_to, _from, next) {
|
||||
const isSame = JSON.stringify(this.recipe) === JSON.stringify(this.originalRecipe);
|
||||
@@ -610,6 +592,8 @@ export default defineComponent({
|
||||
const state = reactive({
|
||||
form: false,
|
||||
scale: 1,
|
||||
scaleTemp: 1,
|
||||
scaleDialog: false,
|
||||
hideImage: false,
|
||||
imageKey: 1,
|
||||
skeleton: false,
|
||||
@@ -619,6 +603,7 @@ export default defineComponent({
|
||||
search: false,
|
||||
mainMenuBar: false,
|
||||
},
|
||||
cookModeToggle: false,
|
||||
});
|
||||
|
||||
const { recipe, loading, fetchRecipe } = useRecipe(slug);
|
||||
@@ -658,6 +643,12 @@ export default defineComponent({
|
||||
// ===========================================================================
|
||||
// Button Click Event Handlers
|
||||
|
||||
function toggleEdit() {
|
||||
state.jsonEditor = false;
|
||||
state.cookModeToggle = false;
|
||||
state.form = true;
|
||||
}
|
||||
|
||||
async function updateRecipe(slug: string, recipe: Recipe) {
|
||||
const { data } = await api.recipes.updateOne(slug, recipe);
|
||||
state.form = false;
|
||||
@@ -701,6 +692,19 @@ export default defineComponent({
|
||||
return recipe.value?.recipeYield;
|
||||
});
|
||||
|
||||
const basicYield = computed(() => {
|
||||
const regMatchNum = /\d+/;
|
||||
const yieldString = recipe.value?.recipeYield;
|
||||
const num = yieldString?.match(regMatchNum);
|
||||
|
||||
if (num && num?.length > 0) {
|
||||
const yieldAsInt = parseInt(num[0]);
|
||||
return yieldString?.replace(num[0], String(yieldAsInt));
|
||||
}
|
||||
|
||||
return recipe.value?.recipeYield;
|
||||
});
|
||||
|
||||
async function uploadImage(fileObject: File) {
|
||||
if (!recipe.value || !recipe.value.slug) {
|
||||
return;
|
||||
@@ -830,6 +834,13 @@ export default defineComponent({
|
||||
|
||||
const drag = ref(false);
|
||||
|
||||
// ===============================================================
|
||||
// Scale
|
||||
|
||||
const setScale = (newScale: number) => {
|
||||
state.scale = newScale;
|
||||
};
|
||||
|
||||
return {
|
||||
// Wake Lock
|
||||
drag,
|
||||
@@ -847,15 +858,18 @@ export default defineComponent({
|
||||
enableLandscape,
|
||||
imageHeight,
|
||||
scaledYield,
|
||||
basicYield,
|
||||
toggleJson,
|
||||
...toRefs(state),
|
||||
recipe,
|
||||
api,
|
||||
loading,
|
||||
addStep,
|
||||
setScale,
|
||||
deleteRecipe,
|
||||
printRecipe,
|
||||
closeEditor,
|
||||
toggleEdit,
|
||||
updateRecipe,
|
||||
uploadImage,
|
||||
validators,
|
||||
|
||||
@@ -50,7 +50,11 @@
|
||||
<v-expansion-panels v-model="panels" multiple>
|
||||
<v-expansion-panel v-for="(ing, index) in parsedIng" :key="index">
|
||||
<v-expansion-panel-header class="my-0 py-0" disable-icon-rotate>
|
||||
{{ ing.input }}
|
||||
<template #default="{ open }">
|
||||
<v-fade-transition>
|
||||
<span v-if="!open" key="0"> {{ ing.input }} </span>
|
||||
</v-fade-transition>
|
||||
</template>
|
||||
<template #actions>
|
||||
<v-icon left :color="isError(ing) ? 'error' : 'success'">
|
||||
{{ isError(ing) ? $globals.icons.alert : $globals.icons.check }}
|
||||
@@ -62,6 +66,7 @@
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content class="pb-0 mb-0">
|
||||
<RecipeIngredientEditor v-model="parsedIng[index].ingredient" />
|
||||
{{ ing.input }}
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<BaseButton
|
||||
@@ -140,6 +145,16 @@ export default defineComponent({
|
||||
const { data } = await api.recipes.parseIngredients(parser.value, raw);
|
||||
|
||||
if (data) {
|
||||
// When we send the recipe ingredient text to be parsed, we lose the reference to the original unparsed ingredient.
|
||||
// Generally this is fine, but if the unparsed ingredient had a title, we lose it; we add back the title for each ingredient here.
|
||||
try {
|
||||
for (let i = 0; i < recipe.value.recipeIngredient.length; i++) {
|
||||
data[i].ingredient.title = recipe.value.recipeIngredient[i].title;
|
||||
}
|
||||
} catch (TypeError) {
|
||||
console.error("Index Mismatch Error during recipe ingredient parsing; did the number of ingredients change?");
|
||||
}
|
||||
|
||||
parsedIng.value = data;
|
||||
|
||||
errors.value = data.map((ing, index: number) => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<v-card-text>
|
||||
Grab the URL of the recipe you want to debug and paste it here. The URL will be scraped by the recipe scraper
|
||||
and the results will be displayed. If you don't see any data returned, the site you are trying to scrape is
|
||||
not supported by Mealie or it's scraper library.
|
||||
not supported by Mealie or its scraper library.
|
||||
<v-text-field
|
||||
v-model="recipeUrl"
|
||||
:label="$t('new-recipe.recipe-url')"
|
||||
|
||||
@@ -4,47 +4,35 @@
|
||||
:icon="$globals.icons.primary"
|
||||
:title="$t('page.all-recipes')"
|
||||
:recipes="recipes"
|
||||
:use-pagination="true"
|
||||
@sortRecipes="assignSorted"
|
||||
@replaceRecipes="replaceRecipes"
|
||||
@appendRecipes="appendRecipes"
|
||||
@delete="removeRecipe"
|
||||
></RecipeCardSection>
|
||||
<v-card v-intersect="infiniteScroll"></v-card>
|
||||
<v-fade-transition>
|
||||
<AppLoader v-if="loading" :loading="loading" />
|
||||
</v-fade-transition>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||
import { useLazyRecipes } from "~/composables/recipes";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: { RecipeCardSection },
|
||||
setup() {
|
||||
const start = ref(0);
|
||||
const limit = ref(30);
|
||||
const increment = ref(30);
|
||||
const ready = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const { recipes, fetchMore } = useLazyRecipes();
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchMore(start.value, limit.value);
|
||||
ready.value = true;
|
||||
});
|
||||
function appendRecipes(val: Array<Recipe>) {
|
||||
val.forEach((recipe) => {
|
||||
recipes.value.push(recipe);
|
||||
});
|
||||
}
|
||||
|
||||
const infiniteScroll = useThrottleFn(() => {
|
||||
if (!ready.value) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
start.value = limit.value + 1;
|
||||
limit.value = limit.value + increment.value;
|
||||
fetchMore(start.value, limit.value);
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
function assignSorted(val: Array<Recipe>) {
|
||||
recipes.value = val;
|
||||
}
|
||||
|
||||
function removeRecipe(slug: string) {
|
||||
for (let i = 0; i < recipes?.value?.length; i++) {
|
||||
@@ -55,7 +43,11 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
return { recipes, infiniteScroll, loading, removeRecipe };
|
||||
function replaceRecipes(val: Array<Recipe>) {
|
||||
recipes.value = val;
|
||||
}
|
||||
|
||||
return { appendRecipes, assignSorted, recipes, removeRecipe, replaceRecipes };
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
@@ -64,4 +56,3 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
head: {
|
||||
title: "Tags",
|
||||
title: "Categories",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<RecipeRating :key="recipe.slug" :value="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
|
||||
</v-card-title>
|
||||
<v-divider class="my-2"></v-divider>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description"> </SafeMarkdown>
|
||||
<v-divider></v-divider>
|
||||
<div class="d-flex justify-center mt-5">
|
||||
<RecipeTimeCard
|
||||
@@ -34,7 +34,7 @@
|
||||
:key="imageKey"
|
||||
:max-width="enableLandscape ? null : '50%'"
|
||||
:height="hideImage ? '50' : imageHeight"
|
||||
:src="recipeImage(recipe.id, imageKey)"
|
||||
:src="recipeImage(recipe.id, recipe.image, imageKey)"
|
||||
class="d-print-none"
|
||||
@error="hideImage = true"
|
||||
>
|
||||
@@ -61,7 +61,7 @@
|
||||
<v-card-title class="pa-0 ma-0 headline">
|
||||
{{ recipe.name }}
|
||||
</v-card-title>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description"> </SafeMarkdown>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form">
|
||||
@@ -273,8 +273,6 @@ import {
|
||||
useMeta,
|
||||
useRoute,
|
||||
} from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
// import { useRecipeMeta } from "~/composables/recipes";
|
||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
|
||||
@@ -296,7 +294,6 @@ export default defineComponent({
|
||||
RecipePrintView,
|
||||
RecipeRating,
|
||||
RecipeTimeCard,
|
||||
VueMarkdown,
|
||||
},
|
||||
layout: "basic",
|
||||
setup() {
|
||||
|
||||
@@ -193,11 +193,11 @@ import { useCopyList } from "~/composables/use-copy";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue";
|
||||
import { MultiPurposeLabelOut } from "~/types/api-types/labels";
|
||||
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/types/api-types/group";
|
||||
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
|
||||
import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
|
||||
import { getDisplayText } from "~/composables/use-display-text";
|
||||
import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
|
||||
|
||||
type CopyTypes = "plain" | "markdown";
|
||||
|
||||
@@ -336,17 +336,9 @@ export default defineComponent({
|
||||
// Labels, Units, Foods
|
||||
// TODO: Extract to Composable
|
||||
|
||||
const allLabels = ref([] as MultiPurposeLabelOut[]);
|
||||
|
||||
const allUnits = useAsync(async () => {
|
||||
const { data } = await userApi.units.getAll();
|
||||
return data ?? [];
|
||||
}, useAsyncKey());
|
||||
|
||||
const allFoods = useAsync(async () => {
|
||||
const { data } = await userApi.foods.getAll();
|
||||
return data ?? [];
|
||||
}, useAsyncKey());
|
||||
const { labels: allLabels } = useLabelStore();
|
||||
const { units: allUnits } = useUnitStore();
|
||||
const { foods: allFoods } = useFoodStore();
|
||||
|
||||
function sortByLabels() {
|
||||
byLabel.value = !byLabel.value;
|
||||
@@ -405,7 +397,10 @@ export default defineComponent({
|
||||
|
||||
async function refreshLabels() {
|
||||
const { data } = await userApi.multiPurposeLabels.getAll();
|
||||
allLabels.value = data ?? [];
|
||||
|
||||
if (data) {
|
||||
allLabels.value = data.items ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
refreshLabels();
|
||||
|
||||
@@ -60,7 +60,12 @@ export default defineComponent({
|
||||
|
||||
async function fetchShoppingLists() {
|
||||
const { data } = await userApi.shopping.lists.getAll();
|
||||
return data;
|
||||
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.items;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<v-container>
|
||||
<RecipeCardSection v-if="user" :icon="$globals.icons.heart" title="User Favorites" :recipes="user.favoriteRecipes">
|
||||
</RecipeCardSection>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
|
||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
|
||||
export default defineComponent({
|
||||
components: { RecipeCardSection },
|
||||
setup() {
|
||||
return {};
|
||||
const api = useUserApi();
|
||||
const route = useRoute();
|
||||
|
||||
const userId = route.value.params.id;
|
||||
|
||||
const user = useAsync(async () => {
|
||||
const { data } = await api.users.getFavorites(userId);
|
||||
return data;
|
||||
}, useAsyncKey());
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
@@ -16,6 +35,5 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Plugin } from "@nuxt/types"
|
||||
import { Plugin } from "@nuxt/types";
|
||||
import { Framework } from "vuetify";
|
||||
import { icons } from "~/utils/icons";
|
||||
import { Icon } from "~/utils/icons/icon-type";
|
||||
|
||||
@@ -15,13 +16,14 @@ declare module "vue/types/vue" {
|
||||
declare module "@nuxt/types" {
|
||||
interface Context {
|
||||
$globals: Globals;
|
||||
$vuetify: Framework;
|
||||
}
|
||||
}
|
||||
|
||||
const globalsPlugin: Plugin = (_, inject) => {
|
||||
inject("globals", {
|
||||
icons
|
||||
icons,
|
||||
});
|
||||
};
|
||||
|
||||
export default globalsPlugin
|
||||
export default globalsPlugin;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||
*/
|
||||
|
||||
export type WebhookType = "mealplan";
|
||||
export type SupportedMigrations = "nextcloud" | "chowdown" | "paprika" | "mealie_alpha";
|
||||
|
||||
export interface CreateGroupPreferences {
|
||||
@@ -25,7 +26,8 @@ export interface CreateWebhook {
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
url?: string;
|
||||
time?: string;
|
||||
webhookType?: WebhookType & string;
|
||||
scheduledTime: string;
|
||||
}
|
||||
export interface DataMigrationCreate {
|
||||
sourceType: SupportedMigrations;
|
||||
@@ -231,7 +233,8 @@ export interface ReadWebhook {
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
url?: string;
|
||||
time?: string;
|
||||
webhookType?: WebhookType & string;
|
||||
scheduledTime: string;
|
||||
groupId: string;
|
||||
id: string;
|
||||
}
|
||||
@@ -304,7 +307,8 @@ export interface SaveWebhook {
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
url?: string;
|
||||
time?: string;
|
||||
webhookType?: WebhookType & string;
|
||||
scheduledTime: string;
|
||||
groupId: string;
|
||||
}
|
||||
export interface SeederConfig {
|
||||
|
||||
@@ -66,6 +66,8 @@ export interface UserOut {
|
||||
}
|
||||
export interface LongLiveTokenOut {
|
||||
token: string;
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
export interface ReadGroupPreferences {
|
||||
privateGroup?: boolean;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user