Merge pull request #1088 from smilerz/search_troubleshooting

add search debug
This commit is contained in:
vabene1111 2021-11-30 17:21:40 +01:00 committed by GitHub
commit 7c985cec23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 812 additions and 782 deletions

1
.gitignore vendored
View File

@ -84,3 +84,4 @@ vetur.config.js
cookbook/static/vue
vue/webpack-stats.json
cookbook/templates/sw.js
.prettierignore

View File

@ -1,17 +1,17 @@
from collections import Counter
from datetime import timedelta
from recipes import settings
from django.contrib.postgres.search import (
SearchQuery, SearchRank, TrigramSimilarity
)
from django.contrib.postgres.search import SearchQuery, SearchRank, TrigramSimilarity
from django.core.cache import caches
from django.db.models import Avg, Case, Count, Func, Max, Q, Subquery, Value, When
from django.db.models.functions import Coalesce
from django.utils import timezone, translation
from cookbook.filters import RecipeFilter
from cookbook.helper.permission_helper import has_group_permission
from cookbook.managers import DICTIONARY
from cookbook.models import Food, Keyword, ViewLog, SearchPreference
from cookbook.models import Food, Keyword, Recipe, SearchPreference, ViewLog
from recipes import settings
class Round(Func):
@ -143,9 +143,9 @@ def search_recipes(request, queryset, params):
# TODO add order by user settings - only do search rank and annotation if rank order is configured
search_rank = (
SearchRank('name_search_vector', search_query, cover_density=True)
+ SearchRank('desc_search_vector', search_query, cover_density=True)
+ SearchRank('steps__search_vector', search_query, cover_density=True)
SearchRank('name_search_vector', search_query, cover_density=True)
+ SearchRank('desc_search_vector', search_query, cover_density=True)
+ SearchRank('steps__search_vector', search_query, cover_density=True)
)
queryset = queryset.filter(query_filter).annotate(rank=search_rank)
orderby += ['-rank']
@ -400,3 +400,13 @@ def annotated_qs(qs, root=False, fill=False):
if start_depth and start_depth > 0:
info['close'] = list(range(0, prev_depth - start_depth + 1))
return result
def old_search(request):
if has_group_permission(request.user, ('guest',)):
params = dict(request.GET)
params['internal'] = None
f = RecipeFilter(params,
queryset=Recipe.objects.filter(space=request.user.userpreference.space).all().order_by('name'),
space=request.space)
return f.qs

View File

@ -345,6 +345,7 @@
localStorage.setItem('SCRIPT_NAME', "{% base_path request 'script' %}")
localStorage.setItem('BASE_PATH', "{% base_path request 'base' %}")
localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}")
localStorage.setItem('DEBUG', "{% is_debug %}")
window.addEventListener("load", () => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: "{% base_path request 'base' %}" + '/'}).then(function (reg) {

View File

@ -2,17 +2,17 @@ from pydoc import locate
from django.urls import include, path
from django.views.generic import TemplateView
from recipes.version import VERSION_NUMBER
from rest_framework import routers, permissions
from rest_framework import permissions, routers
from rest_framework.schemas import get_schema_view
from cookbook.helper import dal
from recipes.settings import DEBUG
from recipes.version import VERSION_NUMBER
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name, Automation,
UserFile, Step)
from .views import api, data, delete, edit, import_export, lists, new, views, telegram
from .models import (Automation, Comment, Food, InviteLink, Keyword, MealPlan, Recipe, RecipeBook,
RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage, Supermarket,
SupermarketCategory, Sync, SyncLog, Unit, UserFile, get_model_name)
from .views import api, data, delete, edit, import_export, lists, new, telegram, views
router = routers.DefaultRouter()
router.register(r'user-name', api.UserNameViewSet, basename='username')
@ -68,8 +68,6 @@ urlpatterns = [
path('history/', views.history, name='view_history'),
path('supermarket/', views.supermarket, name='view_supermarket'),
path('abuse/<slug:token>', views.report_share_abuse, name='view_report_share_abuse'),
path('test/', views.test, name='view_test'),
path('test2/', views.test2, name='view_test2'),
path('import/', import_export.import_recipe, name='view_import'),
path('import-response/<int:pk>/', import_export.import_response, name='view_import_response'),
@ -189,3 +187,7 @@ for m in vue_models:
f'list/{url_name}/', c, name=f'list_{py_name}'
)
)
if DEBUG:
urlpatterns.append(path('test/', views.test, name='view_test'))
urlpatterns.append(path('test2/', views.test2, name='view_test2'))

