diff --git a/.env.template b/.env.template index f0230072a..fca55ec3b 100644 --- a/.env.template +++ b/.env.template @@ -2,6 +2,7 @@ # when unset: 1 (true) - dont unset this, just for development DEBUG=0 SQL_DEBUG=0 +DEBUG_TOOLBAR=0 # HTTP port to bind to # TANDOOR_PORT=8080 diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 629dc2451..072c86fa4 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -69,11 +69,13 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub uses: docker/login-action@v2 + if: github.secret_source == 'Actions' with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry uses: docker/login-action@v2 + if: github.secret_source == 'Actions' with: registry: ghcr.io username: ${{ github.actor }} @@ -100,7 +102,7 @@ jobs: context: . file: ${{ matrix.dockerfile }} pull: true - push: true + push: ${{ github.secret_source == 'Actions' }} platforms: ${{ matrix.platforms }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/cookbook/integration/openeats.py b/cookbook/integration/openeats.py index a8485f050..9188ca8de 100644 --- a/cookbook/integration/openeats.py +++ b/cookbook/integration/openeats.py @@ -2,24 +2,54 @@ import json from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration -from cookbook.models import Ingredient, Recipe, Step - +from cookbook.models import Ingredient, Recipe, Step, Keyword, Comment, CookLog +from django.utils.translation import gettext as _ class OpenEats(Integration): def get_recipe_from_file(self, file): - recipe = Recipe.objects.create(name=file['name'].strip(), created_by=self.request.user, internal=True, + + description = file['info'] + description_max_length = Recipe._meta.get_field('description').max_length + if len(description) > description_max_length: + description = description[0:description_max_length] + + recipe = Recipe.objects.create(name=file['name'].strip(), description=description, created_by=self.request.user, internal=True, servings=file['servings'], space=self.request.space, waiting_time=file['cook_time'], working_time=file['prep_time']) instructions = '' - if file["info"] != '': - instructions += file["info"] if file["directions"] != '': instructions += file["directions"] if file["source"] != '': - instructions += file["source"] + instructions += '\n' + _('Recipe source:') + f'[{file["source"]}]({file["source"]})' + + cuisine_keyword, created = Keyword.objects.get_or_create(name="Cuisine", space=self.request.space) + if file["cuisine"] != '': + keyword, created = Keyword.objects.get_or_create(name=file["cuisine"].strip(), space=self.request.space) + if created: + keyword.move(cuisine_keyword, pos="last-child") + recipe.keywords.add(keyword) + + course_keyword, created = Keyword.objects.get_or_create(name="Course", space=self.request.space) + if file["course"] != '': + keyword, created = Keyword.objects.get_or_create(name=file["course"].strip(), space=self.request.space) + if created: + keyword.move(course_keyword, pos="last-child") + recipe.keywords.add(keyword) + + for tag in file["tags"]: + keyword, created = Keyword.objects.get_or_create(name=tag.strip(), space=self.request.space) + recipe.keywords.add(keyword) + + for comment in file['comments']: + Comment.objects.create(recipe=recipe, text=comment['text'], created_by=self.request.user) + CookLog.objects.create(recipe=recipe, rating=comment['rating'], created_by=self.request.user, space=self.request.space) + + if file["photo"] != '': + recipe.image = f'recipes/openeats-import/{file["photo"]}' + recipe.save() step = Step.objects.create(instruction=instructions, space=self.request.space,) @@ -38,6 +68,9 @@ class OpenEats(Integration): recipe_json = json.loads(file.read()) recipe_dict = {} ingredient_group_dict = {} + cuisine_group_dict = {} + course_group_dict = {} + tag_group_dict = {} for o in recipe_json: if o['model'] == 'recipe.recipe': @@ -50,11 +83,27 @@ class OpenEats(Integration): 'cook_time': o['fields']['cook_time'], 'servings': o['fields']['servings'], 'ingredients': [], + 'photo': o['fields']['photo'], + 'cuisine': o['fields']['cuisine'], + 'course': o['fields']['course'], + 'tags': o['fields']['tags'], + 'comments': [], } if o['model'] == 'ingredient.ingredientgroup': ingredient_group_dict[o['pk']] = o['fields']['recipe'] + if o['model'] == 'recipe_groups.cuisine': + cuisine_group_dict[o['pk']] = o['fields']['title'] + if o['model'] == 'recipe_groups.course': + course_group_dict[o['pk']] = o['fields']['title'] + if o['model'] == 'recipe_groups.tag': + tag_group_dict[o['pk']] = o['fields']['title'] for o in recipe_json: + if o['model'] == 'rating.rating': + recipe_dict[o['fields']['recipe']]["comments"].append({ + "text": o['fields']['comment'], + "rating": o['fields']['rating'] + }) if o['model'] == 'ingredient.ingredient': ingredient = { 'food': o['fields']['title'], @@ -63,6 +112,15 @@ class OpenEats(Integration): } recipe_dict[ingredient_group_dict[o['fields']['ingredient_group']]]['ingredients'].append(ingredient) + for k, r in recipe_dict.items(): + if r["cuisine"] in cuisine_group_dict: + r["cuisine"] = cuisine_group_dict[r["cuisine"]] + if r["course"] in course_group_dict: + r["course"] = course_group_dict[r["course"]] + for index in range(len(r["tags"])): + if r["tags"][index] in tag_group_dict: + r["tags"][index] = tag_group_dict[r["tags"][index]] + return list(recipe_dict.values()) def get_file_from_recipe(self, recipe): diff --git a/cookbook/locale/cs/LC_MESSAGES/django.po b/cookbook/locale/cs/LC_MESSAGES/django.po index aa14acdf8..18f4d74f6 100644 --- a/cookbook/locale/cs/LC_MESSAGES/django.po +++ b/cookbook/locale/cs/LC_MESSAGES/django.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-02-09 18:01+0100\n" -"PO-Revision-Date: 2023-01-08 17:55+0000\n" -"Last-Translator: Joachim Weber \n" +"PO-Revision-Date: 2023-03-25 11:32+0000\n" +"Last-Translator: Matěj Kubla \n" "Language-Team: Czech \n" "Language: cs\n" @@ -553,7 +553,7 @@ msgstr "Cesta musí být v následujícím formátu" #: .\cookbook\templates\batch\monitor.html:27 msgid "Sync Now!" -msgstr "Zahájit synchronizaci" +msgstr "Zahájit synchronizaci!" #: .\cookbook\templates\batch\waiting.html:4 #: .\cookbook\templates\batch\waiting.html:10 @@ -1036,7 +1036,7 @@ msgstr "Tento text je kurzívou" #: .\cookbook\templates\markdown_info.html:61 #: .\cookbook\templates\markdown_info.html:77 msgid "Blockquotes are also possible" -msgstr "Lze použít i kvotace " +msgstr "Lze použít i kvotace" #: .\cookbook\templates\markdown_info.html:84 msgid "Lists" @@ -1106,8 +1106,8 @@ msgid "" "rel=\"noreferrer noopener\" target=\"_blank\">this one." msgstr "" "Ruční vytváření tabulek pomocí značek je složité. Doporučujeme použít " -"například tento tabulkový editor." +"například tento tabulkový editor." #: .\cookbook\templates\markdown_info.html:155 #: .\cookbook\templates\markdown_info.html:157 @@ -1256,22 +1256,36 @@ msgid "" " " msgstr "" "\n" -"

