Compare commits

...

71 Commits
1.2.0 ... 1.2.6

Author SHA1 Message Date
vabene1111
87c2ff73e8 Merge branch 'develop' 2022-05-18 14:38:01 +02:00
Gabriel Tapias
27bb4c9bb8 Translated using Weblate (Spanish)
Currently translated at 48.1% (254 of 528 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/es/
2022-05-17 21:32:15 +00:00
vabene1111
ac647c5ee8 more JS nonsense 2022-05-17 22:00:36 +02:00
vabene1111
3cec891aa1 added missing dependency 2022-05-17 21:57:43 +02:00
vabene1111
3633b9724b fixed image deletion error 2022-05-17 21:53:10 +02:00
vabene1111
420b5c093f updated some dependencies 2022-05-17 21:43:52 +02:00
vabene1111
9bb55dd746 Merge pull request #1758 from TandoorRecipes/dependabot/npm_and_yarn/vue/kangc/v-md-editor-1.7.11
Bump @kangc/v-md-editor from 1.7.9 to 1.7.11 in /vue
2022-05-17 21:38:00 +02:00
dependabot[bot]
1759ad3587 Bump @kangc/v-md-editor from 1.7.9 to 1.7.11 in /vue
Bumps [@kangc/v-md-editor](https://github.com/code-farmer-i/vue-markdown-editor) from 1.7.9 to 1.7.11.
- [Release notes](https://github.com/code-farmer-i/vue-markdown-editor/releases)
- [Changelog](https://github.com/code-farmer-i/vue-markdown-editor/blob/dev/docs/changelog.md)
- [Commits](https://github.com/code-farmer-i/vue-markdown-editor/compare/v1.7.9...v1.7.11)

---
updated-dependencies:
- dependency-name: "@kangc/v-md-editor"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-17 19:30:34 +00:00
vabene1111
956693b7ca changed view mode of file viewer downloadable files
changed it so that the file is always downloaded so that opened files do not open in the application context (to prevent possible XSS issues)
2022-05-17 21:27:40 +02:00
vabene1111
7b2117c019 improved output sanitization of several views 2022-05-17 21:24:27 +02:00
vabene1111
d48fe26a35 added url validation to all server requests 2022-05-17 18:04:43 +02:00
Kaibu
7fd5fca0cf changed mobile logo to png version aswell 2022-05-13 15:11:04 +02:00
vabene1111
378938812c update recipe scrapers 2022-05-11 20:42:28 +02:00
vabene1111
60b494abeb workflow name 2022-05-11 20:30:17 +02:00
vabene1111
34be1dc1d7 removed notification from raspi build 2022-05-11 20:14:53 +02:00
vabene1111
d89a4620f0 meal plan remove old add to shopping 2022-05-11 19:59:04 +02:00
vabene1111
dea83b5720 fixed duplication of values in recipe editor 2022-05-11 19:31:44 +02:00
vabene1111
d9ebe3e0fb fixed original text rendering in recipe edito view 2022-05-11 19:25:48 +02:00
vabene1111
135dde247f fixed static path generation 2022-05-11 19:17:40 +02:00
vabene1111
eb7a667202 fixed cookmate importer 2022-05-11 19:10:56 +02:00
Kaibu
b3cc9967f5 Merge branch 'develop' of https://github.com/TandoorRecipes/recipes into develop 2022-05-11 17:10:18 +02:00
Kaibu
7276cea3d5 multiple ux fixes 2022-05-11 17:10:12 +02:00
vabene1111
c0c996622e made shopping new entry translatable 2022-05-11 17:06:34 +02:00
vabene1111
5556555bca fixed paprika servings import 2022-05-11 17:05:47 +02:00
vabene1111
55a84494c9 fixed issue with plan to eat importer 2022-05-11 16:53:20 +02:00
vabene1111
74d778dcb8 compiled messages 2022-05-11 16:45:34 +02:00
vabene1111
156d68f1b8 added link to shopping recipes 2022-05-11 16:42:52 +02:00
vabene1111
cb59a6340d Merge pull request #1788 from gloriousDan/fix-import
Fix import with recipe-scrapers
2022-05-11 16:37:41 +02:00
vabene1111
5eb013cc2f Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2022-05-11 16:22:18 +02:00
vabene1111
dafb26b500 updated raspi docs 2022-05-11 16:22:13 +02:00
vabene1111
d9416a42dc Merge pull request #1786 from gloriousDan/dockerfile-armv7
Build pillow for armv7/ raspi
2022-05-11 16:17:45 +02:00
Jesse
ad88eff9e3 Translated using Weblate (Dutch)
Currently translated at 100.0% (528 of 528 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/nl/
2022-05-10 15:32:17 +00:00
zeon
4d4f623adf Translated using Weblate (Bulgarian)
Currently translated at 100.0% (412 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/bg/
2022-05-10 15:32:16 +00:00
Mathias Rasmussen
ac9c9cd4e3 Translated using Weblate (Danish)
Currently translated at 100.0% (528 of 528 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/da/
2022-05-10 15:32:15 +00:00
Jesse
580eeef6b7 Translated using Weblate (Dutch)
Currently translated at 100.0% (412 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/nl/
2022-05-10 15:32:13 +00:00
zeon
f25f5a26cf Translated using Weblate (Bulgarian)
Currently translated at 100.0% (528 of 528 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/bg/
2022-05-10 15:32:11 +00:00
Mathias Rasmussen
972d43c2a2 Translated using Weblate (Danish)
Currently translated at 100.0% (412 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/da/
2022-05-10 15:32:09 +00:00
Daniel Schulz
2a7475c435 Call scrape_me first when scraping from url 2022-05-10 00:08:37 +02:00
Daniel Schulz
71b41efe6c Build pillow for armv7/ raspi 2022-05-09 13:24:59 +02:00
zeon
33a7fee1cc Added translation using Weblate (Bulgarian) 2022-05-08 21:25:53 +00:00
zeon
fa7fb644ea Added translation using Weblate (Bulgarian) 2022-05-08 20:44:02 +00:00
Mathias Rasmussen
13b996171a Translated using Weblate (Danish)
Currently translated at 1.9% (8 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/da/
2022-05-07 16:49:26 +00:00
Kim Dannemand
77bb3870bf Translated using Weblate (Danish)
Currently translated at 1.9% (8 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/da/
2022-05-07 16:49:26 +00:00
vabene1111
9863303a5e Merge remote-tracking branch 'origin/develop' into develop 2022-05-06 20:32:14 +02:00
vabene1111
0caccc3da8 updated it and ru languages 2022-05-06 20:31:57 +02:00
vabene1111
b75427b86d Added translation using Weblate (Danish) 2022-05-06 18:30:41 +00:00
vabene1111
054c4ec61a Added translation using Weblate (Danish) 2022-05-06 18:30:05 +00:00
vabene1111
8da21f9914 added specific build files for raspi (armv7) 2022-05-06 20:28:20 +02:00
vabene1111
99ba512862 Merge branch 'beta' into develop 2022-05-06 20:24:27 +02:00
vabene1111
eab59fcbd8 Update docker-publish-latest-raspi.yml 2022-05-06 16:08:13 +02:00
vabene1111
484da2200e added some debug to auto add shopping signal 2022-05-06 15:34:28 +02:00
vabene1111
330bb6d954 testing extra raspi build 2022-05-06 15:07:54 +02:00
axeron2036
d4b6c8da04 Translated using Weblate (Russian)
Currently translated at 80.3% (331 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/ru/
2022-05-06 06:32:14 +00:00
Tomasz Klimczak
a5ef438cfe Translated using Weblate (Polish)
Currently translated at 100.0% (412 of 412 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/pl/
2022-05-04 19:32:09 +00:00
vabene1111
de196c716b build test openssl 2022-05-04 20:33:27 +02:00
vabene1111
cb248a1f19 beta build test libressl 2022-05-04 20:33:10 +02:00
vabene1111
df2f1b2b7c testing armv7 build solutuin
taken from https://github.com/healthchecks/healthchecks/issues/568#issuecomment-942047344
2022-05-04 17:18:25 +02:00
vabene1111
36e26d8009 Merge branch 'master' into beta 2022-05-04 17:18:08 +02:00
vabene1111
a5973de02b Merge branch 'develop' 2022-05-04 17:11:35 +02:00
vabene1111
68f272bc25 fixed adding supermarket category to supermarket frontend desync 2022-05-04 15:20:40 +02:00
vabene1111
b66a5c1ee9 Merge branch 'develop' 2022-05-04 15:01:14 +02:00
vabene1111
bfc42638a4 fixed supermarket category ordering 2022-05-04 15:00:39 +02:00
vabene1111
a8c9689b43 fixed ability to disabled auto sync 2022-05-04 14:47:53 +02:00
vabene1111
26ff3f56ea Merge branch 'develop' into beta 2022-05-03 16:58:44 +02:00
vabene1111
a49993e399 testing new cryptography version 2022-05-03 16:58:39 +02:00
Tomasz Klimczak
9f42226224 Translated using Weblate (Polish)
Currently translated at 100.0% (395 of 395 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/pl/
2022-05-02 22:32:09 +00:00
vabene1111
8f4c00df0b use old property as other sometimes fails 2022-05-02 15:52:15 +02:00
vabene1111
6cebec86c5 Merge branch 'master' into develop 2022-05-02 15:25:47 +02:00
vabene1111
8f5b017857 dont build arm 2022-05-01 21:51:57 +02:00
vabene1111
483bc8f1b7 Merge branch 'develop' into beta 2022-04-29 21:58:13 +02:00
vabene1111
ba493e3e19 Merge branch 'develop' into beta 2022-04-25 09:40:04 +02:00
70 changed files with 53325 additions and 2178 deletions

View File

@@ -0,0 +1,48 @@
name: publish beta raspi image docker
on:
push:
branches:
- 'beta'
jobs:
build:
if: github.repository_owner == 'TandoorRecipes'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
# Update Version number
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = 'beta'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
# Build Vue frontend
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
working-directory: ./vue
run: yarn install
- name: Build dependencies
working-directory: ./vue
run: yarn build
# Build container
- name: Build and publish image
uses: ilteoood/docker_buildx@master
with:
publish: true
imageName: vabene1111/recipes
tag: beta-raspi
dockerFile: Dockerfile-raspi
platform: linux/arm/v7
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}
# Send discord notification
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_BETA_WEBHOOK }}
uses: Ilshidur/action-discord@0.3.2
with:
args: '🚀 The BETA Image has been updated! 🥳'

View File

@@ -35,6 +35,7 @@ jobs:
publish: true
imageName: vabene1111/recipes
tag: beta
platform: linux/amd64,linux/arm64
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}
# Send discord notification

View File

@@ -0,0 +1,45 @@
name: publish latest raspi image docker
on:
push:
tags:
- '*'
jobs:
build:
if: github.repository_owner == 'TandoorRecipes'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Get version number
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}-raspi
# Update Version number
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}-raspi'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
# Build Vue frontend
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
working-directory: ./vue
run: yarn install
- name: Build dependencies
working-directory: ./vue
run: yarn build
# Build container
- name: Build and publish image
uses: ilteoood/docker_buildx@master
with:
publish: true
imageName: vabene1111/recipes
dockerFile: Dockerfile-raspi
platform: linux/arm/v7
tag: latest-raspi
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -38,6 +38,7 @@ jobs:
with:
publish: true
imageName: vabene1111/recipes
platform: linux/amd64,linux/arm64
tag: latest
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -0,0 +1,47 @@
name: publish tagged raspi release docker
on:
release:
types: [published]
jobs:
build:
if: github.repository_owner == 'TandoorRecipes'
runs-on: ubuntu-latest
name: Build image job
steps:
- name: Checkout master
uses: actions/checkout@master
- name: Get version number
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
# Update Version number
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
# Build Vue frontend
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
working-directory: ./vue
run: yarn install
- name: Build dependencies
working-directory: ./vue
run: yarn build
# Build container
- name: Build and publish image
uses: ilteoood/docker_buildx@master
with:
publish: true
imageName: vabene1111/recipes
dockerFile: Dockerfile-raspi
platform: linux/arm/v7
tag: ${{ steps.get_version.outputs.VERSION }}-raspi
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -40,6 +40,7 @@ jobs:
with:
publish: true
imageName: vabene1111/recipes
platform: linux/amd64,linux/arm64
tag: ${{ steps.get_version.outputs.VERSION }}
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -15,7 +15,7 @@ WORKDIR /opt/recipes
COPY requirements.txt ./
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev libressl-dev libffi-dev cargo openssl-dev openldap-dev python3-dev && \
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev && \
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
python -m venv venv && \
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \

33
Dockerfile-raspi Normal file
View File

@@ -0,0 +1,33 @@
# builds of cryptography for raspberry pi (or better arm v7) fail for some
FROM python:3.9-alpine3.15
#Install all dependencies.
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography openldap gcompat
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
#This port will be used by gunicorn.
EXPOSE 8080
#Create app dir and install requirements.
RUN mkdir /opt/recipes
WORKDIR /opt/recipes
COPY requirements.txt ./
RUN \
if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN apk add --no-cache --virtual .build-deps gcc musl-dev zlib-dev jpeg-dev libwebp-dev python3-dev && \
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
python -m venv venv && \
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \
venv/bin/pip install wheel==0.37.1 && \
venv/bin/pip install -r requirements.txt --no-cache-dir --no-binary=Pillow && \
apk --purge del .build-deps
#Copy project and execute it.
COPY . ./
RUN chmod +x boot.sh
ENTRYPOINT ["/opt/recipes/boot.sh"]

View File

@@ -9,6 +9,8 @@ from recipe_scrapers._utils import get_host_name, normalize_string
from cookbook.helper import recipe_url_import as helper
from cookbook.helper.scrapers.scrapers import text_scraper
from recipe_scrapers import scrape_me
from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
def get_recipe_from_source(text, url, request):
@@ -63,34 +65,41 @@ def get_recipe_from_source(text, url, request):
html_data = []
images = []
text = unquote(text)
scrape = None
try:
parse_list.append(remove_graph(json.loads(text)))
if not url and 'url' in parse_list[0]:
url = parse_list[0]['url']
scrape = text_scraper("<script type='application/ld+json'>" + text + "</script>", url=url)
if url:
try:
scrape = scrape_me(url_path=url, wild_mode=True)
except(NoSchemaFoundInWildMode):
pass
if not scrape:
try:
parse_list.append(remove_graph(json.loads(text)))
if not url and 'url' in parse_list[0]:
url = parse_list[0]['url']
scrape = text_scraper("<script type='application/ld+json'>" + text + "</script>", url=url)
except JSONDecodeError:
soup = BeautifulSoup(text, "html.parser")
html_data = get_from_html(soup)
images += get_images_from_source(soup, url)
for el in soup.find_all('script', type='application/ld+json'):
el = remove_graph(el)
if not url and 'url' in el:
url = el['url']
if type(el) == list:
for le in el:
parse_list.append(le)
elif type(el) == dict:
parse_list.append(el)
for el in soup.find_all(type='application/json'):
el = remove_graph(el)
if type(el) == list:
for le in el:
parse_list.append(le)
elif type(el) == dict:
parse_list.append(el)
scrape = text_scraper(text, url=url)
except JSONDecodeError:
soup = BeautifulSoup(text, "html.parser")
html_data = get_from_html(soup)
images += get_images_from_source(soup, url)
for el in soup.find_all('script', type='application/ld+json'):
el = remove_graph(el)
if not url and 'url' in el:
url = el['url']
if type(el) == list:
for le in el:
parse_list.append(le)
elif type(el) == dict:
parse_list.append(el)
for el in soup.find_all(type='application/json'):
el = remove_graph(el)
if type(el) == list:
for le in el:
parse_list.append(le)
elif type(el) == dict:
parse_list.append(el)
scrape = text_scraper(text, url=url)
recipe_json = helper.get_from_scraper(scrape, request)

View File

@@ -114,7 +114,14 @@ def get_from_scraper(scrape, request):
except Exception:
pass
if source_url := scrape.canonical_url():
try:
source_url = scrape.canonical_url()
except Exception:
try:
source_url = scrape.url
except Exception:
pass
if source_url:
recipe_json['source_url'] = source_url
try:
keywords.append(source_url.replace('http://', '').replace('https://', '').split('/')[0])
@@ -129,9 +136,11 @@ def get_from_scraper(scrape, request):
ingredient_parser = IngredientParser(request, True)
recipe_json['steps'] = []
for i in parse_instructions(scrape.instructions()):
recipe_json['steps'].append({'instruction': i, 'ingredients': [], })
try:
for i in parse_instructions(scrape.instructions()):
recipe_json['steps'].append({'instruction': i, 'ingredients': [], })
except Exception:
pass
if len(recipe_json['steps']) == 0:
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })

View File

@@ -1,6 +1,6 @@
from bs4 import BeautifulSoup
from json import JSONDecodeError
from recipe_scrapers import SCRAPERS, get_host_name
from recipe_scrapers import SCRAPERS
from recipe_scrapers._factory import SchemaScraperFactory
from recipe_scrapers._schemaorg import SchemaOrg
@@ -15,13 +15,7 @@ SCRAPERS.update(CUSTOM_SCRAPERS)
def text_scraper(text, url=None):
domain = None
if url:
domain = get_host_name(url)
if domain in SCRAPERS:
scraper_class = SCRAPERS[domain]
else:
scraper_class = SchemaScraperFactory.SchemaScraper
scraper_class = SchemaScraperFactory.SchemaScraper
class TextScraper(scraper_class):
def __init__(

View File

@@ -6,6 +6,7 @@ from gettext import gettext as _
from io import BytesIO
import requests
import validators
import yaml
from cookbook.helper.ingredient_parser import IngredientParser
@@ -59,8 +60,10 @@ class CookBookApp(Integration):
if len(images) > 0:
try:
response = requests.get(images[0])
self.import_recipe_image(recipe, BytesIO(response.content))
url = images[0]
if validators.url(url, public=True):
response = requests.get(url)
self.import_recipe_image(recipe, BytesIO(response.content))
except Exception as e:
print('failed to import image ', str(e))

View File

@@ -5,6 +5,7 @@ from io import BytesIO
from gettext import gettext as _
import requests
import validators
from lxml import etree
from cookbook.helper.ingredient_parser import IngredientParser
@@ -28,17 +29,17 @@ class Cookmate(Integration):
name=recipe_xml.find('title').text.strip(),
created_by=self.request.user, internal=True, space=self.request.space)
if recipe_xml.find('preptime') is not None:
if recipe_xml.find('preptime') is not None and recipe_xml.find('preptime').text is not None:
recipe.working_time = parse_time(recipe_xml.find('preptime').text.strip())
if recipe_xml.find('cooktime') is not None:
if recipe_xml.find('cooktime') is not None and recipe_xml.find('cooktime').text is not None:
recipe.waiting_time = parse_time(recipe_xml.find('cooktime').text.strip())
if recipe_xml.find('quantity') is not None:
if recipe_xml.find('quantity') is not None and recipe_xml.find('quantity').text is not None:
recipe.servings = parse_servings(recipe_xml.find('quantity').text.strip())
recipe.servings_text = parse_servings_text(recipe_xml.find('quantity').text.strip())
if recipe_xml.find('url') is not None:
if recipe_xml.find('url') is not None and recipe_xml.find('url').text is not None:
recipe.source_url = recipe_xml.find('url').text.strip()
if recipe_xml.find('description') is not None: # description is a list of <li>'s with text
@@ -64,7 +65,9 @@ class Cookmate(Integration):
if recipe_xml.find('imageurl') is not None:
try:
response = requests.get(recipe_xml.find('imageurl').text.strip())
url = recipe_xml.find('imageurl').text.strip()
if validators.url(url, public=True):
response = requests.get(url)
self.import_recipe_image(recipe, BytesIO(response.content))
except Exception as e:
print('failed to import image ', str(e))

View File

@@ -27,7 +27,7 @@ class Paprika(Integration):
recipe.description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
try:
if 'servings' in recipe_json['servings']:
if 'servings' in recipe_json:
recipe.servings = parse_servings(recipe_json['servings'])
recipe.servings_text = parse_servings_text(recipe_json['servings'])

View File

@@ -78,7 +78,11 @@ class Plantoeat(Integration):
current_recipe = ''
for fl in file.readlines():
line = fl.decode("windows-1250")
try:
line = fl.decode("utf-8")
except UnicodeDecodeError:
line = fl.decode("windows-1250")
if line.startswith('--------------'):
if current_recipe != '':
recipe_list.append(current_recipe)

View File

@@ -5,6 +5,7 @@ from io import BytesIO
from zipfile import ZipFile
import requests
import validators
from django.utils.translation import gettext as _
from cookbook.helper.image_processing import get_filetype
@@ -123,11 +124,13 @@ class RecetteTek(Integration):
self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name)), filetype=get_filetype(image_file_name))
else:
if file['originalPicture'] != '':
response = requests.get(file['originalPicture'])
if imghdr.what(BytesIO(response.content)) is not None:
self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture']))
else:
raise Exception("Original image failed to download.")
url = file['originalPicture']
if validators.url(url, public=True):
response = requests.get(url)
if imghdr.what(BytesIO(response.content)) is not None:
self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture']))
else:
raise Exception("Original image failed to download.")
except Exception as e:
print(recipe.name, ': failed to import image ', str(e))

View File

@@ -2,6 +2,7 @@ import json
from io import BytesIO
import requests
import validators
from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration
@@ -51,8 +52,10 @@ class RecipeSage(Integration):
if len(file['image']) > 0:
try:
response = requests.get(file['image'][0])
self.import_recipe_image(recipe, BytesIO(response.content))
url = file['image'][0]
if validators.url(url, public=True):
response = requests.get(url)
self.import_recipe_image(recipe, BytesIO(response.content))
except Exception as e:
print('failed to import image ', str(e))

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -9,21 +9,21 @@
# miguel angel <mlopezifu@alumnos.unex.es>, 2020
# Miguel Canteras <mcanteras@gmail.com>, 2021
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-29 18:42+0200\n"
"PO-Revision-Date: 2020-06-02 19:28+0000\n"
"Last-Translator: Miguel Canteras <mcanteras@gmail.com>, 2021\n"
"Language-Team: Spanish (https://www.transifex.com/django-recipes/"
"teams/110507/es/)\n"
"PO-Revision-Date: 2022-05-17 21:32+0000\n"
"Last-Translator: Gabriel Tapias <gtapias@gmail.com>\n"
"Language-Team: Spanish <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/es/>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.10.1\n"
#: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34
#: .\cookbook\templates\space.html:49 .\cookbook\templates\stats.html:28
@@ -31,16 +31,12 @@ msgid "Ingredients"
msgstr "Ingredientes"
#: .\cookbook\forms.py:56
#, fuzzy
#| msgid "Default"
msgid "Default unit"
msgstr "Por defecto"
msgstr "Unidad por defecto"
#: .\cookbook\forms.py:57
#, fuzzy
#| msgid "System Information"
msgid "Use fractions"
msgstr "Información del Sistema"
msgstr "Usar fracciones"
#: .\cookbook\forms.py:58
msgid "Use KJ"
@@ -59,38 +55,28 @@ msgid "Sticky navbar"
msgstr ""
#: .\cookbook\forms.py:62
#, fuzzy
#| msgid "Default"
msgid "Default page"
msgstr "Por defecto"
msgstr "Página por defecto"
#: .\cookbook\forms.py:63
#, fuzzy
#| msgid "Shopping Recipes"
msgid "Show recent recipes"
msgstr "Recetas en el carro de la compra"
msgstr "Mostrar recetas recientes"
#: .\cookbook\forms.py:64
#, fuzzy
#| msgid "Search"
msgid "Search style"
msgstr "Buscar"
msgstr "Estilo de búsqueda"
#: .\cookbook\forms.py:65
msgid "Plan sharing"
msgstr ""
#: .\cookbook\forms.py:66
#, fuzzy
#| msgid "Ingredients"
msgid "Ingredient decimal places"
msgstr "Ingredientes"
msgstr "Número de decimales del ingrediente"
#: .\cookbook\forms.py:67
#, fuzzy
#| msgid "Shopping list currently empty"
msgid "Shopping list auto sync period"
msgstr "Lista de la compra vacía"
msgstr "Período de sincronización automática de la lista de compras"
#: .\cookbook\forms.py:68 .\cookbook\templates\recipe_view.html:21
#: .\cookbook\templates\space.html:76 .\cookbook\templates\stats.html:47
@@ -128,20 +114,14 @@ msgid "Display nutritional energy amounts in joules instead of calories"
msgstr ""
#: .\cookbook\forms.py:80
#, fuzzy
#| msgid ""
#| "Users with whom newly created meal plan/shopping list entries should be "
#| "shared by default."
msgid "Users with whom newly created meal plans should be shared by default."
msgstr ""
"Usuarios con los que las entradas recién creadas del plan de comida/lista de "
"la compra deben compartirse de forma predeterminada."
"Usuarios con los que las entradas recién creadas del plan de comida deben "
"compartirse de forma predeterminada."
#: .\cookbook\forms.py:81
#, fuzzy
#| msgid "Open Shopping List"
msgid "Users with whom to share shopping lists."
msgstr "Abrir Lista de la Compra"
msgstr "Usuarios con quienes compartir listas de compra."
#: .\cookbook\forms.py:83
msgid "Show recently viewed recipes on search page."
@@ -361,10 +341,8 @@ msgid ""
msgstr ""
#: .\cookbook\forms.py:466
#, fuzzy
#| msgid "Search"
msgid "Search Method"
msgstr "Buscar"
msgstr "Método de Búsqueda"
#: .\cookbook\forms.py:467
msgid "Fuzzy Lookups"
@@ -389,10 +367,8 @@ msgid "Fuzzy Search"
msgstr "Buscar"
#: .\cookbook\forms.py:472
#, fuzzy
#| msgid "Text"
msgid "Full Text"
msgstr "Texto"
msgstr "Texto Completo"
#: .\cookbook\forms.py:497
msgid ""
@@ -437,10 +413,8 @@ msgid "Prefix to add when copying list to the clipboard."
msgstr ""
#: .\cookbook\forms.py:514
#, fuzzy
#| msgid "Shopping List"
msgid "Share Shopping List"
msgstr "Lista de la Compra"
msgstr "Compartir Lista de la Compra"
#: .\cookbook\forms.py:515
msgid "Autosync"
@@ -463,10 +437,8 @@ msgid "Default Delay Hours"
msgstr ""
#: .\cookbook\forms.py:520
#, fuzzy
#| msgid "Select Supermarket"
msgid "Filter to Supermarket"
msgstr "Seleccionar supermercado"
msgstr "Filtrar según Supermercado"
#: .\cookbook\forms.py:521
msgid "Recent Days"
@@ -499,10 +471,8 @@ msgid "Fields on food that should be inherited by default."
msgstr "Alimento que se va a reemplazar."
#: .\cookbook\forms.py:548
#, fuzzy
#| msgid "Show recently viewed recipes on search page."
msgid "Show recipe counts on search filters"
msgstr "Muestra recetas vistas recientemente en la página de búsqueda."
msgstr "Mostrar cantidad de recetas en los filtros de búsquedas"
#: .\cookbook\helper\AllAuthCustomAdapter.py:36
msgid ""
@@ -546,10 +516,8 @@ msgid "One of queryset or hash_key must be provided"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:152
#, fuzzy
#| msgid "You must provide at least a recipe or a title."
msgid "You must supply a servings size"
msgstr "Debe proporcionar al menos una receta o un título."
msgstr "Debe proporcionar un tamaño de porción"
#: .\cookbook\helper\template_helper.py:64
#: .\cookbook\helper\template_helper.py:66
@@ -586,22 +554,17 @@ msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:235
#, fuzzy, python-format
#| msgid "Imported new recipe!"
#, python-format
msgid "Imported %s recipes."
msgstr "¡Nueva receta importada!"
msgstr "Se importaron %s recetas."
#: .\cookbook\integration\paprika.py:46
#, fuzzy
#| msgid "Note"
msgid "Notes"
msgstr "Nota"
msgstr "Notas"
#: .\cookbook\integration\paprika.py:49
#, fuzzy
#| msgid "Information"
msgid "Nutritional Information"
msgstr "Información"
msgstr "Información Nutricional"
#: .\cookbook\integration\paprika.py:53
msgid "Source"
@@ -715,10 +678,8 @@ msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1138
#, fuzzy
#| msgid "Food"
msgid "Food Alias"
msgstr "Comida"
msgstr "Alias de la Comida"
#: .\cookbook\models.py:1138
#, fuzzy

View File

@@ -13,10 +13,10 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-29 18:42+0200\n"
"PO-Revision-Date: 2022-04-05 10:31+0000\n"
"PO-Revision-Date: 2022-05-10 15:32+0000\n"
"Last-Translator: Jesse <jesse.kamps@pm.me>\n"
"Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/recipes-"
"backend/nl/>\n"
"Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/nl/>\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -84,7 +84,7 @@ msgstr "Opmerkingen"
#: .\cookbook\forms.py:69
msgid "Left-handed mode"
msgstr ""
msgstr "Linkshandigen modus"
#: .\cookbook\forms.py:73
msgid ""
@@ -161,7 +161,7 @@ msgstr "Sluit ingrediënten die op voorraad zijn uit."
#: .\cookbook\forms.py:93
msgid "Will optimize the UI for use with your left hand."
msgstr ""
msgstr "Optimaliseert de gebruikersinterface voor gebruik met je linkerhand."
#: .\cookbook\forms.py:110
msgid ""
@@ -547,7 +547,7 @@ msgstr "Sjablooncode kon niet verwerkt worden."
#: .\cookbook\integration\copymethat.py:42
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
msgstr "Favoriet"
#: .\cookbook\integration\copymethat.py:71
#: .\cookbook\integration\recettetek.py:53
@@ -722,10 +722,8 @@ msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1163
#, fuzzy
#| msgid "Foods"
msgid "Food"
msgstr "Ingrediënten"
msgstr "Ingrediënt"
#: .\cookbook\models.py:1164 .\cookbook\templates\base.html:138
msgid "Keyword"
@@ -1138,10 +1136,8 @@ msgstr "Geschiedenis"
#: .\cookbook\templates\base.html:252
#: .\cookbook\templates\ingredient_editor.html:7
#: .\cookbook\templates\ingredient_editor.html:13
#, fuzzy
#| msgid "Ingredients"
msgid "Ingredient Editor"
msgstr "Ingrediënten"
msgstr "Ingrediënteneditor"
#: .\cookbook\templates\base.html:264
#: .\cookbook\templates\export_response.html:7
@@ -1198,11 +1194,11 @@ msgstr "Uitloggen"
#: .\cookbook\templates\base.html:342
msgid "You are using the free version of Tandor"
msgstr ""
msgstr "Je gebruikt de gratis versie van Tandoor"
#: .\cookbook\templates\base.html:342
msgid "Upgrade Now"
msgstr ""
msgstr "Upgrade nu"
#: .\cookbook\templates\batch\edit.html:6
msgid "Batch edit Category"
@@ -1731,7 +1727,7 @@ msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
msgstr "Terug"
#: .\cookbook\templates\recipe_view.html:26
msgid "by"
@@ -2180,17 +2176,15 @@ msgstr "Maak Superuser acount"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
#, fuzzy
#| msgid "Social Login"
msgid "Social Network Login Failure"
msgstr "Socials login"
msgstr "Inloggen op sociaal netwerk mislukt"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
#, fuzzy
#| msgid "An error occurred attempting to move "
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr "Er is een error opgetreden bij het verplaatsen "
msgstr ""
"Er is een fout opgetreden tijdens het inloggen via je sociale netwerk "
"account."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:15
@@ -2542,33 +2536,39 @@ msgstr ""
"op volledige tekst ondersteund."
#: .\cookbook\views\api.py:671
#, fuzzy
#| msgid "ID of keyword a recipe should have. For multiple repeat parameter."
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID van etiket dat een recept moet hebben. Herhaal parameter voor meerdere."
"ID van etiket dat een recept moet hebben. Herhaal parameter voor meerdere. "
"Gelijkwaardig aan keywords_or"
#: .\cookbook\views\api.py:674
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Etiket ID, herhaal voor meerdere. Geeft recepten met elk geselecteerd etiket "
"weer"
#: .\cookbook\views\api.py:677
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Etiket ID, herhaal voor meerdere. Geeft recepten met alle geselecteerde "
"etiketten weer."
#: .\cookbook\views\api.py:680
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Etiket ID, herhaal voor meerdere. Sluit recepten met één van de etiketten "
"uit."
#: .\cookbook\views\api.py:683
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Etiket ID, herhaal voor meerdere. Sluit recepten met alle etiketten uit."
#: .\cookbook\views\api.py:685
msgid "ID of food a recipe should have. For multiple repeat parameter."
@@ -2579,18 +2579,25 @@ msgstr ""
#: .\cookbook\views\api.py:688
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. Geeft recepten met elk ingrediënt weer"
#: .\cookbook\views\api.py:690
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. Geef recepten met alle ingrediënten "
"weer."
#: .\cookbook\views\api.py:692
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. sluit recepten met één van de "
"ingrediënten uit."
#: .\cookbook\views\api.py:694
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. Sluit recepten met alle ingrediënten "
"uit."
#: .\cookbook\views\api.py:695
msgid "ID of unit a recipe should have."
@@ -2600,7 +2607,7 @@ msgstr "ID van eenheid dat een recept moet hebben."
msgid ""
"Rating a recipe should have or greater. [0 - 5] Negative value filters "
"rating less than."
msgstr ""
msgstr "Een waardering van een recept gaat van 0 tot 5."
#: .\cookbook\views\api.py:698
msgid "ID of book a recipe should be in. For multiple repeat parameter."
@@ -2609,19 +2616,20 @@ msgstr ""
#: .\cookbook\views\api.py:700
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
msgstr "Boek ID, herhaal voor meerdere. Geeft recepten uit alle boeken weer"
#: .\cookbook\views\api.py:702
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
msgstr "Boek IDs, herhaal voor meerdere. Geeft recepten weer uit alle boeken."
#: .\cookbook\views\api.py:704
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"Boek IDs, herhaal voor meerdere. Sluit recepten uit elk van de boeken uit."
#: .\cookbook\views\api.py:706
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
msgstr "Boek IDs, herhaal voor meerdere. Sluit recepten uit alle boeken uit."
#: .\cookbook\views\api.py:708
msgid "If only internal recipes should be returned. [true/<b>false</b>]"
@@ -2642,37 +2650,46 @@ msgid ""
"Filter recipes cooked X times or more. Negative values returns cooked less "
"than X times"
msgstr ""
"Filter recepten X maal of meer bereid. Negatieve waarden geven minder dan X "
"keer bereide recepten weer"
#: .\cookbook\views\api.py:716
msgid ""
"Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
"Filter recepten op laatst bereid op of na JJJJ-MM-DD. Voorafgaand - filters "
"op of voor datum."
#: .\cookbook\views\api.py:718
msgid ""
"Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
"Filter recepten aangemaakt op of na JJJJ-MM-DD. Voorafgaand - filters op of "
"voor datum."
#: .\cookbook\views\api.py:720
msgid ""
"Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
"Filter recepten op geüpdatet op of na JJJJ-MM-DD. Voorafgaand - filters op "
"of voor datum."
#: .\cookbook\views\api.py:722
msgid ""
"Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
"Filter recepten op laatst bekeken op of na JJJJ-MM-DD. Voorafgaand - filters "
"op of voor datum."
#: .\cookbook\views\api.py:724
#, fuzzy
#| msgid "If only internal recipes should be returned. [true/<b>false</b>]"
msgid "Filter recipes that can be made with OnHand food. [true/<b>false</b>]"
msgstr ""
"Wanneer alleen interne recepten gevonden moeten worden. [waar/<b>onwaar</b>]"
"Filter recepten die bereid kunnen worden met ingrediënten die op voorraad "
"zijn. [waar/<b>onwaar</b>]"
#: .\cookbook\views\api.py:880
msgid ""
@@ -2718,7 +2735,7 @@ msgstr "Verbinding geweigerd."
#: .\cookbook\views\api.py:1200
msgid "Bad URL Schema."
msgstr ""
msgstr "Verkeerd URL schema."
#: .\cookbook\views\api.py:1206
msgid "No usable data could be found."
@@ -2812,10 +2829,8 @@ msgid "Shopping Categories"
msgstr "Boodschappencategorieën"
#: .\cookbook\views\lists.py:187
#, fuzzy
#| msgid "Filter"
msgid "Custom Filters"
msgstr "Filtreren"
msgstr "Aangepaste filters"
#: .\cookbook\views\lists.py:224
msgid "Steps"

View File

@@ -4,6 +4,8 @@ import os
from datetime import datetime
import requests
import validators
from cookbook.models import Recipe, RecipeImport, SyncLog
from cookbook.provider.provider import Provider
@@ -104,9 +106,11 @@ class Dropbox(Provider):
recipe.link = Dropbox.get_share_link(recipe)
recipe.save()
response = requests.get(recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.'))
url = recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.')
if validators.url(url, public=True):
response = requests.get(url)
return io.BytesIO(response.content)
return io.BytesIO(response.content)
@staticmethod
def rename_file(recipe, new_name):

View File

@@ -4,6 +4,7 @@ import tempfile
from datetime import datetime
import requests
import validators
import webdav3.client as wc
from cookbook.models import Recipe, RecipeImport, SyncLog
from cookbook.provider.provider import Provider
@@ -92,20 +93,21 @@ class Nextcloud(Provider):
"Content-Type": "application/json"
}
r = requests.get(
url,
headers=headers,
auth=HTTPBasicAuth(
recipe.storage.username, recipe.storage.password
if validators.url(url, public=True):
r = requests.get(
url,
headers=headers,
auth=HTTPBasicAuth(
recipe.storage.username, recipe.storage.password
)
)
)
response_json = r.json()
for element in response_json['ocs']['data']:
if element['share_type'] == '3':
return element['url']
response_json = r.json()
for element in response_json['ocs']['data']:
if element['share_type'] == '3':
return element['url']
return Nextcloud.create_share_link(recipe)
return Nextcloud.create_share_link(recipe)
@staticmethod
def get_file(recipe):

View File

@@ -104,21 +104,32 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs):
@receiver(post_save, sender=MealPlan)
def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs):
print("MEAL_AUTO_ADD Signal trying to auto add to shopping")
if not instance:
return
user = instance.get_owner()
with scope(space=instance.space):
slr_exists = instance.shoppinglistrecipe_set.exists()
if not created and slr_exists:
for x in instance.shoppinglistrecipe_set.all():
# assuming that permissions checks for the MealPlan have happened upstream
if instance.servings != x.servings:
SLR = RecipeShoppingEditor(id=x.id, user=user, space=instance.space)
SLR.edit_servings(servings=instance.servings)
elif not user.userpreference.mealplan_autoadd_shopping or not instance.recipe:
print("MEAL_AUTO_ADD Instance is none")
return
if created:
SLR = RecipeShoppingEditor(user=user, space=instance.space)
SLR.create(mealplan=instance, servings=instance.servings)
try:
space = instance.get_space()
user = instance.get_owner()
with scope(space=space):
slr_exists = instance.shoppinglistrecipe_set.exists()
if not created and slr_exists:
for x in instance.shoppinglistrecipe_set.all():
# assuming that permissions checks for the MealPlan have happened upstream
if instance.servings != x.servings:
SLR = RecipeShoppingEditor(id=x.id, user=user, space=instance.space)
SLR.edit_servings(servings=instance.servings)
elif not user.userpreference.mealplan_autoadd_shopping or not instance.recipe:
print("MEAL_AUTO_ADD No recipe or no setting")
return
if created:
SLR = RecipeShoppingEditor(user=user, space=space)
SLR.create(mealplan=instance, servings=instance.servings)
print("MEAL_AUTO_ADD Created SLR")
except AttributeError:
pass

View File

@@ -77,7 +77,7 @@
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
<a class="navbar-brand p-0 me-2 justify-content-center" href="{% base_path request 'base' %}"
aria-label="Tandoor">
<img class="brand-icon" src="{% static 'assets/brand_logo.svg' %}" alt="Logo">
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="Logo">
</a>
{% endif %}
{% endif %}
@@ -91,7 +91,7 @@
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
<a class="navbar-brand p-0 me-2 justify-content-center" href="{% base_path request 'base' %}"
aria-label="Tandoor">
<img class="brand-icon" src="{% static 'assets/brand_logo.svg' %}" alt="Logo">
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="Logo">
</a>
{% endif %}
{% endif %}

View File

@@ -15,6 +15,7 @@ from cookbook.helper.mdx_attributes import MarkdownFormatExtension
from cookbook.helper.mdx_urlize import UrlizeExtension
from cookbook.models import Space, get_model_name
from recipes import settings
from recipes.settings import STATIC_URL
register = template.Library()
@@ -157,7 +158,7 @@ def base_path(request, path_type):
elif path_type == 'script':
return request.META.get('HTTP_X_SCRIPT_NAME', '')
elif path_type == 'static_base':
return static('vue/manifest.json').replace('vue/manifest.json', '')
return STATIC_URL
@register.simple_tag

View File

@@ -6,6 +6,7 @@ import uuid
from collections import OrderedDict
import requests
import validators
from PIL import UnidentifiedImageError
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
@@ -14,7 +15,7 @@ from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import FieldError, ValidationError
from django.core.files import File
from django.db.models import (Case, Count, Exists, F, IntegerField, OuterRef, ProtectedError, Q,
from django.db.models import (Case, Count, Exists, OuterRef, ProtectedError, Q,
Subquery, Value, When)
from django.db.models.fields.related import ForeignObjectRel
from django.db.models.functions import Coalesce, Lower
@@ -24,7 +25,6 @@ from django.urls import reverse
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event
from recipe_scrapers import NoSchemaFoundInWildMode, WebsiteNotImplementedError, scrape_me
from requests.exceptions import MissingSchema
from rest_framework import decorators, status, viewsets
from rest_framework.exceptions import APIException, PermissionDenied
@@ -34,6 +34,7 @@ from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response
from rest_framework.viewsets import ViewSetMixin
from treebeard.exceptions import InvalidMoveToDescendant, InvalidPosition, PathOverflow
from validators import ValidationFailure
from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.image_processing import handle_image
@@ -43,7 +44,6 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, Cus
group_required)
from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch, old_search
from cookbook.helper.recipe_url_import import get_from_scraper
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food,
FoodInheritField, ImportLog, Ingredient, Keyword, MealPlan, MealType,
@@ -774,16 +774,18 @@ class RecipeViewSet(viewsets.ModelViewSet):
if serializer.is_valid():
serializer.save()
image = None
filetype = ".jpeg" # fall-back to .jpeg, even if wrong, at least users will know it's an image and most image viewers can open it correctly anyways
filetype = ".jpeg" # fall-back to .jpeg, even if wrong, at least users will know it's an image and most image viewers can open it correctly anyways
if 'image' in serializer.validated_data:
image = obj.image
filetype = mimetypes.guess_extension(serializer.validated_data['image'].content_type) or filetype
elif 'image_url' in serializer.validated_data:
try:
response = requests.get(serializer.validated_data['image_url'])
image = File(io.BytesIO(response.content))
filetype = mimetypes.guess_extension(response.headers['content-type']) or filetype
url = serializer.validated_data['image_url']
if validators.url(url, public=True):
response = requests.get(url)
image = File(io.BytesIO(response.content))
filetype = mimetypes.guess_extension(response.headers['content-type']) or filetype
except UnidentifiedImageError as e:
print(e)
pass
@@ -799,6 +801,10 @@ class RecipeViewSet(viewsets.ModelViewSet):
obj.image = File(img, name=f'{uuid.uuid4()}_{obj.pk}{filetype}')
obj.save()
return Response(serializer.data)
else:
obj.image = None
obj.save()
return Response(serializer.data)
return Response(serializer.errors, 400)
@@ -1188,7 +1194,13 @@ def recipe_from_source(request):
# in manual mode request complete page to return it later
if url:
try:
data = requests.get(url, headers=external_request_headers).content
if validators.url(url, public=True):
data = requests.get(url, headers=external_request_headers).content
else:
return JsonResponse({
'error': True,
'msg': _('Invalid Url')
}, status=400)
except requests.exceptions.ConnectionError:
return JsonResponse({
'error': True,
@@ -1199,6 +1211,7 @@ def recipe_from_source(request):
'error': True,
'msg': _('Bad URL Schema.')
}, status=400)
recipe_json, recipe_tree, recipe_html, recipe_images = get_recipe_from_source(data, url, request)
if len(recipe_tree) == 0 and len(recipe_json) == 0:
return JsonResponse({

View File

@@ -343,6 +343,11 @@ ProxyPassReverse / http://localhost:8080/ # replace port
!!!info
Always wait at least 2-3 minutes after the very first start, since migrations will take some time!
!!!warning
If you want to use Tandoor on a Raspberry Pi running a 32-bit operating system you will need to use the following
docker image tags: `latest-raspi`, `beta-raspi` and the versioned `<x.y.z>-raspi`
We strongly recommend using the new 64-bit Raspian image as the 32-bit version is not tested.
If you're having issues with installing Tandoor on your Raspberry Pi or similar device,
follow these instructions:

View File

@@ -366,8 +366,10 @@ USE_TZ = True
LANGUAGES = [
('hy', _('Armenian ')),
('bg', _('Bulgarian')),
('ca', _('Catalan')),
('cs', _('Czech')),
('da', _('Danish')),
('nl', _('Dutch')),
('en', _('English')),
('fr', _('French')),

View File

@@ -1,5 +1,5 @@
Django==3.2.13
cryptography==36.0.2
cryptography==37.0.1
django-annoying==0.10.6
django-autocomplete-light==3.9.4
django-cleanup==6.0.0
@@ -29,7 +29,7 @@ Jinja2==3.1.1
django-webpack-loader==1.4.1
django-js-reverse==0.9.1
django-allauth==0.50.0
recipe-scrapers==13.32.0
recipe-scrapers==13.32.1
django-scopes==1.2.0
pytest==7.1.1
pytest-django==4.5.2
@@ -43,3 +43,4 @@ python-ldap==3.4.0
django-auth-ldap==4.0.0
pytest-factoryboy==2.1.0
pyppeteer==1.0.2
validators==0.19.0

43106
vue/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,15 @@
},
"dependencies": {
"@babel/eslint-parser": "^7.16.0",
"@kangc/v-md-editor": "^1.7.7",
"@kangc/v-md-editor": "^1.7.11",
"@kevinfaguiar/vue-twemoji-picker": "^5.7.4",
"@popperjs/core": "^2.11.2",
"@riophae/vue-treeselect": "^0.4.0",
"@vue/cli": "^5.0.4",
"axios": "^0.26.1",
"babel": "^6.23.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.2.5",
"bootstrap-vue": "^2.21.2",
"core-js": "^3.20.3",
"html2pdf.js": "^0.10.1",
@@ -30,6 +33,7 @@
"vue-infinite-loading": "^2.4.5",
"vue-multiselect": "^2.1.6",
"vue-property-decorator": "^9.1.2",
"vue-sanitize": "^0.2.2",
"vue-simple-calendar": "^5.0.1",
"vue-template-compiler": "^2.6.14",
"vue2-touch-events": "^3.2.2",
@@ -86,4 +90,4 @@
"@vue/cli-plugin-pwa/workbox-webpack-plugin": "^5.1.3",
"coa": "2.0.2"
}
}
}

View File

@@ -43,7 +43,7 @@
<div class="row">
<div class="col col-md-12">
<label for="id_textarea">{{ $t("Information") }}</label>
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh" v-html="export_info.msg" disabled></textarea>
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh" v-html="$sanitize(export_info.msg)" disabled></textarea>
</div>
</div>
<br />
@@ -65,7 +65,8 @@ import LoadingSpinner from "@/components/LoadingSpinner"
import { ApiApiFactory } from "@/utils/openapi/api.ts"
Vue.use(BootstrapVue)
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
export default {
name: "ExportResponseView",
mixins: [ResolveUrlMixin, ToastMixin],

View File

@@ -22,7 +22,7 @@
}}!
</b-badge>
<b-badge variant="primary" v-else class="float-right">
{{ $t('Import_running') }}
{{ $t('import_running') }}
<b-spinner small class="d-inline-block"></b-spinner>
</b-badge>
</h4>
@@ -143,7 +143,7 @@
<b-card>
<textarea id="id_textarea" ref="output_text" class="form-control"
style="height: 50vh"
v-html="import_info.msg"
v-html="$sanitize(import_info.msg)"
disabled></textarea>
</b-card>
</b-collapse>
@@ -168,7 +168,9 @@ import {ResolveUrlMixin, ToastMixin, RandomIconMixin} from "@/utils/utils";
import LoadingSpinner from "@/components/LoadingSpinner";
import {ApiApiFactory} from "@/utils/openapi/api.ts";
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
Vue.use(BootstrapVue)
export default {

View File

@@ -54,14 +54,20 @@
<div class="col-12 col-md-3 calender-options">
<h5>{{ $t("Planner_Settings") }}</h5>
<b-form>
<b-form-group id="UomInput" :label="$t('Period')" :description="$t('Plan_Period_To_Show')" label-for="UomInput">
<b-form-select id="UomInput" v-model="settings.displayPeriodUom" :options="options.displayPeriodUom"></b-form-select>
<b-form-group id="UomInput" :label="$t('Period')" :description="$t('Plan_Period_To_Show')"
label-for="UomInput">
<b-form-select id="UomInput" v-model="settings.displayPeriodUom"
:options="options.displayPeriodUom"></b-form-select>
</b-form-group>
<b-form-group id="PeriodInput" :label="$t('Periods')" :description="$t('Plan_Show_How_Many_Periods')" label-for="PeriodInput">
<b-form-select id="PeriodInput" v-model="settings.displayPeriodCount" :options="options.displayPeriodCount"></b-form-select>
<b-form-group id="PeriodInput" :label="$t('Periods')"
:description="$t('Plan_Show_How_Many_Periods')" label-for="PeriodInput">
<b-form-select id="PeriodInput" v-model="settings.displayPeriodCount"
:options="options.displayPeriodCount"></b-form-select>
</b-form-group>
<b-form-group id="DaysInput" :label="$t('Starting_Day')" :description="$t('Starting_Day')" label-for="DaysInput">
<b-form-select id="DaysInput" v-model="settings.startingDayOfWeek" :options="dayNames"></b-form-select>
<b-form-group id="DaysInput" :label="$t('Starting_Day')" :description="$t('Starting_Day')"
label-for="DaysInput">
<b-form-select id="DaysInput" v-model="settings.startingDayOfWeek"
:options="dayNames"></b-form-select>
</b-form-group>
<b-form-group id="WeekNumInput" :label="$t('Week_Numbers')">
<b-form-checkbox v-model="settings.displayWeekNumbers" name="week_num">
@@ -73,19 +79,25 @@
<div class="col-12 col-md-9 col-lg-6">
<h5>{{ $t("Meal_Types") }}</h5>
<div>
<draggable :list="meal_types" group="meal_types" :empty-insert-threshold="10" @sort="sortMealTypes()" ghost-class="ghost">
<b-card no-body class="mt-1 list-group-item p-2" style="cursor: move" v-for="(meal_type, index) in meal_types" v-hover :key="meal_type.id">
<draggable :list="meal_types" group="meal_types" :empty-insert-threshold="10"
@sort="sortMealTypes()" ghost-class="ghost">
<b-card no-body class="mt-1 list-group-item p-2" style="cursor: move"
v-for="(meal_type, index) in meal_types" v-hover :key="meal_type.id">
<b-card-header class="p-2 border-0">
<div class="row">
<div class="col-2">
<button type="button" class="btn btn-lg shadow-none"><i class="fas fa-arrows-alt-v"></i></button>
<button type="button" class="btn btn-lg shadow-none"><i
class="fas fa-arrows-alt-v"></i></button>
</div>
<div class="col-10">
<h5 class="mt-1 mb-1">
{{ meal_type.icon }} {{ meal_type.name
{{ meal_type.icon }} {{
meal_type.name
}}<span class="float-right text-primary" style="cursor: pointer"
><i class="fa" v-bind:class="{ 'fa-pen': !meal_type.editing, 'fa-save': meal_type.editing }" @click="editOrSaveMealType(index)" aria-hidden="true"></i
></span>
><i class="fa"
v-bind:class="{ 'fa-pen': !meal_type.editing, 'fa-save': meal_type.editing }"
@click="editOrSaveMealType(index)" aria-hidden="true"></i
></span>
</h5>
</div>
</div>
@@ -93,19 +105,27 @@
<b-card-body class="p-4" v-if="meal_type.editing">
<div class="form-group">
<label>{{ $t("Name") }}</label>
<input class="form-control" :placeholder="$t('Name')" v-model="meal_type.name" />
<input class="form-control" :placeholder="$t('Name')"
v-model="meal_type.name"/>
</div>
<div class="form-group">
<emoji-input :field="'icon'" :label="$t('Icon')" :value="meal_type.icon"></emoji-input>
<emoji-input :field="'icon'" :label="$t('Icon')"
:value="meal_type.icon"></emoji-input>
</div>
<div class="form-group">
<label>{{ $t("Color") }}</label>
<input class="form-control" type="color" name="Name" :value="meal_type.color" @change="meal_type.color = $event.target.value" />
<input class="form-control" type="color" name="Name"
:value="meal_type.color"
@change="meal_type.color = $event.target.value"/>
</div>
<b-form-checkbox id="checkbox-1" v-model="meal_type.default" name="default_checkbox" class="mb-2">
<b-form-checkbox id="checkbox-1" v-model="meal_type.default"
name="default_checkbox" class="mb-2">
{{ $t("Default") }}
</b-form-checkbox>
<button class="btn btn-danger" @click="deleteMealType(index)">{{ $t("Delete") }}</button>
<button class="btn btn-danger" @click="deleteMealType(index)">{{
$t("Delete")
}}
</button>
<button class="btn btn-primary float-right" @click="editOrSaveMealType(index)">
{{ $t("Save") }}
</button>
@@ -129,7 +149,9 @@
openEntryEdit(contextData.originalItem.entry)
"
>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-pen"></i> {{ $t("Edit") }}</a>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-pen"></i> {{
$t("Edit")
}}</a>
</ContextMenuItem>
<ContextMenuItem
v-if="contextData && contextData.originalItem && contextData.originalItem.entry.recipe != null"
@@ -138,7 +160,8 @@
openRecipe(contextData.originalItem.entry.recipe)
"
>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-pizza-slice"></i> {{ $t("Recipe") }}</a>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-pizza-slice"></i>
{{ $t("Recipe") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
@@ -146,7 +169,8 @@
moveEntryLeft(contextData)
"
>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-arrow-left"></i> {{ $t("Move") }}</a>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-arrow-left"></i>
{{ $t("Move") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
@@ -154,7 +178,8 @@
moveEntryRight(contextData)
"
>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-arrow-right"></i> {{ $t("Move") }}</a>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-arrow-right"></i>
{{ $t("Move") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
@@ -164,21 +189,14 @@
>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-copy"></i> {{ $t("Clone") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
$refs.menu.close()
addToShopping(contextData)
"
>
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-shopping-cart"></i> {{ $t("Add_to_Shopping") }}</a>
</ContextMenuItem>
<ContextMenuItem
@click="
$refs.menu.close()
deleteEntry(contextData)
"
>
<a class="dropdown-item p-2 text-danger" href="javascript:void(0)"><i class="fas fa-trash"></i> {{ $t("Delete") }}</a>
<a class="dropdown-item p-2 text-danger" href="javascript:void(0)"><i class="fas fa-trash"></i>
{{ $t("Delete") }}</a>
</ContextMenuItem>
</template>
</ContextMenu>
@@ -190,68 +208,43 @@
@delete-entry="deleteEntry"
@reload-meal-types="refreshMealTypes"
></meal-plan-edit-modal>
<template>
<div>
<b-sidebar id="sidebar-shopping" :title="$t('Shopping_list')" backdrop right shadow="sm">
<div class="row p-1 no-gutters">
<div class="col-12 mt-1" v-if="shopping_list.length === 0">
<p class="p-3">{{ $t("Shopping_List_Empty") }}</p>
</div>
<div class="col-12 mt-1" v-for="entry in shopping_list" v-bind:key="entry.id">
<b-card :header="`${entry.meal_type.icon} ${entry.recipe_name}`" no-body>
<template #footer>
<small class="text-muted">{{ `${$t("Servings")}: ${entry.servings}` }}</small>
</template>
</b-card>
</div>
<div class="col-12 mt-1" v-if="shopping_list.length > 0">
<b-button-group>
<b-button variant="success" @click="saveShoppingList"
><i class="fas fa-external-link-alt"></i>
{{ $t("Open") }}
</b-button>
<b-button variant="danger" @click="shopping_list = []"
><i class="fa fa-trash"></i>
{{ $t("Clear") }}
</b-button>
</b-button-group>
</div>
</div>
</b-sidebar>
</div>
</template>
<transition name="slide-fade">
<div class="row fixed-bottom p-2 b-1 border-top text-center" style="background: rgba(255, 255, 255, 0.6)" v-if="current_tab === 0">
<div class="row fixed-bottom p-2 b-1 border-top text-center" style="background: rgba(255, 255, 255, 0.6)"
v-if="current_tab === 0">
<div class="col-md-3 col-6">
<button class="btn btn-block btn-success shadow-none" @click="createEntryClick(new Date())"><i class="fas fa-calendar-plus"></i> {{ $t("Create") }}</button>
</div>
<div class="col-md-3 col-6">
<button class="btn btn-block btn-primary shadow-none" v-b-toggle.sidebar-shopping><i class="fas fa-shopping-cart"></i> {{ $t("Shopping_list") }}</button>
<button class="btn btn-block btn-success shadow-none" @click="createEntryClick(new Date())"><i
class="fas fa-calendar-plus"></i> {{ $t("Create") }}
</button>
</div>
<div class="col-md-3 col-6">
<a class="btn btn-block btn-primary shadow-none" :href="iCalUrl"
><i class="fas fa-download"></i>
><i class="fas fa-download"></i>
{{ $t("Export_To_ICal") }}
</a>
</div>
<div class="col-md-3 col-6">
<button class="btn btn-block btn-primary shadow-none disabled" v-b-tooltip.focus.top :title="$t('Coming_Soon')">
<button class="btn btn-block btn-primary shadow-none disabled" v-b-tooltip.focus.top
:title="$t('Coming_Soon')">
{{ $t("Auto_Planner") }}
</button>
</div>
<div class="col-12 d-flex justify-content-center mt-2 d-block d-md-none">
<b-button-toolbar key-nav aria-label="Toolbar with button groups">
<b-button-group class="mx-1">
<b-button v-html="'<<'" @click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button>
<b-button v-html="'<'" @click="setStartingDay(-1)"></b-button>
<b-button v-html="'<<'" class="p-2 pr-3 pl-3"
@click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button>
<b-button v-html="'<'" @click="setStartingDay(-1)" class="p-2 pr-3 pl-3"></b-button>
</b-button-group>
<b-button-group class="mx-1">
<b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i class="fas fa-home"></i></b-button>
<b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i
class="fas fa-home"></i></b-button>
<b-form-datepicker button-only button-variant="secondary"></b-form-datepicker>
</b-button-group>
<b-button-group class="mx-1">
<b-button v-html="'>'" @click="setStartingDay(1)"></b-button>
<b-button v-html="'>>'" @click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button>
<b-button v-html="'>'" @click="setStartingDay(1)" class="p-2 pr-3 pl-3"></b-button>
<b-button v-html="'>>'" class="p-2 pr-3 pl-3"
@click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button>
</b-button-group>
</b-button-toolbar>
</div>
@@ -262,7 +255,7 @@
<script>
import Vue from "vue"
import { BootstrapVue } from "bootstrap-vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import ContextMenu from "@/components/ContextMenu/ContextMenu"
@@ -276,11 +269,11 @@ import moment from "moment"
import draggable from "vuedraggable"
import VueCookies from "vue-cookies"
import { ApiMixin, StandardToasts, ResolveUrlMixin } from "@/utils/utils"
import { CalendarView, CalendarMathMixin } from "vue-simple-calendar/src/components/bundle"
import { ApiApiFactory } from "@/utils/openapi/api"
import {ApiMixin, StandardToasts, ResolveUrlMixin} from "@/utils/utils"
import {CalendarView, CalendarMathMixin} from "vue-simple-calendar/src/components/bundle"
import {ApiApiFactory} from "@/utils/openapi/api"
const { makeToast } = require("@/utils/utils")
const {makeToast} = require("@/utils/utils")
Vue.prototype.moment = moment
Vue.use(BootstrapVue)
@@ -318,12 +311,12 @@ export default {
current_context_menu_item: null,
options: {
displayPeriodUom: [
{ text: this.$t("Week"), value: "week" },
{text: this.$t("Week"), value: "week"},
{
text: this.$t("Month"),
value: "month",
},
{ text: this.$t("Year"), value: "year" },
{text: this.$t("Year"), value: "year"},
],
displayPeriodCount: [1, 2, 3],
entryEditing: {
@@ -367,7 +360,7 @@ export default {
dayNames: function () {
let options = []
this.getFormattedWeekdayNames(this.userLocale, "long", 0).forEach((day, index) => {
options.push({ text: day, value: index })
options.push({text: day, value: index})
})
return options
},
@@ -412,27 +405,6 @@ export default {
openRecipe: function (recipe) {
window.open(this.resolveDjangoUrl("view_recipe", recipe.id))
},
addToShopping(entry) {
if (entry.originalItem.entry.recipe !== null) {
this.shopping_list.push(entry.originalItem.entry)
makeToast(this.$t("Success"), this.$t("Added_To_Shopping_List"), "success")
} else {
makeToast(this.$t("Failure"), this.$t("Cannot_Add_Notes_To_Shopping"), "danger")
}
},
saveShoppingList() {
let url = window.SHOPPING_URL
let first = true
for (let se of this.shopping_list) {
if (first) {
url += `?r=[${se.recipe.id},${se.servings}]`
first = false
} else {
url += `&r=[${se.recipe.id},${se.servings}]`
}
}
window.open(url)
},
setStartingDay(days) {
if (this.settings.startingDayOfWeek + days < 0) {
this.settings.startingDayOfWeek = 6
@@ -446,12 +418,12 @@ export default {
let apiClient = new ApiApiFactory()
apiClient
.createMealType({ name: this.$t("Meal_Type") })
.createMealType({name: this.$t("Meal_Type")})
.then((e) => {
this.periodChangedCallback(this.current_period)
})
.catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
this.refreshMealTypes()
@@ -474,7 +446,7 @@ export default {
}
})
.catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
})
},
@@ -488,10 +460,10 @@ export default {
.updateMealType(this.meal_types[index].id, this.meal_types[index])
.then((e) => {
this.periodChangedCallback(this.current_period)
StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE)
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
})
.catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
} else {
this.$set(this.meal_types[index], "editing", true)
@@ -504,10 +476,10 @@ export default {
.destroyMealType(this.meal_types[index].id)
.then((e) => {
this.periodChangedCallback(this.current_period)
StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_DELETE)
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE)
})
.catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_DELETE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
})
},
updateEmoji: function (field, value) {
@@ -583,7 +555,7 @@ export default {
list.splice(index, 1)
})
.catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
})
@@ -635,7 +607,7 @@ export default {
let apiClient = new ApiApiFactory()
apiClient.updateMealPlan(entry.id, entry).catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
},
createEntry(entry) {
@@ -646,7 +618,7 @@ export default {
apiClient
.createMealPlan(entry)
.catch((err) => {
StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
.then((entry_result) => {
this.plan_entries.push(entry_result.data)
@@ -694,7 +666,7 @@ export default {
}
.calender-row {
height: calc(100% - 240px);
height: calc(100vh - 240px);
}
.calender-parent {

View File

@@ -370,7 +370,7 @@
<div v-for="(ingredient, index) in step.ingredients"
:key="ingredient.id">
<hr class="d-md-none"/>
<div class="text-center">
<div class="text-center" v-if="ingredient.original_text !== null">
<small class="text-muted"><i class="fas fa-globe"></i>
{{ ingredient.original_text }}</small>
</div>
@@ -943,6 +943,7 @@ export default {
order: 0,
is_header: false,
no_amount: false,
original_text: null,
})
this.sortIngredients(step)
this.$nextTick(() => document.getElementById(`amount_${this.recipe.steps.indexOf(step)}_${step.ingredients.length - 1}`).select())
@@ -1029,11 +1030,11 @@ export default {
.listUnits(query, 1, this.options_limit)
.then((response) => {
this.units = response.data.results
let unique_units = this.units.map(u => u.name)
if (this.recipe !== undefined) {
for (let s of this.recipe.steps) {
for (let i of s.ingredients) {
if (i.unit !== null && i.unit.id === undefined) {
if (i.unit !== null && i.unit.id === undefined && !unique_units.includes(i.unit.name) ) {
this.units.push(i.unit)
}
}
@@ -1053,11 +1054,11 @@ export default {
.listFoods(query, undefined, undefined, 1, this.options_limit)
.then((response) => {
this.foods = response.data.results
let unique_foods = this.foods.map(f => f.name)
if (this.recipe !== undefined) {
for (let s of this.recipe.steps) {
for (let i of s.ingredients) {
if (i.food !== null && i.food.id === undefined) {
if (i.food !== null && i.food.id === undefined && !unique_foods.includes(i.food.name)) {
this.foods.push(i.food)
}
}

View File

@@ -234,7 +234,7 @@
</thead>
<tr v-for="r in Recipes" :key="r.list_recipe">
<td>{{ r.recipe_mealplan.name }}</td>
<td>{{ r.recipe_mealplan.recipe_name }}</td>
<td><a :href="resolveDjangoUrl('view_recipe', r.recipe_mealplan.recipe)">{{ r.recipe_mealplan.recipe_name }}</a></td>
<td class="block-inline">
<b-form-input min="1" type="number" :debounce="300"
:value="r.recipe_mealplan.servings"
@@ -291,7 +291,10 @@
aria-hidden="true"></i
></span>
</h5>
<span class="text-muted" v-if="supermarket.description !== ''">{{ supermarket.description }}</span>
<span class="text-muted"
v-if="supermarket.description !== ''">{{
supermarket.description
}}</span>
</b-col>
</b-row>
</b-card-header>
@@ -645,7 +648,7 @@
<div class="col-6">
<a class="btn btn-block btn-success shadow-none" @click="entrymode = !entrymode; "
><i class="fas fa-cart-plus"></i>
{{ $t("New Entry") }}
{{ $t("New_Entry") }}
</a>
</div>
<div class="col-6">
@@ -778,7 +781,7 @@ import GenericMultiselect from "@/components/GenericMultiselect"
import LookupInput from "@/components/Modals/LookupInput"
import ShoppingModal from "@/components/Modals/ShoppingModal"
import {ApiMixin, getUserPreference, StandardToasts, makeToast} from "@/utils/utils"
import {ApiMixin, getUserPreference, StandardToasts, makeToast, ResolveUrlMixin} from "@/utils/utils"
import {ApiApiFactory} from "@/utils/openapi/api"
Vue.use(BootstrapVue)
@@ -787,7 +790,7 @@ let SETTINGS_COOKIE_NAME = "shopping_settings"
export default {
name: "ShoppingListView",
mixins: [ApiMixin],
mixins: [ApiMixin,ResolveUrlMixin],
components: {
ContextMenu,
ContextMenuItem,
@@ -1026,20 +1029,23 @@ export default {
"settings.shopping_auto_sync": function (newVal, oldVal) {
clearInterval(this.autosync_id)
this.autosync_id = undefined
if (!newVal) {
window.removeEventListener("online", this.updateOnlineStatus)
window.removeEventListener("offline", this.updateOnlineStatus)
return
} else if (oldVal === 0 && newVal > 0) {
window.addEventListener("online", this.updateOnlineStatus)
window.addEventListener("offline", this.updateOnlineStatus)
}
this.autosync_id = setInterval(() => {
if (this.online && !this.auto_sync_running) {
this.auto_sync_running = true
this.getShoppingList(true)
if (this.settings.shopping_auto_sync > 0) {
if (!newVal) {
window.removeEventListener("online", this.updateOnlineStatus)
window.removeEventListener("offline", this.updateOnlineStatus)
return
} else if (oldVal === 0 && newVal > 0) {
window.addEventListener("online", this.updateOnlineStatus)
window.addEventListener("offline", this.updateOnlineStatus)
}
}, this.settings.shopping_auto_sync * 1000)
this.autosync_id = setInterval(() => {
if (this.online && !this.auto_sync_running) {
this.auto_sync_running = true
this.getShoppingList(true)
}
}, this.settings.shopping_auto_sync * 1000)
}
},
"settings.default_delay": function (newVal, oldVal) {
this.delay = Number(newVal)
@@ -1446,6 +1452,7 @@ export default {
if (supermarket.editing) {
this.$set(this.supermarkets[index], "editing", false)
this.$set(this.supermarkets[index], "category_to_supermarket", this.editing_supermarket_categories)
this.editing_supermarket_categories = []
let apiClient = new ApiApiFactory()
@@ -1618,8 +1625,7 @@ export default {
this.editing_supermarket_categories.forEach((element, index) => {
let apiClient = new ApiApiFactory()
promises.push(apiClient.partialUpdateSupermarketCategoryRelation(element.relation_id, {order: element.order}))
promises.push(apiClient.partialUpdateSupermarketCategoryRelation(element.relation_id, {order: index}))
})
return Promise.all(promises).then(() => {
@@ -1750,7 +1756,7 @@ export default {
flex-grow: 1;
overflow-y: scroll;
overflow-x: hidden;
height: calc(100% - 170px);
height: calc(100vh - 170px);
}
#id_base_container {
@@ -1758,6 +1764,10 @@ export default {
padding-left: 5px;
}
input {
font-size: 16px !important;
}
@media (max-width: 991.9px) {
#shoppinglist {
max-width: none;

View File

@@ -1,11 +1,15 @@
<template>
<span>
<b-button v-if="!item.ignore_shopping" class="btn text-decoration-none fas px-1 py-0 border-0" variant="link" v-b-popover.hover.html :title="Title" :class="IconClass" @click="toggleOnHand" />
<b-button v-if="!item.ignore_shopping" class="btn text-decoration-none fas px-1 py-0 border-0" variant="link" v-b-popover.hover.html :title="$sanitize(Title)" :class="IconClass" @click="toggleOnHand" />
</span>
</template>
<script>
import { ApiMixin } from "@/utils/utils"
import Vue from "vue"
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
export default {
name: "OnHandBadge",

View File

@@ -4,7 +4,7 @@
<i
class="fas"
v-b-popover.hover.html
:title="[shopping ? $t('RemoveFoodFromShopping', { food: item.name }) : $t('AddFoodToShopping', { food: item.name })]"
:title="[shopping ? $t('RemoveFoodFromShopping', { food: $sanitize(item.name) }) : $t('AddFoodToShopping', { food: $sanitize(item.name) })]"
:class="[shopping ? 'text-success fa-shopping-cart' : 'text-muted fa-cart-plus']"
/>
</b-button>
@@ -22,6 +22,9 @@
<script>
import { ApiMixin, StandardToasts } from "@/utils/utils"
import Vue from "vue"
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
export default {
name: "ShoppingBadge",

View File

@@ -14,7 +14,7 @@
<div class="h-20 w-100 border border-primary rounded text-center">
<i class="fas fa-eye-slash fa-2x text-primary mt-2"></i>
<br/>
<a :href="url" target="_blank" rel="noreferrer nofollow" class="mt-4">{{$t('Download')}}</a>
<a :href="url" target="_blank" rel="noreferrer nofollow" class="mt-4" download>{{$t('Download')}}</a>
</div>
</div>

View File

@@ -93,7 +93,7 @@
"
>
<i class="fas fa-expand-arrows-alt fa-fw"></i> <b>{{ $t("Move") }}</b
>: <span v-html="$t('move_confirmation', { child: source.name, parent: item.name })"></span>
>: <span v-html="$t('move_confirmation', { child: $sanitize(source.name), parent: $sanitize(item.name) })"></span>
</b-list-group-item>
<b-list-group-item
v-if="useMerge"
@@ -104,7 +104,7 @@
"
>
<i class="fas fa-compress-arrows-alt fa-fw"></i> <b>{{ $t("Merge") }}</b
>: <span v-html="$t('merge_confirmation', { source: source.name, target: item.name })"></span>
>: <span v-html="$t('merge_confirmation', { source: $sanitize(source.name), target: $sanitize(item.name) })"></span>
</b-list-group-item>
<b-list-group-item
v-if="useMerge"
@@ -115,7 +115,7 @@
"
>
<i class="fas fa-robot fa-fw"></i> <b>{{ $t("Merge") }} & {{ $t("Automate") }}</b
>: <span v-html="$t('merge_confirmation', { source: source.name, target: item.name })"></span> {{ $t("create_rule") }}
>: <span v-html="$t('merge_confirmation', { source: $sanitize(source.name), target: $sanitize(item.name) })"></span> {{ $t("create_rule") }}
<b-badge v-b-tooltip.hover :title="$t('warning_feature_beta')">BETA</b-badge>
</b-list-group-item>
<b-list-group-item action v-on:click="closeMenu()">
@@ -134,6 +134,9 @@ import RecipeCard from "@/components/RecipeCard"
import { mixin as clickaway } from "vue-clickaway"
import { createPopper } from "@popperjs/core"
import {ApiMixin} from "@/utils/utils";
import Vue from "vue"
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
export default {
name: "GenericHorizontalCard",

View File

@@ -59,6 +59,10 @@ import { calculateAmount, ResolveUrlMixin, ApiMixin } from "@/utils/utils"
import OnHandBadge from "@/components/Badges/OnHand"
import ShoppingBadge from "@/components/Badges/Shopping"
import Vue from "vue"
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
export default {
name: "IngredientComponent",
components: { OnHandBadge, ShoppingBadge },
@@ -124,7 +128,7 @@ export default {
},
methods: {
calculateAmount: function (x) {
return calculateAmount(x, this.ingredient_factor)
return this.$sanitize(calculateAmount(x, this.ingredient_factor))
},
// sends parent recipe ingredient to notify complete has been toggled
done: function () {

View File

@@ -55,6 +55,10 @@
<script>
import {calculateAmount, calculateEnergy, energyHeading} from "@/utils/utils";
import Vue from "vue"
import VueSanitize from "vue-sanitize";
Vue.use(VueSanitize);
export default {
name: 'NutritionComponent',
@@ -64,13 +68,13 @@ export default {
},
methods: {
calculateAmount: function (x) {
return calculateAmount(x, this.ingredient_factor)
return this.$sanitize(calculateAmount(x, this.ingredient_factor))
},
calculateEnergy: function (x) {
return calculateEnergy(x, this.ingredient_factor)
return this.$sanitize(calculateEnergy(x, this.ingredient_factor))
},
energy: function (x) {
return energyHeading()
return this.$sanitize(energyHeading())
}
}
}

414
vue/src/locales/bg.json Normal file
View File

@@ -0,0 +1,414 @@
{
"warning_feature_beta": "Тази функция в момента е в състояние на БЕТА (тестване). Моля, очаквайте грешки и евентуално нарушаващи промени в бъдеще (евентуално загуба на данни, свързани с функции), когато използвате тази функция.",
"err_fetching_resource": "Възникна грешка при извличането на ресурс!",
"err_creating_resource": "Възникна грешка при създаването на ресурс!",
"err_updating_resource": "Възникна грешка при актуализирането на ресурс!",
"err_deleting_resource": "Възникна грешка при изтриването на ресурс!",
"err_deleting_protected_resource": "Обектът, който се опитвате да изтриете, все още се използва и не може да бъде изтрит.",
"err_moving_resource": "Възникна грешка при преместването на ресурс!",
"err_merging_resource": "Възникна грешка при обединяването на ресурс!",
"success_fetching_resource": "Ресурсът бе извлечен успешно!",
"success_creating_resource": "Успешно създаден ресурс!",
"success_updating_resource": "Успешно актуализиран ресурс!",
"success_deleting_resource": "Успешно изтрит ресурс!",
"success_moving_resource": "Успешно преместен ресурс!",
"success_merging_resource": "Успешно обединен ресурс!",
"file_upload_disabled": "Качването на файлове не е активирано за вашето пространство.",
"step_time_minutes": "Време за стъпка в минути",
"confirm_delete": "Наистина ли искате да изтриете този {object}?",
"import_running": "Импортирането се изпълнява, моля, изчакайте!",
"all_fields_optional": "Всички полета са незадължителни и могат да бъдат оставени празни.",
"convert_internal": "Превърнете във вътрешна рецепта",
"show_only_internal": "Показване само на вътрешни рецепти",
"show_split_screen": "Разделен изглед",
"Log_Recipe_Cooking": "Дневник на Рецепта за готвене",
"External_Recipe_Image": "Външно изображение на рецептата",
"Add_to_Shopping": "Добавяне към пазаруване",
"Add_to_Plan": "Добавяне към плана",
"Step_start_time": "Стъпка Начално време",
"Sort_by_new": "Сортиране по ново",
"Table_of_Contents": "Съдържание",
"Recipes_per_page": "Рецепти на страница",
"Show_as_header": "Показване като заглавка",
"Hide_as_header": "Скриване като заглавка",
"Add_nutrition_recipe": "Добавете хранителни стойности към рецептата",
"Remove_nutrition_recipe": "Изтрийте хранителните стойности от рецептата",
"Copy_template_reference": "Копирайте препратка към шаблона",
"Save_and_View": "Запазете и прегледайте",
"Manage_Books": "Управление на Книги",
"Meal_Plan": "План на хранене",
"Select_Book": "Изберете Книга",
"Select_File": "Избери файл",
"Recipe_Image": "Изображение на рецептата",
"Import_finished": "Импортирането приключи",
"View_Recipes": "Вижте рецепти",
"Log_Cooking": "Дневник на Готвене",
"New_Recipe": "Нова рецепта",
"Url_Import": "Импортиране на URL адрес",
"Reset_Search": "Нулиране на търсенето",
"Recently_Viewed": "Наскоро разгледани",
"Load_More": "Зареди още",
"New_Keyword": "Нова ключова дума",
"Delete_Keyword": "Изтриване на ключова дума",
"Edit_Keyword": "Редактиране на ключова дума",
"Edit_Recipe": "Редактиране на рецепта",
"Move_Keyword": "Преместване на ключова дума",
"Merge_Keyword": "Обединяване на ключова дума",
"Hide_Keywords": "Скриване на ключова дума",
"Hide_Recipes": "Скриване на рецепти",
"Move_Up": "Премести нагоре",
"Move_Down": "Премести надолу",
"Step_Name": "Стъпка Име",
"Step_Type": "Стъпка Тип",
"Make_Header": "Направете заглавие",
"Make_Ingredient": "Направете съставка",
"Enable_Amount": "Активиране на сумата",
"Disable_Amount": "Деактивиране на сумата",
"Ingredient Editor": "Редактор на съставки",
"Add_Step": "Добавяне Стъпка",
"Keywords": "Ключови думи",
"Books": "Книги",
"Proteins": "Протеини (белтъчини)",
"Fats": "Мазнини",
"Carbohydrates": "Въглехидрати",
"Calories": "Калории",
"Energy": "Енергия",
"Nutrition": "Хранителни стойности",
"Date": "Дата",
"Share": "Споделяне",
"Automation": "Автоматизация",
"Parameter": "Параметър",
"Export": "Експортиране",
"Copy": "Копиране",
"Rating": "Рейтинг",
"Close": "Затвори",
"Cancel": "Откажи",
"Link": "Връзка",
"Add": "Добави",
"New": "Нов",
"Note": "Бележка",
"Success": "Успешно",
"Failure": "Неуспешно",
"Protected": "Защитен",
"Ingredients": "Съставки",
"Supermarket": "Супермаркет",
"Categories": "Категории",
"Category": "Категория",
"Selected": "Избрано",
"min": "мин",
"Servings": "Порции",
"Waiting": "Очакване",
"Preparation": "Подготовка",
"External": "Външен",
"Size": "Размер",
"Files": "Файлове",
"File": "Файл",
"Edit": "Редактиране",
"Image": "Изображение",
"Delete": "Изтрий",
"Open": "Отвори",
"Ok": "Отвори",
"Save": "Запази",
"Step": "Стъпка",
"Search": "Търсене",
"Import": "Импортиране",
"Print": "Печат",
"Settings": "Настройки",
"or": "или",
"and": "и",
"Information": "Информация",
"Download": "Изтегляне",
"Create": "Създаване",
"Search Settings": "Настройки търсене",
"View": "Изглед",
"Recipes": "Рецепти",
"Move": "Премести",
"Merge": "Обединяване",
"Parent": "Родител",
"delete_confirmation": "Сигурни ли сте, че искате да изтриете {source}?",
"move_confirmation": "Преместване на <i>{child}</i> към родител <i>{parent}</i>",
"merge_confirmation": "Заменете <i>{source}</i> с <i>{target}</i>",
"create_rule": "и създават автоматизация",
"move_selection": "Изберете родител {type}, към който да преместите {source}.",
"merge_selection": "Заменете всички срещания на {source} с избрания {type}.",
"Root": "Корен",
"Ignore_Shopping": "Игнорирайте пазаруването",
"Shopping_Category": "Категория за пазаруване",
"Shopping_Categories": "Категории за пазаруване",
"Edit_Food": "Редактиране на храна",
"Move_Food": "Преместете храната",
"New_Food": "Нова храна",
"Hide_Food": "Скриване на храна",
"Food_Alias": "Псевдоним на храната",
"Unit_Alias": "Псевдоним на единица",
"Keyword_Alias": "Псевдоним на ключова дума",
"Delete_Food": "Изтриване на храна",
"No_ID": "Идентификатора не е намерен, не може да се изтрие.",
"Meal_Plan_Days": "Бъдещи планове за хранене",
"merge_title": "Обединяване на {type}",
"move_title": "Преместване {type}",
"Food": "Храна",
"Recipe_Book": "Книга с рецепти",
"del_confirmation_tree": "Сигурни ли сте, че искате да изтриете {source} и всички негови последователи?",
"delete_title": "Изтриване на {type}",
"create_title": "Нов {type}",
"edit_title": "Редактиране на {type}",
"Name": "Име",
"Type": "Тип",
"Description": "Описание",
"Recipe": "Рецепта",
"tree_root": "Корен на дървото",
"Icon": "Икона",
"Unit": "Единица",
"No_Results": "Няма резултати",
"New_Unit": "Нова единица",
"Create_New_Shopping Category": "Създайте нова категория за пазаруване",
"Create_New_Food": "Добавете нова храна",
"Create_New_Keyword": "Добавяне на нова ключова дума",
"Create_New_Unit": "Добавяне на нова единица",
"Create_New_Meal_Type": "Добавете нов тип хранене",
"and_up": "и нагоре",
"and_down": "и надолу",
"Instructions": "Инструкции",
"Unrated": "Без оценка",
"Automate": "Автоматизация",
"Empty": "Празно",
"Key_Ctrl": "Контрол",
"Key_Shift": "Превключване",
"Time": "Време",
"Text": "Текст",
"Shopping_list": "Списък за пазаруване",
"Added_by": "Добавено от",
"Added_on": "Добавено",
"AddToShopping": "Добавяне към списъка за пазаруване",
"IngredientInShopping": "Тази съставка е във вашия списък за пазаруване.",
"NotInShopping": "{food} не е в списъка ви за пазаруване.",
"OnHand": "В момента под ръка",
"FoodOnHand": "Имате {храна} под ръка.",
"FoodNotOnHand": "Нямате {храна} под ръка.",
"Undefined": "Недефиниран",
"Create_Meal_Plan_Entry": "Създайте запис за план за хранене",
"Edit_Meal_Plan_Entry": "Редактиране на записа в плана за хранене",
"Title": "Заглавие",
"Week": "Седмица",
"Month": "Месец",
"Year": "Година",
"Planner": "Планировчик",
"Planner_Settings": "Настройки на планировчика",
"Period": "Период",
"Plan_Period_To_Show": "Покажете седмици, месеци или години",
"Periods": "Периоди",
"Plan_Show_How_Many_Periods": "Колко периода да се показват",
"Starting_Day": "Начален ден от седмицата",
"Meal_Types": "Видове хранене",
"Meal_Type": "Вид хранене",
"Clone": "Клониране",
"Drag_Here_To_Delete": "Плъзнете тук, за да изтриете",
"Meal_Type_Required": "Изисква се вид хранене",
"Title_or_Recipe_Required": "Изисква се избор на заглавие или рецепта",
"Color": "Цвят",
"New_Meal_Type": "Нов вид хранене",
"AddFoodToShopping": "Добавете {food} към списъка си за пазаруване",
"RemoveFoodFromShopping": "Премахнете {food} от списъка си за пазаруване",
"DeleteShoppingConfirm": "Сигурни ли сте, че искате да премахнете цялата {food} от списъка за пазаруване?",
"IgnoredFood": "{food} е настроен да игнорира пазаруването.",
"Add_Servings_to_Shopping": "Добавете {servings} порции към Пазаруване",
"Week_Numbers": "Номера на седмиците",
"Show_Week_Numbers": "Показване на номерата на седмиците?",
"Export_As_ICal": "Експортирайте текущия период във формат iCal",
"Export_To_ICal": "Експортиране на .ics",
"Cannot_Add_Notes_To_Shopping": "Бележки не могат да се добавят към списъка за пазаруване",
"Added_To_Shopping_List": "Добавено към списъка за пазаруване",
"Shopping_List_Empty": "Вашият списък за пазаруване в момента е празен, можете да добавяте артикули чрез контекстното меню на запис на план за хранене (щракнете с десния бутон върху картата или щракнете с левия бутон върху иконата на менюто)",
"Next_Period": "Следващ период",
"Previous_Period": "Предишен период",
"Current_Period": "Текущ период",
"Next_Day": "Следващия ден",
"Previous_Day": "Предишен ден",
"Inherit": "Наследете",
"InheritFields": "Наследяване на стойности на полета",
"FoodInherit": "Хранителни наследствени полета",
"ShowUncategorizedFood": "Покажи неопределено",
"GroupBy": "Групирай по",
"SupermarketCategoriesOnly": "Само категории супермаркети",
"MoveCategory": "Премести към: ",
"CountMore": "...+{count} още",
"IgnoreThis": "Никога не добавяйте автоматично {food} към пазаруване",
"DelayFor": "Закъснение за {hours} часа",
"Warning": "Внимание",
"NoCategory": "Няма избрана категория.",
"InheritWarning": "{food} е настроен да наследява, промените може да не продължат.",
"ShowDelayed": "Показване на забавени артикули",
"Completed": "Завършено",
"OfflineAlert": "Вие сте офлайн, списъкът за пазаруване може да не се синхронизира.",
"shopping_share": "Споделете списък за пазаруване",
"shopping_auto_sync": "Автоматично синхронизиране",
"one_url_per_line": "Един URL на ред",
"mealplan_autoadd_shopping": "Автоматично добавяне на план за хранене",
"mealplan_autoexclude_onhand": "Изключете храната под ръка",
"mealplan_autoinclude_related": "Добавете свързани рецепти",
"default_delay": "Часове на забавяне по подразбиране",
"shopping_share_desc": "Потребителите ще видят всички артикули, които добавите към списъка си за пазаруване. Те трябва да ви добавят, за да видят елементите в техния списък.",
"shopping_auto_sync_desc": "Задаването на 0 ще деактивира автоматичното синхронизиране. Когато разглеждате списък за пазаруване, списъкът се актуализира на всеки зададени секунди, за да синхронизира промените, които някой друг може да е направил. Полезно при пазаруване с множество хора, но ще използва мобилни данни.",
"mealplan_autoadd_shopping_desc": "Автоматично добавяне на съставки за план за хранене към списъка за пазаруване.",
"mealplan_autoexclude_onhand_desc": "Когато добавяте план за хранене към списъка за пазаруване (ръчно или автоматично), изключете съставките, които в момента са под ръка.",
"mealplan_autoinclude_related_desc": "Когато добавяте план за хранене към списъка за пазаруване (ръчно или автоматично), включете всички свързани рецепти.",
"default_delay_desc": "Брой часове по подразбиране за забавяне на записа в списъка за пазаруване.",
"filter_to_supermarket": "Филтрирайте до супермаркет",
"Coming_Soon": "Очаквайте скоро",
"Auto_Planner": "Автоматичен плановик",
"New_Cookbook": "Нова готварска книга",
"Hide_Keyword": "Скриване на ключови думи",
"Clear": "Изчистване",
"err_move_self": "Не може елемента да се премести към себе си",
"nothing": "Няма нищо за правене",
"err_merge_self": "Не може да се слее елемент със себе си",
"show_sql": "Покажи SQL",
"filter_to_supermarket_desc": "По подразбиране филтрирайте списъка за пазаруване, за да включва само категории за избран супермаркет.",
"CategoryName": "Име на категория",
"SupermarketName": "Име на супермаркет",
"CategoryInstruction": "Плъзнете категориите, за да промените категориите за поръчки, които се появяват в списъка за пазаруване.",
"shopping_recent_days_desc": "Дни на последните записи в списъка за пазаруване за показване.",
"shopping_recent_days": "Последни дни",
"download_pdf": "Изтегли PDF",
"download_csv": "Изтегли CSV",
"csv_delim_help": "Ограничител за използване за CSV експортиране.",
"csv_delim_label": "CSV разделител",
"SuccessClipboard": "Списъкът за пазаруване е копиран в клипборда",
"copy_to_clipboard": "Копиране в клипборда",
"csv_prefix_help": "Префикс за добавяне при копиране на списък в клипборда.",
"csv_prefix_label": "Префикс за списък",
"copy_markdown_table": "Копирайте като Markdown Таблица",
"in_shopping": "В списъка за пазаруване",
"DelayUntil": "Забавяне до",
"Pin": "Закачи",
"mark_complete": "Маркирането завършено",
"QuickEntry": "Бързо влизане",
"shopping_add_onhand_desc": "Маркирайте храната „На ръка“, когато сте отметнати от списъка за пазаруване.",
"shopping_add_onhand": "Автоматично под ръка",
"related_recipes": "Свързани рецепти",
"today_recipes": "Днешните рецепти",
"sql_debug": "Отстраняване на грешки в SQL",
"remember_search": "Запомнете търсенето",
"remember_hours": "Часове за запомняне",
"tree_select": "Използвайте Избор на дърво",
"OnHand_help": "Храната е в инвентара и няма да бъде добавена автоматично към списък за пазаруване. Състоянието на ръка се споделя с пазаруващите потребители.",
"ignore_shopping_help": "Никога не добавяйте храна към списъка за пазаруване (например вода)",
"shopping_category_help": "Супермаркетите могат да бъдат поръчани и филтрирани по категория за пазаруване според оформлението на пътеките.",
"food_recipe_help": "Свързването на рецепта тук ще включва свързаната рецепта във всяка друга рецепта, която използва тази храна",
"Foods": "Храни",
"enable_expert": "Активирайте експертния режим",
"expert_mode": "Експертен режим",
"simple_mode": "Опростен режим",
"advanced": "Разширено",
"fields": "Полета",
"show_keywords": "Показване на ключови думи",
"show_foods": "Покажи храни",
"show_books": "Покажи книги",
"show_rating": "Покажи рейтинг",
"show_units": "Показване на единици",
"show_filters": "Показване на филтри",
"not": "не",
"save_filter": "Запазване на филтъра",
"filter_name": "Име на филтъра",
"left_handed": "Режим за лява ръка",
"left_handed_help": "Ще оптимизира потребителския интерфейс за използване с лявата ви ръка.",
"Custom Filter": "Персонализиран филтър",
"shared_with": "Споделено с",
"sort_by": "Сортиране по",
"asc": "Възходящ",
"desc": "Низходящо",
"date_viewed": "Последно разгледан",
"last_cooked": "Последно приготвени",
"times_cooked": "Пъти сготвено",
"date_created": "дата на създаване",
"show_sortby": "Покажи Сортиране по",
"search_rank": "Ранг на търсене",
"make_now": "Направете сега",
"recipe_filter": "Филтър за рецепти",
"book_filter_help": "Включете рецепти от филтъра за рецепти в допълнение към ръчно зададените.",
"review_shopping": "Прегледайте записите за пазаруване, преди да запазите",
"view_recipe": "Вижте рецепта",
"copy_to_new": "Копиране в нова рецепта",
"recipe_name": "Име на рецептата",
"paste_ingredients_placeholder": "Поставете списъка със съставки тук...",
"paste_ingredients": "Постави съставки",
"ingredient_list": "Списък на съставките",
"explain": "Обяснение",
"filter": "Филтрирайте",
"Website": "уебсайт",
"App": "Приложение",
"Bookmarklet": "Книжен пазар",
"click_image_import": "Щракнете върху изображението, което искате да импортирате за тази рецепта",
"no_more_images_found": "Няма намерени допълнителни изображения на уебсайта.",
"import_duplicates": "За да се предотврати дублирането, рецептите със същото име като съществуващите се игнорират. Поставете отметка в това квадратче, за да импортирате всичко.",
"paste_json": "Поставете тук json или html източник, за да заредите рецептата.",
"Click_To_Edit": "Кликнете, за да редактирате",
"search_no_recipes": "Не можах да намеря никакви рецепти!",
"search_import_help_text": "Импортирайте рецепта от външен уебсайт или приложение.",
"search_create_help_text": "Създайте нова рецепта директно в Tandoor.",
"warning_duplicate_filter": "Предупреждение: Поради технически ограничения наличието на множество филтри от една и съща комбинация (и/или/не) може да доведе до неочаквани резултати.",
"reset_children": "Нулиране на наследяването от последователя",
"reset_children_help": "Презаписване на всички последователи със стойности от наследени полета. Наследените полета на последователите ще бъдат зададени на наследяване на полета, освен ако последователите наследяват полета не е зададено.",
"substitute_help": "Заместителите се вземат предвид при търсене на рецепти, които могат да бъдат направени с подръчни съставки.",
"substitute_siblings_help": "Всички храни, които споделят родител на тази храна, се считат за заместители.",
"substitute_children_help": "Всички храни, които са последователи на тази храна, се считат за заместители.",
"substitute_siblings": "Заместители на сродни",
"substitute_children": "Заместители на последователи",
"SubstituteOnHand": "Имате заместител под ръка.",
"ChildInheritFields": "Последователи наследяват полета",
"ChildInheritFields_help": "Последователите ще наследят тези полета по подразбиране.",
"InheritFields_help": "Стойностите на тези полета ще бъдат наследени от родител (Изключение: празни категории за пазаруване не се наследяват)",
"last_viewed": "Последно разгледан",
"created_on": "Създадено на",
"updatedon": "Актуализирано на",
"Imported_From": "Внесено от",
"advanced_search_settings": "Разширени настройки за търсене",
"nothing_planned_today": "Нямате нищо планирано за днес!",
"no_pinned_recipes": "Нямате закачени рецепти!",
"Planned": "Планирано",
"Pinned": "Закачено",
"Imported": "Импортирано",
"Quick actions": "Бързи действия",
"Ratings": "Рейтинги",
"Internal": "Вътрешен",
"Units": "Единици",
"Random Recipes": "Случайни рецепти",
"parameter_count": "Параметър {count}",
"select_keyword": "Изберете Ключова дума",
"add_keyword": "Добавяне на ключова дума",
"select_file": "Избери файл",
"select_recipe": "Изберете рецепта",
"select_unit": "Изберете Единица",
"select_food": "Изберете Храна",
"remove_selection": "Премахнете избора",
"empty_list": "Списъкът е празен.",
"Select": "Изберете",
"Supermarkets": "Супермаркети",
"User": "потребител",
"Keyword": "Ключова дума",
"Advanced": "Разширено",
"Page": "Страница",
"Single": "Единичен",
"Multiple": "Многократни",
"Reset": "Нулиране",
"Options": "Настроики",
"Create Food": "Създайте храна",
"create_food_desc": "Създайте храна и я свържете с тази рецепта.",
"additional_options": "Допълнителни настройки",
"Importer_Help": "Повече информация и помощ за този вносител:",
"Documentation": "Документация",
"Select_App_To_Import": "Моля, изберете приложение, от което да импортирате",
"Import_Supported": "Поддържа се импортиране",
"Export_Supported": "Поддържа се експорт",
"Import_Not_Yet_Supported": "Импортирането все още не се поддържа",
"Export_Not_Yet_Supported": "Експортирането все още не се поддържа",
"Import_Result_Info": "Импортирани са {imported} от {total} рецепти",
"Recipes_In_Import": "Рецепти във вашия файл за импортиране",
"Toggle": "Превключете",
"Import_Error": "Възникна грешка по време на импортирането ви. Моля, разгънете подробностите в долната част на страницата, за да ги видите.",
"Warning_Delete_Supermarket_Category": "Изтриването на категория супермаркет ще изтрие и всички връзки с храни. Сигурен ли си?",
"New_Supermarket": "Създайте нов супермаркет",
"New_Supermarket_Category": "Създаване на нова категория супермаркет",
"Are_You_Sure": "Сигурен ли си?"
}

414
vue/src/locales/da.json Normal file
View File

@@ -0,0 +1,414 @@
{
"warning_feature_beta": "Denne funktion er i øjeblikket i BETA (test) stadie. Forvent fejl og fremtidige ændringer (hvor data kan mistes) ved brug af denne funktion.",
"err_fetching_resource": "Der opstod en fejl under indlæsning af denne ressource!",
"err_creating_resource": "Der opstod en fejl under oprettelsen af denne ressource!",
"err_updating_resource": "Der opstod en fejl under opdateringen af denne ressource!",
"err_deleting_resource": "Der opstod en fejl under sletningen af denne ressource!",
"err_deleting_protected_resource": "Objektet du prøver at slette er stadig i brug, og kan ikke slettes.",
"err_moving_resource": "Der opstod en fejl under flytningen af denne ressource!",
"err_merging_resource": "Der opstod en fejl under sammenfletningen af denne ressource!",
"success_fetching_resource": "Ressourcen blev hentet!",
"success_creating_resource": "Ressourcen blev oprettet!",
"success_updating_resource": "Ressourcen blev opdateret!",
"success_deleting_resource": "Ressourcen blev slettet!",
"success_moving_resource": "Ressourcen blev flyttet!",
"success_merging_resource": "Ressourcen blev sammenflettet!",
"file_upload_disabled": "Upload af filer er ikke slået til i dit rum.",
"step_time_minutes": "Trin varighed (minutter)",
"confirm_delete": "Er du sikker på at du slette dette {object}?",
"import_running": "Importering er i gang, vent venligst!",
"all_fields_optional": "Alle felter er valgfri og kan være tomme.",
"convert_internal": "Konverter til intern opskrift",
"show_only_internal": "Vis kun interne opskrifter",
"show_split_screen": "Opdel skærmen",
"Log_Recipe_Cooking": "Noter tilberedning af opskrift",
"External_Recipe_Image": "Eksternt billede af opskrift",
"Add_to_Shopping": "Tilføj til indkøbsliste",
"Add_to_Plan": "Tilføj til madplan",
"Step_start_time": "Trin starttid",
"Sort_by_new": "Sorter efter nylige",
"Table_of_Contents": "Indholdsfortegnelse",
"Recipes_per_page": "Opskrifter pr. side",
"Show_as_header": "Vis som rubrik",
"Hide_as_header": "Skjul som rubrik",
"Add_nutrition_recipe": "Tilføj næringsindhold til opskrift",
"Remove_nutrition_recipe": "Fjern næringsindhold fra opskrift",
"Copy_template_reference": "Kopier skabelonsreference",
"Save_and_View": "Gem & Vis",
"Manage_Books": "Administrer bøger",
"Meal_Plan": "Madplan",
"Select_Book": "Vælg bog",
"Select_File": "Vælg fil",
"Recipe_Image": "Opskriftsbillede",
"Import_finished": "Importering fuldført",
"View_Recipes": "Vis opskrifter",
"Log_Cooking": "Noter tilberedning",
"New_Recipe": "Ny opskrift",
"Url_Import": "Importer fra link",
"Reset_Search": "Nulstil søgning",
"Recently_Viewed": "Vist for nylig",
"Load_More": "Indlæs mere",
"New_Keyword": "Nyt nøgleord",
"Delete_Keyword": "Slet nøgleord",
"Edit_Keyword": "Rediger nøgleord",
"Edit_Recipe": "Rediger opskrift",
"Move_Keyword": "Flyt nøgleord",
"Merge_Keyword": "Sammenflet nøgleord",
"Hide_Keywords": "Skjul nøgleord",
"Hide_Recipes": "Skjul opskrifter",
"Move_Up": "Flyt up",
"Move_Down": "Flyt ned",
"Step_Name": "Trin navn",
"Step_Type": "Trin type",
"Make_Header": "Opret rubrik",
"Make_Ingredient": "Opret ingredient",
"Enable_Amount": "Aktiver antal",
"Disable_Amount": "Deaktiver antal",
"Ingredient Editor": "Ingrediens redigeringsværktøj",
"Add_Step": "Tilføj trin",
"Keywords": "Nøgleord",
"Books": "Bøger",
"Proteins": "Proteiner",
"Fats": "Fedtstoffer",
"Carbohydrates": "Kulhydrater",
"Calories": "Kalorier",
"Energy": "Energi",
"Nutrition": "Næring",
"Date": "Dato",
"Share": "Del",
"Automation": "Automatisering",
"Parameter": "Parameter",
"Export": "Eksporter",
"Copy": "Kopier",
"Rating": "Bedømmelse",
"Close": "Luk",
"Cancel": "Annuller",
"Link": "Link",
"Add": "Tilføj",
"New": "Ny",
"Note": "Note",
"Success": "Succes",
"Failure": "Mislykkedes",
"Protected": "Beskyttet",
"Ingredients": "Ingredienser",
"Supermarket": "Supermarked",
"Categories": "Kategorier",
"Category": "Kategori",
"Selected": "Valgt",
"min": "min",
"Servings": "Serveringer",
"Waiting": "Venter",
"Preparation": "Forberedelse",
"External": "Ekstern",
"Size": "Størrelse",
"Files": "Filer",
"File": "Fil",
"Edit": "Rediger",
"Image": "Billede",
"Delete": "Slet",
"Open": "Åben",
"Ok": "Åben",
"Save": "Gem",
"Step": "Trin",
"Search": "Søg",
"Import": "Importer",
"Print": "Udskriv",
"Settings": "Indstillinger",
"or": "eller",
"and": "og",
"Information": "Information",
"Download": "Download",
"Create": "Opret",
"Search Settings": "Søgningsindstillinger",
"View": "Vis",
"Recipes": "Opskrifter",
"Move": "Flyt",
"Merge": "Sammenflet",
"Parent": "Forælder",
"delete_confirmation": "Er du sikker på at du vil slette {source}?",
"move_confirmation": "Flyt <i>{child}</i> til forælder <i>parent</i>",
"merge_confirmation": "Erstat <i>{source}</i> med <i>{target}</i>",
"create_rule": "og opret automation",
"move_selection": "Vælg en forælder {type} som {source} skal flyttes til.",
"merge_selection": "Erstat alle forekomster af {source} med det valgte {target}.",
"Root": "Rod",
"Ignore_Shopping": "Ignorer indkøb",
"Shopping_Category": "Indkøbskategori",
"Shopping_Categories": "Indkøbskategorier",
"Edit_Food": "Rediger mad",
"Move_Food": "Flyt mad",
"New_Food": "Ny mad",
"Hide_Food": "Skjul mad",
"Food_Alias": "Alternativt navn til mad",
"Unit_Alias": "Alternativt navn til enhed",
"Keyword_Alias": "Alternativt navn til nøgleord",
"Delete_Food": "Slet mad",
"No_ID": "ID findes ikke, kan ikke slette.",
"Meal_Plan_Days": "Fremtidige madplaner",
"merge_title": "Sammenflet {type}",
"move_title": "Flyt {type}",
"Food": "Mad",
"Recipe_Book": "Opskriftsbog",
"del_confirmation_tree": "Er du sikker på at du vil slette {source} og alle dets børn?",
"delete_title": "Slet {type}",
"create_title": "Ny {type}",
"edit_title": "Rediger {type}",
"Name": "Navn",
"Type": "Type",
"Description": "Beskrivelse",
"Recipe": "Opskrift",
"tree_root": "Roden af træet",
"Icon": "Ikon",
"Unit": "Enhed",
"No_Results": "Ingen resultater",
"New_Unit": "Ny enhed",
"Create_New_Shopping Category": "Opret ny indkøbskategori",
"Create_New_Food": "Tilføj ny mad",
"Create_New_Keyword": "Tilføj nyt nøgleord",
"Create_New_Unit": "Tilføj ny enhed",
"Create_New_Meal_Type": "Tilføj ny måltidstype",
"and_up": "& Op",
"and_down": "& Ned",
"Instructions": "Instruktioner",
"Unrated": "Ikke bedømt",
"Automate": "Automatiser",
"Empty": "Tom",
"Key_Ctrl": "Ctrl",
"Key_Shift": "Shift",
"Time": "Tid",
"Text": "Tekst",
"Shopping_list": "Indkøbsliste",
"Added_by": "Tilføjet af",
"Added_on": "Tilføjet d.",
"AddToShopping": "Tilføj til indkøbsliste",
"IngredientInShopping": "Denne ingrediens er i din indkøbsliste.",
"NotInShopping": "{food} er ikke i din indkøbsliste.",
"OnHand": "Til rådighed",
"FoodOnHand": "Du har {food} til rådighed.",
"FoodNotOnHand": "Du har ikke {food} til rådighed.",
"Undefined": "Ikke defineret",
"Create_Meal_Plan_Entry": "Indsæt punkt i madplan",
"Edit_Meal_Plan_Entry": "Rediger punkt i madplan",
"Title": "Titel",
"Week": "Uge",
"Month": "Måned",
"Year": "År",
"Planner": "Planlægger",
"Planner_Settings": "Planlægger indstillinger",
"Period": "Periode",
"Plan_Period_To_Show": "Vis uger, måneder eller år",
"Periods": "Perioder",
"Plan_Show_How_Many_Periods": "Hvor mange perioder skal vises",
"Starting_Day": "Første dag på ugen",
"Meal_Types": "Måltidstyper",
"Meal_Type": "Måltidstype",
"Clone": "Klon",
"Drag_Here_To_Delete": "Træk herhen for at slette",
"Meal_Type_Required": "Måltidstype påkrævet",
"Title_or_Recipe_Required": "Titel eller valg af opskrift påkrævet",
"Color": "Farve",
"New_Meal_Type": "Ny måltidstype",
"AddFoodToShopping": "Tilføj {food} til indkøbsliste",
"RemoveFoodFromShopping": "Fjern {food} fra indkøbsliste",
"DeleteShoppingConfirm": "Er du sikker på at du vil fjerne {food} fra indkøbsliste?",
"IgnoredFood": "{food} er sat til at ignorere indkøb.",
"Add_Servings_to_Shopping": "Tilføj {servings} serveringer til indkøb",
"Week_Numbers": "Ugenumre",
"Show_Week_Numbers": "Vis ugenumre?",
"Export_As_ICal": "Eksporter nuværende periode til iCal format",
"Export_To_ICal": "Eksporter .ics",
"Cannot_Add_Notes_To_Shopping": "Noter kan ikke tilføjes til indkøbslisten",
"Added_To_Shopping_List": "Tilføjet til indkøbslisten",
"Shopping_List_Empty": "Din indkøbsliste er i øjeblikket tom, du kan tilføje varer via menuen for et madplanspunkt (højreklik på punktet eller venstreklik på menu ikonet)",
"Next_Period": "Næste periode",
"Previous_Period": "Forgående periode",
"Current_Period": "Nuværende periode",
"Next_Day": "Næste dag",
"Previous_Day": "Forgående dag",
"Inherit": "Nedarve",
"InheritFields": "Nedarve feltværdier",
"FoodInherit": "Nedarvelige mad felter",
"ShowUncategorizedFood": "Vis ikke definerede",
"GroupBy": "Grupper efter",
"SupermarketCategoriesOnly": "Kun Supermarked kategorier",
"MoveCategory": "Flyt til: ",
"CountMore": "...+{count} flere",
"IgnoreThis": "Aldrig tilføj {food} automatisk til indkøb",
"DelayFor": "Udskyd i {hours} hours",
"Warning": "Advarsel",
"NoCategory": "Ingen kategori valgt.",
"InheritWarning": "{food} er sat til at nedarve, ændringer bliver måske ikke gemt.",
"ShowDelayed": "Vis udskudte elementer",
"Completed": "Afsluttet",
"OfflineAlert": "Du er offline, indkøbslisten er måske ikke synkroniseret.",
"shopping_share": "Del indkøbsliste",
"shopping_auto_sync": "Synkroniser automatisk",
"one_url_per_line": "Et link pr. linje",
"mealplan_autoadd_shopping": "Tilføj madplan automatisk",
"mealplan_autoexclude_onhand": "Ekskluder tilgængeligt mad",
"mealplan_autoinclude_related": "Tilføj relaterede opskrifter",
"default_delay": "Standard udsættelse i timer",
"shopping_share_desc": "Brugere vil se alle varer du tilføjer til din indkøbsliste. De skal tilføje dig for at se varer på deres liste.",
"shopping_auto_sync_desc": "Hvis den sættes til 0, bliver automatisk synkronisering slået fra. Når en indkøbsliste vises bliver den opdateret efter den indstillede periode i sekunder, for at synkronisere eventuelle ændringer andre har foretaget. Brugbar når man køber ind sammen med andre, men det bruger mobildata.",
"mealplan_autoadd_shopping_desc": "Tilføj madplansingredienser til indkøbsliste automatisk.",
"mealplan_autoexclude_onhand_desc": "Når en madplan tilføjes til indkøbslisten (manuelt eller automatisk), ekskluder tilgængelige ingredienser.",
"mealplan_autoinclude_related_desc": "Når en madplan tilføjes til indkøbslisten (manuelt eller automatisk), inkluder alle relaterede opskrifter.",
"default_delay_desc": "Standard antal timer et punkt på indkøbslisten skal udskydes.",
"filter_to_supermarket": "Filtrer til supermarked",
"Coming_Soon": "Kommer snart",
"Auto_Planner": "Autoplanlægger",
"New_Cookbook": "Ny opskriftsbog",
"Hide_Keyword": "Skjul nøgleord",
"Clear": "Ryd",
"err_move_self": "Kan ikke flytte element til sig selv",
"nothing": "Intet at gøre",
"err_merge_self": "Kan ikke sammenflette element med sig selv",
"show_sql": "Vis SQL",
"filter_to_supermarket_desc": "Filtrer som standard indkøbslisten til kun at inkludere kategorier fra valgte supermarkeder.",
"CategoryName": "Kategorinavn",
"SupermarketName": "Navn på supermarked",
"CategoryInstruction": "Træk rundt på kategorier, for at ændre på rækkefølgen de opstår i på indkøbslisten.",
"shopping_recent_days_desc": "Antal dage seneste varer fra indkøbslister skal vises.",
"shopping_recent_days": "Seneste dage",
"download_pdf": "Download PDF",
"download_csv": "Download CSV",
"csv_delim_help": "Tegn der skal afgrænse elementer i CSV eksporteringer.",
"csv_delim_label": "CSV afgrænsningstegn",
"SuccessClipboard": "Indkøbsliste kopieret til udklipsholder",
"copy_to_clipboard": "Kopier til udklipsholder",
"csv_prefix_help": "Præfiks som skal tilføjes når der kopieres til udklipsholder.",
"csv_prefix_label": "Liste præfiks",
"copy_markdown_table": "Kopier som Markdown tabel",
"in_shopping": "I indkøbsliste",
"DelayUntil": "Udskyd indtil",
"Pin": "Fastgør",
"mark_complete": "Marker som færdig",
"QuickEntry": "Hurtigt indlæg",
"shopping_add_onhand_desc": "Marker mad som 'Tilgængeligt' når det bliver krydset af på indkøbslisten.",
"shopping_add_onhand": "Automatisk tilgængelig markering",
"related_recipes": "Relaterede opskrifter",
"today_recipes": "Opskrifter til i dag",
"sql_debug": "SQL fejlsøgning",
"remember_search": "Husk søgning",
"remember_hours": "Timer at gemme i",
"tree_select": "Benyt træ vælger",
"OnHand_help": "Varen er \"på lager\" og vil ikke automatisk blive tilføjet til indkøbslister. Lagerstatus deles med indkøbsbrugere.",
"ignore_shopping_help": "Aldrig tilføj vare til indkøbslisten (f.eks. vand)",
"shopping_category_help": "Supermarkeder kan blive filtrerede efter indkøbskategori så det passer med indretningen i butikken.",
"food_recipe_help": "Hvis du sammenkæder en opskrift her vil den sammenkædede opskrift blive inkluderet i alle andre opskrifter der bruger denne mad",
"Foods": "Mad",
"enable_expert": "Slå avanceret tilstand til",
"expert_mode": "Avanceret tilstand",
"simple_mode": "Simpel tilstand",
"advanced": "Avanceret",
"fields": "Felter",
"show_keywords": "Vis nøgleord",
"show_foods": "Vis mad",
"show_books": "Vis bøger",
"show_rating": "Vis bedømmelse",
"show_units": "Vis enheder",
"show_filters": "Vis filtre",
"not": "ikke",
"save_filter": "Gem filter",
"filter_name": "Filtrer navn",
"left_handed": "Venstrehåndstilstand",
"left_handed_help": "Optimerer brugerfladen til benyttelse med din venstre hånd.",
"Custom Filter": "Tilpasset filter",
"shared_with": "Delt med",
"sort_by": "Sorter efter",
"asc": "Stigende",
"desc": "Faldende",
"date_viewed": "Sidst Åbnet",
"last_cooked": "Sidst Tilberedt",
"times_cooked": "Antal gange tilberedt",
"date_created": "Oprettelsesdato",
"show_sortby": "Soter efter",
"search_rank": "Søg efter rang",
"make_now": "Lav nu",
"recipe_filter": "Opskriftsfilter",
"book_filter_help": "Inkluder opskrifter fra opskriftsfilter udover de manuelt udvalgte.",
"review_shopping": "Tjek indkøbsvarer inden der gemmes",
"view_recipe": "Vis opskrift",
"copy_to_new": "Kopier til ny opskrift",
"recipe_name": "Navn på opskrift",
"paste_ingredients_placeholder": "Indsæt ingrediensliste her...",
"paste_ingredients": "Indsæt ingredienser",
"ingredient_list": "Ingrediensliste",
"explain": "Forklar",
"filter": "Filter",
"Website": "Hjemmeside",
"App": "App",
"Bookmarklet": "Bogmærke",
"click_image_import": "Klik på billedet du vil importere til denne opskrift",
"no_more_images_found": "Ingen yderligere billeder fundet på hjemmeside.",
"import_duplicates": "For at undgå dubletter bliver opskrifter med det samme navn som eksisterende opskrifter ignoreret. Ving den boks af for at tilføje alt.",
"paste_json": "Indsæt JSON eller HTML kildekode her for at indlæse opskrift.",
"Click_To_Edit": "Klik for at rediger",
"search_no_recipes": "Kunne ikke finde nogen opskrifter!",
"search_import_help_text": "Importer en opskrift fra en ekstern hjemmeside eller applikation.",
"search_create_help_text": "Opret en ny opskrift direkte i Tandoor.",
"warning_duplicate_filter": "Advarsel: På grund af tekniske begrænsninger, kan det give uforventede resultater at have flere filtre med den samme kombination af (og/eller/not).",
"reset_children": "Nulstil barnets nedarvning",
"reset_children_help": "Overskriv alle børn med værdier fra nedarvede felter. Nedarvede felter af børn vil blive sat til at nedarve med mindre de allerede er sat til at nedarve.",
"substitute_help": "Erstatninger bliver taget i betragtning når der søges efter opskrifter der kan laves med tilgængelige ingredienser.",
"substitute_siblings_help": "Alt mad der deler en forælder med denne mad bliver betragtet som erstatninger.",
"substitute_children_help": "Alt mad der er et barn af denne mad bliver betragtet som erstatninger.",
"substitute_siblings": "Erstattende søskende",
"substitute_children": "Erstattende børn",
"SubstituteOnHand": "Du har erstatninger tilgængeligt.",
"ChildInheritFields": "Barn nedarvningsfelter",
"ChildInheritFields_help": "Børn nedarvede disse felter som standard.",
"InheritFields_help": "Værdierne af disse felter vil blive nedarvet fra forælder (Undtagelse: tomme indkøbskategorier bliver ikke nedarvet)",
"last_viewed": "Sidst åbnet",
"created_on": "Oprettet d.",
"updatedon": "Opdateret d.",
"Imported_From": "Importeret fra",
"advanced_search_settings": "Avancerede søgeindstillinger",
"nothing_planned_today": "Du har ikke noget planlagt for i dag!",
"no_pinned_recipes": "Du har ingen fastgjorte opskrifter!",
"Planned": "Planlagt",
"Pinned": "Fastgjort",
"Imported": "Importeret",
"Quick actions": "Hurtige handlinger",
"Ratings": "Bedømmelser",
"Internal": "Interne",
"Units": "Enheder",
"Random Recipes": "Tilfældige opskrifter",
"parameter_count": "Parameter {count}",
"select_keyword": "Vælg nøgleord",
"add_keyword": "Tilføj nøgleord",
"select_file": "Vælg fil",
"select_recipe": "Vælg opskrift",
"select_unit": "Vælg enhed",
"select_food": "Vælg mad",
"remove_selection": "Fjern markering",
"empty_list": "Listen er tom.",
"Select": "Vælg",
"Supermarkets": "Supermarkeder",
"User": "Bruger",
"Keyword": "Nøgleord",
"Advanced": "Avanceret",
"Page": "Side",
"Single": "Enkel",
"Multiple": "Flere",
"Reset": "Nulstil",
"Options": "Indstillinger",
"Create Food": "Opret mad",
"create_food_desc": "Opret mad og sammenkæd den med denne opskrift.",
"additional_options": "Yderligere indstillinger",
"Importer_Help": "Mere information og hjælp til denne importer:",
"Documentation": "Dokumentation",
"Select_App_To_Import": "Vælg venligst en App at importere fra",
"Import_Supported": "Import understøttet",
"Export_Supported": "Eksport understøttet",
"Import_Not_Yet_Supported": "Import endnu ikke understøttet",
"Export_Not_Yet_Supported": "Eksport endnu ikke understøttet",
"Import_Result_Info": "{imported} af {total} opskrifter blev importeret",
"Recipes_In_Import": "Opskrifter i din importerede fil",
"Toggle": "Skift",
"Import_Error": "Der opstod en fejl under din importering. Udvid detaljerne i bunden af siden for at se fejlen.",
"Warning_Delete_Supermarket_Category": "At slette en supermarkedskategori vil også slette alle relationer til mad. Er du sikker?",
"New_Supermarket": "Opret nyt supermarked",
"New_Supermarket_Category": "Opret ny supermarkedskategori",
"Are_You_Sure": "Er du sikker?"
}

View File

@@ -202,6 +202,7 @@
"Starting_Day": "Starting day of the week",
"Meal_Types": "Meal types",
"Meal_Type": "Meal type",
"New_Entry": "New Entry",
"Clone": "Clone",
"Drag_Here_To_Delete": "Drag here to delete",
"Meal_Type_Required": "Meal type is required",

View File

@@ -379,5 +379,40 @@
"Reset": "Herstel",
"remember_search": "Onthoud zoekopdracht",
"tree_select": "Gebruik boomselectie",
"sql_debug": "SQL Debug"
"sql_debug": "SQL Debug",
"Bookmarklet": "Bladwijzer",
"no_more_images_found": "Geen extra afbeeldingen gevonden op website.",
"import_duplicates": "Om dubbelingen te voorkomen worden recepten met dezelfde naam als bestaande genegeerd. Vink dit vakje aan om alles te importeren.",
"paste_json": "Plak json of html bron hier om recept te laden.",
"Click_To_Edit": "Klik om te bewerken",
"Imported": "Geïmporteerd",
"Multiple": "Meerdere",
"Single": "Enkele",
"Options": "Opties",
"Importer_Help": "Meer informatie en hulp over de importtool:",
"Documentation": "Documentatie",
"Select_App_To_Import": "Selecteer een app om van te importeren",
"Import_Supported": "Import ondersteund",
"Export_Supported": "Export ondersteund",
"Import_Not_Yet_Supported": "Import nog niet ondersteund",
"Export_Not_Yet_Supported": "Export nog niet ondersteund",
"Import_Result_Info": "{imported} van {total} recepten zijn geïmporteerd",
"Recipes_In_Import": "Recepten in je importbestand",
"Toggle": "Schakelaar",
"New_Supermarket": "Maak nieuwe supermarkt",
"New_Supermarket_Category": "Maak nieuwe supermarktcategorie",
"Are_You_Sure": "Weet je het zeker?",
"Ingredient Editor": "Ingrediënten editor",
"App": "App",
"click_image_import": "Klik op de afbeelding die je wil importeren voor dit recept",
"err_deleting_protected_resource": "Het object dat je probeert te verwijderen is in gebruik en kan daardoor niet verwijderd worden.",
"one_url_per_line": "Een URL per regel",
"Protected": "Beschermd",
"Website": "Website",
"Imported_From": "Geïmporteerd van",
"Import_Error": "Er is een fout opgetreden tijdens je import. Breid de details aan de onderzijde van de pagina uit om ze te bekijken.",
"Warning_Delete_Supermarket_Category": "Een supermarktcategorie verwijderen verwijdert ook alle relaties naar ingrediënten. Weet je het zeker?",
"create_food_desc": "Maak een ingrediënt en link aan dit recept.",
"additional_options": "Extra opties",
"Create Food": "Maak Ingrediënt"
}

View File

@@ -383,5 +383,34 @@
"additional_options": "Opcje dodatkowe",
"err_deleting_protected_resource": "Obiekt, który próbujesz usunąć, jest nadal używany i nie można go usunąć.",
"Protected": "Chroniony",
"Ingredient Editor": "Edytor składników"
"Ingredient Editor": "Edytor składników",
"one_url_per_line": "Jeden URL na linię",
"Website": "Strona internetowa",
"App": "Aplikacja",
"Bookmarklet": "Skryptozakładka",
"click_image_import": "Kliknij obraz, który chcesz zaimportować do tego przepisu",
"no_more_images_found": "Nie znaleziono dodatkowych zdjęć na stronie internetowej.",
"import_duplicates": "Aby zapobiec duplikatom przepisy tej samej nazwie, co istniejące, są ignorowane. Zaznacz to pole, aby zaimportować wszystko.",
"paste_json": "Wklej tutaj źródło json lub html, aby załadować przepis.",
"Click_To_Edit": "Kliknij aby edytować",
"Imported_From": "Zaimportowane z",
"Options": "Opcje",
"Imported": "Importowany",
"Single": "Pojedynczy",
"Multiple": "Wiele",
"Documentation": "Dokumentacja",
"Import_Supported": "Importowanie wspierane",
"Export_Supported": "Eksportowanie wspierane",
"Import_Not_Yet_Supported": "Importowanie jeszcze nie wspierane",
"Export_Not_Yet_Supported": "Eksportowanie jeszcze nie wspierane",
"Import_Result_Info": "{imported} z {total} przepisów zostało zaimportowanych",
"Recipes_In_Import": "Przepisy w pliku importu",
"Toggle": "Przełącznik",
"Import_Error": "Podczas importowania wystąpił błąd. Rozwiń Szczegóły na dole strony, aby go wyświetlić.",
"Warning_Delete_Supermarket_Category": "Usunięcie kategorii supermarketu spowoduje również usunięcie wszystkich relacji z żywnością. Jesteś pewny?",
"New_Supermarket": "Stwórz nowy supermarket",
"New_Supermarket_Category": "Utwórz nową kategorię supermarketów",
"Are_You_Sure": "Jesteś pewny?",
"Importer_Help": "Więcej informacji i pomoc na temat tego importera:",
"Select_App_To_Import": "Wybierz aplikację, z której chcesz zaimportować"
}

View File

@@ -27,7 +27,7 @@
"Show_as_header": "Показывать как заголовок",
"Hide_as_header": "Скрыть заголовок",
"Copy_template_reference": "Скопировать ссылку на шаблон",
"Save_and_View": "Сохронить и показать",
"Save_and_View": "Сохранить и показать",
"Manage_Books": "Управление книгами",
"Meal_Plan": "Планирование блюд",
"Select_Book": "Выбрать книгу",
@@ -188,18 +188,18 @@
"Title_or_Recipe_Required": "Требуется выбор названия или рецепта",
"Color": "Цвет",
"New_Meal_Type": "Новый тип питания",
"Week_Numbers": "",
"Show_Week_Numbers": "",
"Export_As_ICal": "",
"Week_Numbers": "Номер недели",
"Show_Week_Numbers": "Показать номера недель?",
"Export_As_ICal": "Экспорт текущего периода в iCal формат",
"Export_To_ICal": "",
"Cannot_Add_Notes_To_Shopping": "",
"Added_To_Shopping_List": "",
"Shopping_List_Empty": "",
"Next_Period": "",
"Previous_Period": "",
"Current_Period": "",
"Next_Day": "",
"Previous_Day": "",
"Added_To_Shopping_List": "Добавлено в список покупок",
"Shopping_List_Empty": "В настоящее время ваш список покупок пуст, вы можете добавить пункты через контекстное меню записи плана питания (щелкните правой кнопкой мыши на карточке или щелкните левой кнопкой мыши на значке меню)",
"Next_Period": "Следующий период",
"Previous_Period": "Предыдущий период",
"Current_Period": "Текущий период",
"Next_Day": "Следующий день",
"Previous_Day": "Предыдущий день",
"Add_nutrition_recipe": "Добавьте питательные вещества в рецепт",
"and_down": "Вниз",
"Added_by": "Добавлено",
@@ -217,6 +217,121 @@
"Search Settings": "Искать настройки",
"err_merging_resource": "Произошла ошибка при перемещении ресурса!",
"Remove_nutrition_recipe": "Уберите питательные вещества из рецепта",
"err_moving_resource": "Ошибка при перемещении ресурса!",
"NotInShopping": "{food} отсутствует в вашем списке покупок."
"err_moving_resource": "Произошла ошибка при перемещении ресурса!",
"NotInShopping": "{food} отсутствует в вашем списке покупок.",
"RemoveFoodFromShopping": "Удалить {food} из вашего списка покупок",
"ShowDelayed": "Показать отложенные элементы",
"Completed": "Завершено",
"save_filter": "Сохранить фильтр",
"sort_by": "Сортировать по",
"shared_with": "Совместно с",
"Custom Filter": "Пользовательский фильтр",
"filter_name": "Имя фильтра",
"created_on": "Создано на",
"last_viewed": "Последний просмотренный",
"select_keyword": "Выбрать ключевое слово",
"parameter_count": "Параметр {count}",
"Units": "Единицы",
"left_handed": "Режим для левшей",
"User": "Пользователь",
"Page": "Страница",
"Advanced": "Расширенный",
"Auto_Planner": "Автопланировщик",
"Hide_Keyword": "Скрыть ключевые слова",
"Clear": "Очистить",
"search_rank": "Поисковый рейтинг",
"show_rating": "Показать рейтинг",
"show_books": "Показать книги",
"show_foods": "Показать продукты",
"Documentation": "Документация",
"Reset": "Сбросить",
"Imported": "Импортировано",
"no_pinned_recipes": "У Вас нет закреплённых рецептов!",
"advanced_search_settings": "Расширенные настройки поиска",
"updatedon": "Обновлено",
"copy_to_new": "Скопировать в новый рецепт",
"recipe_filter": "Фильтр рецептов",
"New_Supermarket": "Создание нового супермаркета",
"Import_Error": "Во время импорта произошла ошибка. Для просмотра разверните \"Подробности\" в нижней части страницы.",
"OfflineAlert": "Вы находитесь вне сети, список покупок может не синхронизироваться.",
"Coming_Soon": "Скоро",
"Ingredient Editor": "Редактор ингредиентов",
"New_Cookbook": "Новая кулинарная книга",
"Are_You_Sure": "Вы уверены?",
"Protected": "Защищено",
"Warning_Delete_Supermarket_Category": "Удаление категории супермаркета также приведет к удалению всех связей с продуктами. Вы уверены?",
"Multiple": "Несколько",
"add_keyword": "Добавить ключевое слово",
"Random Recipes": "Случайные рецепты",
"nothing_planned_today": "Вы ничего не запланировали на сегодня!",
"book_filter_help": "Включать рецепты из фильтра рецептов в дополнение к назначенным вручную.",
"make_now": "Сделать сейчас",
"date_created": "Дата создана",
"left_handed_help": "Оптимизирует пользовательский интерфейс для использования левой рукой.",
"show_keywords": "Показать ключевые слова",
"DeleteShoppingConfirm": "Вы уверены, что хотите удалить все {food} из вашего списка покупок?",
"Inherit": "Наследовать",
"InheritFields": "Наследование значений полей",
"FoodInherit": "Наследуемые поля продуктов питания",
"ShowUncategorizedFood": "Показать неопределенное",
"SupermarketCategoriesOnly": "Только категории супермаркетов",
"CountMore": "...+{count} больше",
"Warning": "Предупреждение",
"mealplan_autoexclude_onhand": "Исключить продукты питания, имеющиеся в наличии",
"NoCategory": "Категория не выбрана.",
"shopping_share": "Поделиться списком покупок",
"shopping_auto_sync": "Автосинхронизация",
"mealplan_autoadd_shopping": "Автоматическое добавление плана питания",
"food_recipe_help": "Если вы разместите здесь ссылку на рецепт, то он будет включен в любой другой рецепт, в котором используется это блюдо",
"expert_mode": "Экспертный режим",
"enable_expert": "Включить экспертный режим",
"review_shopping": "Просмотрите записи о покупках перед сохранением",
"empty_list": "Список пуст",
"default_delay_desc": "Число часов по умолчанию для отсрочки записи в списке покупок.",
"one_url_per_line": "Один URL в строке",
"mealplan_autoinclude_related": "Добавить сопутствующие рецепты",
"default_delay": "Часы задержки по умолчанию",
"shopping_share_desc": "Пользователи будут видеть все товары, которые вы добавляете в список покупок. Они должны добавить вас, чтобы увидеть предметы в своем списке.",
"shopping_auto_sync_desc": "Установка значения 0 отключает автоматическую синхронизацию. При просмотре списка покупок список обновляется каждые несколько секунд, чтобы синхронизировать изменения, которые мог внести кто-то другой. Полезно, когда покупки совершают несколько человек, но при этом используются мобильные данные.",
"mealplan_autoadd_shopping_desc": "Автоматическое добавление ингредиентов плана питания в список покупок.",
"mealplan_autoexclude_onhand_desc": "При добавлении плана питания в список покупок (вручную или автоматически) исключайте ингредиенты, которые в данный момент есть под рукой.",
"mealplan_autoinclude_related_desc": "При добавлении плана питания в список покупок (вручную или автоматически) включайте все связанные с ним рецепты.",
"filter_to_supermarket": "Фильтр для супермаркета",
"err_move_self": "Невозможно переместить элемент на себя",
"nothing": "Нечего делать",
"err_merge_self": "Невозможно объединить элемент с самим собой",
"show_sql": "Показать SQL",
"show_filters": "Показать фильтры",
"show_units": "Показать единицы",
"fields": "Поля",
"advanced": "Расширенный",
"simple_mode": "Простой режим",
"ingredient_list": "Список ингредиентов",
"paste_ingredients": "Добавить ингредиенты",
"paste_ingredients_placeholder": "Вставьте сюда список ингредиентов...",
"recipe_name": "Название рецепта",
"view_recipe": "Посмотреть рецепт",
"times_cooked": "Время готовки",
"last_cooked": "Последнее приготовленое",
"date_viewed": "Последний просмотренный",
"Internal": "Внутренний",
"Imported_From": "Импортировано из",
"Keyword": "Ключевое слово",
"Supermarkets": "Супермаркеты",
"Select": "Выбрать",
"remove_selection": "Отменить выбор",
"select_food": "Выберите продукты питания",
"select_unit": "Выберите единицу",
"select_recipe": "Выбрать рецепт",
"select_file": "Выбрать файл",
"Ratings": "Рейтинги",
"Quick actions": "Быстрые действия",
"Pinned": "Прикрепленный",
"Planned": "Запланировано",
"Options": "Опции",
"additional_options": "Дополнительные опции",
"create_food_desc": "Создайте блюдо и свяжите его с этим рецептом.",
"err_deleting_protected_resource": "Объект, который вы пытаетесь удалить, все еще используется и не может быть удален.",
"tree_select": "Выбор дерева для использования",
"remember_search": "Запомнить поиск"
}

File diff suppressed because it is too large Load Diff