View File

@ -2,11 +2,11 @@ import io
import json
import re
import uuid
from collections import OrderedDict
import requests
from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
from collections import OrderedDict
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity
@ -15,12 +15,12 @@ from django.core.files import File
from django.db.models import Case, ProtectedError, Q, Value, When
from django.db.models.fields.related import ForeignObjectRel
from django.http import FileResponse, HttpResponse, JsonResponse
from django_scopes import scopes_disabled
from django.shortcuts import redirect, get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode
from recipe_scrapers import NoSchemaFoundInWildMode, WebsiteNotImplementedError, scrape_me
from rest_framework import decorators, status, viewsets
from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework.pagination import PageNumberPagination
@ -28,41 +28,39 @@ from rest_framework.parsers import MultiPartParser
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response
from rest_framework.viewsets import ViewSetMixin
from treebeard.exceptions import PathOverflow, InvalidMoveToDescendant, InvalidPosition
from treebeard.exceptions import InvalidMoveToDescendant, InvalidPosition, PathOverflow
from cookbook.helper.image_processing import handle_image
from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
CustomIsOwner, CustomIsShare,
CustomIsShared, CustomIsUser,
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner,
CustomIsShare, CustomIsShared, CustomIsUser,
group_required)
from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_search import search_recipes, get_facet
from cookbook.helper.recipe_search import get_facet, old_search, search_recipes
from cookbook.helper.recipe_url_import import get_from_scraper
from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
MealType, Recipe, RecipeBook, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Step,
Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile, ShareLink, SupermarketCategoryRelation, Automation)
from cookbook.models import (Automation, BookmarkletImport, CookLog, Food, ImportLog, Ingredient,
Keyword, MealPlan, MealType, Recipe, RecipeBook, RecipeBookEntry,
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Step,
Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation,
Sync, SyncLog, Unit, UserFile, UserPreference, ViewLog)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
from cookbook.schemas import FilterSchema, RecipeSchema, TreeSchema, QueryOnlySchema
from cookbook.serializer import (FoodSerializer, IngredientSerializer,
KeywordSerializer, MealPlanSerializer,
MealTypeSerializer, RecipeBookSerializer,
RecipeImageSerializer, RecipeSerializer,
ShoppingListAutoSyncSerializer,
ShoppingListEntrySerializer,
ShoppingListRecipeSerializer,
ShoppingListSerializer, StepSerializer,
StorageSerializer, SyncLogSerializer,
SyncSerializer, UnitSerializer,
UserNameSerializer, UserPreferenceSerializer,
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer,
RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer,
BookmarkletImportSerializer, SupermarketCategorySerializer, UserFileSerializer, SupermarketCategoryRelationSerializer, AutomationSerializer)
from cookbook.schemas import FilterSchema, QueryOnlySchema, RecipeSchema, TreeSchema
from cookbook.serializer import (AutomationSerializer, BookmarkletImportSerializer,
CookLogSerializer, FoodSerializer, ImportLogSerializer,
IngredientSerializer, KeywordSerializer, MealPlanSerializer,
MealTypeSerializer, RecipeBookEntrySerializer,
RecipeBookSerializer, RecipeImageSerializer,
RecipeOverviewSerializer, RecipeSerializer,
ShoppingListAutoSyncSerializer, ShoppingListEntrySerializer,
ShoppingListRecipeSerializer, ShoppingListSerializer,
StepSerializer, StorageSerializer,
SupermarketCategoryRelationSerializer,
SupermarketCategorySerializer, SupermarketSerializer,
SyncLogSerializer, SyncSerializer, UnitSerializer,
UserFileSerializer, UserNameSerializer, UserPreferenceSerializer,
ViewLogSerializer)
from recipes import settings
@ -547,7 +545,16 @@ class RecipeViewSet(viewsets.ModelViewSet):
return super().get_queryset()
def list(self, request, *args, **kwargs):
if self.request.GET.get('debug', False):
return JsonResponse({
'new': str(self.get_queryset().query),
'old': str(old_search(request).query)
})
return super().list(request, *args, **kwargs)
# TODO write extensive tests for permissions
def get_serializer_class(self):
if self.action == 'list':
return RecipeOverviewSerializer

