add search debug

This commit is contained in:
smilerz 2021-11-24 12:10:15 -06:00
parent 3fe5340592
commit 55a0304700
8 changed files with 812 additions and 782 deletions

1
.gitignore vendored
View File

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

View File

@ -1,17 +1,17 @@
from collections import Counter from collections import Counter
from datetime import timedelta 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.core.cache import caches
from django.db.models import Avg, Case, Count, Func, Max, Q, Subquery, Value, When from django.db.models import Avg, Case, Count, Func, Max, Q, Subquery, Value, When
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils import timezone, translation 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.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): 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 # TODO add order by user settings - only do search rank and annotation if rank order is configured
search_rank = ( search_rank = (
SearchRank('name_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('desc_search_vector', search_query, cover_density=True)
+ SearchRank('steps__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) queryset = queryset.filter(query_filter).annotate(rank=search_rank)
orderby += ['-rank'] orderby += ['-rank']
@ -400,3 +400,13 @@ def annotated_qs(qs, root=False, fill=False):
if start_depth and start_depth > 0: if start_depth and start_depth > 0:
info['close'] = list(range(0, prev_depth - start_depth + 1)) info['close'] = list(range(0, prev_depth - start_depth + 1))
return result 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('SCRIPT_NAME', "{% base_path request 'script' %}")
localStorage.setItem('BASE_PATH', "{% base_path request 'base' %}") localStorage.setItem('BASE_PATH', "{% base_path request 'base' %}")
localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}") localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}")
localStorage.setItem('DEBUG', "{% is_debug %}")
window.addEventListener("load", () => { window.addEventListener("load", () => {
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: "{% base_path request 'base' %}" + '/'}).then(function (reg) { 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.urls import include, path
from django.views.generic import TemplateView from django.views.generic import TemplateView
from recipes.version import VERSION_NUMBER from rest_framework import permissions, routers
from rest_framework import routers, permissions
from rest_framework.schemas import get_schema_view from rest_framework.schemas import get_schema_view
from cookbook.helper import dal 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, from .models import (Automation, Comment, Food, InviteLink, Keyword, MealPlan, Recipe, RecipeBook,
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage, Supermarket,
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name, Automation, SupermarketCategory, Sync, SyncLog, Unit, UserFile, get_model_name)
UserFile, Step) from .views import api, data, delete, edit, import_export, lists, new, telegram, views
from .views import api, data, delete, edit, import_export, lists, new, views, telegram
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'user-name', api.UserNameViewSet, basename='username') router.register(r'user-name', api.UserNameViewSet, basename='username')
@ -68,8 +68,6 @@ urlpatterns = [
path('history/', views.history, name='view_history'), path('history/', views.history, name='view_history'),
path('supermarket/', views.supermarket, name='view_supermarket'), path('supermarket/', views.supermarket, name='view_supermarket'),
path('abuse/<slug:token>', views.report_share_abuse, name='view_report_share_abuse'), 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/', import_export.import_recipe, name='view_import'),
path('import-response/<int:pk>/', import_export.import_response, name='view_import_response'), 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}' 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 json
import re import re
import uuid import uuid
from collections import OrderedDict
import requests import requests
from annoying.decorators import ajax_request from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None from annoying.functions import get_object_or_None
from collections import OrderedDict
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity 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 import Case, ProtectedError, Q, Value, When
from django.db.models.fields.related import ForeignObjectRel from django.db.models.fields.related import ForeignObjectRel
from django.http import FileResponse, HttpResponse, JsonResponse from django.http import FileResponse, HttpResponse, JsonResponse
from django_scopes import scopes_disabled from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event 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 import decorators, status, viewsets
from rest_framework.exceptions import APIException, PermissionDenied from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework.pagination import PageNumberPagination 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.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ViewSetMixin 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.image_processing import handle_image
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner,
CustomIsOwner, CustomIsShare, CustomIsShare, CustomIsShared, CustomIsUser,
CustomIsShared, CustomIsUser,
group_required) group_required)
from cookbook.helper.recipe_html_import import get_recipe_from_source from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_search import get_facet, old_search, search_recipes
from cookbook.helper.recipe_search import search_recipes, get_facet
from cookbook.helper.recipe_url_import import get_from_scraper from cookbook.helper.recipe_url_import import get_from_scraper
from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan, from cookbook.models import (Automation, BookmarkletImport, CookLog, Food, ImportLog, Ingredient,
MealType, Recipe, RecipeBook, ShoppingList, Keyword, MealPlan, MealType, Recipe, RecipeBook, RecipeBookEntry,
ShoppingListEntry, ShoppingListRecipe, Step, ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Step,
Storage, Sync, SyncLog, Unit, UserPreference, Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation,
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile, ShareLink, SupermarketCategoryRelation, Automation) Sync, SyncLog, Unit, UserFile, UserPreference, ViewLog)
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud from cookbook.provider.nextcloud import Nextcloud
from cookbook.schemas import FilterSchema, RecipeSchema, TreeSchema, QueryOnlySchema from cookbook.schemas import FilterSchema, QueryOnlySchema, RecipeSchema, TreeSchema
from cookbook.serializer import (FoodSerializer, IngredientSerializer, from cookbook.serializer import (AutomationSerializer, BookmarkletImportSerializer,
KeywordSerializer, MealPlanSerializer, CookLogSerializer, FoodSerializer, ImportLogSerializer,
MealTypeSerializer, RecipeBookSerializer, IngredientSerializer, KeywordSerializer, MealPlanSerializer,
RecipeImageSerializer, RecipeSerializer, MealTypeSerializer, RecipeBookEntrySerializer,
ShoppingListAutoSyncSerializer, RecipeBookSerializer, RecipeImageSerializer,
ShoppingListEntrySerializer, RecipeOverviewSerializer, RecipeSerializer,
ShoppingListRecipeSerializer, ShoppingListAutoSyncSerializer, ShoppingListEntrySerializer,
ShoppingListSerializer, StepSerializer, ShoppingListRecipeSerializer, ShoppingListSerializer,
StorageSerializer, SyncLogSerializer, StepSerializer, StorageSerializer,
SyncSerializer, UnitSerializer, SupermarketCategoryRelationSerializer,
UserNameSerializer, UserPreferenceSerializer, SupermarketCategorySerializer, SupermarketSerializer,
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer, SyncLogSerializer, SyncSerializer, UnitSerializer,
RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer, UserFileSerializer, UserNameSerializer, UserPreferenceSerializer,
BookmarkletImportSerializer, SupermarketCategorySerializer, UserFileSerializer, SupermarketCategoryRelationSerializer, AutomationSerializer) ViewLogSerializer)
from recipes import settings from recipes import settings
@ -547,7 +545,16 @@ class RecipeViewSet(viewsets.ModelViewSet):
return super().get_queryset() 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 # TODO write extensive tests for permissions
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'list': if self.action == 'list':
return RecipeOverviewSerializer 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.core.exceptions import ValidationError
from django.db.models import Avg, Q, Sum from django.db.models import Avg, Q, Sum
from django.http import HttpResponseRedirect, JsonResponse 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.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -22,16 +22,15 @@ from django_tables2 import RequestConfig
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from cookbook.filters import RecipeFilter from cookbook.filters import RecipeFilter
from cookbook.forms import (CommentForm, Recipe, User, from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm,
UserCreateForm, UserNameForm, UserPreference, SpaceJoinForm, User, UserCreateForm, UserNameForm, UserPreference,
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm, UserPreferenceForm)
SearchPreferenceForm) from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission from cookbook.models import (Comment, CookLog, Food, InviteLink, Keyword, MealPlan, RecipeImport,
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan, SearchFields, SearchPreference, ShareLink, ShoppingList, Space, Unit,
ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit, UserFile, ViewLog)
Food, UserFile, ShareLink, SearchPreference, SearchFields) from cookbook.tables import (CookLogTable, InviteLinkTable, RecipeTable, RecipeTableSmall,
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall, ViewLogTable)
ViewLogTable, InviteLinkTable)
from cookbook.views.data import Object from cookbook.views.data import Object
from recipes.version import BUILD_REF, VERSION_NUMBER from recipes.version import BUILD_REF, VERSION_NUMBER
@ -331,10 +330,10 @@ def user_settings(request):
if not sp: if not sp:
sp = SearchPreferenceForm(user=request.user) sp = SearchPreferenceForm(user=request.user)
fields_searched = ( fields_searched = (
len(search_form.cleaned_data['icontains']) len(search_form.cleaned_data['icontains'])
+ len(search_form.cleaned_data['istartswith']) + len(search_form.cleaned_data['istartswith'])
+ len(search_form.cleaned_data['trigram']) + len(search_form.cleaned_data['trigram'])
+ len(search_form.cleaned_data['fulltext']) + len(search_form.cleaned_data['fulltext'])
) )
if fields_searched == 0: if fields_searched == 0:
search_form.add_error(None, _('You must select at least one field to search!')) search_form.add_error(None, _('You must select at least one field to search!'))
@ -382,7 +381,7 @@ def user_settings(request):
if up: if up:
preference_form = UserPreferenceForm(instance=up, space=request.space) preference_form = UserPreferenceForm(instance=up, space=request.space)
else: 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( fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(
sp.fulltext.all()) 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.", "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_fetching_resource": "There was an error fetching a resource!",
"err_creating_resource": "There was an error creating a resource!", "err_creating_resource": "There was an error creating a resource!",
"err_updating_resource": "There was an error updating a resource!", "err_updating_resource": "There was an error updating a resource!",
"err_deleting_resource": "There was an error deleting a resource!", "err_deleting_resource": "There was an error deleting a resource!",
"success_fetching_resource": "Successfully fetched a resource!", "success_fetching_resource": "Successfully fetched a resource!",
"success_creating_resource": "Successfully created a resource!", "success_creating_resource": "Successfully created a resource!",
"success_updating_resource": "Successfully updated a resource!", "success_updating_resource": "Successfully updated a resource!",
"success_deleting_resource": "Successfully deleted a resource!", "success_deleting_resource": "Successfully deleted a resource!",
"file_upload_disabled": "File upload is not enabled for your space.", "file_upload_disabled": "File upload is not enabled for your space.",
"step_time_minutes": "Step time in minutes", "step_time_minutes": "Step time in minutes",
"confirm_delete": "Are you sure you want to delete this {object}?", "confirm_delete": "Are you sure you want to delete this {object}?",
"import_running": "Import running, please wait!", "import_running": "Import running, please wait!",
"all_fields_optional": "All fields are optional and can be left empty.", "all_fields_optional": "All fields are optional and can be left empty.",
"convert_internal": "Convert to internal recipe", "convert_internal": "Convert to internal recipe",
"show_only_internal": "Show only internal recipes", "show_only_internal": "Show only internal recipes",
"show_split_screen": "Split View", "show_split_screen": "Split View",
"Log_Recipe_Cooking": "Log Recipe Cooking", "Log_Recipe_Cooking": "Log Recipe Cooking",
"External_Recipe_Image": "External Recipe Image", "External_Recipe_Image": "External Recipe Image",
"Add_to_Shopping": "Add to Shopping", "Add_to_Shopping": "Add to Shopping",
"Add_to_Plan": "Add to Plan", "Add_to_Plan": "Add to Plan",
"Step_start_time": "Step start time", "Step_start_time": "Step start time",
"Sort_by_new": "Sort by new", "Sort_by_new": "Sort by new",
"Table_of_Contents": "Table of Contents", "Table_of_Contents": "Table of Contents",
"Recipes_per_page": "Recipes per Page", "Recipes_per_page": "Recipes per Page",
"Show_as_header": "Show as header", "Show_as_header": "Show as header",
"Hide_as_header": "Hide as header", "Hide_as_header": "Hide as header",
"Add_nutrition_recipe": "Add nutrition to recipe", "Add_nutrition_recipe": "Add nutrition to recipe",
"Remove_nutrition_recipe": "Delete nutrition from recipe", "Remove_nutrition_recipe": "Delete nutrition from recipe",
"Copy_template_reference": "Copy template reference", "Copy_template_reference": "Copy template reference",
"Save_and_View": "Save & View", "Save_and_View": "Save & View",
"Manage_Books": "Manage Books", "Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan", "Meal_Plan": "Meal Plan",
"Select_Book": "Select Book", "Select_Book": "Select Book",
"Select_File": "Select File", "Select_File": "Select File",
"Recipe_Image": "Recipe Image", "Recipe_Image": "Recipe Image",
"Import_finished": "Import finished", "Import_finished": "Import finished",
"View_Recipes": "View Recipes", "View_Recipes": "View Recipes",
"Log_Cooking": "Log Cooking", "Log_Cooking": "Log Cooking",
"New_Recipe": "New Recipe", "New_Recipe": "New Recipe",
"Url_Import": "Url Import", "Url_Import": "Url Import",
"Reset_Search": "Reset Search", "Reset_Search": "Reset Search",
"Recently_Viewed": "Recently Viewed", "Recently_Viewed": "Recently Viewed",
"Load_More": "Load More", "Load_More": "Load More",
"New_Keyword": "New Keyword", "New_Keyword": "New Keyword",
"Delete_Keyword": "Delete Keyword", "Delete_Keyword": "Delete Keyword",
"Edit_Keyword": "Edit Keyword", "Edit_Keyword": "Edit Keyword",
"Edit_Recipe": "Edit Recipe", "Edit_Recipe": "Edit Recipe",
"Move_Keyword": "Move Keyword", "Move_Keyword": "Move Keyword",
"Merge_Keyword": "Merge Keyword", "Merge_Keyword": "Merge Keyword",
"Hide_Keywords": "Hide Keyword", "Hide_Keywords": "Hide Keyword",
"Hide_Recipes": "Hide Recipes", "Hide_Recipes": "Hide Recipes",
"Move_Up": "Move up", "Move_Up": "Move up",
"Move_Down": "Move down", "Move_Down": "Move down",
"Step_Name": "Step Name", "Step_Name": "Step Name",
"Step_Type": "Step Type", "Step_Type": "Step Type",
"Make_header": "Make_Header", "Make_header": "Make_Header",
"Make_Ingredient": "Make_Ingredient", "Make_Ingredient": "Make_Ingredient",
"Enable_Amount": "Enable Amount", "Enable_Amount": "Enable Amount",
"Disable_Amount": "Disable Amount", "Disable_Amount": "Disable Amount",
"Add_Step": "Add Step", "Add_Step": "Add Step",
"Keywords": "Keywords", "Keywords": "Keywords",
"Books": "Books", "Books": "Books",
"Proteins": "Proteins", "Proteins": "Proteins",
"Fats": "Fats", "Fats": "Fats",
"Carbohydrates": "Carbohydrates", "Carbohydrates": "Carbohydrates",
"Calories": "Calories", "Calories": "Calories",
"Energy": "Energy", "Energy": "Energy",
"Nutrition": "Nutrition", "Nutrition": "Nutrition",
"Date": "Date", "Date": "Date",
"Share": "Share", "Share": "Share",
"Automation": "Automation", "Automation": "Automation",
"Parameter": "Parameter", "Parameter": "Parameter",
"Export": "Export", "Export": "Export",
"Copy": "Copy", "Copy": "Copy",
"Rating": "Rating", "Rating": "Rating",
"Close": "Close", "Close": "Close",
"Cancel": "Cancel", "Cancel": "Cancel",
"Link": "Link", "Link": "Link",
"Add": "Add", "Add": "Add",
"New": "New", "New": "New",
"Note": "Note", "Note": "Note",
"Success": "Success", "Success": "Success",
"Failure": "Failure", "Failure": "Failure",
"Ingredients": "Ingredients", "Ingredients": "Ingredients",
"Supermarket": "Supermarket", "Supermarket": "Supermarket",
"Categories": "Categories", "Categories": "Categories",
"Category": "Category", "Category": "Category",
"Selected": "Selected", "Selected": "Selected",
"min": "min", "min": "min",
"Servings": "Servings", "Servings": "Servings",
"Waiting": "Waiting", "Waiting": "Waiting",
"Preparation": "Preparation", "Preparation": "Preparation",
"External": "External", "External": "External",
"Size": "Size", "Size": "Size",
"Files": "Files", "Files": "Files",
"File": "File", "File": "File",
"Edit": "Edit", "Edit": "Edit",
"Image": "Image", "Image": "Image",
"Delete": "Delete", "Delete": "Delete",
"Open": "Open", "Open": "Open",
"Ok": "Open", "Ok": "Open",
"Save": "Save", "Save": "Save",
"Step": "Step", "Step": "Step",
"Search": "Search", "Search": "Search",
"Import": "Import", "Import": "Import",
"Print": "Print", "Print": "Print",
"Settings": "Settings", "Settings": "Settings",
"or": "or", "or": "or",
"and": "and", "and": "and",
"Information": "Information", "Information": "Information",
"Download": "Download", "Download": "Download",
"Create": "Create", "Create": "Create",
"Advanced Search Settings": "Advanced Search Settings", "Advanced Search Settings": "Advanced Search Settings",
"View": "View", "View": "View",
"Recipes": "Recipes", "Recipes": "Recipes",
"Move": "Move", "Move": "Move",
"Merge": "Merge", "Merge": "Merge",
"Parent": "Parent", "Parent": "Parent",
"delete_confirmation": "Are you sure that you want to delete {source}?", "delete_confirmation": "Are you sure that you want to delete {source}?",
"move_confirmation": "Move <i>{child}</i> to parent <i>{parent}</i>", "move_confirmation": "Move <i>{child}</i> to parent <i>{parent}</i>",
"merge_confirmation": "Replace <i>{source}</i> with <i>{target}</i>", "merge_confirmation": "Replace <i>{source}</i> with <i>{target}</i>",
"create_rule": "and create automation", "create_rule": "and create automation",
"move_selection": "Select a parent {type} to move {source} to.", "move_selection": "Select a parent {type} to move {source} to.",
"merge_selection": "Replace all occurrences of {source} with the selected {type}.", "merge_selection": "Replace all occurrences of {source} with the selected {type}.",
"Root": "Root", "Root": "Root",
"Ignore_Shopping": "Ignore Shopping", "Ignore_Shopping": "Ignore Shopping",
"Shopping_Category": "Shopping Category", "Shopping_Category": "Shopping Category",
"Edit_Food": "Edit Food", "Edit_Food": "Edit Food",
"Move_Food": "Move Food", "Move_Food": "Move Food",
"New_Food": "New Food", "New_Food": "New Food",
"Hide_Food": "Hide Food", "Hide_Food": "Hide Food",
"Food_Alias": "Food Alias", "Food_Alias": "Food Alias",
"Unit_Alias": "Unit Alias", "Unit_Alias": "Unit Alias",
"Keyword_Alias": "Keyword Alias", "Keyword_Alias": "Keyword Alias",
"Delete_Food": "Delete Food", "Delete_Food": "Delete Food",
"No_ID": "ID not found, cannot delete.", "No_ID": "ID not found, cannot delete.",
"Meal_Plan_Days": "Future meal plans", "Meal_Plan_Days": "Future meal plans",
"merge_title": "Merge {type}", "merge_title": "Merge {type}",
"move_title": "Move {type}", "move_title": "Move {type}",
"Food": "Food", "Food": "Food",
"Recipe_Book": "Recipe Book", "Recipe_Book": "Recipe Book",
"del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?", "del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?",
"delete_title": "Delete {type}", "delete_title": "Delete {type}",
"create_title": "New {type}", "create_title": "New {type}",
"edit_title": "Edit {type}", "edit_title": "Edit {type}",
"Name": "Name", "Name": "Name",
"Type": "Type", "Type": "Type",
"Description": "Description", "Description": "Description",
"Recipe": "Recipe", "Recipe": "Recipe",
"tree_root": "Root of Tree", "tree_root": "Root of Tree",
"Icon": "Icon", "Icon": "Icon",
"Unit": "Unit", "Unit": "Unit",
"No_Results": "No Results", "No_Results": "No Results",
"New_Unit": "New Unit", "New_Unit": "New Unit",
"Create_New_Shopping Category": "Create New Shopping Category", "Create_New_Shopping Category": "Create New Shopping Category",
"Create_New_Food": "Add New Food", "Create_New_Food": "Add New Food",
"Create_New_Keyword": "Add New Keyword", "Create_New_Keyword": "Add New Keyword",
"Create_New_Unit": "Add New Unit", "Create_New_Unit": "Add New Unit",
"Create_New_Meal_Type": "Add New Meal Type", "Create_New_Meal_Type": "Add New Meal Type",
"and_up": "& Up", "and_up": "& Up",
"Instructions": "Instructions", "Instructions": "Instructions",
"Unrated": "Unrated", "Unrated": "Unrated",
"Automate": "Automate", "Automate": "Automate",
"Empty": "Empty", "Empty": "Empty",
"Key_Ctrl": "Ctrl", "Key_Ctrl": "Ctrl",
"Key_Shift": "Shift", "Key_Shift": "Shift",
"Time": "Time", "Time": "Time",
"Text": "Text", "Text": "Text",
"Shopping_list": "Shopping List", "Shopping_list": "Shopping List",
"Create_Meal_Plan_Entry": "Create meal plan entry", "Create_Meal_Plan_Entry": "Create meal plan entry",
"Edit_Meal_Plan_Entry": "Edit meal plan entry", "Edit_Meal_Plan_Entry": "Edit meal plan entry",
"Title": "Title", "Title": "Title",
"Week": "Week", "Week": "Week",
"Month": "Month", "Month": "Month",
"Year": "Year", "Year": "Year",
"Planner": "Planner", "Planner": "Planner",
"Planner_Settings": "Planner settings", "Planner_Settings": "Planner settings",
"Period": "Period", "Period": "Period",
"Plan_Period_To_Show": "Show weeks, months or years", "Plan_Period_To_Show": "Show weeks, months or years",
"Periods": "Periods", "Periods": "Periods",
"Plan_Show_How_Many_Periods": "How many periods to show", "Plan_Show_How_Many_Periods": "How many periods to show",
"Starting_Day": "Starting day of the week", "Starting_Day": "Starting day of the week",
"Meal_Types": "Meal types", "Meal_Types": "Meal types",
"Meal_Type": "Meal type", "Meal_Type": "Meal type",
"Clone": "Clone", "Clone": "Clone",
"Drag_Here_To_Delete": "Drag here to delete", "Drag_Here_To_Delete": "Drag here to delete",
"Meal_Type_Required": "Meal type is required", "Meal_Type_Required": "Meal type is required",
"Title_or_Recipe_Required": "Title or recipe selection required", "Title_or_Recipe_Required": "Title or recipe selection required",
"Color": "Color", "Color": "Color",
"New_Meal_Type": "New Meal type", "New_Meal_Type": "New Meal type",
"Week_Numbers": "Week numbers", "Week_Numbers": "Week numbers",
"Show_Week_Numbers": "Show week numbers ?", "Show_Week_Numbers": "Show week numbers ?",
"Export_As_ICal": "Export current period to iCal format", "Export_As_ICal": "Export current period to iCal format",
"Export_To_ICal": "Export .ics", "Export_To_ICal": "Export .ics",
"Cannot_Add_Notes_To_Shopping": "Notes cannot be added to the shopping list", "Cannot_Add_Notes_To_Shopping": "Notes cannot be added to the shopping list",
"Added_To_Shopping_List": "Added to 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)", "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", "Next_Period": "Next Period",
"Previous_Period": "Previous Period", "Previous_Period": "Previous Period",
"Current_Period": "Current Period", "Current_Period": "Current Period",
"Next_Day": "Next Day", "Next_Day": "Next Day",
"Previous_Day": "Previous Day", "Previous_Day": "Previous Day",
"Coming_Soon": "Coming-Soon", "Coming_Soon": "Coming-Soon",
"Auto_Planner": "Auto-Planner", "Auto_Planner": "Auto-Planner",
"New_Cookbook": "New cookbook", "New_Cookbook": "New cookbook",
"Hide_Keyword": "Hide keywords", "Hide_Keyword": "Hide keywords",
"Clear": "Clear" "Clear": "Clear",
"show_sql": "Show SQL"
} }