Merge branch 'develop' into feature/vue3

This commit is contained in:
vabene1111
2024-09-23 10:28:01 +02:00
12 changed files with 177 additions and 74 deletions

View File

@@ -16,15 +16,11 @@ class CookbookConfig(AppConfig):
import cookbook.signals # noqa
if not settings.DISABLE_EXTERNAL_CONNECTORS:
try:
from cookbook.connectors.connector_manager import ConnectorManager # Needs to be here to prevent loading race condition of oauth2 modules in models.py
handler = ConnectorManager()
post_save.connect(handler, dispatch_uid="connector_manager")
post_delete.connect(handler, dispatch_uid="connector_manager")
except Exception as e:
traceback.print_exc()
print('Failed to initialize connectors')
pass
from cookbook.connectors.connector_manager import ConnectorManager # Needs to be here to prevent loading race condition of oauth2 modules in models.py
handler = ConnectorManager()
post_save.connect(handler, dispatch_uid="post_save-connector_manager")
post_delete.connect(handler, dispatch_uid="post_delete-connector_manager")
# if not settings.DISABLE_TREE_FIX_STARTUP:
# # when starting up run fix_tree to:
# # a) make sure that nodes are sorted when switching between sort modes
@@ -45,4 +41,4 @@ class CookbookConfig(AppConfig):
# except Exception:
# if DEBUG:
# traceback.print_exc()
# pass # dont break startup just because fix could not run, need to investigate cases when this happens
# pass # dont break startup just because fix could not run, need to investigate cases when this happens

View File

@@ -31,6 +31,15 @@ class Work:
actionType: ActionType
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# The way ConnectionManager works is as follows:
# 1. On init, it starts a worker & creates a queue for 'Work'
# 2. Then any time its called, it verifies the type of action (create/update/delete) and if the item is of interest, pushes the Work (non-blocking) to the queue.
@@ -39,7 +48,8 @@ class Work:
# 3.2 If work is of type REGISTERED_CLASSES, it asynchronously fires of all connectors and wait for them to finish (runtime should depend on the 'slowest' connector)
# 4. Work is marked as consumed, and next entry of the queue is consumed.
# Each 'Work' is processed in sequential by the worker, so the throughput is about [workers * the slowest connector]
class ConnectorManager:
# The Singleton class is used for ConnectorManager to have a self-reference and so Python does not garbage collect it
class ConnectorManager(metaclass=Singleton):
_logger: Logger
_queue: queue.Queue
_listening_to_classes = REGISTERED_CLASSES | ConnectorConfig

View File