Modul jídelníčku umožňuje plánovat jídlo pomocí receptů i poznámek.

\n" -"

Jednoduše vyberte recept ze seznamu naposledy navštívených receptů, nebo ho vyhledejte\n" -" s přetáhněte na požadovaný den v rozvrhu. Můžete také přidat poznámku s popiskem\n" -" a poté přetáhnout recept pro vytvoření plánu s vlatními popisky. Vytvořením samotné poznámky\n" -" je možné přetažením pole poznámky do rozvrhu.

\n" -"

Kliknutím na recept zobrazíte detailní náhled. Odtud lze také přidat položky\n" -" do nákupního seznamu. Do nákupního seznamu můžete také přidat všechny recepty na daný den\n" -" kliknutím na ikonu nákupního košíku na horní straně tabulky.

\n" -"

V běžném případě se jídelníček plánuje hromadně, proto můžete v nastavení definovat\n" -" se kterými uživateli si přejete jídelníčky sdílet.\n" +"

Modul jídelníčku umožňuje plánovat jídlo " +"pomocí receptů i poznámek.

\n" +"

Jednoduše vyberte recept ze seznamu naposledy " +"navštívených receptů, nebo ho vyhledejte\n" +" s přetáhněte na požadovaný den v rozvrhu. " +"Můžete také přidat poznámku s popiskem\n" +" a poté přetáhnout recept pro vytvoření plánu " +"s vlatními popisky. Vytvořením samotné poznámky\n" +" je možné přetažením pole poznámky do " +"rozvrhu.