View File

@ -13,7 +13,7 @@ from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.db.models import Avg, Q, Sum
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render, redirect
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.translation import gettext as _
@ -22,16 +22,15 @@ from django_tables2 import RequestConfig
from rest_framework.authtoken.models import Token
from cookbook.filters import RecipeFilter
from cookbook.forms import (CommentForm, Recipe, User,
UserCreateForm, UserNameForm, UserPreference,
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm,
SearchPreferenceForm)
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit,
Food, UserFile, ShareLink, SearchPreference, SearchFields)
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
ViewLogTable, InviteLinkTable)
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm,
SpaceJoinForm, User, UserCreateForm, UserNameForm, UserPreference,
UserPreferenceForm)
from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid
from cookbook.models import (Comment, CookLog, Food, InviteLink, Keyword, MealPlan, RecipeImport,
SearchFields, SearchPreference, ShareLink, ShoppingList, Space, Unit,
UserFile, ViewLog)
from cookbook.tables import (CookLogTable, InviteLinkTable, RecipeTable, RecipeTableSmall,
ViewLogTable)
from cookbook.views.data import Object
from recipes.version import BUILD_REF, VERSION_NUMBER
@ -331,10 +330,10 @@ def user_settings(request):
if not sp:
sp = SearchPreferenceForm(user=request.user)
fields_searched = (
len(search_form.cleaned_data['icontains'])
+ len(search_form.cleaned_data['istartswith'])
+ len(search_form.cleaned_data['trigram'])
+ len(search_form.cleaned_data['fulltext'])
len(search_form.cleaned_data['icontains'])
+ len(search_form.cleaned_data['istartswith'])
+ len(search_form.cleaned_data['trigram'])
+ len(search_form.cleaned_data['fulltext'])
)
if fields_searched == 0:
search_form.add_error(None, _('You must select at least one field to search!'))
@ -382,7 +381,7 @@ def user_settings(request):
if up:
preference_form = UserPreferenceForm(instance=up, space=request.space)
else:
preference_form = UserPreferenceForm( space=request.space)
preference_form = UserPreferenceForm(space=request.space)
fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(
sp.fulltext.all())

File diff suppressed because it is too large Load Diff

View File

