Merge branch 'develop' into feature/vue3

# Conflicts:
#	cookbook/views/api.py
#	cookbook/views/views.py
#	requirements.txt
This commit is contained in:
vabene1111
2024-11-12 16:45:21 +01:00
20 changed files with 2797 additions and 1083 deletions

View File

@@ -89,12 +89,13 @@ class ImportExportBase(forms.Form):
COOKMATE = 'COOKMATE'
REZEPTSUITEDE = 'REZEPTSUITEDE'
PDF = 'PDF'
GOURMET = 'GOURMET'
type = forms.ChoiceField(choices=((DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'),
(SAFFRON, 'Saffron'), (CHEFTAP, 'ChefTap'), (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'),
(DOMESTICA, 'Domestica'), (MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'),
(COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de')))
(COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de'), (GOURMET, 'Gourmet')))
class MultipleFileInput(forms.ClearableFileInput):

View File

@@ -0,0 +1,211 @@
import base64
from io import BytesIO
from lxml import etree
import requests
from pathlib import Path
from bs4 import BeautifulSoup, Tag
from cookbook.helper.HelperFunctions import validate_import_url
from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_url_import import parse_servings, parse_servings_text, parse_time, iso_duration_to_minutes
from cookbook.integration.integration import Integration
from cookbook.models import Ingredient, Recipe, Step, Keyword
from recipe_scrapers import scrape_html
class Gourmet(Integration):
def split_recipe_file(self, file):
encoding = 'utf-8'
byte_string = file.read()
text_obj = byte_string.decode(encoding, errors="ignore")
soup = BeautifulSoup(text_obj, "html.parser")
return soup.find_all("div", {"class": "recipe"})
def get_ingredients_recursive(self, step, ingredients, ingredient_parser):
if isinstance(ingredients, Tag):
for ingredient in ingredients.children:
if not isinstance(ingredient, Tag):
continue
if ingredient.name in ["li"]:
step_name = "".join(ingredient.findAll(text=True, recursive=False)).strip().rstrip(":")
step.ingredients.add(Ingredient.objects.create(
is_header=True,
note=step_name[:256],
original_text=step_name,
space=self.request.space,
))
next_ingrediets = ingredient.find("ul", {"class": "ing"})
self.get_ingredients_recursive(step, next_ingrediets, ingredient_parser)
else:
try:
amount, unit, food, note = ingredient_parser.parse(ingredient.text.strip())
f = ingredient_parser.get_food(food)
u = ingredient_parser.get_unit(unit)
step.ingredients.add(
Ingredient.objects.create(
food=f,
unit=u,
amount=amount,
note=note,
original_text=ingredient.text.strip(),
space=self.request.space,
)
)
except ValueError:
pass
def get_recipe_from_file(self, file):
# 'file' comes is as a beautifulsoup object
source_url = None
for item in file.find_all('a'):
if item.has_attr('href'):
source_url = item.get("href")
break
name = file.find("p", {"class": "title"}).find("span", {"itemprop": "name"}).text.strip()
recipe = Recipe.objects.create(
name=name[:128],
source_url=source_url,
created_by=self.request.user,
internal=True,
space=self.request.space,
)
for category in file.find_all("span", {"itemprop": "recipeCategory"}):
keyword, created = Keyword.objects.get_or_create(name=category.text, space=self.request.space)
recipe.keywords.add(keyword)
try:
recipe.servings = parse_servings(file.find("span", {"itemprop": "recipeYield"}).text.strip())
except AttributeError:
pass
try:
prep_time = file.find("span", {"itemprop": "prepTime"}).text.strip().split()
prep_time[0] = prep_time[0].replace(',', '.')
if prep_time[1].lower() in ['stunde', 'stunden', 'hour', 'hours']:
prep_time_min = int(float(prep_time[0]) * 60)
elif prep_time[1].lower() in ['tag', 'tage', 'day', 'days']:
prep_time_min = int(float(prep_time[0]) * 60 * 24)
else:
prep_time_min = int(prep_time[0])
recipe.waiting_time = prep_time_min
except AttributeError:
pass
try:
cook_time = file.find("span", {"itemprop": "cookTime"}).text.strip().split()
cook_time[0] = cook_time[0].replace(',', '.')
if cook_time[1].lower() in ['stunde', 'stunden', 'hour', 'hours']:
cook_time_min = int(float(cook_time[0]) * 60)
elif cook_time[1].lower() in ['tag', 'tage', 'day', 'days']:
cook_time_min = int(float(cook_time[0]) * 60 * 24)
else:
cook_time_min = int(cook_time[0])
recipe.working_time = cook_time_min
except AttributeError:
pass
for cuisine in file.find_all('span', {'itemprop': 'recipeCuisine'}):
cuisine_name = cuisine.text
keyword = Keyword.objects.get_or_create(space=self.request.space, name=cuisine_name)
if len(keyword):
recipe.keywords.add(keyword[0])
for category in file.find_all('span', {'itemprop': 'recipeCategory'}):
category_name = category.text
keyword = Keyword.objects.get_or_create(space=self.request.space, name=category_name)
if len(keyword):
recipe.keywords.add(keyword[0])
step = Step.objects.create(
instruction='',
space=self.request.space,
show_ingredients_table=self.request.user.userpreference.show_step_ingredients,
)
ingredient_parser = IngredientParser(self.request, True)
ingredients = file.find("ul", {"class": "ing"})
self.get_ingredients_recursive(step, ingredients, ingredient_parser)
instructions = file.find("div", {"class": "instructions"})
if isinstance(instructions, Tag):
for instruction in instructions.children:
if not isinstance(instruction, Tag) or instruction.text == "":
continue
if instruction.name == "h3":
if step.instruction:
step.save()
recipe.steps.add(step)
step = Step.objects.create(
instruction='',
space=self.request.space,
)
step.name = instruction.text.strip()[:128]
else:
if instruction.name == "div":
for instruction_step in instruction.children:
for br in instruction_step.find_all("br"):
br.replace_with("\n")
step.instruction += instruction_step.text.strip() + ' \n\n'
notes = file.find("div", {"class": "modifications"})
if notes:
for n in notes.children:
if n.text == "":
continue
if n.name == "h3":
step.instruction += f'*{n.text.strip()}:* \n\n'
else:
for br in n.find_all("br"):
br.replace_with("\n")
step.instruction += '*' + n.text.strip() + '* \n\n'
description = ''
try:
description = file.find("div", {"id": "description"}).text.strip()
except AttributeError:
pass
if len(description) <= 512:
recipe.description = description
else:
recipe.description = description[:480] + ' ... (full description below)'
step.instruction += '*Description:* \n\n*' + description + '* \n\n'
step.save()
recipe.steps.add(step)
# import the Primary recipe image that is stored in the Zip
try:
image_path = file.find("img").get("src")
image_filename = image_path.split("\\")[1]
for f in self.import_zip.filelist:
zip_file_name = Path(f.filename).name
if image_filename == zip_file_name:
image_file = self.import_zip.read(f)
image_bytes = BytesIO(image_file)
self.import_recipe_image(recipe, image_bytes, filetype='.jpeg')
break
except Exception as e:
print(recipe.name, ': failed to import image ', str(e))
recipe.save()
return recipe
def get_files_from_recipes(self, recipes, el, cookie):
raise NotImplementedError('Method not implemented in storage integration')
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -153,6 +153,19 @@ class Integration:
il.total_recipes = len(new_file_list)
file_list = new_file_list
if isinstance(self, cookbook.integration.gourmet.Gourmet):
self.import_zip = import_zip
new_file_list = []
for file in file_list:
if file.file_size == 0:
next
if file.filename.startswith("index.htm"):
next
if file.filename.endswith(".htm"):
new_file_list += self.split_recipe_file(BytesIO(import_zip.read(file.filename)))
il.total_recipes = len(new_file_list)
file_list = new_file_list
for z in file_list:
try:
if not hasattr(z, 'filename') or isinstance(z, Tag):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-01 15:04+0200\n"
"PO-Revision-Date: 2024-08-27 07:58+0000\n"
"Last-Translator: Known Rabbit <opensource@rabit.pw>\n"
"PO-Revision-Date: 2024-11-04 10:29+0000\n"
"Last-Translator: Johnny Ip <ip.iohnny@gmail.com>\n"
"Language-Team: Chinese (Simplified) <http://translate.tandoor.dev/projects/"
"tandoor/recipes-backend/zh_Hans/>\n"
"Language: zh_CN\n"
@@ -1988,9 +1988,11 @@ msgid ""
" your installation.\n"
" "
msgstr ""
"<b>不推荐</b> 使用 gunicorn/python 提供媒体文件\n"
" 请按照 <a href=\"https://github.com/vabene1111/recipes/releases/"
"tag/0.8.1\">这里</a> 描述的步骤操作更新安装。\n"
"<b>不推荐</b> 使用 gunicorn/python 提供媒体文件\n"
" 请按照\n"
" <a href=\"https://github.com/vabene1111/recipes/releases/tag/0.8."
"1\">这里</a> 描述的步骤\n"
" 操作更新安装。\n"
" "
#: .\cookbook\templates\system.html:55 .\cookbook\templates\system.html:70

File diff suppressed because it is too large Load Diff

View File

@@ -82,9 +82,9 @@
{% else %}
{% trans 'Everything is fine!' %}
{% endif %}
<h4 class="mt-3">{% trans 'Allowed Hosts' %} <span
class="badge badge-{% if '*' in allowed_hosts %}warning{% else %}success{% endif %}">{% if '*' in allowed_hosts %}
class="badge badge-{% if '*' in allowed_hosts %}warning{% else %}success{% endif %}">{% if '*' in allowed_hosts %}
{% trans 'Warning' %}{% else %}{% trans 'Ok' %}{% endif %}</span></h4>
{% if debug %}
{% blocktrans %}
@@ -176,6 +176,33 @@
{#{% endfor %}#}
{# </textarea>#}
<h4 class="mt-3">API Stats</h4>
<h6 >Space Stats</h6>
<table class="table table-bordered table-striped">
{% for r in api_space_stats %}
<tr>
{% for c in r %}
<td>
{{ c }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<h6 >Endpoint Stats</h6>
<table class="table table-bordered table-striped">
{% for r in api_stats %}
<tr>
{% for c in r %}
<td>
{{ c }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<h4 class="mt-3">Debug</h4>
<textarea class="form-control" rows="20">
Gunicorn Media: {{ gunicorn_media }}

View File

@@ -13,6 +13,7 @@ from urllib.parse import unquote
from zipfile import ZipFile
import PIL.Image
import redis
import requests
from PIL import UnidentifiedImageError
from django.contrib import messages
@@ -29,6 +30,7 @@ from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.datetime_safe import date
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from drf_spectacular.types import OpenApiTypes
@@ -109,6 +111,44 @@ from recipes.settings import DRF_THROTTLE_RECIPE_URL_IMPORT, FDC_API_KEY, GOOGLE
DateExample = OpenApiExample('Date Format', value='1972-12-05', request_only=True)
BeforeDateExample = OpenApiExample('Before Date Format', value='-1972-12-05', request_only=True)
class LoggingMixin(object):
"""
logs request counts to redis cache total/per user/
"""
def initial(self, request, *args, **kwargs):
super(LoggingMixin, self).initial(request, *args, **kwargs)
if settings.REDIS_HOST:
d = date.today().isoformat()
space = request.space
endpoint = request.resolver_match.url_name
r = redis.StrictRedis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
username=settings.REDIS_USERNAME,
password=settings.REDIS_PASSWORD,
db=settings.REDIS_DATABASES['STATS'],
)
pipe = r.pipeline()
# Global and daily tallies for all URLs.
pipe.incr('api:request-count')
pipe.incr(f'api:request-count:{d}')
# Use a sorted set to store the user stats, with the score representing
# the number of queries the user made total or on a given day.
pipe.zincrby(f'api:space-request-count', 1, space.pk)
pipe.zincrby(f'api:space-request-count:{d}', 1, space.pk)
# Use a sorted set to store all the endpoints with score representing
# the number of queries the endpoint received total or on a given day.
pipe.zincrby(f'api:endpoint-request-count', 1, endpoint)
pipe.zincrby(f'api:endpoint-request-count:{d}', 1, endpoint)
pipe.execute()
@extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive',
@@ -423,7 +463,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin):
@extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='filter_list', description='User IDs, repeat for multiple', type=str, many=True),
]))
class UserViewSet(viewsets.ModelViewSet):
class UserViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = User.objects
serializer_class = UserSerializer
permission_classes = [CustomUserPermission & CustomTokenHasReadWriteScope]
@@ -442,7 +482,7 @@ class UserViewSet(viewsets.ModelViewSet):
return queryset
class GroupViewSet(viewsets.ModelViewSet):
class GroupViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
@@ -450,7 +490,7 @@ class GroupViewSet(viewsets.ModelViewSet):
http_method_names = ['get', ]
class SpaceViewSet(viewsets.ModelViewSet):
class SpaceViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Space.objects
serializer_class = SpaceSerializer
permission_classes = [IsReadOnlyDRF & CustomIsGuest | CustomIsOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
@@ -471,7 +511,7 @@ class SpaceViewSet(viewsets.ModelViewSet):
@extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='internal_note', description='text field to store information about the invite link', type=str),
]))
class UserSpaceViewSet(viewsets.ModelViewSet):
class UserSpaceViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = UserSpace.objects
serializer_class = UserSpaceSerializer
permission_classes = [(CustomIsSpaceOwner | CustomIsOwnerReadOnly) & CustomTokenHasReadWriteScope]
@@ -494,7 +534,7 @@ class UserSpaceViewSet(viewsets.ModelViewSet):
return self.queryset.filter(user=self.request.user, space=self.request.space)
class UserPreferenceViewSet(viewsets.ModelViewSet):
class UserPreferenceViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = UserPreference.objects
serializer_class = UserPreferenceSerializer
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
@@ -506,7 +546,7 @@ class UserPreferenceViewSet(viewsets.ModelViewSet):
return self.queryset.filter(user=self.request.user)
class StorageViewSet(viewsets.ModelViewSet):
class StorageViewSet(LoggingMixin, viewsets.ModelViewSet):
# TODO handle delete protect error and adjust test
queryset = Storage.objects
serializer_class = StorageSerializer
@@ -517,7 +557,7 @@ class StorageViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class ConnectorConfigConfigViewSet(viewsets.ModelViewSet):
class ConnectorConfigConfigViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ConnectorConfig.objects
serializer_class = ConnectorConfigConfigSerializer
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
@@ -527,7 +567,7 @@ class ConnectorConfigConfigViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class SyncViewSet(viewsets.ModelViewSet):
class SyncViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Sync.objects
serializer_class = SyncSerializer
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
@@ -537,7 +577,7 @@ class SyncViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class SyncLogViewSet(viewsets.ReadOnlyModelViewSet):
class SyncLogViewSet(LoggingMixin, viewsets.ReadOnlyModelViewSet):
queryset = SyncLog.objects
serializer_class = SyncLogSerializer
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
@@ -547,7 +587,7 @@ class SyncLogViewSet(viewsets.ReadOnlyModelViewSet):
return self.queryset.filter(sync__space=self.request.space)
class SupermarketViewSet(StandardFilterModelViewSet):
class SupermarketViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = Supermarket.objects
serializer_class = SupermarketSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -559,7 +599,7 @@ class SupermarketViewSet(StandardFilterModelViewSet):
# TODO does supermarket category have settings to support fuzzy filtering and/or merge?
class SupermarketCategoryViewSet(FuzzyFilterMixin, MergeMixin):
class SupermarketCategoryViewSet(LoggingMixin, FuzzyFilterMixin, MergeMixin):
queryset = SupermarketCategory.objects
model = SupermarketCategory
serializer_class = SupermarketCategorySerializer
@@ -571,7 +611,7 @@ class SupermarketCategoryViewSet(FuzzyFilterMixin, MergeMixin):
return super().get_queryset()
class SupermarketCategoryRelationViewSet(StandardFilterModelViewSet):
class SupermarketCategoryRelationViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = SupermarketCategoryRelation.objects
serializer_class = SupermarketCategoryRelationSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -582,7 +622,7 @@ class SupermarketCategoryRelationViewSet(StandardFilterModelViewSet):
return super().get_queryset()
class KeywordViewSet(TreeMixin):
class KeywordViewSet(LoggingMixin, TreeMixin):
queryset = Keyword.objects
model = Keyword
serializer_class = KeywordSerializer
@@ -590,7 +630,7 @@ class KeywordViewSet(TreeMixin):
pagination_class = DefaultPagination
class UnitViewSet(MergeMixin, FuzzyFilterMixin):
class UnitViewSet(LoggingMixin, MergeMixin, FuzzyFilterMixin):
queryset = Unit.objects
model = Unit
serializer_class = UnitSerializer
@@ -598,7 +638,7 @@ class UnitViewSet(MergeMixin, FuzzyFilterMixin):
pagination_class = DefaultPagination
class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
class FoodInheritFieldViewSet(LoggingMixin, viewsets.ReadOnlyModelViewSet):
queryset = FoodInheritField.objects
serializer_class = FoodInheritFieldSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -610,7 +650,7 @@ class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
return super().get_queryset()
class FoodViewSet(TreeMixin):
class FoodViewSet(LoggingMixin, TreeMixin):
queryset = Food.objects
model = Food
serializer_class = FoodSerializer
@@ -761,7 +801,7 @@ class FoodViewSet(TreeMixin):
OpenApiParameter(name='order_direction', description='Order ascending or descending', type=str,
enum=['asc', 'desc']),
]))
class RecipeBookViewSet(StandardFilterModelViewSet):
class RecipeBookViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = RecipeBook.objects
serializer_class = RecipeBookSerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
@@ -785,7 +825,7 @@ class RecipeBookViewSet(StandardFilterModelViewSet):
OpenApiParameter(name='recipe', description='id of recipe - only return books for that recipe', type=int),
OpenApiParameter(name='book', description='id of book - only return recipes in that book', type=int),
]))
class RecipeBookEntryViewSet(viewsets.ModelViewSet):
class RecipeBookEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = RecipeBookEntry.objects
serializer_class = RecipeBookEntrySerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
@@ -820,7 +860,7 @@ MealPlanViewQueryParameters = [
@extend_schema_view(list=extend_schema(parameters=MealPlanViewQueryParameters),
ical=extend_schema(parameters=MealPlanViewQueryParameters,
responses={(200, 'text/calendar'): OpenApiTypes.STR}))
class MealPlanViewSet(viewsets.ModelViewSet):
class MealPlanViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = MealPlan.objects
serializer_class = MealPlanSerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
@@ -851,7 +891,7 @@ class MealPlanViewSet(viewsets.ModelViewSet):
return meal_plans_to_ical(self.get_queryset(), f'meal_plan_{from_date}-{to_date}.ics')
class AutoPlanViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
class AutoPlanViewSet(LoggingMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = AutoMealPlanSerializer
http_method_names = ['post', 'options']
@@ -915,7 +955,7 @@ class AutoPlanViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
return Response(serializer.errors, 400)
class MealTypeViewSet(viewsets.ModelViewSet):
class MealTypeViewSet(LoggingMixin, viewsets.ModelViewSet):
"""
returns list of meal types created by the
requesting user ordered by the order field.
@@ -935,7 +975,7 @@ class MealTypeViewSet(viewsets.ModelViewSet):
OpenApiParameter(name='food', description='ID of food to filter for', type=int),
OpenApiParameter(name='unit', description='ID of unit to filter for', type=int),
]))
class IngredientViewSet(viewsets.ModelViewSet):
class IngredientViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Ingredient.objects
serializer_class = IngredientSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -964,7 +1004,7 @@ class IngredientViewSet(viewsets.ModelViewSet):
type=int, many=True),
OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against object name.'), type=str),
]))
class StepViewSet(viewsets.ModelViewSet):
class StepViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Step.objects
serializer_class = StepSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1082,7 +1122,7 @@ class RecipePagination(PageNumberPagination):
description=_('Filter recipes that can be made with OnHand food. [''true''/''<b>false</b>'']'),
type=bool),
]))
class RecipeViewSet(viewsets.ModelViewSet):
class RecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Recipe.objects
serializer_class = RecipeSerializer
# TODO split read and write permission for meal plan guest
@@ -1244,7 +1284,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
@extend_schema_view(list=extend_schema(
parameters=[OpenApiParameter(name='food_id', description='ID of food to filter for', type=int), ]))
class UnitConversionViewSet(viewsets.ModelViewSet):
class UnitConversionViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = UnitConversion.objects
serializer_class = UnitConversionSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1267,7 +1307,7 @@ class UnitConversionViewSet(viewsets.ModelViewSet):
enum=[m[0] for m in PropertyType.CHOICES])
]
))
class PropertyTypeViewSet(viewsets.ModelViewSet):
class PropertyTypeViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = PropertyType.objects
serializer_class = PropertyTypeSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1281,7 +1321,7 @@ class PropertyTypeViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class PropertyViewSet(viewsets.ModelViewSet):
class PropertyViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = Property.objects
serializer_class = PropertySerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1291,7 +1331,7 @@ class PropertyViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ShoppingListRecipe.objects
serializer_class = ShoppingListRecipeSerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
@@ -1317,7 +1357,7 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
description=_('Returns the shopping list entries sorted by supermarket category order.'),
type=int),
]))
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ShoppingListEntry.objects
serializer_class = ShoppingListEntrySerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
@@ -1395,7 +1435,7 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
return Response(serializer.errors, 400)
class ViewLogViewSet(viewsets.ModelViewSet):
class ViewLogViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ViewLog.objects
serializer_class = ViewLogSerializer
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
@@ -1408,7 +1448,7 @@ class ViewLogViewSet(viewsets.ModelViewSet):
@extend_schema_view(list=extend_schema(
parameters=[OpenApiParameter(name='recipe', description='Filter for entries with the given recipe', type=int), ]))
class CookLogViewSet(viewsets.ModelViewSet):
class CookLogViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = CookLog.objects
serializer_class = CookLogSerializer
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
@@ -1420,7 +1460,7 @@ class CookLogViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class ImportLogViewSet(viewsets.ModelViewSet):
class ImportLogViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ImportLog.objects
serializer_class = ImportLogSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1430,7 +1470,7 @@ class ImportLogViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class ExportLogViewSet(viewsets.ModelViewSet):
class ExportLogViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ExportLog.objects
serializer_class = ExportLogSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1440,7 +1480,7 @@ class ExportLogViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space)
class BookmarkletImportViewSet(viewsets.ModelViewSet):
class BookmarkletImportViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = BookmarkletImport.objects
serializer_class = BookmarkletImportSerializer
permission_classes = [CustomIsUser & CustomTokenHasScope]
@@ -1456,7 +1496,7 @@ class BookmarkletImportViewSet(viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space).all()
class UserFileViewSet(StandardFilterModelViewSet):
class UserFileViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = UserFile.objects
serializer_class = UserFileSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1468,7 +1508,7 @@ class UserFileViewSet(StandardFilterModelViewSet):
return super().get_queryset()
class AutomationViewSet(StandardFilterModelViewSet):
class AutomationViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = Automation.objects
serializer_class = AutomationSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
@@ -1497,7 +1537,7 @@ class AutomationViewSet(StandardFilterModelViewSet):
@extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='internal_note', description=_('I have no idea what internal_note is for.'), type=str)
]))
class InviteLinkViewSet(StandardFilterModelViewSet):
class InviteLinkViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = InviteLink.objects
serializer_class = InviteLinkSerializer
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
@@ -1524,7 +1564,7 @@ class InviteLinkViewSet(StandardFilterModelViewSet):
enum=[m[0] for m in CustomFilter.MODELS])
]
))
class CustomFilterViewSet(StandardFilterModelViewSet):
class CustomFilterViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = CustomFilter.objects
serializer_class = CustomFilterSerializer
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
@@ -1540,7 +1580,7 @@ class CustomFilterViewSet(StandardFilterModelViewSet):
return super().get_queryset()
class AccessTokenViewSet(viewsets.ModelViewSet):
class AccessTokenViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = AccessToken.objects
serializer_class = AccessTokenSerializer
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]

View File

@@ -31,6 +31,7 @@ from cookbook.integration.recipesage import RecipeSage
from cookbook.integration.rezeptsuitede import Rezeptsuitede
from cookbook.integration.rezkonv import RezKonv
from cookbook.integration.saffron import Saffron
from cookbook.integration.gourmet import Gourmet
from cookbook.models import ExportLog, Recipe
from recipes import settings
@@ -80,6 +81,8 @@ def get_integration(request, export_type):
return Cookmate(request, export_type)
if export_type == ImportExportBase.REZEPTSUITEDE:
return Rezeptsuitede(request, export_type)
if export_type == ImportExportBase.GOURMET:
return Gourmet(request, export_type)
@group_required('user')

View File

@@ -1,9 +1,10 @@
import os
import re
from datetime import datetime
from datetime import datetime, timedelta
from io import StringIO
from uuid import UUID
import redis
from django.apps import apps
from django.conf import settings
from django.contrib import messages
@@ -17,6 +18,7 @@ from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.datetime_safe import date
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from drf_spectacular.views import SpectacularRedocView, SpectacularSwaggerView
@@ -354,6 +356,43 @@ def system(request):
for key in migration_info.keys():
migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len(migration_info[key]['applied_migrations'])
# API endpoint logging
r = redis.StrictRedis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
password='',
username='',
db=settings.REDIS_DATABASES['STATS'],
)
api_stats = [['Endpoint', 'Total']]
api_space_stats = [['User', 'Total']]
total_stats = ['All', int(r.get('api:request-count'))]
for i in range(0, 6):
d = (date.today() - timedelta(days=i)).isoformat()
api_stats[0].append(d)
api_space_stats[0].append(d)
total_stats.append(int(r.get(f'api:request-count:{d}')) if r.get(f'api:request-count:{d}') else 0)
api_stats.append(total_stats)
for x in r.zrange('api:endpoint-request-count', 0, -1, withscores=True, desc=True):
endpoint = x[0].decode('utf-8')
endpoint_stats = [endpoint, x[1]]
for i in range(0, 6):
d = (date.today() - timedelta(days=i)).isoformat()
endpoint_stats.append(r.zscore(f'api:endpoint-request-count:{d}', endpoint))
api_stats.append(endpoint_stats)
for x in r.zrange('api:space-request-count', 0, 20, withscores=True, desc=True):
s = x[0].decode('utf-8')
space_stats = [Space.objects.get(pk=s).name, x[1]]
for i in range(0, 6):
d = (date.today() - timedelta(days=i)).isoformat()
space_stats.append(r.zscore(f'api:space-request-count:{d}', s))
api_space_stats.append(space_stats)
return render(
request, 'system.html', {
'gunicorn_media': settings.GUNICORN_MEDIA,

View File

@@ -1,4 +1,4 @@
This application features a very versatile import and export feature in order
This application features a very versatile import and export feature in order
to offer the best experience possible and allow you to freely choose where your data goes.
!!! WARNING "WIP"
@@ -12,7 +12,7 @@ Feel like there is an important integration missing? Just take a look at the [in
if your favorite one is missing.
!!! info "Export"
I strongly believe in everyone's right to use their data as they please and therefore want to give you
I strongly believe in everyone's right to use their data as they please and therefore want to give you
the best possible flexibility with your recipes.
That said for most of the people getting this application running with their recipes is the biggest priority.
Because of this importing as many formats as possible is prioritized over exporting.
@@ -21,50 +21,55 @@ if your favorite one is missing.
Overview of the capabilities of the different integrations.
| Integration | Import | Export | Images |
|--------------------| ------ | -- | ------ |
| Default | ✔️ | ✔️ | ✔️ |
| Nextcloud | ✔️ | ⌚ | ✔️ |
| Mealie | ✔️ | ⌚ | ✔️ |
| Chowdown | ✔️ | ⌚ | ✔️ |
| Safron | ✔️ | ✔️ | ❌ |
| Paprika | ✔️ | ⌚ | ✔️ |
| ChefTap | ✔️ | ❌ | ❌ |
| Pepperplate | ✔️ | ⌚ | ❌ |
| RecipeSage | ✔️ | ✔️ | ✔️ |
| Rezeptsuite.de | ✔️ | ❌ | ✔️ |
| Domestica | ✔️ | ⌚ | ✔️ |
| MealMaster | ✔️ | ❌ | ❌ |
| RezKonv | ✔️ | ❌ | ❌ |
| OpenEats | ✔️ | ❌ | ⌚ |
| Plantoeat | ✔️ | ❌ | |
| CookBookApp | ✔️ | ⌚ | ✔️ |
| CopyMeThat | ✔️ | ❌ | ✔️ |
| Melarecipes | ✔️ | ⌚ | ✔️ |
| Cookmate | ✔️ | ⌚ | ✔️ |
| PDF (experimental) | ⌚️ | ✔️ | ✔️ |
| ------------------ | ------ | ------ | ------ |
| Default | ✔️ | ✔️ | ✔️ |
| Nextcloud | ✔️ | ⌚ | ✔️ |
| Mealie | ✔️ | ⌚ | ✔️ |
| Chowdown | ✔️ | ⌚ | ✔️ |
| Safron | ✔️ | ✔️ | ❌ |
| Paprika | ✔️ | ⌚ | ✔️ |
| ChefTap | ✔️ | ❌ | ❌ |
| Pepperplate | ✔️ | ⌚ | ❌ |
| RecipeSage | ✔️ | ✔️ | ✔️ |
| Rezeptsuite.de | ✔️ | ❌ | ✔️ |
| Domestica | ✔️ | ⌚ | ✔️ |
| MealMaster | ✔️ | ❌ | ❌ |
| RezKonv | ✔️ | ❌ | ❌ |
| OpenEats | ✔️ | ❌ | ⌚ |
| Plantoeat | ✔️ | ❌ | ✔ |
| CookBookApp | ✔️ | ⌚ | ✔️ |
| CopyMeThat | ✔️ | ❌ | ✔️ |
| Melarecipes | ✔️ | ⌚ | ✔️ |
| Cookmate | ✔️ | ⌚ | ✔️ |
| PDF (experimental) | ⌚️ | ✔️ | ✔️ |
| Gourmet | ✔️ | ❌ | ✔️ |
✔️ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
## Default
The default integration is the built in (and preferred) way to import and export recipes.
It is maintained with new fields added and contains all data to transfer your recipes from one installation to another.
It is also one of the few recipe formats that is actually structured in a way that allows for
easy machine readability if you want to use the data for any other purpose.
It is also one of the few recipe formats that is actually structured in a way that allows for
easy machine readability if you want to use the data for any other purpose.
## RecipeSage
Go to Settings > Export Recipe Data and select `EXPORT AS JSON-LD (BEST)`. Then simply upload the exported file
Go to Settings > Export Recipe Data and select `EXPORT AS JSON-LD (BEST)`. Then simply upload the exported file
to Tandoor.
The RecipeSage integration also allows exporting. To migrate from Tandoor to RecipeSage simply export with Recipe Sage
The RecipeSage integration also allows exporting. To migrate from Tandoor to RecipeSage simply export with Recipe Sage
selected and import the json file in RecipeSage. Images are currently not supported for exporting.
## Domestica
Go to Import/Export and select `Export Recipes`. Then simply upload the exported file
Go to Import/Export and select `Export Recipes`. Then simply upload the exported file
to Tandoor.
## Nextcloud
Importing recipes from Nextcloud cookbook is very easy and since Nextcloud Cookbook provides nice, standardized and
Importing recipes from Nextcloud cookbook is very easy and since Nextcloud Cookbook provides nice, standardized and
structured information most of your recipe is going to be intact.
Follow these steps to import your recipes
@@ -77,10 +82,9 @@ Follow these steps to import your recipes
You will get a `Recipes.zip` file. Simply upload the file and choose the Nextcloud Cookbook type.
!!! WARNING "Folder Structure"
Importing only works if the folder structure is correct. If you do not use the standard path or create the
zip file in any other way make sure the structure is as follows
```
Recipes.zip/
Importing only works if the folder structure is correct. If you do not use the standard path or create the
zip file in any other way make sure the structure is as follows
` Recipes.zip/
└── Recipes/
├── Recipe1/
│ ├── recipe.json
@@ -88,27 +92,29 @@ You will get a `Recipes.zip` file. Simply upload the file and choose the Nextclo
└── Recipe2/
├── recipe.json
└── full.jpg
```
`
## Mealie
Mealie provides structured data similar to nextcloud.
To migrate your recipes
Mealie provides structured data similar to nextcloud.
To migrate your recipes
1. Go to your Mealie settings and create a new Backup.
2. Download the backup by clicking on it and pressing download (this wasn't working for me, so I had to manually pull it from the server).
3. Upload the entire `.zip` file to the importer page and import everything.
## Chowdown
Chowdown stores all your recipes in plain text markdown files in a directory called `_recipes`.
Chowdown stores all your recipes in plain text markdown files in a directory called `_recipes`.
Images are saved in a directory called `images`.
In order to import your Chowdown recipes simply create a `.zip` file from those two folders and import them.
In order to import your Chowdown recipes simply create a `.zip` file from those two folders and import them.
The folder structure should look as follows
!!! info "_recipes"
For some reason chowdown uses `_` before the `recipes` folder. To avoid confusion the import supports both
`_recipes` and `recipes`
For some reason chowdown uses `_`before the`recipes`folder. To avoid confusion the import supports both
`\_recipes`and`recipes`
```
Recipes.zip/
@@ -123,31 +129,35 @@ Recipes.zip/
```
## Safron
Go to your safron settings page and export your recipes.
Then simply upload the entire `.zip` file to the importer.
!!! warning "Images"
Safron exports do not contain any images. They will be lost during import.
Safron exports do not contain any images. They will be lost during import.
## Paprika
A Paprika export contains a folder with a html representation of your recipes and a `.paprikarecipes` file.
The `.paprikarecipes` file is basically just a zip with gzipped contents. Simply upload the whole file and import
all your recipes.
The `.paprikarecipes` file is basically just a zip with gzipped contents. Simply upload the whole file and import
all your recipes.
## Pepperplate
Pepperplate provides a `.zip` file containing all of your recipes as `.txt` files. These files are well-structured and allow
the import of all data without losing anything.
Simply export the recipes from Pepperplate and upload the zip to Tandoor. Images are not included in the export and
Simply export the recipes from Pepperplate and upload the zip to Tandoor. Images are not included in the export and
thus cannot be imported.
## ChefTap
ChefTaps allows you to export your recipes from the app (I think). The export is a zip file containing a folder called
`cheftap_export` which in turn contains `.txt` files with your recipes.
This format is basically completely unstructured and every export looks different. This makes importing it very hard
and leads to suboptimal results. Images are also not supported as they are not included in the export (at least
and leads to suboptimal results. Images are also not supported as they are not included in the export (at least
the tests I had).
Usually the import should recognize all ingredients and put everything else into the instructions. If your import fails
@@ -156,31 +166,36 @@ or is worse than this feel free to provide me with more example data and I can t
As ChefTap cannot import these files anyway there won't be an exporter implemented in Tandoor.
## MealMaster
Meal master can be imported by uploading one or more meal master files.
The files should either be `.txt`, `.MMF` or `.MM` files.
Meal master can be imported by uploading one or more meal master files.
The files should either be `.txt`, `.MMF` or `.MM` files.
The MealMaster spec allows for many variations. Currently, only the one column format for ingredients is supported.
Second line notes to ingredients are currently also not imported as a note but simply put into the instructions.
If you have MealMaster recipes that cannot be imported feel free to raise an issue.
## RezKonv
The RezKonv format is primarily used in the german recipe manager RezKonv Suite.
The RezKonv format is primarily used in the german recipe manager RezKonv Suite.
To migrate from RezKonv Suite to Tandoor select `Export > Gesamtes Kochbuch exportieren` (the last option in the export menu).
The generated file can simply be imported into Tandoor.
As I only had limited sample data feel free to open an issue if your RezKonv export cannot be imported.
## Recipekeeper
Recipe keeper allows you to export a zip file containing recipes and images using its apps.
Recipe keeper allows you to export a zip file containing recipes and images using its apps.
This zip file can simply be imported into Tandoor.
## OpenEats
OpenEats does not provide any way to export the data using the interface. Luckily it is relatively easy to export it from the command line.
You need to run the command `python manage.py dumpdata recipe ingredient` inside of the application api container.
If you followed the default installation method you can use the following command `docker-compose -f docker-prod.yml run --rm --entrypoint 'sh' api ./manage.py dumpdata recipe ingredient`.
This command might also work `docker exec -it openeats_api_1 ./manage.py dumpdata recipe ingredient rating recipe_groups > recipe_ingredients.json`
Store the outputted json string in a `.json` file and simply import it using the importer. The file should look something like this
```json
[
{
@@ -231,30 +246,44 @@ CookBookApp can export .zip files containing .html files. Upload the entire ZIP
CopyMeThat can export .zip files containing an `.html` file as well as a folder containing all the images. Upload the entire ZIP to Tandoor to import all included recipes.
## Cookmate
Cookmate allows you to export a `.mcb` file which you can simply upload to tandoor and import all your recipes.
## RecetteTek
RecetteTek exports are `.rtk` files which can simply be uploaded to tandoor to import all your recipes.
RecetteTek exports are `.rtk` files which can simply be uploaded to tandoor to import all your recipes.
## Rezeptsuite.de
Rezeptsuite.de exports are `.xml` files which can simply be uploaded to tandoor to import all your recipes.
It appears that Reptsuite, depending on the client, might export a `.zip` file containing a `.cml` file.
If this happens just unzip the zip file and change `.cml` to `.xml` to import your recipes.
If this happens just unzip the zip file and change `.cml` to `.xml` to import your recipes.
## Melarecipes
Melarecipes provides multiple export formats but only the `MelaRecipes` format can export the complete collection.
Perform this export and open the `.melarecipes` file using your favorite archive opening program (e.g 7zip).
Perform this export and open the `.melarecipes` file using your favorite archive opening program (e.g 7zip).
Repeat this if the file contains another `.melarecipes` file until you get a list of one or many `.melarecipe` files.
Upload all `.melarecipe` files you want to import to tandoor and start the import.
## PDF
The PDF Exporter is an experimental feature that uses the puppeteer browser renderer to render each recipe and export it to PDF.
For that to work it downloads a chromium binary of about 140 MB to your server and then renders the PDF files using that.
The PDF Exporter is an experimental feature that uses the puppeteer browser renderer to render each recipe and export it to PDF.
For that to work it downloads a chromium binary of about 140 MB to your server and then renders the PDF files using that.
Since that is something some server administrators might not want there the PDF exporter is disabled by default and can be enabled with `ENABLE_PDF_EXPORT=1` in `.env`.
See [this issue](https://github.com/TandoorRecipes/recipes/pull/1211) for more discussion on this and
See [this issue](https://github.com/TandoorRecipes/recipes/pull/1211) for more discussion on this and
[this issue](https://github.com/TandoorRecipes/recipes/issues/781) for the future plans to support server side rendering.
## Gourmet
An importer for files from [Gourmet](https://github.com/thinkle/gourmet/). As the `.grmt` files appears to lack the unit for ingredients
a file with `.zip` file with `.htm` and `.jpg`is expected.
To generate the file export to 'html' in Gourmet and zip the folder generated.
The import of menues is not supported
Export is not supported due to problems with `.grmt` format.

View File

@@ -148,6 +148,14 @@ PRIVACY_URL = os.getenv('PRIVACY_URL', '')
IMPRINT_URL = os.getenv('IMPRINT_URL', '')
HOSTED = extract_bool('HOSTED', False)
REDIS_HOST = os.getenv('REDIS_HOST', None)
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_USERNAME = os.getenv('REDIS_USERNAME', None)
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', None)
REDIS_DATABASES = {
'STATS': 0
}
MESSAGE_TAGS = {messages.ERROR: 'danger'}
# Application definition

View File

@@ -13,12 +13,12 @@ django-oauth-toolkit==2.4.0
django-debug-toolbar==4.3.0
bleach==6.0.0
gunicorn==22.0.0
lxml==5.1.0
lxml==5.3.0
Markdown==3.5.1
Pillow==10.4.0
psycopg2-binary==2.9.9
python-dotenv==1.0.0
requests==2.32.0
requests==2.32.3
six==1.16.0
webdavclient3==3.14.6
whitenoise==6.7.0
@@ -35,7 +35,7 @@ django-allauth==0.61.1
recipe-scrapers==15.2.1
django-scopes==2.0.0
django-treebeard==4.7
django-cors-headers==4.3.1
django-cors-headers==4.6.0
django-storages==1.14.2
boto3==1.28.75
django-prometheus==2.2.0
@@ -46,6 +46,7 @@ pyppeteer==2.0.0
pytube==15.0.0
aiohttp==3.10.2
inflection==0.5.1
redis==5.2.0
django-vite==3.0.3
google-generativeai==0.5.3
@@ -58,5 +59,5 @@ pytest-html==4.1.1
pytest-asyncio==0.23.5
pytest-xdist==3.6.1
autopep8==2.0.4
flake8==6.1.0
flake8==7.1.1
yapf==0.40.2

View File

@@ -2,12 +2,12 @@
"err_fetching_resource": "Si è verificato un errore durante il recupero di una risorsa!",
"err_creating_resource": "Si è verificato un errore durante la creazione di una risorsa!",
"err_updating_resource": "Si è verificato un errore durante l'aggiornamento di una risorsa!",
"err_deleting_resource": "Si è verificato un errore durante la cancellazione di una risorsa!",
"err_deleting_resource": "Si è verificato un errore durante l'eliminazione di una risorsa!",
"success_fetching_resource": "Risorsa recuperata con successo!",
"success_creating_resource": "Risorsa creata con successo!",
"success_updating_resource": "Risorsa aggiornata con successo!",
"success_deleting_resource": "Risorsa eliminata con successo!",
"import_running": "Importazione in corso, attendere prego!",
"import_running": "Importazione in corso, attendere!",
"all_fields_optional": "Tutti i campi sono opzionali e possono essere lasciati vuoti.",
"convert_internal": "Converti come ricetta interna",
"show_only_internal": "Mostra solo ricette interne",
@@ -15,29 +15,29 @@
"Log_Recipe_Cooking": "Aggiungi a ricette cucinate",
"External_Recipe_Image": "Immagine ricetta esterna",
"Add_to_Shopping": "Aggiunti agli acquisti",
"Add_to_Plan": "Aggiungi a Piano",
"Step_start_time": "Ora di inizio dello Step",
"Add_to_Plan": "Aggiungi a piano",
"Step_start_time": "Ora di inizio dello step",
"Sort_by_new": "Prima i nuovi",
"Recipes_per_page": "Ricette per pagina",
"Manage_Books": "Gestisci Libri",
"Manage_Books": "Gestisci libri",
"Meal_Plan": "Piano alimentare",
"Select_Book": "Seleziona Libro",
"Select_Book": "Seleziona libro",
"Recipe_Image": "Immagine ricetta",
"Import_finished": "Importazione completata",
"View_Recipes": "Mostra ricette",
"Log_Cooking": "Registro ricette cucinate",
"New_Recipe": "Nuova Ricetta",
"New_Recipe": "Nuova ricetta",
"Url_Import": "Importa da URL",
"Reset_Search": "Ripristina Ricerca",
"Reset_Search": "Ripristina ricerca",
"Recently_Viewed": "Visualizzato di recente",
"Load_More": "Carica di più",
"Load_More": "Carica altro",
"New_Keyword": "Nuova parola chiave",
"Delete_Keyword": "Elimina parola chiave",
"Edit_Keyword": "Modifica parola chiave",
"Move_Keyword": "Sposta parola chiave",
"Merge_Keyword": "Unisci parola chiave",
"Hide_Keywords": "Nascondi parola chiave",
"Hide_Recipes": "Nascondi Ricette",
"Hide_Recipes": "Nascondi ricette",
"Keywords": "Parole chiave",
"Books": "Libri",
"Proteins": "Proteine",
@@ -53,7 +53,7 @@
"Rating": "Valutazione",
"Close": "Chiudi",
"Cancel": "Annulla",
"Link": "Link",
"Link": "Collegamento",
"Add": "Aggiungi",
"New": "Nuovo",
"Success": "Riuscito",
@@ -96,15 +96,15 @@
"move_confirmation": "Sposta <i>{child}</i> al primario <i>{parent}</i>",
"merge_confirmation": "Sostituisci <i>{source}</i> con <i>{target}</i>",
"move_selection": "Scegli un primario {type} dove spostare {source}.",
"merge_selection": "Sostituisci tutte le voci di {source} con il {type} selezionato.",
"merge_selection": "Sostituisci tutte le occorrenze di {source} con {type} selezionato.",
"Root": "Radice",
"Ignore_Shopping": "Ignora spesa",
"delete_confirmation": "Sei sicuro di voler eliminare {source}?",
"Description": "Descrizione",
"Icon": "Icona",
"Unit": "Unità di misura",
"Unit": "Unità",
"No_ID": "ID non trovato, non è possibile eliminare.",
"Recipe_Book": "Libro di Ricette",
"Recipe_Book": "Libro di ricette",
"create_title": "Nuovo {type}",
"edit_title": "Modifica {type}",
"Name": "Nome",
@@ -122,10 +122,10 @@
"merge_title": "Unisci {type}",
"Key_Shift": "Maiusc",
"del_confirmation_tree": "Sei sicuro di voler eliminare {source} e tutti gli elementi dipendenti?",
"Disable_Amount": "Disabilita Quantità",
"Disable_Amount": "Disabilita quantità",
"Key_Ctrl": "Ctrl",
"No_Results": "Nessun risultato",
"Create_New_Shopping Category": "Crea nuova categoria della spesa",
"Create_New_Shopping Category": "Crea nuova categoria di spesa",
"Create_New_Keyword": "Aggiungi nuova parola chiave",
"and_up": "& Su",
"step_time_minutes": "Tempo dello step in minuti",
@@ -133,23 +133,23 @@
"Show_as_header": "Mostra come intestazione",
"Hide_as_header": "Nascondi come intestazione",
"Copy_template_reference": "Copia riferimento template",
"Save_and_View": "Salva & Mostra",
"Save_and_View": "Salva e mostra",
"Text": "Testo",
"Edit_Recipe": "Modifica Ricetta",
"Move_Up": "Sposta Sopra",
"Move_Down": "Sposta Sotto",
"Step_Name": "Nome dello Step",
"Step_Type": "Tipo di Step",
"Enable_Amount": "Abilita Quantità",
"Add_Step": "Aggiungi Step",
"Edit_Recipe": "Modifica ricetta",
"Move_Up": "Sposta sopra",
"Move_Down": "Sposta sotto",
"Step_Name": "Nome dello step",
"Step_Type": "Tipo di step",
"Enable_Amount": "Abilita quantità",
"Add_Step": "Aggiungi step",
"Note": "Nota",
"Create_New_Food": "Aggiungi nuovo alimento",
"Make_Header": "Crea Intestazione",
"Make_Ingredient": "Crea Ingrediente",
"Make_Header": "Crea intestazione",
"Make_Ingredient": "Crea ingrediente",
"Create_New_Unit": "Aggiungi nuova unità",
"Instructions": "Istruzioni",
"Time": "Tempo",
"Shopping_Category": "Categoria Spesa",
"Shopping_Category": "Categoria spesa",
"Meal_Plan_Days": "Piani alimentari futuri",
"tree_root": "Radice dell'albero",
"Automation": "Automazione",
@@ -158,11 +158,11 @@
"Automate": "Automatizza",
"create_rule": "e crea automazione",
"Empty": "Vuoto",
"Food_Alias": "Alias Alimento",
"Unit_Alias": "Alias Unità",
"Keyword_Alias": "Alias Parola Chiave",
"Food_Alias": "Alias alimento",
"Unit_Alias": "Alias unità",
"Keyword_Alias": "Alias parola chiave",
"Table_of_Contents": "Indice dei contenuti",
"warning_feature_beta": "Questa funzione è attualmente in BETA (non è completa). Potrebbero verificarsi delle anomalie e modifiche che in futuro potrebbero bloccare la funzionalità stessa o rimuove i dati correlati ad essa.",
"warning_feature_beta": "Questa funzione è attualmente in BETA (fase di test). Potrebbero verificarsi delle anomalie e modifiche che in futuro potrebbero bloccare la funzionalità stessa o rimuove i dati correlati ad essa.",
"Shopping_list": "Lista della spesa",
"Title": "Titolo",
"Create_New_Meal_Type": "Aggiungi nuovo tipo di pasto",
@@ -188,7 +188,7 @@
"New_Meal_Type": "Nuovo tipo di pasto",
"Select_File": "Seleziona file",
"Image": "Immagine",
"Export_As_ICal": "Esporta il periodo attuale in formato .iCal",
"Export_As_ICal": "Esporta il periodo attuale in formato iCal",
"Week_Numbers": "Numeri della settimana",
"Show_Week_Numbers": "Mostra numeri della settimana?",
"file_upload_disabled": "Il caricamento dei file non è abilitato in questa istanza.",
@@ -202,12 +202,12 @@
"Previous_Day": "Giorno precedente",
"Add_nutrition_recipe": "Aggiungi nutrienti alla ricetta",
"Remove_nutrition_recipe": "Elimina nutrienti dalla ricetta",
"Coming_Soon": "In-Arrivo",
"Coming_Soon": "In arrivo",
"Auto_Planner": "Pianificazione automatica",
"New_Cookbook": "Nuovo libro di ricette",
"Hide_Keyword": "Nascondi parole chiave",
"Clear": "Pulisci",
"Shopping_List_Empty": "La tua lista della spesa è vuota, puoi aggiungere elementi dal menù contestuale di una voce nel piano alimentare (clicca con il tasto destro sulla scheda o clicca con il tasto sinistro sull'icona del menù)",
"Shopping_List_Empty": "La tua lista della spesa è vuota, puoi aggiungere elementi dal menù contestuale di una voce nel piano alimentare (fai clic con il tasto destro sulla scheda o fai clic con il tasto sinistro sull'icona del menù)",
"success_moving_resource": "Risorsa spostata con successo!",
"Shopping_Categories": "Categorie di spesa",
"IngredientInShopping": "Questo ingrediente è nella tua lista della spesa.",
@@ -236,7 +236,7 @@
"Completed": "Completato",
"shopping_share": "Condividi lista della spesa",
"shopping_auto_sync": "Sincronizzazione automatica",
"err_move_self": "Non è possibile muovere un elemento in sé stesso",
"err_move_self": "Non è possibile spostare un elemento in sé stesso",
"nothing": "Nulla da fare",
"show_sql": "Mostra SQL",
"Search Settings": "Impostazioni di ricerca",
@@ -256,8 +256,8 @@
"Use_Fractions_Help": "Converti automaticamente i decimali in frazioni quando apri una ricetta.",
"Language": "Lingua",
"Theme": "Tema",
"SupermarketCategoriesOnly": "Solo categorie supermercati",
"CountMore": "...più +{count}",
"SupermarketCategoriesOnly": "Solo categorie di supermercati",
"CountMore": "...+{count} in più",
"IgnoreThis": "Non aggiungere mai {food} alla spesa",
"InheritWarning": "{food} è impostato per ereditare, i cambiamenti potrebbero non essere applicati.",
"mealplan_autoadd_shopping": "Aggiungi automaticamente al piano alimentare",
@@ -277,7 +277,7 @@
"Copy Token": "Copia token",
"mealplan_autoinclude_related": "Aggiungi ricette correlate",
"default_delay": "Ore di ritardo predefinite",
"shopping_share_desc": "Gli utenti vedranno tutti gli elementi che aggiungi alla tua lista della spesa Per poter vedere gli elementi della loro lista, loro dovranno aggiungerti.",
"shopping_share_desc": "Gli utenti vedranno tutti gli elementi che aggiungi alla tua lista della spesa. Per poter vedere gli elementi della loro lista, loro dovranno aggiungerti.",
"mealplan_autoexclude_onhand_desc": "Quando aggiungi un piano alimentare alla lista della spesa (manualmente o automaticamente), escludi gli ingredienti che sono già disponibili.",
"default_delay_desc": "Il numero predefinito di ore per ritardare l'inserimento di una lista della spesa.",
"filter_to_supermarket": "Filtra per supermercato",
@@ -322,20 +322,20 @@
"Custom Filter": "Filtro personalizzato",
"shared_with": "Condiviso con",
"sort_by": "Ordina per",
"Ingredient Overview": "Panoramica Ingredienti",
"Ingredient Overview": "Panoramica ingredienti",
"show_units": "Mostra unità di misura",
"select_unit": "Seleziona unità di misura",
"Ingredient Editor": "Editor Ingredienti",
"Ingredient Editor": "Editor degli ingredienti",
"Private_Recipe": "Ricetta privata",
"Private_Recipe_Help": "La ricetta viene mostrata solo a te e a chi l'hai condivisa.",
"Protected": "Protetto",
"Copy Link": "Copia link",
"Copy Link": "Copia collegamento",
"Create_New_Shopping_Category": "Aggiungi nuova categoria di spesa",
"and_down": "& Giù",
"OnHand": "Attualmente disponibili",
"New_Entry": "Nuova voce",
"Use_Fractions": "Usa frazioni",
"FoodInherit": "Campi ereditabili dagli Alimenti",
"FoodInherit": "Campi ereditabili dagli alimenti",
"one_url_per_line": "Un indirizzo per riga",
"mealplan_autoexclude_onhand": "Escludi alimenti disponibili",
"mealplan_autoadd_shopping_desc": "Aggiungi automaticamente gli ingredienti del piano alimentare alla lista della spesa.",
@@ -353,10 +353,10 @@
"Use_Plural_Food_Always": "Usa sempre il plurale per gli alimenti",
"Use_Plural_Food_Simple": "Usa dinamicamente il plurale per gli alimenti",
"plural_usage_info": "Usa il plurale per le unità di misura e gli alimenti messi qui.",
"reusable_help_text": "Il link di invito dovrebbe essere usabile per più di un utente.",
"reusable_help_text": "Il collegamento di invito dovrebbe essere usabile per più di un utente.",
"empty_list": "La lista è vuota.",
"no_pinned_recipes": "Non hai ricette fissate!",
"recipe_name": "Nome Ricetta",
"recipe_name": "Nome ricetta",
"advanced_search_settings": "Impostazioni avanzate di ricerca",
"search_no_recipes": "Non sono state trovate ricette!",
"SubstituteOnHand": "Hai un sostituto disponibile.",
@@ -371,7 +371,7 @@
"add_keyword": "Aggiungi parola chiave",
"Export_Not_Yet_Supported": "Esportazione non ancora supportata",
"Import_Result_Info": "{imported} di {total} ricette sono state importate",
"Recipes_In_Import": "Rocette nel tuo file di importazione",
"Recipes_In_Import": "Ricette nel tuo file di importazione",
"Toggle": "Attiva/Disattiva",
"Import_Not_Yet_Supported": "Importazione non ancora supportata",
"Are_You_Sure": "Sei sicuro?",
@@ -386,7 +386,7 @@
"CategoryInstruction": "Trascina le categorie per cambiare l'ordine in cui appaiono nella lista della spesa.",
"show_sortby": "Mostra Ordina per",
"Page": "Pagina",
"Auto_Sort": "Ordinamento Automatico",
"Auto_Sort": "Ordinamento automatico",
"date_created": "Data di creazione",
"times_cooked": "Cucinato N volte",
"recipe_filter": "Filtro ricette",
@@ -403,10 +403,10 @@
"Nav_Color_Help": "Cambia il colore di navigazione.",
"Use_Kj": "Usa kJ invece di kcal",
"Comments_setting": "Mostra commenti",
"click_image_import": "Clicca sull'immagine che vuoi importare per questa ricetta",
"click_image_import": "Fai clic sull'immagine che vuoi importare per questa ricetta",
"no_more_images_found": "Non sono state trovate altre immagini sul sito web.",
"Click_To_Edit": "Clicca per modificare",
"search_import_help_text": "Importa una ricetta da un sito web o da una applicazione.",
"Click_To_Edit": "Fai clic per modificare",
"search_import_help_text": "Importa una ricetta da un sito web o da un'applicazione.",
"Bookmarklet": "Segnalibro",
"paste_json": "Incolla qui il codice sorgente html o json per caricare la ricetta.",
"Imported_From": "Importato da",
@@ -414,7 +414,7 @@
"Imported": "Importato",
"Quick actions": "Azioni rapide",
"Internal": "Interno",
"ingredient_list": "Lista Ingredienti",
"ingredient_list": "Lista ingredienti",
"show_ingredient_overview": "Mostra la lista degli ingredienti all'inizio della ricetta.",
"Change_Password": "Cambia password",
"Social_Authentication": "Autenticazione social",
@@ -467,11 +467,108 @@
"make_now": "Fai ora",
"Amount": "Quantità",
"show_step_ingredients_setting": "Mostra gli ingredienti vicino ai passaggi della ricetta",
"substitute_siblings_help": "Tutti gli alimenti che condividono un genitore di questo alimento sono considerati sostituti",
"reset_children": "Reimposta l'eredità degli eredi",
"substitute_siblings": "Sostituire prodotti eredi",
"ChildInheritFields": "Gli eredi ereditano i valori",
"substitute_siblings_help": "Tutti gli alimenti che condividono un genitore di questo alimento sono considerati sostituti.",
"reset_children": "Ripristina l'eredità degli eredi",
"substitute_siblings": "Sostituisci relativi",
"ChildInheritFields": "Figli ereditano i campi",
"recipe_property_info": "Puoi anche aggiungere proprietà ai cibi per calcolarli automaticamente in base alla tua ricetta!",
"err_importing_recipe": "Si è verificato un errore durante l'importazione della ricetta!",
"per_serving": "per porzioni"
"per_serving": "per porzioni",
"open_data_help_text": "Il progetto Tandoor Open Data presenta i dati forniti dalla comunità per Tandoor. Questo campo viene riempito automaticamente al momento dell'importazione e consente aggiornamenti in futuro.",
"Open_Data_Import": "Importazione Open Data",
"imperial_fluid_ounce": "oncia liquida imperiale [imp fl oz] (UK, volume)",
"imperial_pint": "pinta imperiale [imp pt] (UK, volume)",
"imperial_gallon": "gallone imperiale [imp gal] (UK, volume)",
"imperial_tbsp": "cucchiaio da tavola imperiale [imp tbsp] (UK, volume)",
"Create Recipe": "Crea ricetta",
"Properties": "Proprietà",
"Created": "Creata",
"imperial_quart": "quarto imperiale [imp qt] (UK, volume)",
"us_cup": "tazza (US, volume)",
"Data_Import_Info": "Arricchisci la tua istanza importando un elenco di alimenti, unità e altro ancora, curato dalla comunità, per migliorare la tua raccolta di ricette.",
"CustomLogoHelp": "Carica immagini quadrate di diverse dimensioni da trasformare in logo nella scheda del browser e nell'applicazione web installata.",
"show_step_ingredients_setting_help": "Aggiungi la tabella degli ingredienti accanto ai passaggi della ricetta. Si applica al momento della creazione. Può essere sovrascritto nella vista di modifica della ricetta.",
"Show_Logo_Help": "Mostra il logo di Tandoor o dell'istanza nella barra di navigazione.",
"Space_Cosmetic_Settings": "Alcune impostazioni cosmetiche possono essere modificate dagli amministratori dell'istanza e sovrascriveranno le impostazioni client per quell'istanza.",
"reset_food_inheritance": "Ripristina ereditarietà",
"Update_Existing_Data": "Aggiorna i dati esistenti",
"converted_amount": "Quantità convertita",
"ounce": "oncia [oz] (peso)",
"fluid_ounce": "oncia liquida [fl oz] (US, volume)",
"gallon": "gallone [gal] (US, volume)",
"Use_Metric": "Usa unità metriche",
"Undo": "Annulla",
"NoMoreUndo": "Nessuna modifica da annullare.",
"show_step_ingredients": "Mostra ingredienti dello step",
"hide_step_ingredients": "Nascondi gli ingredienti dello step",
"Logo": "Logo",
"Show_Logo": "Mostra logo",
"substitute_help": "Quando si cercano ricette che possono essere realizzate con ingredienti a disposizione, si prendono in considerazione anche i sostituti.",
"pound": "libbra (peso)",
"ml": "millilitro [ml] (metrico, volume)",
"kg": "chilogrammo [kg] (metrico, peso)",
"g": "grammo [g] (metrico, peso)",
"Back": "Indietro",
"Properties_Food_Amount": "Proprietà Quantità alimento",
"Properties_Food_Unit": "Proprietà Unità alimento",
"total": "totale",
"Welcome": "Benvenuto",
"CustomTheme": "Tema personalizzato",
"CustomThemeHelp": "Sostituisci gli stili del tema selezionato caricando un file CSS personalizzato.",
"CustomImageHelp": "Carica un'immagine da mostrare nella panoramica dell'istanza.",
"Shopping_input_placeholder": "ad es. patata/100 patate/100 g patate",
"Property": "Proprietà",
"Property_Editor": "Editor delle proprietà",
"Conversion": "Conversione",
"created_by": "Creato da",
"CustomNavLogoHelp": "Carica un'immagine da utilizzare come logo della barra di navigazione.",
"CustomLogos": "Loghi personalizzati",
"ShowRecentlyCompleted": "Mostra gli elementi completati di recente",
"ShoppingBackgroundSyncWarning": "Rete scadente, in attesa di sincronizzazione...",
"OrderInformation": "Gli oggetti sono ordinati dal numero più piccolo al più grande.",
"Updated": "Aggiornata",
"Unchanged": "Non modificata",
"Error": "Errore",
"Nav_Text_Mode_Help": "Si comporta in modo diverso per ogni tema.",
"Number of Objects": "Numero di oggetti",
"StartDate": "Data d'inizio",
"EndDate": "Data di fine",
"Datatype": "Tipo di dato",
"substitute_children_help": "Tutti gli alimenti derivati da questo alimento sono considerati sostituti.",
"Enable": "Abilita",
"Transpose_Words": "Trasponi parole",
"imperial_tsp": "cucchiaio da tè imperiale [imp tsp] (UK, volume)",
"Choose_Category": "Scegli categoria",
"Import Recipe": "Importa ricetta",
"Food_Replace": "Sostituisci alimento",
"Name_Replace": "Sostituisci nome",
"Unit_Replace": "Sostituisci unità",
"Alignment": "Allineamento",
"Learn_More": "Scopri altro",
"converted_unit": "Unità convertita",
"base_unit": "Unità base",
"base_amount": "Quantità base",
"Calculator": "Calcolatore",
"Delete_All": "Elimina tutti",
"DefaultPage": "Pagina predefinita",
"tsp": "cucchiaio da tè [tsp] (US, volume)",
"l": "litro [l] (metrico, volume)",
"pint": "pinta [pt] (US, volume)",
"quart": "quarto [qt] (US, volume)",
"tbsp": "cucchiaio da tavola [tbsp] (US, volume)",
"reset_children_help": "Sovrascrivi tutti i figli con valori da campi ereditati. I campi ereditati dei figli saranno impostati su Eredita i campi a meno che Figli ereditano i campi non sia impostato.",
"reset_food_inheritance_info": "Ripristina tutti gli alimenti ai campi ereditati predefiniti e ai rispettivi valori padre.",
"substitute_children": "Sostituisci figli",
"Input": "Immissione",
"show_ingredients_table": "Visualizza una tabella degli ingredienti accanto al testo dello step",
"Open_Data_Slug": "Open Data Slug",
"make_now_count": "Per lo più ingredienti mancanti",
"Nav_Text_Mode": "Modalità di navigazione testo",
"FDC_ID": "FDC ID",
"FDC_ID_help": "ID database FDC",
"ChildInheritFields_help": "In modo predefinito, i figli erediteranno questi campi.",
"InheritFields_help": "I valori di questi campi saranno ereditati dal genitore (eccezione: le categorie di acquisto vuote non vengono ereditate)",
"Never_Unit": "Mai unità",
"FDC_Search": "Ricerca FDC",
"property_type_fdc_hint": "Solo le proprietà con un ID FDC possono essere aggiornate automaticamente dal database FDC"
}

572
vue/src/locales/lv.json Normal file
View File

@@ -0,0 +1,572 @@
{
"warning_feature_beta": "Šī funkcionalitāte šobrīd ir BETA (testēšanā). To izmantojot ir iespējamas kļūdas gan šobrīd, gan nākotnē (iespējams ar funkcionalitāti saistīto datu zudums).",
"err_fetching_resource": "Ir notikusi kļūda datu saņemšanas laikā!",
"err_creating_resource": "Ir notikusi kļūda izveidojot resursu!",
"err_updating_resource": "Ir notikusi kļūda mainot resursu!",
"err_deleting_resource": "Ir notikusi kļūda dzēšot resursu!",
"err_deleting_protected_resource": "Objekts, kuru Jūs mēģinat dzēst, vēlarvien tiek izmantots un to nevar izdzēst.",
"err_moving_resource": "Notika kļūda pārvietojot resursu!",
"err_merging_resource": "Notika kļūda apvienojot resursu!",
"err_importing_recipe": "Notika kļūda importējot recepti!",
"success_fetching_resource": "Resurss veiksmīgi saņemts!",
"success_creating_resource": "Resurss veiksmīgi izveidots!",
"success_updating_resource": "Resurss veiksmīgi labots!",
"success_deleting_resource": "Resurss veiksmīgi izdzēsts!",
"success_moving_resource": "Resurss veiksmīgi pārvietots!",
"success_merging_resource": "Resurss veiksmīgi apvienots!",
"file_upload_disabled": "Failu ielāde šajā vietnē nav iespējota.",
"recipe_property_info": "",
"warning_space_delete": "",
"food_inherit_info": "",
"step_time_minutes": "",
"confirm_delete": "",
"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": "",
"per_serving": "",
"Save_and_View": "",
"Manage_Books": "",
"Meal_Plan": "",
"Select_Book": "",
"Select_File": "",
"Recipe_Image": "",
"Import_finished": "",
"View_Recipes": "",
"Log_Cooking": "",
"New_Recipe": "",
"Url_Import": "",
"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": "",
"Amount": "",
"Enable_Amount": "",
"Disable_Amount": "",
"Ingredient Editor": "",
"Description_Replace": "",
"Instruction_Replace": "",
"Auto_Sort": "",
"Auto_Sort_Help": "",
"Private_Recipe": "",
"Private_Recipe_Help": "",
"reusable_help_text": "",
"open_data_help_text": "",
"Open_Data_Slug": "",
"Open_Data_Import": "",
"Properties_Food_Amount": "",
"Properties_Food_Unit": "",
"Calculator": "",
"FDC_ID": "",
"FDC_Search": "",
"FDC_ID_help": "",
"property_type_fdc_hint": "",
"Data_Import_Info": "",
"Update_Existing_Data": "",
"Use_Metric": "",
"Learn_More": "",
"converted_unit": "",
"converted_amount": "",
"base_unit": "",
"base_amount": "",
"Datatype": "",
"Input": "",
"Undo": "",
"NoMoreUndo": "",
"Number of Objects": "",
"Add_Step": "",
"Keywords": "",
"Books": "",
"Proteins": "",
"Fats": "",
"Carbohydrates": "",
"Calories": "",
"Energy": "",
"Nutrition": "",
"Date": "",
"StartDate": "",
"EndDate": "",
"Share": "",
"Automation": "",
"Parameter": "",
"Export": "",
"Copy": "",
"Rating": "",
"Close": "",
"Cancel": "",
"Link": "",
"Add": "",
"New": "",
"Note": "",
"Alignment": "",
"Success": "",
"Failure": "",
"Protected": "",
"Ingredients": "",
"Supermarket": "",
"Categories": "",
"Category": "",
"Selected": "",
"min": "",
"Servings": "",
"Waiting": "",
"Preparation": "",
"External": "",
"Size": "",
"Files": "",
"File": "",
"Edit": "",
"Image": "",
"Delete": "",
"Delete_All": "",
"Open": "",
"Ok": "",
"Save": "",
"Step": "",
"Search": "",
"Import": "",
"Print": "",
"Settings": "",
"or": "",
"and": "",
"Information": "",
"Download": "",
"Create": "",
"Search Settings": "",
"View": "",
"Recipes": "",
"Welcome": "",
"Move": "",
"Merge": "",
"Parent": "",
"Copy Link": "",
"Copy Token": "",
"delete_confirmation": "",
"move_confirmation": "",
"merge_confirmation": "",
"create_rule": "",
"move_selection": "",
"merge_selection": "",
"Root": "",
"Ignore_Shopping": "",
"Shopping_Category": "",
"Shopping_Categories": "",
"Shopping_input_placeholder": "",
"Edit_Food": "",
"Move_Food": "",
"New_Food": "",
"Hide_Food": "",
"Food_Alias": "",
"Unit_Alias": "",
"Keyword_Alias": "",
"Delete_Food": "",
"No_ID": "",
"Meal_Plan_Days": "",
"merge_title": "",
"move_title": "",
"Food": "",
"Property": "",
"Property_Editor": "",
"Conversion": "",
"Original_Text": "",
"Recipe_Book": "",
"del_confirmation_tree": "",
"delete_title": "",
"create_title": "",
"edit_title": "",
"Name": "",
"Properties": "",
"Type": "",
"Description": "",
"Recipe": "",
"tree_root": "",
"Icon": "",
"Unit": "",
"Decimals": "",
"Default_Unit": "",
"No_Results": "",
"New_Unit": "",
"Create_New_Shopping Category": "",
"Create_New_Food": "",
"Create_New_Keyword": "",
"Create_New_Unit": "",
"Create_New_Meal_Type": "",
"Create_New_Shopping_Category": "",
"and_up": "",
"and_down": "",
"Instructions": "",
"Unrated": "",
"Automate": "",
"Empty": "",
"Key_Ctrl": "",
"Key_Shift": "",
"Time": "",
"Text": "",
"Shopping_list": "",
"Added_by": "",
"Added_on": "",
"AddToShopping": "",
"IngredientInShopping": "",
"NotInShopping": "",
"OnHand": "",
"FoodOnHand": "",
"FoodNotOnHand": "",
"Undefined": "",
"Create_Meal_Plan_Entry": "",
"Edit_Meal_Plan_Entry": "",
"Title": "",
"Week": "",
"Month": "",
"Year": "",
"created_by": "",
"Planner": "",
"Planner_Settings": "",
"Period": "",
"Plan_Period_To_Show": "",
"Periods": "",
"Plan_Show_How_Many_Periods": "",
"Starting_Day": "",
"Meal_Types": "",
"Meal_Type": "",
"New_Entry": "",
"Clone": "",
"Drag_Here_To_Delete": "",
"Meal_Type_Required": "",
"Title_or_Recipe_Required": "",
"Color": "",
"New_Meal_Type": "",
"Use_Fractions": "",
"Use_Fractions_Help": "",
"AddFoodToShopping": "",
"RemoveFoodFromShopping": "",
"DeleteShoppingConfirm": "",
"IgnoredFood": "",
"Add_Servings_to_Shopping": "",
"Week_Numbers": "",
"Show_Week_Numbers": "",
"Export_As_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": "",
"Inherit": "",
"InheritFields": "",
"FoodInherit": "",
"ShowUncategorizedFood": "",
"GroupBy": "",
"Language": "",
"Theme": "",
"CustomTheme": "",
"CustomThemeHelp": "",
"CustomImageHelp": "",
"CustomNavLogoHelp": "",
"CustomLogoHelp": "",
"CustomLogos": "",
"SupermarketCategoriesOnly": "",
"MoveCategory": "",
"CountMore": "",
"IgnoreThis": "",
"DelayFor": "",
"Warning": "",
"NoCategory": "",
"InheritWarning": "",
"ShowDelayed": "",
"ShowRecentlyCompleted": "",
"Completed": "",
"OfflineAlert": "",
"ShoppingBackgroundSyncWarning": "",
"shopping_share": "",
"shopping_auto_sync": "",
"one_url_per_line": "",
"mealplan_autoadd_shopping": "",
"mealplan_autoexclude_onhand": "",
"mealplan_autoinclude_related": "",
"default_delay": "",
"plan_share_desc": "",
"shopping_share_desc": "",
"shopping_auto_sync_desc": "",
"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": "",
"Hour": "",
"Hours": "",
"Day": "",
"Days": "",
"Second": "",
"Seconds": "",
"Clear": "",
"Users": "",
"Invites": "",
"err_move_self": "",
"nothing": "",
"err_merge_self": "",
"show_sql": "",
"filter_to_supermarket_desc": "",
"CategoryName": "",
"SupermarketName": "",
"CategoryInstruction": "",
"OrderInformation": "",
"shopping_recent_days_desc": "",
"shopping_recent_days": "",
"download_pdf": "",
"download_csv": "",
"csv_delim_help": "",
"csv_delim_label": "",
"SuccessClipboard": "",
"copy_to_clipboard": "",
"csv_prefix_help": "",
"csv_prefix_label": "",
"copy_markdown_table": "",
"in_shopping": "",
"DelayUntil": "",
"Pin": "",
"Unpin": "",
"PinnedConfirmation": "",
"UnpinnedConfirmation": "",
"mark_complete": "",
"QuickEntry": "",
"shopping_add_onhand_desc": "",
"shopping_add_onhand": "",
"related_recipes": "",
"today_recipes": "",
"sql_debug": "",
"remember_search": "",
"remember_hours": "",
"tree_select": "",
"OnHand_help": "",
"ignore_shopping_help": "",
"shopping_category_help": "",
"food_recipe_help": "",
"Foods": "",
"Account": "",
"Cosmetic": "",
"API": "",
"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": "",
"show_step_ingredients_setting": "",
"show_step_ingredients_setting_help": "",
"show_step_ingredients": "",
"hide_step_ingredients": "",
"Custom Filter": "",
"shared_with": "",
"sort_by": "",
"asc": "",
"desc": "",
"date_viewed": "",
"last_cooked": "",
"times_cooked": "",
"date_created": "",
"show_sortby": "",
"search_rank": "",
"make_now": "",
"Created": "",
"Updated": "",
"Unchanged": "",
"Error": "",
"make_now_count": "",
"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": "",
"Message": "",
"Bookmarklet": "",
"Sticky_Nav": "",
"Sticky_Nav_Help": "",
"Logo": "",
"Show_Logo": "",
"Show_Logo_Help": "",
"Nav_Color": "",
"Nav_Text_Mode": "",
"Nav_Text_Mode_Help": "",
"Nav_Color_Help": "",
"Space_Cosmetic_Settings": "",
"Use_Kj": "",
"Comments_setting": "",
"click_image_import": "",
"no_more_images_found": "",
"import_duplicates": "",
"paste_json": "",
"Click_To_Edit": "",
"search_no_recipes": "",
"search_import_help_text": "",
"search_create_help_text": "",
"warning_duplicate_filter": "",
"reset_children": "",
"reset_children_help": "",
"reset_food_inheritance": "",
"reset_food_inheritance_info": "",
"substitute_help": "",
"substitute_siblings_help": "",
"substitute_children_help": "",
"substitute_siblings": "",
"substitute_children": "",
"SubstituteOnHand": "",
"ChildInheritFields": "",
"ChildInheritFields_help": "",
"InheritFields_help": "",
"show_ingredients_table": "",
"show_ingredient_overview": "",
"Ingredient Overview": "",
"last_viewed": "",
"created_on": "",
"updatedon": "",
"Imported_From": "",
"advanced_search_settings": "",
"nothing_planned_today": "",
"no_pinned_recipes": "",
"Planned": "",
"Pinned": "",
"Imported": "",
"Quick actions": "",
"Ratings": "",
"Internal": "",
"Units": "",
"Manage_Emails": "",
"Change_Password": "",
"Social_Authentication": "",
"Random Recipes": "",
"parameter_count": "",
"select_keyword": "",
"add_keyword": "",
"select_file": "",
"select_recipe": "",
"select_unit": "",
"select_food": "",
"remove_selection": "",
"empty_list": "",
"Select": "",
"Supermarkets": "",
"User": "",
"Username": "",
"First_name": "",
"Last_name": "",
"Keyword": "",
"Advanced": "",
"Page": "",
"DefaultPage": "",
"Single": "",
"Multiple": "",
"Reset": "",
"Disabled": "",
"Disable": "",
"Enable": "",
"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": "",
"Recipes_In_Import": "",
"Toggle": "",
"total": "",
"Import_Error": "",
"Warning_Delete_Supermarket_Category": "",
"New_Supermarket": "",
"New_Supermarket_Category": "",
"Are_You_Sure": "",
"Valid Until": "",
"Split_All_Steps": "",
"Combine_All_Steps": "",
"Plural": "",
"plural_short": "",
"g": "",
"kg": "",
"ounce": "",
"pound": "",
"ml": "",
"l": "",
"fluid_ounce": "",
"us_cup": "",
"pint": "",
"quart": "",
"gallon": "",
"tbsp": "",
"tsp": "",
"imperial_fluid_ounce": "",
"imperial_pint": "",
"imperial_quart": "",
"imperial_gallon": "",
"imperial_tbsp": "",
"imperial_tsp": "",
"Choose_Category": "",
"Back": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": "",
"Create Recipe": "",
"Import Recipe": "",
"Never_Unit": "",
"Transpose_Words": "",
"Name_Replace": "",
"Food_Replace": "",
"Unit_Replace": ""
}

View File

@@ -316,5 +316,9 @@
"converted_amount": "Pretvorjena Količina",
"base_unit": "Osnovna Enota",
"base_amount": "Osnovna Količina",
"Amount": "Količina"
"Amount": "Količina",
"err_importing_recipe": "Pri uvozu recepta je prišlo do napake!",
"Properties_Food_Amount": "Lastnosti Količina hrane",
"Properties_Food_Unit": "Lastnosti Hrana Enota",
"Calculator": "Kalkulator"
}

View File

@@ -23,68 +23,68 @@
"Select_Book": "選擇書籍",
"Recipe_Image": "食譜圖片",
"Import_finished": "匯入完成",
"View_Recipes": "",
"Log_Cooking": "",
"New_Recipe": "",
"Url_Import": "",
"Reset_Search": "",
"Recently_Viewed": "",
"Load_More": "",
"Keywords": "",
"Books": "",
"Proteins": "",
"Fats": "",
"Carbohydrates": "",
"Calories": "",
"Energy": "",
"Nutrition": "",
"Date": "",
"Share": "",
"Export": "",
"Copy": "",
"Rating": "",
"Close": "",
"Link": "",
"Add": "",
"New": "",
"Success": "",
"Failure": "",
"Ingredients": "",
"Supermarket": "",
"Categories": "",
"Category": "",
"Selected": "",
"min": "",
"Servings": "",
"Waiting": "",
"Preparation": "",
"External": "",
"Size": "",
"Files": "",
"File": "",
"Edit": "",
"Cancel": "",
"Delete": "",
"Open": "",
"Ok": "",
"Save": "",
"Step": "",
"Search": "",
"Import": "",
"Print": "",
"Settings": "",
"or": "",
"and": "",
"Information": "",
"Download": "",
"Create": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": "",
"View_Recipes": "查看食譜",
"Log_Cooking": "記錄烹飪",
"New_Recipe": "新食譜",
"Url_Import": "網址匯入",
"Reset_Search": "重置搜尋",
"Recently_Viewed": "最近瀏覽",
"Load_More": "載入更多",
"Keywords": "關鍵字",
"Books": "書籍",
"Proteins": "蛋白質",
"Fats": "脂肪",
"Carbohydrates": "碳水化合物",
"Calories": "卡路里",
"Energy": "能量",
"Nutrition": "營養",
"Date": "日期",
"Share": "分享",
"Export": "匯出",
"Copy": "複製",
"Rating": "評分",
"Close": "關閉",
"Link": "連結",
"Add": "新增",
"New": "新的",
"Success": "成功",
"Failure": "失敗",
"Ingredients": "食材",
"Supermarket": "超市",
"Categories": "分類",
"Category": "類別",
"Selected": "已選擇",
"min": "分鐘",
"Servings": "份量",
"Waiting": "等待",
"Preparation": "準備",
"External": "外部",
"Size": "大小",
"Files": "檔案",
"File": "檔案",
"Edit": "編輯",
"Cancel": "取消",
"Delete": "刪除",
"Open": "打開",
"Ok": "確定",
"Save": "儲存",
"Step": "步驟",
"Search": "搜尋",
"Import": "匯入",
"Print": "列印",
"Settings": "設定",
"or": "",
"and": "",
"Information": "資訊",
"Download": "下載",
"Create": "建立",
"Plural": "複數",
"plural_short": "複數簡寫",
"Use_Plural_Unit_Always": "總是使用複數單位",
"Use_Plural_Unit_Simple": "簡單使用複數單位",
"Use_Plural_Food_Always": "總是使用複數食物",
"Use_Plural_Food_Simple": "簡單使用複數食物",
"plural_usage_info": "在此空間內使用複數形式表示單位和食物。",
"Table_of_Contents": "目錄",
"Select_File": "選擇檔案",
"file_upload_disabled": "您的空間未啟用檔案上傳功能。",
@@ -107,5 +107,466 @@
"Hide_as_header": "隱藏為標題",
"err_importing_recipe": "匯入食譜時發生錯誤!",
"recipe_property_info": "您也可以為食材添加屬性,以便根據您的食譜自動計算它們!",
"Show_as_header": "顯示為標題"
"Show_as_header": "顯示為標題",
"Key_Ctrl": "Ctrl 鍵",
"Recipe": "食譜",
"Icon": "圖標",
"Image": "圖片",
"Import_Result_Info": "匯入結果信息",
"Unit": "單位",
"Merge": "合併",
"Hide_Recipes": "隱藏食譜",
"Recipes": "食譜",
"Move": "移動",
"search_create_help_text": "直接在 Tandoor 中創建新食譜。",
"ShowDelayed": "顯示延遲",
"and_up": "及以上",
"mealplan_autoadd_shopping_desc": "自動將膳食計劃食材添加到購物清單中。",
"Shopping_list": "購物清單",
"Meal_Type": "餐類型",
"Foods": "食物",
"left_handed_help": "將使用左手模式優化界面顯示。",
"Pin": "釘住",
"us_cup": "美制杯",
"Create Recipe": "創建食譜",
"Import Recipe": "匯入食譜",
"Create_New_Food": "創建新食物",
"Week_Numbers": "週數",
"Coming_Soon": "即將推出",
"Auto_Planner": "自動計劃",
"copy_to_clipboard": "複製到剪貼板",
"Merge_Keyword": "合併關鍵字",
"converted_unit": "轉換單位",
"converted_amount": "轉換數量",
"Bookmarklet": "書籤小工具",
"ignore_shopping_help": "忽略購物幫助",
"Meal_Plan_Days": "餐計劃天數",
"del_confirmation_tree": "你確定要刪除 {source} 及其所有子項嗎?",
"Empty": "空",
"Make_Header": "設為標題",
"review_shopping": "審查購物",
"shopping_category_help": "超市可以按購物分類進行篩選使其與商店的內部佈局相匹配。",
"plan_share_desc": "新的膳食計劃條目將自動與選定的用戶共享。",
"food_recipe_help": "食物食譜幫助",
"show_step_ingredients_setting_help": "在食譜步驟旁邊添加成分表。在創建時應用。可以在編輯配方視圖中覆蓋。",
"show_step_ingredients": "顯示步驟食材",
"hide_step_ingredients": "隱藏步驟食材",
"sort_by": "排序依據",
"search_rank": "搜索排名",
"make_now": "立即製作",
"New_Keyword": "新關鍵字",
"Delete_Keyword": "刪除關鍵字",
"Open_Data_Import": "開放數據匯入",
"Properties_Food_Unit": "食物單位屬性",
"property_type_fdc_hint": "屬性類型 FDC 提示",
"Data_Import_Info": "通過導入社區精選的食物、單位等列表來增強您的空間,以提升您的食譜收藏。",
"Update_Existing_Data": "更新現有數據",
"Use_Metric": "使用公制",
"Learn_More": "了解更多",
"base_unit": "基礎單位",
"Shopping_Category": "購物分類",
"Unit_Alias": "單位別名",
"Keyword_Alias": "關鍵字別名",
"Delete_Food": "刪除食物",
"No_ID": "未找到標識,不能刪除。",
"merge_title": "合併標題",
"move_title": "移動標題",
"Property": "屬性",
"Property_Editor": "屬性編輯器",
"Conversion": "轉換",
"Original_Text": "原始文本",
"FoodNotOnHand": "你還沒有 {food}。",
"Recipe_Book": "食譜書",
"delete_title": "刪除標題",
"create_title": "創建標題",
"Properties": "屬性",
"Type": "類型",
"Create_New_Unit": "創建新單位",
"Create_New_Meal_Type": "創建新餐類型",
"and_down": "及以下",
"Automate": "自動化",
"Time": "時間",
"Text": "文本",
"OnHand": "手頭有",
"FoodOnHand": "你手上有 {food}。",
"Undefined": "未定義",
"Create_Meal_Plan_Entry": "創建餐計劃條目",
"Title": "標題",
"Color": "顏色",
"New_Meal_Type": "新餐類型",
"IgnoredFood": "已忽略購買 {food}。",
"Show_Week_Numbers": "顯示周數?",
"Export_To_ICal": "匯出到 iCal",
"Cannot_Add_Notes_To_Shopping": "無法添加備註到購物",
"Added_To_Shopping_List": "已添加到購物清單",
"Shopping_List_Empty": "購物清單空",
"Next_Period": "下一期間",
"Previous_Period": "上一期間",
"InheritFields": "繼承字段",
"FoodInherit": "食物繼承",
"ShowUncategorizedFood": "顯示未分類食物",
"GroupBy": "分組依據",
"Language": "語言",
"Theme": "主題",
"SupermarketCategoriesOnly": "僅超市分類",
"MoveCategory": "移動分類 ",
"IgnoreThis": "忽略這個",
"DelayFor": "延遲",
"Warning": "警告",
"NoCategory": "未選擇分類。",
"InheritWarning": "{food} 設置為繼承, 更改可能無法保存。",
"ShowRecentlyCompleted": "顯示最近完成",
"Completed": "已完成",
"ShoppingBackgroundSyncWarning": "網絡狀況不佳,正在等待進行同步……",
"shopping_auto_sync": "購物自動同步",
"one_url_per_line": "每行一個網址",
"mealplan_autoadd_shopping": "餐計劃自動添加購物",
"mealplan_autoexclude_onhand": "餐計劃自動排除手頭有的",
"mealplan_autoinclude_related": "餐計劃自動包含相關",
"default_delay": "默認延遲",
"shopping_share_desc": "用戶將看到您添加到購物清單中的所有商品。他們必須添加你才能看到他們清單上的內容。",
"shopping_auto_sync_desc": "設置為0將禁用自動同步。當查看購物列表時該列表每隔一秒更新一次以同步其他人可能做出的更改。在多人購物時很有用但會使用移動數據。",
"mealplan_autoinclude_related_desc": "將膳食計劃(手動或自動)添加到購物清單時,包括所有相關食譜。",
"default_delay_desc": "延遲購物清單條目的默認小時數。",
"filter_to_supermarket": "篩選到超市",
"New_Cookbook": "新食譜書",
"Hide_Keyword": "隱藏關鍵字",
"Hour": "小時",
"Hours": "小時",
"Second": "秒",
"Seconds": "秒",
"Clear": "清除",
"Users": "用戶",
"Invites": "邀請",
"err_move_self": "錯誤移動自己",
"nothing": "無",
"filter_to_supermarket_desc": "默認情況下,過濾購物清單只包括所選超市的類別。",
"SupermarketName": "超市名稱",
"CategoryInstruction": "拖動類別可更改出現在購物清單中的訂單類別。",
"OrderInformation": "對象按照從小到大的順序排列。",
"shopping_recent_days_desc": "顯示最近幾天的購物清單列表。",
"shopping_recent_days": "購物最近天數",
"download_pdf": "下載 PDF",
"csv_delim_help": "用於 CSV 導出的分隔符。",
"SuccessClipboard": "成功複製到剪貼板",
"csv_prefix_help": "將清單複製到剪貼板時要添加的前綴。",
"csv_prefix_label": "CSV 前綴標籤",
"copy_markdown_table": "複製 Markdown 表格",
"in_shopping": "在購物中",
"DelayUntil": "延遲到",
"Unpin": "取消釘住",
"PinnedConfirmation": "{recipe} 已固定。",
"UnpinnedConfirmation": "{recipe} 已取消固定。",
"QuickEntry": "快速輸入",
"shopping_add_onhand_desc": "在核對購物清單時,將食物標記為“入手”。",
"shopping_add_onhand": "購物添加手頭有的",
"related_recipes": "相關食譜",
"today_recipes": "今天的食譜",
"sql_debug": "SQL 調試",
"remember_search": "記住搜索",
"remember_hours": "記住小時",
"OnHand_help": "食物在庫存中時,不會自動添加到購物清單中。 並且現有狀態會與購物用戶共享。",
"Account": "賬戶",
"Cosmetic": "外觀",
"API": "API",
"enable_expert": "啟用專家模式",
"simple_mode": "簡單模式",
"advanced": "高級",
"show_keywords": "顯示關鍵字",
"show_units": "顯示單位",
"show_filters": "顯示篩選器",
"filter_name": "篩選器名稱",
"left_handed": "左撇子",
"show_step_ingredients_setting": "顯示步驟食材設置",
"Custom Filter": "自定義篩選器",
"shared_with": "共享給",
"show_sortby": "顯示排序依據",
"Created": "創建",
"Updated": "更新",
"Unchanged": "未更改",
"Error": "錯誤",
"view_recipe": "查看食譜",
"copy_to_new": "複製到新",
"recipe_name": "食譜名稱",
"paste_ingredients_placeholder": "在此處粘貼食材表...",
"explain": "解釋",
"Website": "網站",
"Sticky_Nav": "固定導航",
"Show_Logo": "顯示標誌",
"Show_Logo_Help": "在導航欄中顯示 Tandoor 或空間徽標。",
"Nav_Text_Mode": "導航文本模式",
"Nav_Text_Mode_Help": "每個主題的行為都不同。",
"Space_Cosmetic_Settings": "空間管理員可以更改某些裝飾設置,並將覆蓋該空間的客戶端設置。",
"paste_json": "在此處粘貼 json 或 html 源代碼以加載食譜。",
"Click_To_Edit": "點擊編輯",
"search_no_recipes": "找不到任何食譜!",
"substitute_children_help": "所有與這種食物相同子級的食物都被視作替代品。",
"show_ingredients_table": "顯示食材表",
"Internal": "內部",
"Units": "單位",
"Manage_Emails": "管理電子郵件",
"Change_Password": "更改密碼",
"Supermarkets": "超市",
"Reset": "重置",
"Are_You_Sure": "你確定嗎?",
"Valid Until": "有效期至",
"Split_All_Steps": "將所有行拆分為單獨的步驟。",
"Combine_All_Steps": "將所有步驟合併到一個字段中。",
"imperial_tsp": "英制茶匙",
"not": "不是",
"save_filter": "保存篩選器",
"substitute_help": "搜索可以用現有食材製作的食譜時,會考慮替代品。",
"ChildInheritFields": "子項繼承字段",
"ChildInheritFields_help": "默認情況下,子項將繼承這些字段。",
"created_on": "創建於",
"updatedon": "更新於",
"Imported_From": "匯入自",
"advanced_search_settings": "高級搜索設置",
"nothing_planned_today": "你今天沒有任何計劃!",
"select_keyword": "選擇關鍵字",
"Advanced": "高級",
"Options": "選項",
"Create Food": "創建食物",
"Parameter": "參數",
"Export_Supported": "支持匯出",
"Toggle": "切換",
"asc": "升序",
"times_cooked": "烹飪次數",
"Import_Error": "導入時發生錯誤。 請跳轉至頁面底部的詳細信息進行查看。",
"Protected": "受保護",
"Ingredient Editor": "食材編輯器",
"move_selection": "選擇要將 {source} 移動到的父級 {type}。",
"merge_selection": "將所有出現的 {source} 替換為 {type}。",
"Copy Link": "複製連結",
"Added_by": "添加者",
"Added_on": "添加於",
"AddToShopping": "添加到購物",
"IngredientInShopping": "此食材已在購物清單中。",
"NotInShopping": "購物清單中沒有 {food}。",
"show_sql": "顯示 SQL",
"App": "應用程式",
"Message": "消息",
"book_filter_help": "除手動選擇的食譜外,還包括篩選中的食譜。",
"ingredient_list": "食材清單",
"New_Entry": "新條目",
"Drag_Here_To_Delete": "拖到這裡刪除",
"Add_Servings_to_Shopping": "添加份量到購物",
"Export_As_ICal": "匯出為 iCal",
"Inherit": "繼承",
"Nav_Color": "導航顏色",
"CategoryName": "分類名稱",
"substitute_siblings_help": "所有與這種食物相同父級的食物都被視作替代品。",
"mark_complete": "標記完成",
"Search Settings": "搜尋設定",
"Nav_Color_Help": "改變導航欄顏色。",
"date_created": "創建日期",
"Auto_Sort": "自動排序",
"Auto_Sort_Help": "將所有食材移動到最恰當的步驟。",
"reusable_help_text": "邀請鏈接是否可用於多個用戶。",
"csv_delim_label": "CSV 分隔符標籤",
"Ignore_Shopping": "忽略購物",
"edit_title": "編輯標題",
"Name": "名稱",
"Clone": "克隆",
"shopping_share": "購物分享",
"Day": "天",
"desc": "降序",
"date_viewed": "查看日期",
"last_cooked": "最後烹飪",
"Description_Replace": "描述替換",
"Instruction_Replace": "指示替換",
"create_rule": "創建規則",
"Decimals": "小數",
"DeleteShoppingConfirm": "確定要移除購物清單中所有 {food} 嗎?",
"Current_Period": "當前期間",
"Next_Day": "下一天",
"Previous_Day": "前一天",
"recipe_filter": "食譜篩選器",
"Week": "週",
"OfflineAlert": "您處於離線狀態,購物清單可能無法同步。",
"Planner": "計劃者",
"Meal_Types": "餐類型",
"err_merge_self": "錯誤合併自己",
"show_foods": "顯示食物",
"show_books": "顯示書籍",
"show_rating": "顯示評分",
"Sticky_Nav_Help": "始終在屏幕頂部顯示導航菜單。",
"Logo": "標誌",
"Private_Recipe": "私人食譜",
"Private_Recipe_Help": "食譜只有你和共享的人會顯示。",
"Period": "期間",
"Use_Kj": "使用千焦",
"Title_or_Recipe_Required": "需要標題或食譜",
"Comments_setting": "評論設置",
"click_image_import": "點擊圖片匯入",
"select_unit": "選擇單位",
"select_food": "選擇食物",
"import_duplicates": "為防止食譜與現有食譜同名,將被忽略。 選中此框以導入所有內容。",
"Use_Fractions": "使用分數",
"search_import_help_text": "從外部網站或應用程序導入食譜。",
"expert_mode": "專家模式",
"Create_New_Shopping_Category": "創建新購物分類",
"Use_Fractions_Help": "查看食譜時自動將小數轉換為分數。",
"warning_duplicate_filter": "警告:由於技術限制,使用相同組合(和/或/不)的多個篩選器可能會產生意想不到的結果。",
"reset_children": "重置子項",
"Username": "用戶名",
"Step_Name": "步驟名稱",
"Move_Down": "下移",
"Step_Type": "步驟類型",
"Make_Ingredient": "設為食材",
"reset_children_help": "用繼承字段中的值覆蓋所有子項。 繼承的子字段將設置為繼承,除非它們已設置為繼承。",
"no_more_images_found": "沒有在網站上找到其他圖片。",
"download_csv": "下載 CSV",
"reset_food_inheritance_info": "將所有食物重置為默認繼承字段及其父值。",
"substitute_siblings": "替代兄弟項",
"substitute_children": "替代子項",
"SubstituteOnHand": "你手頭有一個替代品。",
"InheritFields_help": "繼承字段幫助",
"Note": "備註",
"Alignment": "對齊",
"merge_confirmation": "將 <i>{source}</i> 替換為 <i>{target}</i>",
"Shopping_Categories": "購物分類",
"Food_Alias": "食物別名",
"Food": "食物",
"Warning_Delete_Supermarket_Category": "刪除超市類別也會刪除與食品的所有關係。 你確定嗎?",
"New_Supermarket": "新超市",
"open_data_help_text": "Tandoor開放數據項目為Tandoor提供社區貢獻的數據。該字段在導入時會自動填充並可以之後更新。",
"Open_Data_Slug": "開放數據標籤",
"Amount": "數量",
"make_now_count": "立即製作次數",
"paste_ingredients": "粘貼食材",
"Pinned": "釘住",
"Imported": "匯入",
"Quick actions": "快速操作",
"tsp": "茶匙",
"imperial_fluid_ounce": "英制液盎司",
"imperial_pint": "英制品脫",
"imperial_quart": "英制夸脫",
"imperial_gallon": "英制加侖",
"imperial_tbsp": "英制湯匙",
"Transpose_Words": "轉置單詞",
"Name_Replace": "名稱替換",
"Food_Replace": "食物替換",
"Never_Unit": "從不使用單位",
"Unit_Replace": "單位替換",
"StartDate": "開始日期",
"EndDate": "結束日期",
"Parent": "父項",
"View": "查看",
"Welcome": "歡迎",
"Copy Token": "複製令牌",
"last_viewed": "最後查看",
"Select": "選擇",
"Input": "輸入",
"Undo": "撤銷",
"NoMoreUndo": "沒有可撤消的更改。",
"Delete_All": "刪除全部",
"delete_confirmation": "你確定要刪除 {source} 嗎?",
"move_confirmation": "移動 <i>{child}</i> 到 <i>{parent}</i>",
"Root": "根目錄",
"Move_Food": "移動食物",
"New_Food": "新食物",
"Edit_Meal_Plan_Entry": "編輯餐計劃條目",
"Month": "月",
"Year": "年",
"created_by": "創建者",
"Planner_Settings": "計劃者設定",
"Plan_Period_To_Show": "顯示計劃期間",
"Periods": "期間",
"Plan_Show_How_Many_Periods": "顯示多少期間",
"Starting_Day": "開始日",
"Meal_Type_Required": "需要餐類型",
"AddFoodToShopping": "添加食物到購物",
"RemoveFoodFromShopping": "從購物中移除食物",
"Days": "天",
"g": "克",
"kg": "千克",
"ounce": "盎司",
"pound": "磅",
"ml": "毫升",
"Shopping_input_placeholder": "購物輸入佔位符",
"Hide_Food": "隱藏食物",
"tree_root": "樹根",
"Default_Unit": "默認單位",
"New_Unit": "新單位",
"Create_New_Shopping Category": "創建新購物分類",
"Create_New_Keyword": "創建新關鍵字",
"Instructions": "指示",
"Unrated": "未評分",
"Key_Shift": "Shift 鍵",
"mealplan_autoexclude_onhand_desc": "將膳食計劃添加到購物清單時(手動或自動),排除當前手頭上的食材。",
"reset_food_inheritance": "重置食物繼承",
"show_ingredient_overview": "在開始時顯示食譜中所有食材的列表。",
"Ingredient Overview": "食材概覽",
"no_pinned_recipes": "你沒有固定的食譜!",
"Planned": "計劃",
"Ratings": "評分",
"Social_Authentication": "社交認證",
"Random Recipes": "隨機食譜",
"parameter_count": "參數計數",
"add_keyword": "添加關鍵字",
"select_file": "選擇文件",
"select_recipe": "選擇食譜",
"remove_selection": "移除選擇",
"empty_list": "列表為空。",
"User": "用戶",
"First_name": "名字",
"Last_name": "姓氏",
"Keyword": "關鍵字",
"Page": "頁面",
"Single": "單一",
"Multiple": "多個",
"New_Supermarket_Category": "新超市分類",
"FDC_Search": "FDC 搜尋",
"Edit_Food": "編輯食物",
"filter": "篩選",
"FDC_ID": "FDC ID",
"FDC_ID_help": "FDC ID 幫助",
"base_amount": "基礎數量",
"Edit_Keyword": "編輯關鍵字",
"Edit_Recipe": "編輯食譜",
"Move_Keyword": "移動關鍵字",
"Hide_Keywords": "隱藏關鍵字",
"Move_Up": "上移",
"Enable_Amount": "啟用數量",
"Disable_Amount": "禁用數量",
"Properties_Food_Amount": "食物數量屬性",
"l": "升",
"fluid_ounce": "液盎司",
"pint": "品脫",
"quart": "夸脫",
"gallon": "加侖",
"tbsp": "湯匙",
"Choose_Category": "選擇分類",
"Back": "返回",
"Import_Supported": "支持匯入",
"DefaultPage": "默認頁面",
"Disabled": "禁用",
"Disable": "禁用",
"Enable": "啟用",
"create_food_desc": "創建食物並將其鏈接到此食譜。",
"additional_options": "附加選項",
"Importer_Help": "有關此進口商的更多信息和幫助:",
"Documentation": "文檔",
"Select_App_To_Import": "選擇應用程式匯入",
"Import_Not_Yet_Supported": "匯入尚不支持",
"Export_Not_Yet_Supported": "匯出尚不支持",
"Recipes_In_Import": "匯入中的食譜",
"total": "總計",
"tree_select": "樹選擇",
"CustomTheme": "自定義主題",
"CustomThemeHelp": "通過上傳自定義 CSS 文件覆蓋所選主題的樣式。",
"CustomImageHelp": "上傳圖片以在空間概覽中顯示。",
"CustomNavLogoHelp": "上傳圖像以用作導航欄徽標。",
"CustomLogoHelp": "上傳不同尺寸的方形圖像以更改為瀏覽器選項卡和安裝的網絡應用程序中的徽標。",
"CustomLogos": "自定義標誌",
"CountMore": "計數更多",
"Description": "描述",
"Calculator": "計算器",
"Datatype": "數據類型",
"Number of Objects": "對象數量",
"Add_Step": "添加步驟",
"Automation": "自動化",
"No_Results": "無結果",
"fields": "字段"
}

View File

@@ -22,4 +22,5 @@ export const INTEGRATIONS = [
{id: 'REZKONV', name: "Rezkonv", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#rezkonv'},
{id: 'SAFRON', name: "Safron", import: true, export: true, help_url: 'https://docs.tandoor.dev/features/import_export/#safron'},
{id: 'REZEPTSUITEDE', name: "Rezeptsuite.de", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#rezeptsuitede'},
{id: 'GOURMET', name: "Gourmet", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#gourmet'},
]

View File

@@ -5185,9 +5185,9 @@ electron-to-chromium@^1.4.477:
integrity sha512-VTq6vjk3kCfG2qdzQRd/i9dIyVVm0dbtZIgFzrLgfB73mXDQT2HPKVRc1EoZcAVUv9XhXAu08DWqJuababdGGg==
elliptic@^6.5.3, elliptic@^6.5.4:
version "6.5.7"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
version "6.6.0"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210"
integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==
dependencies:
bn.js "^4.11.9"
brorand "^1.1.0"