@@ -3,7 +3,7 @@ from logging import Logger
from typing import Dict, Tuple
from urllib.parse import urljoin
from aiohttp import ClientError, request
from aiohttp import request, ClientResponseError
from cookbook.connectors.connector import Connector
from cookbook.models import ShoppingListEntry, ConnectorConfig, Space
@@ -13,6 +13,8 @@ class HomeAssistant(Connector):
_config: ConnectorConfig
_logger: Logger
_required_foreign_keys = ("food", "unit", "created_by")
def __init__(self, config: ConnectorConfig):
if not config.token or not config.url or not config.todo_entity:
raise ValueError("config for HomeAssistantConnector in incomplete")
@@ -38,18 +40,20 @@ class HomeAssistant(Connector):
item, description = _format_shopping_list_entry(shopping_list_entry)
self._logger.debug(f"adding {item=}")
self._logger.debug(f"adding {item=} with {description=} to {self._config.todo_entity}")
data = {
"entity_id": self._config.todo_entity,
"item": item,
"description": description,
}
if self._config.supports_description_field:
data["description"] = description
try:
await self.homeassistant_api_call("POST", "services/todo/add_item", data)
except ClientError as err:
self._logger.warning(f"received an exception from the api: {err=}, {type(err)=}")
except ClientResponseError as err:
self._logger.warning(f"received an exception from the api: {err.request_info.url=}, {err.request_info.method=}, {err.status=}, {err.message=}, {type(err)=}")
async def on_shopping_list_entry_updated(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
if not self._config.on_shopping_list_entry_updated_enabled:
@@ -60,14 +64,14 @@ class HomeAssistant(Connector):
if not self._config.on_shopping_list_entry_deleted_enabled:
return
if not hasattr(shopping_list_entry._state.fields_cache, "food"):
if not all(k in shopping_list_entry._state.fields_cache for k in self._required_foreign_keys):
# Sometimes the food foreign key is not loaded, and we cant load it from an async process
self._logger.debug("required property was not present in ShoppingListEntry")
return
item, _ = _format_shopping_list_entry(shopping_list_entry)
self._logger.debug(f"removing {item=}")
self._logger.debug(f"removing {item=} from {self._config.todo_entity}")
data = {
"entity_id": self._config.todo_entity,
@@ -76,9 +80,9 @@ class HomeAssistant(Connector):
try:
await self.homeassistant_api_call("POST", "services/todo/remove_item", data)
except ClientError as err:
except ClientResponseError as err:
# This error will always trigger if the item is not present/found
self._logger.debug(f"received an exception from the api: {err=}, {type(err)=}")
self._logger.debug(f"received an exception from the api: {err.request_info.url=}, {err.request_info.method=}, {err.status=}, {err.message=}, {type(err)=}")
async def close(self) -> None:
pass

View File

@@ -182,6 +182,12 @@ class ConnectorConfigForm(forms.ModelForm):
required=False,
)
supports_description_field = forms.BooleanField(
help_text="Does the connector todo entity support the description field",
initial=True,
required=False,
)
update_token = forms.CharField(
widget=forms.TextInput(attrs={'autocomplete': 'update-token', 'type': 'password'}),
required=False,
@@ -198,7 +204,7 @@ class ConnectorConfigForm(forms.ModelForm):
fields = (
'name', 'type', 'enabled', 'on_shopping_list_entry_created_enabled', 'on_shopping_list_entry_updated_enabled',
'on_shopping_list_entry_deleted_enabled', 'url', 'todo_entity',
'on_shopping_list_entry_deleted_enabled', 'supports_description_field', 'url', 'todo_entity',
)
help_texts = {

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-03-11 13:02+0000\n"
"Last-Translator: Kn <kn@users.noreply.translate.tandoor.dev>\n"
"PO-Revision-Date: 2024-09-17 11:58+0000\n"
"Last-Translator: Bebbe K <kajolekk91@gmail.com>\n"
"Language-Team: Swedish <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/sv/>\n"
"Language: sv\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.4.2\n"
"X-Generator: Weblate 5.6.2\n"
#: .\cookbook\forms.py:45
msgid ""
@@ -151,48 +151,61 @@ msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices."
msgstr ""
"Välj typ av sökmetod. Klicka <a href=\"/docs/search/\">här</a> för "
"fullständig beskrivning av alternativen."
#: .\cookbook\forms.py:341
msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes."
msgstr ""
"Använd \"fuzzy\" matchning på enheter, nyckelord och ingredienser när du "
"redigerar och importerar recept."
#: .\cookbook\forms.py:342
msgid ""
"Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language"
msgstr ""
"Fält att söka medan man ignorerar accenter. Val av detta alternativ kan "
"förbättra eller försämra sökkvaliteten beroende på språk"
#: .\cookbook\forms.py:343
msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')"
msgstr ""
"Fält att söka efter delvisa matchningar. (t.ex. att söka efter 'Pie' kommer "
"att returnera 'pie' och 'piece' och 'soapie')"
#: .\cookbook\forms.py:344
msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')"
msgstr ""
"Fält att söka för matchningar i början av ord. (t.ex. att söka efter 'sa' "
"kommer att returnera 'salad' och 'sandwich')"
#: .\cookbook\forms.py:345
msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr ""
"Fält för 'fuzzy'-sökning. (t.ex. att söka efter 'recpie' kommer att hitta "
"'recipe'.Observera: detta alternativet kommer att komma i konflikt med "
"sökmetoderna 'web' och 'raw'.)"
#: .\cookbook\forms.py:346
msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields."
msgstr ""
"Fält för fulltextsökning. Observera: Sökmetoderna 'web', 'phrase' och 'raw' "
"fungerar endast med fulltextfält."
#: .\cookbook\forms.py:350
#, fuzzy
#| msgid "Search"
msgid "Search Method"
msgstr "Sök"
msgstr "Sök Metod"
#: .\cookbook\forms.py:350
msgid "Fuzzy Lookups"
@@ -211,22 +224,20 @@ msgid "Starts With"
msgstr "Börjar med"
#: .\cookbook\forms.py:351
#, fuzzy
#| msgid "Search"
msgid "Fuzzy Search"
msgstr "Sök"
msgstr "Fuzzy Sök"
#: .\cookbook\forms.py:351
#, fuzzy
#| msgid "Text"
msgid "Full Text"
msgstr "Text"
msgstr "Hel Text"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"För att förhindra spam, så skickades inte den begärda e-posten. Vänta några "
"minuter och försök igen."
#: .\cookbook\helper\permission_helper.py:164
#: .\cookbook\helper\permission_helper.py:187 .\cookbook\views\views.py:117
@@ -255,19 +266,19 @@ msgstr "Du kan inte interagera med detta objekt för att det ägs inte av dig!"
#: .\cookbook\helper\permission_helper.py:402
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
msgstr "Du har nått det maximala antalet recept för din utrymme."
#: .\cookbook\helper\permission_helper.py:414
msgid "You have more users than allowed in your space."
msgstr ""
msgstr "Du har mer användare än tillåtet för ditt utrymme."
#: .\cookbook\helper\recipe_url_import.py:310
msgid "reverse rotation"
msgstr ""
msgstr "återvänd rotering"
#: .\cookbook\helper\recipe_url_import.py:311
msgid "careful rotation"
msgstr ""
msgstr "försiktig rotering"
#: .\cookbook\helper\recipe_url_import.py:312
msgid "knead"
@@ -291,7 +302,7 @@ msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
msgstr "Du måste ange en portionstorlek"
#: .\cookbook\helper\template_helper.py:95
#: .\cookbook\helper\template_helper.py:97
@@ -305,7 +316,7 @@ msgstr "Favorit"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
msgstr "Jag gjorde den här"
#: .\cookbook\integration\integration.py:209
msgid ""
@@ -319,6 +330,8 @@ msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Ett oväntat fel uppstod under importeringen. Se till att du har laddat upp "
"en giltig fil."
#: .\cookbook\integration\integration.py:217
msgid "The following recipes were ignored because they already existed:"
@@ -330,10 +343,8 @@ msgid "Imported %s recipes."
msgstr "Importerade %s recept."
#: .\cookbook\integration\openeats.py:28
#, fuzzy
#| msgid "Recipe Home"
msgid "Recipe source:"
msgstr "Recept Hem"
msgstr "Recept källa:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
@@ -374,23 +385,25 @@ msgstr "Sektion"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
msgstr "Korrigerar livsmedel med "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
msgstr "Återuppbygger fulltextsökningens index för Recept"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Endast Postgresql-databaser använder fulltextsökning, inget index att "
"återuppbygga med"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
msgstr "Recept index återbyggning färdig."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
msgstr "Recept index återbyggning misslyckades."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
@@ -439,6 +452,8 @@ msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maximal lagringsutrymme för utrymme i MB. 0 för obegränsad, -1 för att "
"inaktivera filuppladdning."
#: .\cookbook\models.py:454 .\cookbook\templates\search.html:7
#: .\cookbook\templates\settings.html:18
@@ -462,7 +477,7 @@ msgstr "Handla"
#: .\cookbook\models.py:752
msgid " is part of a recipe step and cannot be deleted"
msgstr ""
msgstr " är en del av ett receptsteg och kan inte tas bort"
#: .\cookbook\models.py:918
msgid "Nutrition"
@@ -501,34 +516,24 @@ msgid "Food Alias"
msgstr "Alternativt namn för mat"
#: .\cookbook\models.py:1468
#, fuzzy
#| msgid "Units"
msgid "Unit Alias"
msgstr "Enheter"
msgstr "Enhetsalias"
#: .\cookbook\models.py:1469
#, fuzzy
#| msgid "Keywords"
msgid "Keyword Alias"
msgstr "Nyckelord"
msgstr "Nyckelordsalias"
#: .\cookbook\models.py:1470
#, fuzzy
#| msgid "Description"
msgid "Description Replace"
msgstr "Beskrivning"
msgstr "Beskrivning Ersätt"
#: .\cookbook\models.py:1471
#, fuzzy
#| msgid "Instructions"
msgid "Instruction Replace"
msgstr "Instruktioner"
msgstr "Instruktioner Ersätt"
#: .\cookbook\models.py:1472
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Ny enhet"
msgstr "Aldrig Enhet"
#: .\cookbook\models.py:1473
msgid "Transpose Words"
@@ -539,10 +544,8 @@ msgid "Food Replace"
msgstr "Ersätt mat"
#: .\cookbook\models.py:1475
#, fuzzy
#| msgid "Edit Recipe"
msgid "Unit Replace"
msgstr "Redigera recept"
msgstr "Ersätt Enhet"
#: .\cookbook\models.py:1476
msgid "Name Replace"

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.15 on 2024-09-15 10:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0218_alter_mealplan_from_date_alter_mealplan_to_date'),
]
operations = [
migrations.AddField(
model_name='connectorconfig',
name='supports_description_field',
field=models.BooleanField(default=True, help_text='Does the todo entity support the description field'),
),
]

View File

@@ -406,6 +406,7 @@ class ConnectorConfig(models.Model, PermissionModelMixin):
on_shopping_list_entry_created_enabled = models.BooleanField(default=False)
on_shopping_list_entry_updated_enabled = models.BooleanField(default=False)
on_shopping_list_entry_deleted_enabled = models.BooleanField(default=False)
supports_description_field = models.BooleanField(default=True, help_text="Does the todo entity support the description field")
url = models.URLField(blank=True, null=True)
token = models.CharField(max_length=512, blank=True, null=True)

View File

@@ -470,7 +470,7 @@ class ConnectorConfigConfigSerializer(SpacedModelSerializer):
fields = (
'id', 'name', 'url', 'token', 'todo_entity', 'enabled',
'on_shopping_list_entry_created_enabled', 'on_shopping_list_entry_updated_enabled',
'on_shopping_list_entry_deleted_enabled', 'created_by'
'on_shopping_list_entry_deleted_enabled', 'supports_description_field', 'created_by'
)
read_only_fields = ('created_by',)

View File

@@ -99,11 +99,23 @@ def test_add(arg, request, a1_s2, obj_1):
assert r.status_code == arg[1]
if r.status_code == 201:
assert response['name'] == 'test'
assert response['supports_description_field'] == True
r = c.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 200
r = a1_s2.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 404
def test_add_with_supports_description_field_false(a1_s2):
r = a1_s2.post(
reverse(LIST_URL),
{'name': 'test', 'url': 'http://localhost:8123/api', 'token': '1234', 'enabled': 'true', 'supports_description_field': 'false'},
content_type='application/json'
)
response = json.loads(r.content)
print(r.content)
assert r.status_code == 201
assert response['name'] == 'test'
assert response['supports_description_field'] == False
def test_delete(a1_s1, a1_s2, obj_1):
r = a1_s2.delete(

View File

@@ -517,5 +517,56 @@
"imperial_gallon": "αυτοκρατορικό γαλόνι [imp gal] (Ηνωμένο Βασίλειο, όγκος)",
"imperial_tbsp": "αυτοκρατορικό κουτάλι της σούπας [imp tbsp] (Ηνωμένο Βασίλειο, όγκος)",
"Choose_Category": "Επιλογή κατηγορίας",
"Back": "Πίσω"
"Back": "Πίσω",
"Calculator": "Υπολογιστής",
"us_cup": "φλιτζάνι (ΗΠΑ, όγκος)",
"Created": "Δημιουργήθηκε",
"Logo": "Λογότυπο",
"Nav_Text_Mode_Help": "Συμπεριφέρεται διαφορετικά για κάθε θέμα.",
"Properties_Food_Amount": "Ιδιότητες Ποσότητα Φαγητού",
"show_step_ingredients": "Εμφάνιση των συστατικών του βήματος",
"make_now_count": "Το πολύ να λείπουν συστατικά",
"Error": "Σφάλμα",
"Nav_Text_Mode": "Λειτουγία κειμένου πλοήγησης",
"Input": "Εισαγογή",
"Undo": "Ανέρεση",
"NoMoreUndo": "Δεν υπάρχουν αλλαγές για ανέρεση.",
"Delete_All": "Διαγραφή όλων",
"created_by": "Δημιουργήθηκε από",
"CustomLogoHelp": "Ανεβάστε τετράγωνες εικόνες σε διαφορετικά μεγέθη για αλλαγή σε λογότυπο στην καρτέλα του προγράμματος περιήγησης και στο εγκατεστημένο Web App.",
"CustomNavLogoHelp": "Μεταφορτώστε μια εικόνα για χρήση ως λογότυπο της γραμμής πλοήγησης.",
"ShowRecentlyCompleted": "Πρόσφατα αντικείμενα που ολοκληρώθηκαν",
"ShoppingBackgroundSyncWarning": "Κακό δίκτυο, αναμονή συγχρονισμού...",
"Name_Replace": "Αντικατάσταση Ονόματος",
"Transpose_Words": "Μεταφορά λέξεων",
"err_importing_recipe": "Υπάρχει σφάλμα στην εισαγωγή της σύνταγης!",
"Properties_Food_Unit": "Ιδιότητες Μονάδα Φαγητού",
"show_step_ingredients_setting": "Εμφάνιση συστατικών δίπλα στα βήματα της συνταγής",
"show_step_ingredients_setting_help": "Προσθέστε τον πίνακα συστατικών δίπλα στα βήματα της συνταγής. Ισχύει κατά τη δημιουργία. Μπορεί να παρακαμφθεί στην προβολή επεξεργασίας συνταγής.",
"hide_step_ingredients": "Απόκρυψη των συστατικών του βήματος",
"property_type_fdc_hint": "Μόνο οι τύποι ιδιοτήτων με ταυτότητα FDC, μπορούν να τραβήξουν αυτόματα δεδομένα απο την βάση δεδομένων του FDC",
"FDC_Search": "Αναζήτηση FDC",
"StartDate": "Ημερομηνία Έναρξης",
"CustomImageHelp": "Ανεβάστε μια εικόνα για να εμφανίζεται στην επισκόπηση χώρου",
"CustomThemeHelp": "Αντικαταστήστε τα στυλ του επιλεγμένου θέματος μεταφορτώνοντας ένα προσαρμοσμένο αρχείο CSS.",
"CustomTheme": "Προσαρμοσμένο Θέμα",
"Shopping_input_placeholder": "π.χ. Πατάτα/100 Πατάτες/100 γρ Πατατες",
"Property_Editor": "Επεξεργαστής Ιδιοτήτων",
"CustomLogos": "Προσαρμοσμένα λογότυπα",
"Updated": "Επεξεργάστηκε",
"Unchanged": "Αμετάβλητο",
"Show_Logo": "Εμφάνιση λογότυπου",
"Show_Logo_Help": "Εμφάνιση λογότυπου του Tandoor ή του space στη γραμμή πλοήγησης.",
"Space_Cosmetic_Settings": "Ορισμένες ρυθμίσεις εμφάνισης μπορούν να αλλάξουν από τους διαχειριστές του χώρου και θα παρακάμψουν τις ρυθμίσεις πελάτη για αυτόν τον χώρο.",
"show_ingredients_table": "Εμφάνιση ένός πίνακα με τα συστατικά δίπλα στο κείμενο του βήματος",
"Enable": "Ενεργοποίηση",
"Alignment": "Ευθυγράμμιση",
"FDC_ID_help": "Ταυτότητα βάσης δεδομένων FDC",
"EndDate": "Ημερομηνία Λήξης",
"FDC_ID": "Ταυτότητα FDC",
"OrderInformation": "Τα αντικείμενα ταξινομούνται από μικρό σε μεγάλο αριθμό.",
"Never_Unit": "Ποτέ Μονάδα",
"DefaultPage": "Προεπιλεγμένη σελίδα",
"Food_Replace": "Αντικατάσταση Φαγητού",
"Unit_Replace": "Αντικατάσταση Μονάδας"
}

View File

@@ -8,11 +8,11 @@
"err_moving_resource": "Hiba történt egy erőforrás áthelyezésekor!",
"err_merging_resource": "Hiba történt egy erőforrás egyesítésekor!",
"success_fetching_resource": "Sikeresen lekérdezett erőforrást!",
"success_creating_resource": "",
"success_updating_resource": "",
"success_deleting_resource": "",
"success_moving_resource": "",
"success_merging_resource": "",
"success_creating_resource": "Sikeresen létrehoztam egy erőforrást!",
"success_updating_resource": "Sikeresen frissítettem egy erőforrást!",
"success_deleting_resource": "Sikeresen töröltem egy erőforrást!",
"success_moving_resource": "Sikeresen áthelyeztem egy erőforrást!",
"success_merging_resource": "Sikeresen egyesítettem egy erőforrást!",
"file_upload_disabled": "A fájlfeltöltés nincs engedélyezve az Ön teréhez.",
"step_time_minutes": "Lépés időtartama percben",
"confirm_delete": "Biztos, hogy törölni akarja ezt a {object}?",

View File

@@ -1,5 +1,5 @@
{
"warning_feature_beta": "Данный функционал находится в стадии BETA (тестируется). Возможны баги и серьезные изменения функционала в будущем.",
"warning_feature_beta": "Данный функционал находится в стадии бета-тестирования. Возможны баги и серьезные изменения функционала в будущем.",
"err_fetching_resource": "Ошибка при загрузке продукта!",
"err_creating_resource": "Ошибка при создании продукта!",
"err_updating_resource": "Ошибка при редактировании продукта!",
@@ -359,5 +359,7 @@
"Auto_Sort": "Автоматическая сортировка",
"Amount": "Количество",
"per_serving": "за порцию",
"Instruction_Replace": "Изменить Инструкцию"
"Instruction_Replace": "Изменить Инструкцию",
"recipe_property_info": "Вы также можете добавить свойства к продуктам, чтобы автоматически рассчитывать их на основе вашего рецепта!",
"open_data_help_text": "Проект Tandoor Open Data предоставляет предоставленные сообществом данные для Tandoor. Это поле заполняется автоматически при импорте и допускает обновления в будущем."
}