@ -1,210 +1,211 @@
{
"warning_feature_beta": "This feature is currently in a BETA (testing) state. Please expect bugs and possibly breaking changes in the future (possibly loosing feature related data) when using this feature.",
"err_fetching_resource": "There was an error fetching a resource!",
"err_creating_resource": "There was an error creating a resource!",
"err_updating_resource": "There was an error updating a resource!",
"err_deleting_resource": "There was an error deleting a resource!",
"success_fetching_resource": "Successfully fetched a resource!",
"success_creating_resource": "Successfully created a resource!",
"success_updating_resource": "Successfully updated a resource!",
"success_deleting_resource": "Successfully deleted a resource!",
"file_upload_disabled": "File upload is not enabled for your space.",
"step_time_minutes": "Step time in minutes",
"confirm_delete": "Are you sure you want to delete this {object}?",
"import_running": "Import running, please wait!",
"all_fields_optional": "All fields are optional and can be left empty.",
"convert_internal": "Convert to internal recipe",
"show_only_internal": "Show only internal recipes",
"show_split_screen": "Split View",
"Log_Recipe_Cooking": "Log Recipe Cooking",
"External_Recipe_Image": "External Recipe Image",
"Add_to_Shopping": "Add to Shopping",
"Add_to_Plan": "Add to Plan",
"Step_start_time": "Step start time",
"Sort_by_new": "Sort by new",
"Table_of_Contents": "Table of Contents",
"Recipes_per_page": "Recipes per Page",
"Show_as_header": "Show as header",
"Hide_as_header": "Hide as header",
"Add_nutrition_recipe": "Add nutrition to recipe",
"Remove_nutrition_recipe": "Delete nutrition from recipe",
"Copy_template_reference": "Copy template reference",
"Save_and_View": "Save & View",
"Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan",
"Select_Book": "Select Book",
"Select_File": "Select File",
"Recipe_Image": "Recipe Image",
"Import_finished": "Import finished",
"View_Recipes": "View Recipes",
"Log_Cooking": "Log Cooking",
"New_Recipe": "New Recipe",
"Url_Import": "Url Import",
"Reset_Search": "Reset Search",
"Recently_Viewed": "Recently Viewed",
"Load_More": "Load More",
"New_Keyword": "New Keyword",
"Delete_Keyword": "Delete Keyword",
"Edit_Keyword": "Edit Keyword",
"Edit_Recipe": "Edit Recipe",
"Move_Keyword": "Move Keyword",
"Merge_Keyword": "Merge Keyword",
"Hide_Keywords": "Hide Keyword",
"Hide_Recipes": "Hide Recipes",
"Move_Up": "Move up",
"Move_Down": "Move down",
"Step_Name": "Step Name",
"Step_Type": "Step Type",
"Make_header": "Make_Header",
"Make_Ingredient": "Make_Ingredient",
"Enable_Amount": "Enable Amount",
"Disable_Amount": "Disable Amount",
"Add_Step": "Add Step",
"Keywords": "Keywords",
"Books": "Books",
"Proteins": "Proteins",
"Fats": "Fats",
"Carbohydrates": "Carbohydrates",
"Calories": "Calories",
"Energy": "Energy",
"Nutrition": "Nutrition",
"Date": "Date",
"Share": "Share",
"Automation": "Automation",
"Parameter": "Parameter",
"Export": "Export",
"Copy": "Copy",
"Rating": "Rating",
"Close": "Close",
"Cancel": "Cancel",
"Link": "Link",
"Add": "Add",
"New": "New",
"Note": "Note",
"Success": "Success",
"Failure": "Failure",
"Ingredients": "Ingredients",
"Supermarket": "Supermarket",
"Categories": "Categories",
"Category": "Category",
"Selected": "Selected",
"min": "min",
"Servings": "Servings",
"Waiting": "Waiting",
"Preparation": "Preparation",
"External": "External",
"Size": "Size",
"Files": "Files",
"File": "File",
"Edit": "Edit",
"Image": "Image",
"Delete": "Delete",
"Open": "Open",
"Ok": "Open",
"Save": "Save",
"Step": "Step",
"Search": "Search",
"Import": "Import",
"Print": "Print",
"Settings": "Settings",
"or": "or",
"and": "and",
"Information": "Information",
"Download": "Download",
"Create": "Create",
"Advanced Search Settings": "Advanced Search Settings",
"View": "View",
"Recipes": "Recipes",
"Move": "Move",
"Merge": "Merge",
"Parent": "Parent",
"delete_confirmation": "Are you sure that you want to delete {source}?",
"move_confirmation": "Move <i>{child}</i> to parent <i>{parent}</i>",
"merge_confirmation": "Replace <i>{source}</i> with <i>{target}</i>",
"create_rule": "and create automation",
"move_selection": "Select a parent {type} to move {source} to.",
"merge_selection": "Replace all occurrences of {source} with the selected {type}.",
"Root": "Root",
"Ignore_Shopping": "Ignore Shopping",
"Shopping_Category": "Shopping Category",
"Edit_Food": "Edit Food",
"Move_Food": "Move Food",
"New_Food": "New Food",
"Hide_Food": "Hide Food",
"Food_Alias": "Food Alias",
"Unit_Alias": "Unit Alias",
"Keyword_Alias": "Keyword Alias",
"Delete_Food": "Delete Food",
"No_ID": "ID not found, cannot delete.",
"Meal_Plan_Days": "Future meal plans",
"merge_title": "Merge {type}",
"move_title": "Move {type}",
"Food": "Food",
"Recipe_Book": "Recipe Book",
"del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?",
"delete_title": "Delete {type}",
"create_title": "New {type}",
"edit_title": "Edit {type}",
"Name": "Name",
"Type": "Type",
"Description": "Description",
"Recipe": "Recipe",
"tree_root": "Root of Tree",
"Icon": "Icon",
"Unit": "Unit",
"No_Results": "No Results",
"New_Unit": "New Unit",
"Create_New_Shopping Category": "Create New Shopping Category",
"Create_New_Food": "Add New Food",
"Create_New_Keyword": "Add New Keyword",
"Create_New_Unit": "Add New Unit",
"Create_New_Meal_Type": "Add New Meal Type",
"and_up": "& Up",
"Instructions": "Instructions",
"Unrated": "Unrated",
"Automate": "Automate",
"Empty": "Empty",
"Key_Ctrl": "Ctrl",
"Key_Shift": "Shift",
"Time": "Time",
"Text": "Text",
"Shopping_list": "Shopping List",
"Create_Meal_Plan_Entry": "Create meal plan entry",
"Edit_Meal_Plan_Entry": "Edit meal plan entry",
"Title": "Title",
"Week": "Week",
"Month": "Month",
"Year": "Year",
"Planner": "Planner",
"Planner_Settings": "Planner settings",
"Period": "Period",
"Plan_Period_To_Show": "Show weeks, months or years",
"Periods": "Periods",
"Plan_Show_How_Many_Periods": "How many periods to show",
"Starting_Day": "Starting day of the week",
"Meal_Types": "Meal types",
"Meal_Type": "Meal type",
"Clone": "Clone",
"Drag_Here_To_Delete": "Drag here to delete",
"Meal_Type_Required": "Meal type is required",
"Title_or_Recipe_Required": "Title or recipe selection required",
"Color": "Color",
"New_Meal_Type": "New Meal type",
"Week_Numbers": "Week numbers",
"Show_Week_Numbers": "Show week numbers ?",
"Export_As_ICal": "Export current period to iCal format",
"Export_To_ICal": "Export .ics",
"Cannot_Add_Notes_To_Shopping": "Notes cannot be added to the shopping list",
"Added_To_Shopping_List": "Added to shopping list",
"Shopping_List_Empty": "Your shopping list is currently empty, you can add items via the context menu of a meal plan entry (right click on the card or left click the menu icon)",
"Next_Period": "Next Period",
"Previous_Period": "Previous Period",
"Current_Period": "Current Period",
"Next_Day": "Next Day",
"Previous_Day": "Previous Day",
"Coming_Soon": "Coming-Soon",
"Auto_Planner": "Auto-Planner",
"New_Cookbook": "New cookbook",
"Hide_Keyword": "Hide keywords",
"Clear": "Clear"
"warning_feature_beta": "This feature is currently in a BETA (testing) state. Please expect bugs and possibly breaking changes in the future (possibly loosing feature related data) when using this feature.",
"err_fetching_resource": "There was an error fetching a resource!",
"err_creating_resource": "There was an error creating a resource!",
"err_updating_resource": "There was an error updating a resource!",
"err_deleting_resource": "There was an error deleting a resource!",
"success_fetching_resource": "Successfully fetched a resource!",
"success_creating_resource": "Successfully created a resource!",
"success_updating_resource": "Successfully updated a resource!",
"success_deleting_resource": "Successfully deleted a resource!",
"file_upload_disabled": "File upload is not enabled for your space.",
"step_time_minutes": "Step time in minutes",
"confirm_delete": "Are you sure you want to delete this {object}?",
"import_running": "Import running, please wait!",
"all_fields_optional": "All fields are optional and can be left empty.",
"convert_internal": "Convert to internal recipe",
"show_only_internal": "Show only internal recipes",
"show_split_screen": "Split View",
"Log_Recipe_Cooking": "Log Recipe Cooking",
"External_Recipe_Image": "External Recipe Image",
"Add_to_Shopping": "Add to Shopping",
"Add_to_Plan": "Add to Plan",
"Step_start_time": "Step start time",
"Sort_by_new": "Sort by new",
"Table_of_Contents": "Table of Contents",
"Recipes_per_page": "Recipes per Page",
"Show_as_header": "Show as header",
"Hide_as_header": "Hide as header",
"Add_nutrition_recipe": "Add nutrition to recipe",
"Remove_nutrition_recipe": "Delete nutrition from recipe",
"Copy_template_reference": "Copy template reference",
"Save_and_View": "Save & View",
"Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan",
"Select_Book": "Select Book",
"Select_File": "Select File",
"Recipe_Image": "Recipe Image",
"Import_finished": "Import finished",
"View_Recipes": "View Recipes",
"Log_Cooking": "Log Cooking",
"New_Recipe": "New Recipe",
"Url_Import": "Url Import",
"Reset_Search": "Reset Search",
"Recently_Viewed": "Recently Viewed",
"Load_More": "Load More",
"New_Keyword": "New Keyword",
"Delete_Keyword": "Delete Keyword",
"Edit_Keyword": "Edit Keyword",
"Edit_Recipe": "Edit Recipe",
"Move_Keyword": "Move Keyword",
"Merge_Keyword": "Merge Keyword",
"Hide_Keywords": "Hide Keyword",
"Hide_Recipes": "Hide Recipes",
"Move_Up": "Move up",
"Move_Down": "Move down",
"Step_Name": "Step Name",
"Step_Type": "Step Type",
"Make_header": "Make_Header",
"Make_Ingredient": "Make_Ingredient",
"Enable_Amount": "Enable Amount",
"Disable_Amount": "Disable Amount",
"Add_Step": "Add Step",
"Keywords": "Keywords",
"Books": "Books",
"Proteins": "Proteins",
"Fats": "Fats",
"Carbohydrates": "Carbohydrates",
"Calories": "Calories",
"Energy": "Energy",
"Nutrition": "Nutrition",
"Date": "Date",
"Share": "Share",
"Automation": "Automation",
"Parameter": "Parameter",
"Export": "Export",
"Copy": "Copy",
"Rating": "Rating",
"Close": "Close",
"Cancel": "Cancel",
"Link": "Link",
"Add": "Add",
"New": "New",
"Note": "Note",
"Success": "Success",
"Failure": "Failure",
"Ingredients": "Ingredients",
"Supermarket": "Supermarket",
"Categories": "Categories",
"Category": "Category",
"Selected": "Selected",
"min": "min",
"Servings": "Servings",
"Waiting": "Waiting",
"Preparation": "Preparation",
"External": "External",
"Size": "Size",
"Files": "Files",
"File": "File",
"Edit": "Edit",
"Image": "Image",
"Delete": "Delete",
"Open": "Open",
"Ok": "Open",
"Save": "Save",
"Step": "Step",
"Search": "Search",
"Import": "Import",
"Print": "Print",
"Settings": "Settings",
"or": "or",
"and": "and",
"Information": "Information",
"Download": "Download",
"Create": "Create",
"Advanced Search Settings": "Advanced Search Settings",
"View": "View",
"Recipes": "Recipes",
"Move": "Move",
"Merge": "Merge",
"Parent": "Parent",
"delete_confirmation": "Are you sure that you want to delete {source}?",
"move_confirmation": "Move <i>{child}</i> to parent <i>{parent}</i>",
"merge_confirmation": "Replace <i>{source}</i> with <i>{target}</i>",
"create_rule": "and create automation",
"move_selection": "Select a parent {type} to move {source} to.",
"merge_selection": "Replace all occurrences of {source} with the selected {type}.",
"Root": "Root",
"Ignore_Shopping": "Ignore Shopping",
"Shopping_Category": "Shopping Category",
"Edit_Food": "Edit Food",
"Move_Food": "Move Food",
"New_Food": "New Food",
"Hide_Food": "Hide Food",
"Food_Alias": "Food Alias",
"Unit_Alias": "Unit Alias",
"Keyword_Alias": "Keyword Alias",
"Delete_Food": "Delete Food",
"No_ID": "ID not found, cannot delete.",
"Meal_Plan_Days": "Future meal plans",
"merge_title": "Merge {type}",
"move_title": "Move {type}",
"Food": "Food",
"Recipe_Book": "Recipe Book",
"del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?",
"delete_title": "Delete {type}",
"create_title": "New {type}",
"edit_title": "Edit {type}",
"Name": "Name",
"Type": "Type",
"Description": "Description",
"Recipe": "Recipe",
"tree_root": "Root of Tree",
"Icon": "Icon",
"Unit": "Unit",
"No_Results": "No Results",
"New_Unit": "New Unit",
"Create_New_Shopping Category": "Create New Shopping Category",
"Create_New_Food": "Add New Food",
"Create_New_Keyword": "Add New Keyword",
"Create_New_Unit": "Add New Unit",
"Create_New_Meal_Type": "Add New Meal Type",
"and_up": "& Up",
"Instructions": "Instructions",
"Unrated": "Unrated",
"Automate": "Automate",
"Empty": "Empty",
"Key_Ctrl": "Ctrl",
"Key_Shift": "Shift",
"Time": "Time",
"Text": "Text",
"Shopping_list": "Shopping List",
"Create_Meal_Plan_Entry": "Create meal plan entry",
"Edit_Meal_Plan_Entry": "Edit meal plan entry",
"Title": "Title",
"Week": "Week",
"Month": "Month",
"Year": "Year",
"Planner": "Planner",
"Planner_Settings": "Planner settings",
"Period": "Period",
"Plan_Period_To_Show": "Show weeks, months or years",
"Periods": "Periods",
"Plan_Show_How_Many_Periods": "How many periods to show",
"Starting_Day": "Starting day of the week",
"Meal_Types": "Meal types",
"Meal_Type": "Meal type",
"Clone": "Clone",
"Drag_Here_To_Delete": "Drag here to delete",
"Meal_Type_Required": "Meal type is required",
"Title_or_Recipe_Required": "Title or recipe selection required",
"Color": "Color",
"New_Meal_Type": "New Meal type",
"Week_Numbers": "Week numbers",
"Show_Week_Numbers": "Show week numbers ?",
"Export_As_ICal": "Export current period to iCal format",
"Export_To_ICal": "Export .ics",
"Cannot_Add_Notes_To_Shopping": "Notes cannot be added to the shopping list",
"Added_To_Shopping_List": "Added to shopping list",
"Shopping_List_Empty": "Your shopping list is currently empty, you can add items via the context menu of a meal plan entry (right click on the card or left click the menu icon)",
"Next_Period": "Next Period",
"Previous_Period": "Previous Period",
"Current_Period": "Current Period",
"Next_Day": "Next Day",
"Previous_Day": "Previous Day",
"Coming_Soon": "Coming-Soon",
"Auto_Planner": "Auto-Planner",
"New_Cookbook": "New cookbook",
"Hide_Keyword": "Hide keywords",
"Clear": "Clear",
"show_sql": "Show SQL"
}