\n" +"

Kliknutím na recept zobrazíte detailní " +"náhled. Odtud lze také přidat položky\n" +" do nákupního seznamu. Do nákupního seznamu " +"můžete také přidat všechny recepty na daný den\n" +" kliknutím na ikonu nákupního košíku na horní " +"straně tabulky.

\n" +"

V běžném případě se jídelníček plánuje " +"hromadně, proto můžete v nastavení definovat\n" +" se kterými uživateli si přejete jídelníčky " +"sdílet.\n" "

\n" -"

Můžete také upravovat typy jídel, které si přejete naplánovat. Pokud budete sdílet jídelníček \n" +"

Můžete také upravovat typy jídel, které si " +"přejete naplánovat. Pokud budete sdílet jídelníček \n" " s někým, kdo\n" -" má přidána jiná jídla, jeho typy jídel se objeví i ve vašem seznamu. Pro předcházení\n" +" má přidána jiná jídla, jeho typy jídel se " +"objeví i ve vašem seznamu. Pro předcházení\n" " duplicitám (např. Ostatní, Jiná)\n" -" pojmenujte váš typ jídla stejně, jako uživatel se kterým své seznamy sdílíte. Tím budou seznamy sloučeny.

\n" +" pojmenujte váš typ jídla stejně, jako " +"uživatel se kterým své seznamy sdílíte. Tím budou seznamy\n" +" sloučeny.

\n" " " #: .\cookbook\templates\meal_plan_entry.html:6 @@ -1333,12 +1347,12 @@ msgstr "Obrázek receptu" #: .\cookbook\templates\recipes_table.html:46 #: .\cookbook\templates\url_import.html:55 msgid "Preparation time ca." -msgstr "Doba přípravy cca" +msgstr "Doba přípravy cca." #: .\cookbook\templates\recipes_table.html:52 #: .\cookbook\templates\url_import.html:60 msgid "Waiting time ca." -msgstr "Doba čekání cca" +msgstr "Doba čekání cca." #: .\cookbook\templates\recipes_table.html:55 msgid "External" @@ -1386,7 +1400,7 @@ msgid "" " in the following examples:" msgstr "" "Použijte tajný klíč jako autorizační hlavičku definovanou slovním klíčem, " -"jak je uvedeno v následujících příkladech." +"jak je uvedeno v následujících příkladech:" #: .\cookbook\templates\settings.html:94 msgid "or" @@ -1808,7 +1822,7 @@ msgstr "Import není pro tohoto poskytovatele implementován!" #: .\cookbook\views\import_export.py:58 msgid "Exporting is not implemented for this provider" -msgstr "Eport není pro tohoto poskytovatele implementován!" +msgstr "Export není pro tohoto poskytovatele implementován!" #: .\cookbook\views\lists.py:42 msgid "Import Log" @@ -1840,7 +1854,7 @@ msgstr "Komentář uložen!" #: .\cookbook\views\views.py:152 msgid "This recipe is already linked to the book!" -msgstr "Tento recept už v kuchařce existuje." +msgstr "Tento recept už v kuchařce existuje!" #: .\cookbook\views\views.py:158 msgid "Bookmark saved!" diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 3e47531c1..e562005fe 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1471,17 +1471,17 @@ def sync_all(request): return redirect('list_recipe_import') +@api_view(['GET']) +# @schema(AutoSchema()) #TODO add proper schema +@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope]) def share_link(request, pk): - if request.user.is_authenticated: - if request.space.allow_sharing and has_group_permission(request.user, ('user',)): - recipe = get_object_or_404(Recipe, pk=pk, space=request.space) - link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space) - return JsonResponse({'pk': pk, 'share': link.uuid, - 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))}) - else: - return JsonResponse({'error': 'sharing_disabled'}, status=403) - - return JsonResponse({'error': 'not_authenticated'}, status=403) + if request.space.allow_sharing and has_group_permission(request.user, ('user',)): + recipe = get_object_or_404(Recipe, pk=pk, space=request.space) + link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space) + return JsonResponse({'pk': pk, 'share': link.uuid, + 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))}) + else: + return JsonResponse({'error': 'sharing_disabled'}, status=403) @group_required('user') diff --git a/docs/features/import_export.md b/docs/features/import_export.md index d775cf9fc..a2fa3040f 100644 --- a/docs/features/import_export.md +++ b/docs/features/import_export.md @@ -178,7 +178,7 @@ This zip file can simply be imported into Tandoor. 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 > recipe_ingredients.json` +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 @@ -216,6 +216,8 @@ Store the outputted json string in a `.json` file and simply import it using the ``` +To import your images you'll need to create the folder `openeats-import` in your Tandoor's `recipes` media folder (which is usually found inside `/opt/recipes/mediafiles`). After that you'll need to copy the `/code/site-media/upload` folder from the openeats API docker container to the `openeats` folder you created. You should now have the file path `/opt/recipes/mediafiles/recipes/openeats-import/upload/...` in Tandoor. + ## Plantoeat Plan to eat allows you to export a text file containing all your recipes. Simply upload that text file to Tandoor to import all recipes diff --git a/recipes/settings.py b/recipes/settings.py index 3dd93bf2d..7a992037e 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -25,6 +25,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV' DEBUG = bool(int(os.getenv('DEBUG', True))) +DEBUG_TOOLBAR = bool(int(os.getenv('DEBUG_TOOLBAR', True))) SOCIAL_DEFAULT_ACCESS = bool(int(os.getenv('SOCIAL_DEFAULT_ACCESS', False))) SOCIAL_DEFAULT_GROUP = os.getenv('SOCIAL_DEFAULT_GROUP', 'guest') @@ -158,7 +159,7 @@ MIDDLEWARE = [ 'cookbook.helper.scope_middleware.ScopeMiddleware', ] -if DEBUG: +if DEBUG_TOOLBAR: MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) INSTALLED_APPS += ('debug_toolbar',) diff --git a/vue/src/locales/cs.json b/vue/src/locales/cs.json new file mode 100644 index 000000000..0ff8bafd9 --- /dev/null +++ b/vue/src/locales/cs.json @@ -0,0 +1,482 @@ +{ + "warning_feature_beta": "", + "err_fetching_resource": "", + "err_creating_resource": "", + "err_updating_resource": "", + "err_deleting_resource": "", + "err_deleting_protected_resource": "", + "err_moving_resource": "", + "err_merging_resource": "", + "success_fetching_resource": "", + "success_creating_resource": "", + "success_updating_resource": "", + "success_deleting_resource": "", + "success_moving_resource": "", + "success_merging_resource": "", + "file_upload_disabled": "", + "warning_space_delete": "", + "food_inherit_info": "", + "facet_count_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": "", + "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": "", + "Add_Step": "", + "Keywords": "", + "Books": "", + "Proteins": "", + "Fats": "", + "Carbohydrates": "", + "Calories": "", + "Energy": "", + "Nutrition": "", + "Date": "", + "Share": "", + "Automation": "", + "Parameter": "", + "Export": "", + "Copy": "", + "Rating": "", + "Close": "", + "Cancel": "", + "Link": "", + "Add": "", + "New": "", + "Note": "", + "Success": "", + "Failure": "", + "Protected": "", + "Ingredients": "", + "Supermarket": "", + "Categories": "", + "Category": "", + "Selected": "", + "min": "", + "Servings": "", + "Waiting": "", + "Preparation": "", + "External": "", + "Size": "", + "Files": "", + "File": "", + "Edit": "", + "Image": "", + "Delete": "", + "Open": "", + "Ok": "", + "Save": "", + "Step": "", + "Search": "", + "Import": "", + "Print": "", + "Settings": "", + "or": "", + "and": "", + "Information": "", + "Download": "", + "Create": "", + "Search Settings": "", + "View": "", + "Recipes": "", + "Move": "", + "Merge": "", + "Parent": "", + "Copy Link": "", + "Copy Token": "", + "delete_confirmation": "", + "move_confirmation": "", + "merge_confirmation": "", + "create_rule": "", + "move_selection": "", + "merge_selection": "", + "Root": "", + "Ignore_Shopping": "", + "Shopping_Category": "", + "Shopping_Categories": "", + "Edit_Food": "", + "Move_Food": "", + "New_Food": "", + "Hide_Food": "", + "Food_Alias": "", + "Unit_Alias": "", + "Keyword_Alias": "", + "Delete_Food": "", + "No_ID": "", + "Meal_Plan_Days": "", + "merge_title": "", + "move_title": "", + "Food": "", + "Original_Text": "", + "Recipe_Book": "", + "del_confirmation_tree": "", + "delete_title": "", + "create_title": "", + "edit_title": "", + "Name": "", + "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": "", + "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": "", + "SupermarketCategoriesOnly": "", + "MoveCategory": "", + "CountMore": "", + "IgnoreThis": "", + "DelayFor": "", + "Warning": "", + "NoCategory": "", + "InheritWarning": "", + "ShowDelayed": "", + "Completed": "", + "OfflineAlert": "", + "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": "", + "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": "", + "Custom Filter": "", + "shared_with": "", + "sort_by": "", + "asc": "", + "desc": "", + "date_viewed": "", + "last_cooked": "", + "times_cooked": "", + "date_created": "", + "show_sortby": "", + "search_rank": "", + "make_now": "", + "recipe_filter": "", + "book_filter_help": "", + "review_shopping": "", + "view_recipe": "", + "copy_to_new": "", + "recipe_name": "", + "paste_ingredients_placeholder": "", + "paste_ingredients": "", + "ingredient_list": "", + "explain": "", + "filter": "", + "Website": "", + "App": "", + "Message": "", + "Bookmarklet": "", + "Sticky_Nav": "", + "Sticky_Nav_Help": "", + "Nav_Color": "", + "Nav_Color_Help": "", + "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_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": "", + "Single": "", + "Multiple": "", + "Reset": "", + "Disabled": "", + "Disable": "", + "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": "", + "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": "", + "Use_Plural_Unit_Always": "", + "Use_Plural_Unit_Simple": "", + "Use_Plural_Food_Always": "", + "Use_Plural_Food_Simple": "", + "plural_usage_info": "", + "Create Recipe": "", + "Import Recipe": "" +}