From 9205a09d356a9ada2ba59f871a5114ee17036754 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 1 May 2021 21:52:59 -0800 Subject: [PATCH 1/4] New Crowdin updates (#383) * New translations en-US.json (German) * New translations en-US.json (Japanese) * New translations en-US.json (Vietnamese) * New translations en-US.json (Chinese Traditional) * New translations en-US.json (Ukrainian) * New translations en-US.json (Turkish) * New translations en-US.json (Swedish) * New translations en-US.json (Serbian (Cyrillic)) * New translations en-US.json (Russian) * New translations en-US.json (Portuguese) * New translations en-US.json (Polish) * New translations en-US.json (Norwegian) * New translations en-US.json (Dutch) * New translations en-US.json (Korean) * New translations en-US.json (Italian) * New translations en-US.json (French) * New translations en-US.json (Hungarian) * New translations en-US.json (Hebrew) * New translations en-US.json (Finnish) * New translations en-US.json (Greek) * New translations en-US.json (Danish) * New translations en-US.json (Czech) * New translations en-US.json (Catalan) * New translations en-US.json (Arabic) * New translations en-US.json (Afrikaans) * New translations en-US.json (Spanish) * New translations en-US.json (Romanian) * New translations en-US.json (Chinese Simplified) * New translations en-US.json (Portuguese, Brazilian) --- frontend/src/locales/messages/af-ZA.json | 1 + frontend/src/locales/messages/ar-SA.json | 1 + frontend/src/locales/messages/ca-ES.json | 1 + frontend/src/locales/messages/cs-CZ.json | 1 + frontend/src/locales/messages/da-DK.json | 1 + frontend/src/locales/messages/de-DE.json | 1 + frontend/src/locales/messages/el-GR.json | 1 + frontend/src/locales/messages/es-ES.json | 1 + frontend/src/locales/messages/fi-FI.json | 1 + frontend/src/locales/messages/fr-FR.json | 1 + frontend/src/locales/messages/he-IL.json | 1 + frontend/src/locales/messages/hu-HU.json | 1 + frontend/src/locales/messages/it-IT.json | 1 + frontend/src/locales/messages/ja-JP.json | 1 + frontend/src/locales/messages/ko-KR.json | 1 + frontend/src/locales/messages/nl-NL.json | 1 + frontend/src/locales/messages/no-NO.json | 1 + frontend/src/locales/messages/pl-PL.json | 1 + frontend/src/locales/messages/pt-BR.json | 1 + frontend/src/locales/messages/pt-PT.json | 1 + frontend/src/locales/messages/ro-RO.json | 1 + frontend/src/locales/messages/ru-RU.json | 1 + frontend/src/locales/messages/sr-SP.json | 1 + frontend/src/locales/messages/sv-SE.json | 1 + frontend/src/locales/messages/tr-TR.json | 1 + frontend/src/locales/messages/uk-UA.json | 1 + frontend/src/locales/messages/vi-VN.json | 1 + frontend/src/locales/messages/zh-CN.json | 1 + frontend/src/locales/messages/zh-TW.json | 1 + 29 files changed, 29 insertions(+) diff --git a/frontend/src/locales/messages/af-ZA.json b/frontend/src/locales/messages/af-ZA.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/af-ZA.json +++ b/frontend/src/locales/messages/af-ZA.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/ar-SA.json b/frontend/src/locales/messages/ar-SA.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/ar-SA.json +++ b/frontend/src/locales/messages/ar-SA.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/ca-ES.json b/frontend/src/locales/messages/ca-ES.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/ca-ES.json +++ b/frontend/src/locales/messages/ca-ES.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/cs-CZ.json b/frontend/src/locales/messages/cs-CZ.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/cs-CZ.json +++ b/frontend/src/locales/messages/cs-CZ.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/da-DK.json b/frontend/src/locales/messages/da-DK.json index 1d10a2fe5170..b63b879986f9 100644 --- a/frontend/src/locales/messages/da-DK.json +++ b/frontend/src/locales/messages/da-DK.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Importere", "keyword": "Keyword", + "link": "Link", "monday": "Mandag", "name": "Navn", "no": "Nej", diff --git a/frontend/src/locales/messages/de-DE.json b/frontend/src/locales/messages/de-DE.json index 208d95aca133..acd1f0370485 100644 --- a/frontend/src/locales/messages/de-DE.json +++ b/frontend/src/locales/messages/de-DE.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Importieren", "keyword": "Keyword", + "link": "Link", "monday": "Montag", "name": "Name", "no": "Nein", diff --git a/frontend/src/locales/messages/el-GR.json b/frontend/src/locales/messages/el-GR.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/el-GR.json +++ b/frontend/src/locales/messages/el-GR.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/es-ES.json b/frontend/src/locales/messages/es-ES.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/es-ES.json +++ b/frontend/src/locales/messages/es-ES.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/fi-FI.json b/frontend/src/locales/messages/fi-FI.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/fi-FI.json +++ b/frontend/src/locales/messages/fi-FI.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/fr-FR.json b/frontend/src/locales/messages/fr-FR.json index e0a988b5ce24..c90cfab6b702 100644 --- a/frontend/src/locales/messages/fr-FR.json +++ b/frontend/src/locales/messages/fr-FR.json @@ -55,6 +55,7 @@ "image-upload-failed": "Le téléchargement de l'image a échoué", "import": "Importer", "keyword": "Mot-clé", + "link": "Link", "monday": "Lundi", "name": "Nom", "no": "Non", diff --git a/frontend/src/locales/messages/he-IL.json b/frontend/src/locales/messages/he-IL.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/he-IL.json +++ b/frontend/src/locales/messages/he-IL.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/hu-HU.json b/frontend/src/locales/messages/hu-HU.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/hu-HU.json +++ b/frontend/src/locales/messages/hu-HU.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/it-IT.json b/frontend/src/locales/messages/it-IT.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/it-IT.json +++ b/frontend/src/locales/messages/it-IT.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/ja-JP.json b/frontend/src/locales/messages/ja-JP.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/ja-JP.json +++ b/frontend/src/locales/messages/ja-JP.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/ko-KR.json b/frontend/src/locales/messages/ko-KR.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/ko-KR.json +++ b/frontend/src/locales/messages/ko-KR.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/nl-NL.json b/frontend/src/locales/messages/nl-NL.json index c2a2735303da..18e258ace095 100644 --- a/frontend/src/locales/messages/nl-NL.json +++ b/frontend/src/locales/messages/nl-NL.json @@ -55,6 +55,7 @@ "image-upload-failed": "Afbeelding uploaden mislukt", "import": "Importeren", "keyword": "Trefwoord", + "link": "Link", "monday": "Maandag", "name": "Naam", "no": "Nee", diff --git a/frontend/src/locales/messages/no-NO.json b/frontend/src/locales/messages/no-NO.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/no-NO.json +++ b/frontend/src/locales/messages/no-NO.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/pl-PL.json b/frontend/src/locales/messages/pl-PL.json index cfaeb48ddc12..23fc17fb9186 100644 --- a/frontend/src/locales/messages/pl-PL.json +++ b/frontend/src/locales/messages/pl-PL.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Importuj", "keyword": "Keyword", + "link": "Link", "monday": "Poniedziałek", "name": "Nazwa", "no": "Nie", diff --git a/frontend/src/locales/messages/pt-BR.json b/frontend/src/locales/messages/pt-BR.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/pt-BR.json +++ b/frontend/src/locales/messages/pt-BR.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/pt-PT.json b/frontend/src/locales/messages/pt-PT.json index b54a8d11561b..37a42cd90cc2 100644 --- a/frontend/src/locales/messages/pt-PT.json +++ b/frontend/src/locales/messages/pt-PT.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Importar", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Nome", "no": "No", diff --git a/frontend/src/locales/messages/ro-RO.json b/frontend/src/locales/messages/ro-RO.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/ro-RO.json +++ b/frontend/src/locales/messages/ro-RO.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/ru-RU.json b/frontend/src/locales/messages/ru-RU.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/ru-RU.json +++ b/frontend/src/locales/messages/ru-RU.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/sr-SP.json b/frontend/src/locales/messages/sr-SP.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/sr-SP.json +++ b/frontend/src/locales/messages/sr-SP.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/sv-SE.json b/frontend/src/locales/messages/sv-SE.json index 94515b94db24..54534e5aaf8d 100644 --- a/frontend/src/locales/messages/sv-SE.json +++ b/frontend/src/locales/messages/sv-SE.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Importera", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Namn", "no": "No", diff --git a/frontend/src/locales/messages/tr-TR.json b/frontend/src/locales/messages/tr-TR.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/tr-TR.json +++ b/frontend/src/locales/messages/tr-TR.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/uk-UA.json b/frontend/src/locales/messages/uk-UA.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/uk-UA.json +++ b/frontend/src/locales/messages/uk-UA.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/vi-VN.json b/frontend/src/locales/messages/vi-VN.json index 446fe4b86dc7..0ca827e7fb88 100644 --- a/frontend/src/locales/messages/vi-VN.json +++ b/frontend/src/locales/messages/vi-VN.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "Import", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "Name", "no": "No", diff --git a/frontend/src/locales/messages/zh-CN.json b/frontend/src/locales/messages/zh-CN.json index a7f93f761793..827cc157ae20 100644 --- a/frontend/src/locales/messages/zh-CN.json +++ b/frontend/src/locales/messages/zh-CN.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "导入", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "名称", "no": "否", diff --git a/frontend/src/locales/messages/zh-TW.json b/frontend/src/locales/messages/zh-TW.json index 37b0ced6ef20..facd4c1e8a8e 100644 --- a/frontend/src/locales/messages/zh-TW.json +++ b/frontend/src/locales/messages/zh-TW.json @@ -55,6 +55,7 @@ "image-upload-failed": "Image upload failed", "import": "導入", "keyword": "Keyword", + "link": "Link", "monday": "Monday", "name": "名稱", "no": "No", From f2d2b79a5709657e0530e595530a8739ce910604 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 May 2021 18:23:41 -0800 Subject: [PATCH 2/4] New Crowdin updates (#385) * New translations en-US.json (Spanish) * New translations en-US.json (Spanish) * New translations en-US.json (French) --- frontend/src/locales/messages/es-ES.json | 144 +++++++++++------------ frontend/src/locales/messages/fr-FR.json | 2 +- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/frontend/src/locales/messages/es-ES.json b/frontend/src/locales/messages/es-ES.json index 0ca827e7fb88..1fd0f815c081 100644 --- a/frontend/src/locales/messages/es-ES.json +++ b/frontend/src/locales/messages/es-ES.json @@ -1,61 +1,61 @@ { "404": { - "page-not-found": "404 Page Not Found", - "take-me-home": "Take me Home" + "page-not-found": "404 Página No Encontrada", + "take-me-home": "Ir a Inicio" }, "about": { - "about-mealie": "About Mealie", - "api-docs": "API Docs", - "api-port": "API Port", - "application-mode": "Application Mode", - "database-type": "Database Type", - "default-group": "Default Group", - "demo": "Demo", - "demo-status": "Demo Status", - "development": "Development", - "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", - "not-demo": "Not Demo", - "production": "Production", - "sqlite-file": "SQLite File", - "version": "Version" + "about-mealie": "Acerca de Mealie", + "api-docs": "Documentación API", + "api-port": "Puerto API", + "application-mode": "Modo de Aplicación", + "database-type": "Tipo de base de datos", + "default-group": "Grupo Predeterminado", + "demo": "Versión Demo", + "demo-status": "Estado Demo", + "development": "Desarrollo", + "download-log": "Descargar Log", + "download-recipe-json": "Descargar Receta JSON", + "not-demo": "No Demo", + "production": "Producción", + "sqlite-file": "Archivo SQLite", + "version": "Versión" }, "category": { - "category-created": "Category created", - "category-creation-failed": "Category creation failed", - "category-deleted": "Category Deleted", - "category-deletion-failed": "Category deletion failed", - "category-filter": "Category Filter", - "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-created": "Categoría creada", + "category-creation-failed": "Error al crear categoría", + "category-deleted": "Categoría Eliminada", + "category-deletion-failed": "Error al eliminar categoría", + "category-filter": "Filtros de Categorías", + "category-update-failed": "Error al actualizar categoría", + "category-updated": "Categoría actualizada" }, "general": { - "apply": "Apply", - "cancel": "Cancel", - "close": "Close", - "confirm": "Confirm", - "create": "Create", - "current-parenthesis": "(Current)", - "delete": "Delete", - "disabled": "Disabled", - "download": "Download", - "edit": "Edit", - "enabled": "Enabled", - "exception": "Exception", - "failed-count": "Failed: {count}", - "failure-uploading-file": "Failure uploading file", - "field-required": "Field Required", - "file-folder-not-found": "File/folder not found", - "file-uploaded": "File uploaded", - "filter": "Filter", - "friday": "Friday", - "get": "Get", - "groups": "Groups", - "image": "Image", - "image-upload-failed": "Image upload failed", - "import": "Import", - "keyword": "Keyword", - "link": "Link", + "apply": "Aplicar", + "cancel": "Cancelar", + "close": "Cerrar", + "confirm": "Confirmar", + "create": "Crear", + "current-parenthesis": "(Actual)", + "delete": "Eliminar", + "disabled": "Deshabilitado", + "download": "Descargar", + "edit": "Editar", + "enabled": "Habilitado", + "exception": "Excepción", + "failed-count": "Error: {count}", + "failure-uploading-file": "Error al cargar el archivo", + "field-required": "Campo Requerido", + "file-folder-not-found": "No se ha encontrado el archivo o la carpeta", + "file-uploaded": "Archivo cargado", + "filter": "Filtro", + "friday": "Viernes", + "get": "Obtener", + "groups": "Grupos", + "image": "Imagen", + "image-upload-failed": "Error al subir la imagen", + "import": "Importar", + "keyword": "Etiqueta", + "link": "Enlace", "monday": "Monday", "name": "Name", "no": "No", @@ -85,23 +85,23 @@ "url": "URL", "users": "Users", "wednesday": "Wednesday", - "yes": "Yes" + "yes": "Si" }, "group": { - "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete {groupName}?", - "cannot-delete-default-group": "Cannot delete default group", - "cannot-delete-group-with-users": "Cannot delete group with users", - "confirm-group-deletion": "Confirm Group Deletion", - "create-group": "Create Group", - "error-updating-group": "Error updating group", - "group": "Group", - "group-deleted": "Group deleted", - "group-deletion-failed": "Group deletion failed", - "group-id-with-value": "Group ID: {groupID}", - "group-name": "Group Name", - "group-not-found": "Group not found", - "groups": "Groups", - "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators", + "are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar {groupName}", + "cannot-delete-default-group": "No se puede eliminar el grupo predeterminado", + "cannot-delete-group-with-users": "No se puede eliminar el grupo con usuarios", + "confirm-group-deletion": "Confirmar Eliminación de Grupo", + "create-group": "Crear Grupo", + "error-updating-group": "Error al actualizar grupo", + "group": "Grupo", + "group-deleted": "Grupo eliminado", + "group-deletion-failed": "Error al eliminar grupo", + "group-id-with-value": "ID del Grupo: {groupID}", + "group-name": "Nombre del Grupo", + "group-not-found": "Grupo no encontrado", + "groups": "Grupos", + "groups-can-only-be-set-by-administrators": "Los grupos sólo pueden ser establecidos por administradores", "user-group": "User Group", "user-group-created": "User Group Created", "user-group-creation-failed": "User Group Creation Failed" @@ -110,13 +110,13 @@ "create-a-new-meal-plan": "Create a New Meal Plan", "dinner-this-week": "Dinner This Week", "dinner-today": "Dinner Today", - "edit-meal-plan": "Edit Meal Plan", - "end-date": "End Date", - "group": "Group (Beta)", - "meal-planner": "Meal Planner", - "meal-plans": "Meal Plans", - "mealplan-created": "Mealplan created", - "mealplan-creation-failed": "Mealplan creation failed", + "edit-meal-plan": "Editar Plan de Comida", + "end-date": "Fecha de Finalización", + "group": "Grupo (Beta)", + "meal-planner": "Plan de Comidas", + "meal-plans": "Planes de Comidas", + "mealplan-created": "Plan de Comida creado", + "mealplan-creation-failed": "Error al crear Plan de Comida", "mealplan-deleted": "Mealplan Deleted", "mealplan-deletion-failed": "Mealplan deletion failed", "mealplan-update-failed": "Mealplan update failed", diff --git a/frontend/src/locales/messages/fr-FR.json b/frontend/src/locales/messages/fr-FR.json index c90cfab6b702..17f27227a09a 100644 --- a/frontend/src/locales/messages/fr-FR.json +++ b/frontend/src/locales/messages/fr-FR.json @@ -55,7 +55,7 @@ "image-upload-failed": "Le téléchargement de l'image a échoué", "import": "Importer", "keyword": "Mot-clé", - "link": "Link", + "link": "Lien", "monday": "Lundi", "name": "Nom", "no": "Non", From 5580d177c3f5fdc8985994d8caacc9bf5d86e8ad Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 May 2021 19:32:37 -0800 Subject: [PATCH 3/4] feature/finish-recipe-assets (#384) * add features to readme * Copy markdown reference * prop as whole recipe * parameter as url instead of query * add card styling to editor * move images to /recipes/{slug}/images * add image to breaking changes * fix delete and import errors * fix debug/about response * logger updates * dashboard ui * add server side events * unorganized routes * default slot * add backup viewer to dashboard * format * add dialog to backup imports * initial event support * delete assets when removed Co-authored-by: hay-kot --- Caddyfile | 4 +- README.md | 8 +- docs/docs/changelog/v0.5.0.md | 3 + frontend/src/api/about.js | 59 +++++++ frontend/src/api/index.js | 2 + frontend/src/api/meta.js | 18 ++- frontend/src/api/recipe.js | 10 +- .../src/components/Recipe/Parts/Assets.vue | 25 ++- .../components/Recipe/RecipeEditor/index.vue | 45 ++++-- .../components/Recipe/RecipeViewer/index.vue | 52 +++---- .../components/UI/Buttons/TheDownloadBtn.vue | 10 +- .../components/UI/Buttons/TheUploadBtn.vue | 12 +- frontend/src/components/UI/LogCard.vue | 81 ++++++++++ frontend/src/components/UI/TheSidebar.vue | 5 + .../src/locales/dateTimeFormats/en-US.json | 9 ++ .../src/locales/dateTimeFormats/es-ES.json | 9 ++ frontend/src/locales/messages/en-US.json | 8 +- frontend/src/pages/Admin/About/index.vue | 19 ++- .../Admin/Backup/AvailableBackupCard.vue | 2 +- .../src/pages/Admin/Backup/ImportDialog.vue | 2 +- .../pages/Admin/Dashboard/BackupViewer.vue | 144 ++++++++++++++++++ .../src/pages/Admin/Dashboard/EventViewer.vue | 110 +++++++++++++ .../src/pages/Admin/Dashboard/StatCard.vue | 100 ++++++++++++ frontend/src/pages/Admin/Dashboard/index.vue | 119 +++++++++++++++ .../Admin/ManageUsers/TheSignUpTable.vue | 4 +- .../src/pages/Admin/ManageUsers/index.vue | 26 ++-- .../pages/Admin/ToolBox/RecipeOrganizer.vue | 72 +++++++++ frontend/src/pages/Admin/ToolBox/index.vue | 29 +++- frontend/src/pages/HomePage.vue | 1 - frontend/src/pages/Recipe/ViewRecipe.vue | 17 +-- frontend/src/routes/admin.js | 8 + mealie/app.py | 4 + mealie/core/root_logger.py | 8 +- mealie/db/database.py | 33 +++- mealie/db/db_base.py | 19 +++ mealie/db/init_db.py | 2 + mealie/db/models/_all_models.py | 5 +- mealie/db/models/event.py | 17 +++ mealie/db/models/model_base.py | 4 +- mealie/routes/about/__init__.py | 7 + mealie/routes/about/events.py | 28 ++++ mealie/routes/backup_routes.py | 12 +- mealie/routes/debug_routes.py | 17 ++- mealie/routes/mealplans/crud.py | 2 +- mealie/routes/recipe/__init__.py | 4 +- mealie/routes/recipe/all_recipe_routes.py | 12 +- mealie/routes/recipe/recipe_crud_routes.py | 22 +-- .../{recipe_assets.py => recipe_media.py} | 25 +-- mealie/routes/users/crud.py | 5 +- mealie/routes/users/sign_up.py | 10 +- mealie/schema/{debug.py => about.py} | 10 ++ mealie/schema/events.py | 31 ++++ mealie/schema/recipe.py | 30 +++- mealie/services/backups/exports.py | 23 +-- mealie/services/backups/imports.py | 34 +++-- mealie/services/events.py | 40 +++++ mealie/services/image/image.py | 50 +----- mealie/services/image/minify.py | 38 +---- mealie/services/recipe/__init__.py | 0 mealie/services/recipe/media.py | 34 +++++ mealie/utils/post_webhooks.py | 3 + 61 files changed, 1276 insertions(+), 266 deletions(-) create mode 100644 frontend/src/api/about.js create mode 100644 frontend/src/components/UI/LogCard.vue create mode 100644 frontend/src/pages/Admin/Dashboard/BackupViewer.vue create mode 100644 frontend/src/pages/Admin/Dashboard/EventViewer.vue create mode 100644 frontend/src/pages/Admin/Dashboard/StatCard.vue create mode 100644 frontend/src/pages/Admin/Dashboard/index.vue create mode 100644 frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue create mode 100644 mealie/db/models/event.py create mode 100644 mealie/routes/about/__init__.py create mode 100644 mealie/routes/about/events.py rename mealie/routes/recipe/{recipe_assets.py => recipe_media.py} (73%) rename mealie/schema/{debug.py => about.py} (59%) create mode 100644 mealie/schema/events.py create mode 100644 mealie/services/events.py create mode 100644 mealie/services/recipe/__init__.py create mode 100644 mealie/services/recipe/media.py diff --git a/Caddyfile b/Caddyfile index ba5d2e454488..0faf9fe7d519 100644 --- a/Caddyfile +++ b/Caddyfile @@ -10,8 +10,8 @@ encode gzip uri strip_suffix / - handle_path /api/recipes/image/* { - root * /app/data/img/ + handle_path /api/recipes/media/* { + root * /app/data/recipes/ file_server } diff --git a/README.md b/README.md index 72a9a3176ff6..b8b0833a0847 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,16 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a ## Key Features - 🔍 Fuzzy search -- 🏷️ Tag recipes with categories or tags to flexible sorting +- 🏷️ Tag recipes with categories or tags for flexible sorting - 🕸 Import recipes from around the web by URL +- 💪 Powerful bulk Category/Tag assignment - 📱 Beautiful Mobile Views - 📆 Create Meal Plans - 🛒 Generate shopping lists - 🐳 Easy setup with Docker -- 🎨 Customize your interface with color themes layouts -- 💾 Export all your data in any format with Jinja2 Templates, with easy data restoration from the user interface. +- 🎨 Customize your interface with color themes +- 💾 Export all your data in any format with Jinja2 Templates +- 🔒 Keep your data safe with automated backup and easy restore options - 🌍 localized in many languages - ➕ Plus tons more! - Flexible API diff --git a/docs/docs/changelog/v0.5.0.md b/docs/docs/changelog/v0.5.0.md index 369df11247c7..e8e665d3537a 100644 --- a/docs/docs/changelog/v0.5.0.md +++ b/docs/docs/changelog/v0.5.0.md @@ -11,6 +11,9 @@ #### Database Database version has been bumped from v0.4.x -> v0.5.0. You will need to export and import your data. + #### Image Directory + the /data/img directory has been depreciated. All images are now stored in the /recipes/{slug}/image directory. Images should be migrated automatically, but you may experience issues related to this change. + ## Bug Fixes - Fixed #332 - Language settings are saved for one browser - Fixes #281 - Slow Handling of Large Sets of Recipes diff --git a/frontend/src/api/about.js b/frontend/src/api/about.js new file mode 100644 index 000000000000..f04322939937 --- /dev/null +++ b/frontend/src/api/about.js @@ -0,0 +1,59 @@ +import { baseURL } from "./api-utils"; +import { apiReq } from "./api-utils"; + +const prefix = baseURL + "about"; + +const aboutURLs = { + version: `${prefix}/version`, + debug: `${prefix}`, + lastRecipe: `${prefix}/last-recipe-json`, + demo: `${prefix}/is-demo`, + log: num => `${prefix}/log/${num}`, + statistics: `${prefix}/statistics`, + events: `${prefix}/events`, + event: id => `${prefix}/events/${id}`, +}; + +export const aboutAPI = { + async getEvents() { + const resposne = await apiReq.get(aboutURLs.events); + return resposne.data; + }, + async deleteEvent(id) { + const resposne = await apiReq.delete(aboutURLs.event(id)); + return resposne.data; + }, + async deleteAllEvents() { + const resposne = await apiReq.delete(aboutURLs.events); + return resposne.data; + }, + // async getAppInfo() { + // const response = await apiReq.get(aboutURLs.version); + // return response.data; + // }, + + // async getDebugInfo() { + // const response = await apiReq.get(aboutURLs.debug); + // return response.data; + // }, + + // async getLogText(num) { + // const response = await apiReq.get(aboutURLs.log(num)); + // return response.data; + // }, + + // async getLastJson() { + // const response = await apiReq.get(aboutURLs.lastRecipe); + // return response.data; + // }, + + // async getIsDemo() { + // const response = await apiReq.get(aboutURLs.demo); + // return response.data; + // }, + + // async getStatistics() { + // const response = await apiReq.get(aboutURLs.statistics); + // return response.data; + // }, +}; diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index aa1c612fc56d..53d22e8acc82 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -11,6 +11,7 @@ import { userAPI } from "./users"; import { signupAPI } from "./signUps"; import { groupAPI } from "./groups"; import { siteSettingsAPI } from "./siteSettings"; +import { aboutAPI } from "./about"; /** * The main object namespace for interacting with the backend database @@ -30,4 +31,5 @@ export const api = { users: userAPI, signUps: signupAPI, groups: groupAPI, + about: aboutAPI, }; diff --git a/frontend/src/api/meta.js b/frontend/src/api/meta.js index 59183c0c5ea1..16f7477b77ea 100644 --- a/frontend/src/api/meta.js +++ b/frontend/src/api/meta.js @@ -8,11 +8,13 @@ const debugURLs = { debug: `${prefix}`, lastRecipe: `${prefix}/last-recipe-json`, demo: `${prefix}/is-demo`, + log: num => `${prefix}/log/${num}`, + statistics: `${prefix}/statistics`, }; export const metaAPI = { async getAppInfo() { - let response = await apiReq.get(debugURLs.version); + const response = await apiReq.get(debugURLs.version); return response.data; }, @@ -21,13 +23,23 @@ export const metaAPI = { return response.data; }, + async getLogText(num) { + const response = await apiReq.get(debugURLs.log(num)); + return response.data; + }, + async getLastJson() { - let response = await apiReq.get(debugURLs.lastRecipe); + const response = await apiReq.get(debugURLs.lastRecipe); return response.data; }, async getIsDemo() { - let response = await apiReq.get(debugURLs.demo); + const response = await apiReq.get(debugURLs.demo); + return response.data; + }, + + async getStatistics() { + const response = await apiReq.get(debugURLs.statistics); return response.data; }, }; diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index fe755f6f771c..d35c4d1bc9b8 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -14,9 +14,9 @@ const recipeURLs = { recipe: slug => prefix + slug, update: slug => prefix + slug, delete: slug => prefix + slug, + createAsset: slug => `${prefix}media/${slug}/assets`, recipeImage: slug => `${prefix}${slug}/image`, updateImage: slug => `${prefix}${slug}/image`, - createAsset: slug => `${prefix}${slug}/asset`, }; export const recipeAPI = { @@ -84,7 +84,7 @@ export const recipeAPI = { fd.append("extension", fileObject.name.split(".").pop()); fd.append("name", name); fd.append("icon", icon); - let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd); + const response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd); return response; }, @@ -135,14 +135,14 @@ export const recipeAPI = { }, recipeImage(recipeSlug) { - return `/api/recipes/image/${recipeSlug}/original.webp`; + return `/api/recipes/media/${recipeSlug}/image/original.webp`; }, recipeSmallImage(recipeSlug) { - return `/api/recipes/image/${recipeSlug}/min-original.webp`; + return `/api/recipes/media/${recipeSlug}/image/min-original.webp`; }, recipeTinyImage(recipeSlug) { - return `/api/recipes/image/${recipeSlug}/tiny-original.webp`; + return `/api/recipes/media/${recipeSlug}/image/tiny-original.webp`; }, }; diff --git a/frontend/src/components/Recipe/Parts/Assets.vue b/frontend/src/components/Recipe/Parts/Assets.vue index bb84178050e8..62119ba21ad0 100644 --- a/frontend/src/components/Recipe/Parts/Assets.vue +++ b/frontend/src/components/Recipe/Parts/Assets.vue @@ -18,15 +18,20 @@ v-if="!edit" color="primary" icon - :href="`/api/recipes/${slug}/asset?file_name=${item.fileName}`" + :href="`/api/recipes/media/${slug}/assets/${item.fileName}`" target="_blank" top > mdi-download - - mdi-delete - +
+ + mdi-delete + + + mdi-content-copy + +
@@ -107,6 +112,11 @@ export default { ], }; }, + computed: { + baseURL() { + return window.location.origin; + }, + }, methods: { setFileObject(obj) { this.fileObject = obj; @@ -124,6 +134,13 @@ export default { deleteAsset(index) { this.value.splice(index, 1); }, + copyLink(name, fileName) { + const copyText = `![${name}](${this.baseURL}/api/recipes/media/${this.slug}/assets/${fileName})`; + navigator.clipboard.writeText(copyText).then( + () => console.log("Copied", copyText), + () => console.log("Copied Failed", copyText) + ); + }, }, }; diff --git a/frontend/src/components/Recipe/RecipeEditor/index.vue b/frontend/src/components/Recipe/RecipeEditor/index.vue index ef735d13de31..bdc153ff8772 100644 --- a/frontend/src/components/Recipe/RecipeEditor/index.vue +++ b/frontend/src/components/Recipe/RecipeEditor/index.vue @@ -27,23 +27,36 @@ + + + {{ $t("recipe.categories") }} + + + + + + -

{{ $t("recipe.categories") }}

- - -

{{ $t("tag.tags") }}

- + + + {{ $t("tag.tags") }} + + + + + + diff --git a/frontend/src/components/Recipe/RecipeViewer/index.vue b/frontend/src/components/Recipe/RecipeViewer/index.vue index aef80568ce38..d4a4fac9a68a 100644 --- a/frontend/src/components/Recipe/RecipeViewer/index.vue +++ b/frontend/src/components/Recipe/RecipeViewer/index.vue @@ -1,14 +1,14 @@ + + \ No newline at end of file diff --git a/frontend/src/components/UI/TheSidebar.vue b/frontend/src/components/UI/TheSidebar.vue index 834c60d977bd..69cfad55250e 100644 --- a/frontend/src/components/UI/TheSidebar.vue +++ b/frontend/src/components/UI/TheSidebar.vue @@ -147,6 +147,11 @@ export default { }, adminLinks() { return [ + { + icon: "mdi-view-dashboard", + to: "/admin/dashboard", + title: this.$t("general.dashboard"), + }, { icon: "mdi-cog", to: "/admin/settings", diff --git a/frontend/src/locales/dateTimeFormats/en-US.json b/frontend/src/locales/dateTimeFormats/en-US.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/en-US.json +++ b/frontend/src/locales/dateTimeFormats/en-US.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/es-ES.json b/frontend/src/locales/dateTimeFormats/es-ES.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/es-ES.json +++ b/frontend/src/locales/dateTimeFormats/es-ES.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/messages/en-US.json b/frontend/src/locales/messages/en-US.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/en-US.json +++ b/frontend/src/locales/messages/en-US.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/pages/Admin/About/index.vue b/frontend/src/pages/Admin/About/index.vue index efbb5b843ccd..b410b3bfec8e 100644 --- a/frontend/src/pages/Admin/About/index.vue +++ b/frontend/src/pages/Admin/About/index.vue @@ -22,19 +22,26 @@ - - + + + + - - diff --git a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue index 591b31b826c8..b4e333c9c7d7 100644 --- a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue +++ b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue @@ -19,7 +19,7 @@
{{ backup.name }}
-
{{ $d(new Date(backup.date), "medium") }}
+
{{ $d(Date.parse(backup.date), "medium") }}
diff --git a/frontend/src/pages/Admin/Backup/ImportDialog.vue b/frontend/src/pages/Admin/Backup/ImportDialog.vue index 9d4aceef395a..0aed4c0e2f46 100644 --- a/frontend/src/pages/Admin/Backup/ImportDialog.vue +++ b/frontend/src/pages/Admin/Backup/ImportDialog.vue @@ -15,7 +15,7 @@ {{ name }} - {{ $d(new Date(date), "medium") }} + {{ $d(new Date(date), "medium") }} diff --git a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue new file mode 100644 index 000000000000..5ef1259685c6 --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue @@ -0,0 +1,144 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/Dashboard/EventViewer.vue b/frontend/src/pages/Admin/Dashboard/EventViewer.vue new file mode 100644 index 000000000000..5070c677b6eb --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/EventViewer.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/Dashboard/StatCard.vue b/frontend/src/pages/Admin/Dashboard/StatCard.vue new file mode 100644 index 000000000000..f79cc8289d3e --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/StatCard.vue @@ -0,0 +1,100 @@ +w + + + + diff --git a/frontend/src/pages/Admin/Dashboard/index.vue b/frontend/src/pages/Admin/Dashboard/index.vue new file mode 100644 index 000000000000..771cc4da0635 --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/index.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue index 6deac3ce1b60..21138a520b1e 100644 --- a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue +++ b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue @@ -160,10 +160,10 @@ export default { methods: { updateClipboard(newClip) { navigator.clipboard.writeText(newClip).then( - function() { + () => { console.log("Copied", newClip); }, - function() { + () => { console.log("Copy Failed", newClip); } ); diff --git a/frontend/src/pages/Admin/ManageUsers/index.vue b/frontend/src/pages/Admin/ManageUsers/index.vue index fbbb1aef5f37..74aa8bd27726 100644 --- a/frontend/src/pages/Admin/ManageUsers/index.vue +++ b/frontend/src/pages/Admin/ManageUsers/index.vue @@ -4,30 +4,30 @@ - + {{ $t("user.users") }} mdi-account - + {{ $t("signup.sign-up-links") }} mdi-account-plus-outline - + {{ $t("group.groups") }} mdi-account-group - + - + - + @@ -42,9 +42,17 @@ import TheSignUpTable from "./TheSignUpTable"; export default { components: { TheUserTable, GroupDashboard, TheSignUpTable }, data() { - return { - tab: 0, - }; + return {}; + }, + computed: { + tab: { + set(tab) { + this.$router.replace({ query: { ...this.$route.query, tab } }); + }, + get() { + return this.$route.query.tab; + }, + }, }, mounted() { this.$store.dispatch("requestAllGroups"); diff --git a/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue b/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue new file mode 100644 index 000000000000..b83cdfcde624 --- /dev/null +++ b/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue @@ -0,0 +1,72 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/ToolBox/index.vue b/frontend/src/pages/Admin/ToolBox/index.vue index 50333cd6ddca..d5d029bf2e49 100644 --- a/frontend/src/pages/Admin/ToolBox/index.vue +++ b/frontend/src/pages/Admin/ToolBox/index.vue @@ -4,20 +4,25 @@ - + {{ $t("recipe.categories") }} mdi-tag-multiple-outline - + {{ $t("tag.tags") }} mdi-tag-multiple-outline + + Organize + mdi-broom + - - + + + @@ -25,14 +30,24 @@ diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue index 3b2bc453602d..abc1f7d52d71 100644 --- a/frontend/src/pages/HomePage.vue +++ b/frontend/src/pages/HomePage.vue @@ -37,7 +37,6 @@ export default { return this.$store.getters.getSiteSettings; }, recentRecipes() { - console.log("Recent Recipes"); return this.$store.getters.getRecentRecipes; }, }, diff --git a/frontend/src/pages/Recipe/ViewRecipe.vue b/frontend/src/pages/Recipe/ViewRecipe.vue index ea97cb941880..8ee5c02930e4 100644 --- a/frontend/src/pages/Recipe/ViewRecipe.vue +++ b/frontend/src/pages/Recipe/ViewRecipe.vue @@ -25,22 +25,7 @@ class="sticky" /> - + logging.Logger: return logger +root_logger = logger_init() + + def get_logger(module=None) -> logging.Logger: """ Returns a child logger for mealie """ global root_logger @@ -38,6 +41,3 @@ def get_logger(module=None) -> logging.Logger: return root_logger return root_logger.getChild(module) - - -root_logger = logger_init() diff --git a/mealie/db/database.py b/mealie/db/database.py index 31aad381ab8b..0881d29bd21d 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -1,6 +1,7 @@ from logging import getLogger from mealie.db.db_base import BaseDocument +from mealie.db.models.event import Event from mealie.db.models.group import Group from mealie.db.models.mealplan import MealPlanModel from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag @@ -9,6 +10,7 @@ from mealie.db.models.sign_up import SignUp from mealie.db.models.theme import SiteThemeModel from mealie.db.models.users import User from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse +from mealie.schema.events import Event as EventSchema from mealie.schema.meal import MealPlanInDB from mealie.schema.recipe import Recipe from mealie.schema.settings import CustomPageOut @@ -18,7 +20,6 @@ from mealie.schema.theme import SiteTheme from mealie.schema.user import GroupInDB, UserInDB from sqlalchemy.orm.session import Session - logger = getLogger() @@ -35,6 +36,26 @@ class _Recipes(BaseDocument): return f"{slug}.{extension}" + def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int: + eff_schema = override_schema or self.schema + if count: + return session.query(self.sql_model).filter(RecipeModel.recipe_category == None).count() # noqa: 711 + else: + return [ + eff_schema.from_orm(x) + for x in session.query(self.sql_model).filter(RecipeModel.tags == None).all() # noqa: 711 + ] + + def count_untagged(self, session: Session, count=True, override_schema=None) -> int: + eff_schema = override_schema or self.schema + if count: + return session.query(self.sql_model).filter(RecipeModel.tags == None).count() # noqa: 711 + else: + return [ + eff_schema.from_orm(x) + for x in session.query(self.sql_model).filter(RecipeModel.tags == None).all() # noqa: 711 + ] + class _Categories(BaseDocument): def __init__(self) -> None: @@ -110,8 +131,6 @@ class _Groups(BaseDocument): """ group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none() - # Potentially not needed? column is sorted by SqlAlchemy based on startDate - # return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate) return group.mealplans @@ -129,6 +148,13 @@ class _CustomPages(BaseDocument): self.schema = CustomPageOut +class _Events(BaseDocument): + def __init__(self) -> None: + self.primary_key = "id" + self.sql_model = Event + self.schema = EventSchema + + class Database: def __init__(self) -> None: self.recipes = _Recipes() @@ -141,6 +167,7 @@ class Database: self.sign_ups = _SignUps() self.groups = _Groups() self.custom_pages = _CustomPages() + self.events = _Events() db = Database() diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index b2c239ef8585..9d178adf45b3 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -23,6 +23,14 @@ class BaseDocument: ) -> List[dict]: eff_schema = override_schema or self.schema + if order_by: + order_attr = getattr(self.sql_model, str(order_by)) + + return [ + eff_schema.from_orm(x) + for x in session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all() + ] + return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()] def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]: @@ -154,3 +162,14 @@ class BaseDocument: session.commit() return results_as_model + + def delete_all(self, session: Session) -> None: + session.query(self.sql_model).delete() + session.commit() + + def count_all(self, session: Session, match_key=None, match_value=None) -> int: + + if None in [match_key, match_value]: + return session.query(self.sql_model).count() + else: + return session.query(self.sql_model).filter_by(**{match_key: match_value}).count() diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py index f5cfa746f7b1..a9a243a9fb27 100644 --- a/mealie/db/init_db.py +++ b/mealie/db/init_db.py @@ -5,6 +5,7 @@ from mealie.db.database import db from mealie.db.db_setup import create_session from mealie.schema.settings import SiteSettings from mealie.schema.theme import SiteTheme +from mealie.services.events import create_general_event from sqlalchemy.orm import Session logger = root_logger.get_logger("init_db") @@ -58,6 +59,7 @@ def main(): else: print("Database Doesn't Exists, Initializing...") init_db() + create_general_event("Initialize Database", "Initialize database with default values", session) if __name__ == "__main__": diff --git a/mealie/db/models/_all_models.py b/mealie/db/models/_all_models.py index 8034475ca490..0e5daca17d56 100644 --- a/mealie/db/models/_all_models.py +++ b/mealie/db/models/_all_models.py @@ -1,7 +1,8 @@ +from mealie.db.models.event import * +from mealie.db.models.group import * from mealie.db.models.mealplan import * from mealie.db.models.recipe.recipe import * from mealie.db.models.settings import * +from mealie.db.models.sign_up import * from mealie.db.models.theme import * from mealie.db.models.users import * -from mealie.db.models.sign_up import * -from mealie.db.models.group import * diff --git a/mealie/db/models/event.py b/mealie/db/models/event.py new file mode 100644 index 000000000000..1ee63b12065b --- /dev/null +++ b/mealie/db/models/event.py @@ -0,0 +1,17 @@ +import sqlalchemy as sa +from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase + + +class Event(SqlAlchemyBase, BaseMixins): + __tablename__ = "events" + id = sa.Column(sa.Integer, primary_key=True) + title = sa.Column(sa.String) + text = sa.Column(sa.String) + time_stamp = sa.Column(sa.DateTime) + category = sa.Column(sa.String) + + def __init__(self, title, text, time_stamp, category, *args, **kwargs) -> None: + self.title = title + self.text = text + self.time_stamp = time_stamp + self.category = category diff --git a/mealie/db/models/model_base.py b/mealie/db/models/model_base.py index 57662ab5309f..2eb385f8f1fb 100644 --- a/mealie/db/models/model_base.py +++ b/mealie/db/models/model_base.py @@ -4,5 +4,5 @@ SqlAlchemyBase = dec.declarative_base() class BaseMixins: - def _pass_on_me(): - pass + def update(self, *args, **kwarg): + self.__init__(*args, **kwarg) diff --git a/mealie/routes/about/__init__.py b/mealie/routes/about/__init__.py new file mode 100644 index 000000000000..e36affaa90d1 --- /dev/null +++ b/mealie/routes/about/__init__.py @@ -0,0 +1,7 @@ +from fastapi import APIRouter + +from .events import router as events_router + +about_router = APIRouter(prefix="/api/about") + +about_router.include_router(events_router) diff --git a/mealie/routes/about/events.py b/mealie/routes/about/events.py new file mode 100644 index 000000000000..3eed7690f962 --- /dev/null +++ b/mealie/routes/about/events.py @@ -0,0 +1,28 @@ +from fastapi import APIRouter, Depends +from mealie.db.database import db +from mealie.db.db_setup import generate_session +from mealie.routes.deps import get_current_user +from mealie.schema.events import EventsOut +from sqlalchemy.orm.session import Session + +router = APIRouter(prefix="/events", tags=["App Events"]) + + +@router.get("", response_model=EventsOut) +async def get_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)): + """ Get event from the Database """ + # Get Item + return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp")) + + +@router.delete("") +async def delete_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)): + """ Get event from the Database """ + # Get Item + return db.events.delete_all(session) + + +@router.delete("/{id}") +async def delete_event(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)): + """ Delete event from the Database """ + return db.events.delete(session, id) diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py index 864a1380ac6f..d4fef6c357bd 100644 --- a/mealie/routes/backup_routes.py +++ b/mealie/routes/backup_routes.py @@ -1,17 +1,21 @@ import operator import shutil +from pathlib import Path from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from mealie.core.config import app_dirs +from mealie.core.root_logger import get_logger from mealie.core.security import create_file_token from mealie.db.db_setup import generate_session from mealie.routes.deps import get_current_user from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup from mealie.services.backups import imports from mealie.services.backups.exports import backup_all +from mealie.services.events import create_backup_event from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)]) +logger = get_logger() @router.get("/available", response_model=Imports) @@ -43,8 +47,10 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session export_users=data.options.users, export_groups=data.options.groups, ) + create_backup_event("Database Backup", f"Manual Backup Created '{Path(export_path).name}'", session) return {"export_path": export_path} - except Exception: + except Exception as e: + logger.error(e) raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -72,7 +78,7 @@ async def download_backup_file(file_name: str): def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)): """ Import a database backup file generated from Mealie. """ - return imports.import_database( + db_import = imports.import_database( session=session, archive=import_data.name, import_recipes=import_data.recipes, @@ -84,6 +90,8 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D force_import=import_data.force, rebase=import_data.rebase, ) + create_backup_event("Database Restore", f"Restored Database File {file_name}", session) + return db_import @router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK) diff --git a/mealie/routes/debug_routes.py b/mealie/routes/debug_routes.py index 3e5d6c4b82c3..04f1516bbc96 100644 --- a/mealie/routes/debug_routes.py +++ b/mealie/routes/debug_routes.py @@ -2,8 +2,11 @@ from fastapi import APIRouter, Depends from mealie.core.config import APP_VERSION, app_dirs, settings from mealie.core.root_logger import LOGGER_FILE from mealie.core.security import create_file_token +from mealie.db.database import db +from mealie.db.db_setup import generate_session from mealie.routes.deps import get_current_user -from mealie.schema.debug import AppInfo, DebugInfo +from mealie.schema.about import AppInfo, AppStatistics, DebugInfo +from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/debug", tags=["Debug"]) @@ -18,11 +21,23 @@ async def get_debug_info(current_user=Depends(get_current_user)): demo_status=settings.IS_DEMO, api_port=settings.API_PORT, api_docs=settings.API_DOCS, + db_type=settings.DB_ENGINE, db_url=settings.DB_URL, default_group=settings.DEFAULT_GROUP, ) +@router.get("/statistics") +async def get_app_statistics(session: Session = Depends(generate_session)): + return AppStatistics( + total_recipes=db.recipes.count_all(session), + uncategorized_recipes=db.recipes.count_uncategorized(session), + untagged_recipes=db.recipes.count_untagged(session), + total_users=db.users.count_all(session), + total_groups=db.groups.count_all(session), + ) + + @router.get("/version") async def get_mealie_version(): """ Returns the current version of mealie""" diff --git a/mealie/routes/mealplans/crud.py b/mealie/routes/mealplans/crud.py index 7b58853d7533..bb2f17672ec1 100644 --- a/mealie/routes/mealplans/crud.py +++ b/mealie/routes/mealplans/crud.py @@ -88,7 +88,7 @@ def get_todays_image(session: Session = Depends(generate_session), group_name: s recipe = get_todays_meal(session, group_in_db) if recipe: - recipe_image = image.read_image(recipe.slug, image_type=image.IMG_OPTIONS.ORIGINAL_IMAGE) + recipe_image = recipe.image_dir.joinpath(image.ImageOptions.ORIGINAL_IMAGE) else: raise HTTPException(status.HTTP_404_NOT_FOUND) if recipe_image: diff --git a/mealie/routes/recipe/__init__.py b/mealie/routes/recipe/__init__.py index 8af92586aae6..1d54034f0bf7 100644 --- a/mealie/routes/recipe/__init__.py +++ b/mealie/routes/recipe/__init__.py @@ -1,10 +1,10 @@ from fastapi import APIRouter -from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_assets, recipe_crud_routes, tag_routes +from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, recipe_media, tag_routes router = APIRouter() router.include_router(all_recipe_routes.router) router.include_router(recipe_crud_routes.router) -router.include_router(recipe_assets.router) +router.include_router(recipe_media.router) router.include_router(category_routes.router) router.include_router(tag_routes.router) diff --git a/mealie/routes/recipe/all_recipe_routes.py b/mealie/routes/recipe/all_recipe_routes.py index 014815774ada..9a61751c9383 100644 --- a/mealie/routes/recipe/all_recipe_routes.py +++ b/mealie/routes/recipe/all_recipe_routes.py @@ -8,7 +8,7 @@ from sqlalchemy.orm.session import Session router = APIRouter(tags=["Query All Recipes"]) -@router.get("/api/recipes/summary") +@router.get("/api/recipes/summary", response_model=list[RecipeSummary]) async def get_recipe_summary( start=0, limit=9999, @@ -29,6 +29,16 @@ async def get_recipe_summary( return db.recipes.get_all(session, limit=limit, start=start, override_schema=RecipeSummary) +@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary]) +async def get_untagged_recipes(session: Session = Depends(generate_session)): + return db.recipes.count_untagged(session, False, override_schema=RecipeSummary) + + +@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary]) +async def get_uncategorized_recipes(session: Session = Depends(generate_session)): + return db.recipes.count_uncategorized(session, False, override_schema=RecipeSummary) + + @router.post("/api/recipes/category") def filter_by_category(categories: list, session: Session = Depends(generate_session)): """ pass a list of categories and get a list of recipes associated with those categories """ diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 8547aa09fe5d..6604b84e664f 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -4,7 +4,9 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.deps import get_current_user from mealie.schema.recipe import Recipe, RecipeURLIn -from mealie.services.image.image import delete_image, rename_image, scrape_image, write_image +from mealie.services.events import create_recipe_event +from mealie.services.image.image import scrape_image, write_image +from mealie.services.recipe.media import check_assets, delete_assets from mealie.services.scraper.scraper import create_from_url from sqlalchemy.orm.session import Session @@ -21,6 +23,8 @@ def create_from_json( """ Takes in a JSON string and loads data into the database as a new entry""" recipe: Recipe = db.recipes.create(session, data.dict()) + create_recipe_event("Recipe Created", f"Recipe '{recipe.name}' created", session=session) + return recipe.slug @@ -34,6 +38,7 @@ def parse_recipe_url( recipe = create_from_url(url.url) recipe: Recipe = db.recipes.create(session, recipe.dict()) + create_recipe_event("Recipe Created (URL)", f"'{recipe.name}' by {current_user.full_name}", session=session) return recipe.slug @@ -57,8 +62,7 @@ def update_recipe( recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict()) print(recipe.assets) - if recipe_slug != recipe.slug: - rename_image(original_slug=recipe_slug, new_slug=recipe.slug) + check_assets(original_slug=recipe_slug, recipe=recipe) return recipe @@ -75,8 +79,8 @@ def patch_recipe( recipe: Recipe = db.recipes.patch( session, recipe_slug, new_data=data.dict(exclude_unset=True, exclude_defaults=True) ) - if recipe_slug != recipe.slug: - rename_image(original_slug=recipe_slug, new_slug=recipe.slug) + + check_assets(original_slug=recipe_slug, recipe=recipe) return recipe @@ -90,10 +94,10 @@ def delete_recipe( """ Deletes a recipe by slug """ try: - delete_data = db.recipes.delete(session, recipe_slug) - delete_image(recipe_slug) - - return delete_data + recipe: Recipe = db.recipes.delete(session, recipe_slug) + delete_assets(recipe_slug=recipe_slug) + create_recipe_event("Recipe Deleted", f"'{recipe.name}' deleted by {current_user.full_name}", session=session) + return recipe except Exception: raise HTTPException(status.HTTP_400_BAD_REQUEST) diff --git a/mealie/routes/recipe/recipe_assets.py b/mealie/routes/recipe/recipe_media.py similarity index 73% rename from mealie/routes/recipe/recipe_assets.py rename to mealie/routes/recipe/recipe_media.py index ebab8d246c27..b89605c72c82 100644 --- a/mealie/routes/recipe/recipe_assets.py +++ b/mealie/routes/recipe/recipe_media.py @@ -3,7 +3,6 @@ from enum import Enum from fastapi import APIRouter, Depends, File, Form, HTTPException, status from fastapi.datastructures import UploadFile -from mealie.core.config import app_dirs from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.deps import get_current_user @@ -12,7 +11,7 @@ from slugify import slugify from sqlalchemy.orm.session import Session from starlette.responses import FileResponse -router = APIRouter(prefix="/api/recipes", tags=["Recipe Media"]) +router = APIRouter(prefix="/api/recipes/media", tags=["Recipe Media"]) class ImageType(str, Enum): @@ -21,25 +20,30 @@ class ImageType(str, Enum): tiny = "tiny-original.webp" -@router.get("/image/{recipe_slug}/{file_name}") +@router.get("/{recipe_slug}/image/{file_name}") async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original): """Takes in a recipe slug, returns the static image. This route is proxied in the docker image and should not hit the API in production""" - recipe_image = app_dirs.IMG_DIR.joinpath(recipe_slug, file_name.value) + recipe_image = Recipe(slug=recipe_slug).image_dir.joinpath(file_name.value) + if recipe_image: return FileResponse(recipe_image) else: raise HTTPException(status.HTTP_404_NOT_FOUND) -@router.get("/{recipe_slug}/asset") -async def get_recipe_asset(recipe_slug, file_name: str): +@router.get("/{recipe_slug}/assets/{file_name}") +async def get_recipe_asset(recipe_slug: str, file_name: str): """ Returns a recipe asset """ - file = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name) - return FileResponse(file) + file = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) + + try: + return FileResponse(file) + except Exception: + raise HTTPException(status.HTTP_404_NOT_FOUND) -@router.post("/{recipe_slug}/asset", response_model=RecipeAsset) +@router.post("/{recipe_slug}/assets", response_model=RecipeAsset) def upload_recipe_asset( recipe_slug: str, name: str = Form(...), @@ -52,8 +56,7 @@ def upload_recipe_asset( """ Upload a file to store as a recipe asset """ file_name = slugify(name) + "." + extension asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name) - dest = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name) - dest.parent.mkdir(exist_ok=True, parents=True) + dest = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) with dest.open("wb") as buffer: shutil.copyfileobj(file.file, buffer) diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 3398545d81b2..63d11dbdb9a7 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -1,6 +1,6 @@ import shutil -from fastapi import APIRouter, Depends, File, UploadFile, status, HTTPException +from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from fastapi.responses import FileResponse from mealie.core import security from mealie.core.config import app_dirs, settings @@ -9,6 +9,7 @@ from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.deps import get_current_user from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut +from mealie.services.events import create_sign_up_event from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/users", tags=["Users"]) @@ -22,7 +23,7 @@ async def create_user( ): new_user.password = get_password_hash(new_user.password) - + create_sign_up_event("User Created", f"Created by {current_user.full_name}", session=session) return db.users.create(session, new_user.dict()) diff --git a/mealie/routes/users/sign_up.py b/mealie/routes/users/sign_up.py index c62d4f20414e..9d6bdaf798b1 100644 --- a/mealie/routes/users/sign_up.py +++ b/mealie/routes/users/sign_up.py @@ -1,14 +1,14 @@ import uuid +from fastapi import APIRouter, Depends, HTTPException, status from mealie.core.security import get_password_hash from mealie.db.database import db from mealie.db.db_setup import generate_session -from fastapi import APIRouter, Depends from mealie.routes.deps import get_current_user from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken from mealie.schema.user import UserIn, UserInDB +from mealie.services.events import create_sign_up_event from sqlalchemy.orm.session import Session -from fastapi import HTTPException, status router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"]) @@ -20,9 +20,7 @@ async def get_all_open_sign_ups( ): """ Returns a list of open sign up links """ - all_sign_ups = db.sign_ups.get_all(session) - - return all_sign_ups + return db.sign_ups.get_all(session) @router.post("", response_model=SignUpToken) @@ -41,6 +39,7 @@ async def create_user_sign_up_key( "name": key_data.name, "admin": key_data.admin, } + create_sign_up_event("Sign-up Token Created", f"Created by {current_user.full_name}", session=session) return db.sign_ups.create(session, sign_up) @@ -63,6 +62,7 @@ async def create_user_with_token( db.users.create(session, new_user.dict()) # DeleteToken + create_sign_up_event("Sign-up Token Used", f"New User {new_user.full_name}", session=session) db.sign_ups.delete(session, token) diff --git a/mealie/schema/debug.py b/mealie/schema/about.py similarity index 59% rename from mealie/schema/debug.py rename to mealie/schema/about.py index d00c17719325..202264a97125 100644 --- a/mealie/schema/debug.py +++ b/mealie/schema/about.py @@ -1,7 +1,16 @@ from pathlib import Path + from fastapi_camelcase import CamelModel +class AppStatistics(CamelModel): + total_recipes: int + total_users: int + total_groups: int + uncategorized_recipes: int + untagged_recipes: int + + class AppInfo(CamelModel): production: bool version: str @@ -11,5 +20,6 @@ class AppInfo(CamelModel): class DebugInfo(AppInfo): api_port: int api_docs: bool + db_type: str db_url: Path default_group: str diff --git a/mealie/schema/events.py b/mealie/schema/events.py new file mode 100644 index 000000000000..5ee4e8a77510 --- /dev/null +++ b/mealie/schema/events.py @@ -0,0 +1,31 @@ +from datetime import datetime +from enum import Enum +from typing import Optional + +from fastapi_camelcase import CamelModel +from pydantic import Field + + +class EventCategory(str, Enum): + general = "general" + recipe = "recipe" + backup = "backup" + scheduled = "scheduled" + migration = "migration" + sign_up = "signup" + + +class Event(CamelModel): + id: Optional[int] + title: str + text: str + time_stamp: datetime = Field(default_factory=datetime.now) + category: EventCategory = EventCategory.general + + class Config: + orm_mode = True + + +class EventsOut(CamelModel): + total: int + events: list[Event] diff --git a/mealie/schema/recipe.py b/mealie/schema/recipe.py index 1ff2aabf7116..debdec1d5cd4 100644 --- a/mealie/schema/recipe.py +++ b/mealie/schema/recipe.py @@ -1,7 +1,9 @@ import datetime +from pathlib import Path from typing import Any, Optional from fastapi_camelcase import CamelModel +from mealie.core.config import app_dirs from mealie.db.models.recipe.recipe import RecipeModel from pydantic import BaseModel, Field, validator from pydantic.utils import GetterDict @@ -58,8 +60,8 @@ class Nutrition(CamelModel): class RecipeSummary(CamelModel): id: Optional[int] - name: str - slug: Optional[str] = "" + name: Optional[str] + slug: str = "" image: Optional[Any] description: Optional[str] @@ -98,6 +100,28 @@ class Recipe(RecipeSummary): org_url: Optional[str] = Field(None, alias="orgURL") extras: Optional[dict] = {} + @staticmethod + def directory_from_slug(slug) -> Path: + return app_dirs.RECIPE_DATA_DIR.joinpath(slug) + + @property + def directory(self) -> Path: + dir = app_dirs.RECIPE_DATA_DIR.joinpath(self.slug) + dir.mkdir(exist_ok=True, parents=True) + return dir + + @property + def asset_dir(self) -> Path: + dir = self.directory.joinpath("assets") + dir.mkdir(exist_ok=True, parents=True) + return dir + + @property + def image_dir(self) -> Path: + dir = self.directory.joinpath("images") + dir.mkdir(exist_ok=True, parents=True) + return dir + class Config: orm_mode = True @@ -140,6 +164,8 @@ class Recipe(RecipeSummary): @validator("slug", always=True, pre=True) def validate_slug(slug: str, values): + if not values["name"]: + return slug name: str = values["name"] calc_slug: str = slugify(name) diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py index afd9a735eb84..321dd9e04e3a 100644 --- a/mealie/services/backups/exports.py +++ b/mealie/services/backups/exports.py @@ -9,6 +9,7 @@ from mealie.core import root_logger from mealie.core.config import app_dirs from mealie.db.database import db from mealie.db.db_setup import create_session +from mealie.services.events import create_backup_event from pathvalidate import sanitize_filename from pydantic.main import BaseModel @@ -32,7 +33,7 @@ class ExportDatabase: export_tag = datetime.now().strftime("%Y-%b-%d") self.main_dir = app_dirs.TEMP_DIR.joinpath(export_tag) - self.img_dir = self.main_dir.joinpath("images") + self.recipes = self.main_dir.joinpath("recipes") self.templates_dir = self.main_dir.joinpath("templates") try: @@ -43,7 +44,7 @@ class ExportDatabase: required_dirs = [ self.main_dir, - self.img_dir, + self.recipes, self.templates_dir, ] @@ -67,10 +68,10 @@ class ExportDatabase: with open(out_file, "w") as f: f.write(content) - def export_images(self): - shutil.copytree(app_dirs.IMG_DIR, self.img_dir, dirs_exist_ok=True) + def export_recipe_dirs(self): + shutil.copytree(app_dirs.RECIPE_DATA_DIR, self.recipes, dirs_exist_ok=True) - def export_items(self, items: list[BaseModel], folder_name: str, export_list=True): + def export_items(self, items: list[BaseModel], folder_name: str, export_list=True, slug_folder=False): items = [x.dict() for x in items] out_dir = self.main_dir.joinpath(folder_name) out_dir.mkdir(parents=True, exist_ok=True) @@ -79,8 +80,10 @@ class ExportDatabase: ExportDatabase._write_json_file(items, out_dir.joinpath(f"{folder_name}.json")) else: for item in items: - filename = sanitize_filename(f"{item.get('name')}.json") - ExportDatabase._write_json_file(item, out_dir.joinpath(filename)) + final_dest = out_dir if not slug_folder else out_dir.joinpath(item.get("slug")) + final_dest.mkdir(exist_ok=True) + filename = sanitize_filename(f"{item.get('slug')}.json") + ExportDatabase._write_json_file(item, final_dest.joinpath(filename)) @staticmethod def _write_json_file(data: Union[dict, list], out_file: Path): @@ -121,9 +124,9 @@ def backup_all( if export_recipes: all_recipes = db.recipes.get_all(session) - db_export.export_items(all_recipes, "recipes", export_list=False) + db_export.export_recipe_dirs() + db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True) db_export.export_templates(all_recipes) - db_export.export_images() if export_settings: all_settings = db.settings.get_all(session) @@ -148,3 +151,5 @@ def auto_backup_job(): session = create_session() backup_all(session=session, tag="Auto", templates=templates) logger.info("Auto Backup Called") + create_backup_event("Automated Backup", "Automated backup created", session) + session.close() diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index b561752bd6d8..864a21fb4aa9 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -2,7 +2,7 @@ import json import shutil import zipfile from pathlib import Path -from typing import Callable, List +from typing import Callable from mealie.core.config import app_dirs from mealie.db.database import db @@ -49,7 +49,7 @@ class ImportDatabase: def import_recipes(self): recipe_dir: Path = self.import_dir.joinpath("recipes") imports = [] - successful_imports = [] + successful_imports = {} recipes = ImportDatabase.read_models_file( file_path=recipe_dir, model=Recipe, single_file=False, migrate=ImportDatabase._recipe_migration @@ -68,7 +68,7 @@ class ImportDatabase: ) if import_status.status: - successful_imports.append(recipe.slug) + successful_imports.update({recipe.slug: recipe}) imports.append(import_status) @@ -105,15 +105,25 @@ class ImportDatabase: return recipe_dict - def _import_images(self, successful_imports: List[str]): + def _import_images(self, successful_imports: list[Recipe]): image_dir = self.import_dir.joinpath("images") - for image in image_dir.iterdir(): - if image.stem in successful_imports: - if image.is_dir(): - dest = app_dirs.IMG_DIR.joinpath(image.stem) - shutil.copytree(image, dest, dirs_exist_ok=True) - if image.is_file(): - shutil.copy(image, app_dirs.IMG_DIR) + + if image_dir.exists(): # Migrate from before v0.5.0 + for image in image_dir.iterdir(): + item: Recipe = successful_imports.get(image.stem) + + if item: + dest_dir = item.image_dir + + if image.is_dir(): + shutil.copytree(image, dest_dir, dirs_exist_ok=True) + + if image.is_file(): + shutil.copy(image, dest_dir) + + else: + recipe_dir = self.import_dir.joinpath("recipes") + shutil.copytree(recipe_dir, app_dirs.RECIPE_DATA_DIR, dirs_exist_ok=True) minify.migrate_images() @@ -227,7 +237,7 @@ class ImportDatabase: return [model(**g) for g in file_data] all_models = [] - for file in file_path.glob("*.json"): + for file in file_path.glob("**/*.json"): with open(file, "r") as f: file_data = json.loads(f.read()) diff --git a/mealie/services/events.py b/mealie/services/events.py new file mode 100644 index 000000000000..b47edf5dc79a --- /dev/null +++ b/mealie/services/events.py @@ -0,0 +1,40 @@ +from mealie.db.database import db +from mealie.db.db_setup import create_session +from mealie.schema.events import Event, EventCategory +from sqlalchemy.orm.session import Session + + +def save_event(title, text, category, session: Session): + event = Event(title=title, text=text, category=category) + session = session or create_session() + db.events.create(session, event.dict()) + + +def create_general_event(title, text, session=None): + category = EventCategory.general + save_event(title=title, text=text, category=category, session=session) + + +def create_recipe_event(title, text, session=None): + category = EventCategory.recipe + save_event(title=title, text=text, category=category, session=session) + + +def create_backup_event(title, text, session=None): + category = EventCategory.backup + save_event(title=title, text=text, category=category, session=session) + + +def create_scheduled_event(title, text, session=None): + category = EventCategory.scheduled + save_event(title=title, text=text, category=category, session=session) + + +def create_migration_event(title, text, session=None): + category = EventCategory.migration + save_event(title=title, text=text, category=category, session=session) + + +def create_sign_up_event(title, text, session=None): + category = EventCategory.sign_up + save_event(title=title, text=text, category=category, session=session) diff --git a/mealie/services/image/image.py b/mealie/services/image/image.py index ed5f90ab0cb0..229b2d08dea2 100644 --- a/mealie/services/image/image.py +++ b/mealie/services/image/image.py @@ -4,7 +4,7 @@ from pathlib import Path import requests from mealie.core import root_logger -from mealie.core.config import app_dirs +from mealie.schema.recipe import Recipe from mealie.services.image import minify logger = root_logger.get_logger() @@ -20,47 +20,11 @@ class ImageOptions: IMG_OPTIONS = ImageOptions() -def read_image(recipe_slug: str, image_type: str = "original") -> Path: - """returns the path to the image file for the recipe base of image_type - - Args: - recipe_slug (str): Recipe Slug - image_type (str, optional): Glob Style Matcher "original*" | "min-original* | "tiny-original*" - - Returns: - Path: [description] - """ - recipe_slug = recipe_slug.split(".")[0] # Incase of File Name - recipe_image_dir = app_dirs.IMG_DIR.joinpath(recipe_slug) - - for file in recipe_image_dir.glob(image_type): - return file - - return None - - -def rename_image(original_slug, new_slug) -> Path: - current_path = app_dirs.IMG_DIR.joinpath(original_slug) - new_path = app_dirs.IMG_DIR.joinpath(new_slug) - - try: - new_path = current_path.rename(new_path) - except FileNotFoundError: - logger.error(f"Image Directory {original_slug} Doesn't Exist") - - return new_path - - def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path: - try: - delete_image(recipe_slug) - except Exception: - pass - - image_dir = Path(app_dirs.IMG_DIR.joinpath(f"{recipe_slug}")) - image_dir.mkdir(exist_ok=True, parents=True) + image_dir = Recipe(slug=recipe_slug).image_dir extension = extension.replace(".", "") image_path = image_dir.joinpath(f"original.{extension}") + image_path.unlink(missing_ok=True) if isinstance(file_data, Path): shutil.copy2(file_data, image_path) @@ -77,12 +41,6 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path: return image_path -def delete_image(recipe_slug: str) -> str: - recipe_slug = recipe_slug.split(".")[0] - for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"): - return shutil.rmtree(file) - - def scrape_image(image_url: str, slug: str) -> Path: if isinstance(image_url, str): # Handles String Types image_url = image_url @@ -96,7 +54,7 @@ def scrape_image(image_url: str, slug: str) -> Path: image_url = image_url.get("url") filename = slug + "." + image_url.split(".")[-1] - filename = app_dirs.IMG_DIR.joinpath(filename) + filename = Recipe(slug=slug).image_dir.joinpath(filename) try: r = requests.get(image_url, stream=True) diff --git a/mealie/services/image/minify.py b/mealie/services/image/minify.py index e33bbb38e106..9dddcb63bc86 100644 --- a/mealie/services/image/minify.py +++ b/mealie/services/image/minify.py @@ -4,10 +4,8 @@ from pathlib import Path from mealie.core import root_logger from mealie.core.config import app_dirs -from mealie.db.database import db -from mealie.db.db_setup import create_session +from mealie.schema.recipe import Recipe from PIL import Image -from sqlalchemy.orm.session import Session logger = root_logger.get_logger() @@ -20,11 +18,7 @@ class ImageSizes: def get_image_sizes(org_img: Path, min_img: Path, tiny_img: Path) -> ImageSizes: - return ImageSizes( - org=sizeof_fmt(org_img), - min=sizeof_fmt(min_img), - tiny=sizeof_fmt(tiny_img), - ) + return ImageSizes(org=sizeof_fmt(org_img), min=sizeof_fmt(min_img), tiny=sizeof_fmt(tiny_img)) def minify_image(image_file: Path) -> ImageSizes: @@ -110,28 +104,9 @@ def move_all_images(): if new_file.is_file(): new_file.unlink() image_file.rename(new_file) - - -def validate_slugs_in_database(session: Session = None): - def check_image_path(image_name: str, slug_path: str) -> bool: - existing_path: Path = app_dirs.IMG_DIR.joinpath(image_name) - slug_path: Path = app_dirs.IMG_DIR.joinpath(slug_path) - - if existing_path.is_dir(): - slug_path.rename(existing_path) - else: - logger.info("No Image Found") - - session = session or create_session() - all_recipes = db.recipes.get_all(session) - - slugs_and_images = [(x.slug, x.image) for x in all_recipes] - - for slug, image in slugs_and_images: - image_slug = image.split(".")[0] # Remove Extension - if slug != image_slug: - logger.info(f"{slug}, Doesn't Match '{image_slug}'") - check_image_path(image, slug) + if image_file.is_dir(): + slug = image_file.name + image_file.rename(Recipe(slug=slug).image_dir) def migrate_images(): @@ -139,7 +114,7 @@ def migrate_images(): move_all_images() - for image in app_dirs.IMG_DIR.glob("*/original.*"): + for image in app_dirs.RECIPE_DATA_DIR.glob("**/original.*"): minify_image(image) @@ -148,4 +123,3 @@ def migrate_images(): if __name__ == "__main__": migrate_images() - validate_slugs_in_database() diff --git a/mealie/services/recipe/__init__.py b/mealie/services/recipe/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mealie/services/recipe/media.py b/mealie/services/recipe/media.py new file mode 100644 index 000000000000..9be89214329b --- /dev/null +++ b/mealie/services/recipe/media.py @@ -0,0 +1,34 @@ +from pathlib import Path +from shutil import copytree, rmtree + +from mealie.core.config import app_dirs +from mealie.core.root_logger import get_logger +from mealie.schema.recipe import Recipe + +logger = get_logger() + + +def check_assets(original_slug, recipe: Recipe) -> None: + if original_slug != recipe.slug: + current_dir = app_dirs.RECIPE_DATA_DIR.joinpath(original_slug) + + try: + copytree(current_dir, recipe.directory, dirs_exist_ok=True) + + except FileNotFoundError: + logger.error(f"Recipe Directory not Found: {original_slug}") + logger.info(f"Renaming Recipe Directory: {original_slug} -> {recipe.slug}") + + all_asset_files = [x.file_name for x in recipe.assets] + for file in recipe.asset_dir.iterdir(): + file: Path + if file.is_dir(): + continue + if file.name not in all_asset_files: + file.unlink() + + +def delete_assets(recipe_slug): + recipe_dir = Recipe(slug=recipe_slug).directory + rmtree(recipe_dir, ignore_errors=True) + logger.info(f"Recipe Directory Removed: {recipe_slug}") diff --git a/mealie/utils/post_webhooks.py b/mealie/utils/post_webhooks.py index 2ccaa28c0b99..a9884c024e73 100644 --- a/mealie/utils/post_webhooks.py +++ b/mealie/utils/post_webhooks.py @@ -2,6 +2,7 @@ import requests from mealie.db.database import db from mealie.db.db_setup import create_session from mealie.schema.user import GroupInDB +from mealie.services.events import create_scheduled_event from mealie.services.meal_services import get_todays_meal from sqlalchemy.orm.session import Session @@ -21,4 +22,6 @@ def post_webhooks(group: int, session: Session = None): for url in group_settings.webhook_urls: requests.post(url, json=todays_recipe.json()) + create_scheduled_event("Meal Plan Webhook", f"Meal plan webhook executed for group '{group}'") + session.close() From 4e3d09ac7af1c3cc2b725a567a0fb3942287dae1 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 May 2021 20:26:13 -0800 Subject: [PATCH 4/4] New Crowdin updates (#386) * New translations en-US.json (Romanian) * New translations en-US.json (Italian) * New translations en-US.json (Romanian) * New translations en-US.json (Spanish) * New translations en-US.json (Afrikaans) * New translations en-US.json (Arabic) * New translations en-US.json (Catalan) * New translations en-US.json (Czech) * New translations en-US.json (Danish) * New translations en-US.json (Greek) * New translations en-US.json (Finnish) * New translations en-US.json (Hebrew) * New translations en-US.json (Hungarian) * New translations en-US.json (Japanese) * New translations en-US.json (French) * New translations en-US.json (Korean) * New translations en-US.json (Dutch) * New translations en-US.json (Norwegian) * New translations en-US.json (Polish) * New translations en-US.json (Portuguese) * New translations en-US.json (Russian) * New translations en-US.json (Serbian (Cyrillic)) * New translations en-US.json (Swedish) * New translations en-US.json (Turkish) * New translations en-US.json (Ukrainian) * New translations en-US.json (Chinese Traditional) * New translations en-US.json (Vietnamese) * New translations en-US.json (Chinese Simplified) * New translations en-US.json (Portuguese, Brazilian) * New translations en-US.json (French) * New translations en-US.json (Italian) * New translations en-US.json (Afrikaans) * New translations en-US.json (Arabic) * New translations en-US.json (Catalan) * New translations en-US.json (Czech) * New translations en-US.json (Danish) * New translations en-US.json (German) * New translations en-US.json (German) * New translations en-US.json (Greek) * New translations en-US.json (Finnish) * New translations en-US.json (Hebrew) * New translations en-US.json (Hungarian) * New translations en-US.json (Japanese) * New translations en-US.json (Vietnamese) * New translations en-US.json (Korean) * New translations en-US.json (Dutch) * New translations en-US.json (Norwegian) * New translations en-US.json (Polish) * New translations en-US.json (Portuguese) * New translations en-US.json (Russian) * New translations en-US.json (Serbian (Cyrillic)) * New translations en-US.json (Swedish) * New translations en-US.json (Turkish) * New translations en-US.json (Ukrainian) * New translations en-US.json (Chinese Simplified) * New translations en-US.json (Chinese Traditional) * New translations en-US.json (Portuguese, Brazilian) --- frontend/src/locales/dateTimeFormats/af-ZA.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ar-SA.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ca-ES.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/cs-CZ.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/da-DK.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/de-DE.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/el-GR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/fi-FI.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/fr-FR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/he-IL.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/hu-HU.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/it-IT.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ja-JP.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ko-KR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/nl-NL.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/no-NO.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/pl-PL.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/pt-BR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/pt-PT.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ro-RO.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ru-RU.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/sr-SP.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/sv-SE.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/tr-TR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/uk-UA.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/vi-VN.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/zh-CN.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/zh-TW.json | 9 +++++++++ frontend/src/locales/messages/af-ZA.json | 8 +++++--- frontend/src/locales/messages/ar-SA.json | 8 +++++--- frontend/src/locales/messages/ca-ES.json | 8 +++++--- frontend/src/locales/messages/cs-CZ.json | 8 +++++--- frontend/src/locales/messages/da-DK.json | 8 +++++--- frontend/src/locales/messages/de-DE.json | 8 +++++--- frontend/src/locales/messages/el-GR.json | 8 +++++--- frontend/src/locales/messages/es-ES.json | 8 +++++--- frontend/src/locales/messages/fi-FI.json | 8 +++++--- frontend/src/locales/messages/fr-FR.json | 8 +++++--- frontend/src/locales/messages/he-IL.json | 8 +++++--- frontend/src/locales/messages/hu-HU.json | 8 +++++--- frontend/src/locales/messages/it-IT.json | 8 +++++--- frontend/src/locales/messages/ja-JP.json | 8 +++++--- frontend/src/locales/messages/ko-KR.json | 8 +++++--- frontend/src/locales/messages/nl-NL.json | 8 +++++--- frontend/src/locales/messages/no-NO.json | 8 +++++--- frontend/src/locales/messages/pl-PL.json | 8 +++++--- frontend/src/locales/messages/pt-BR.json | 8 +++++--- frontend/src/locales/messages/pt-PT.json | 8 +++++--- frontend/src/locales/messages/ro-RO.json | 8 +++++--- frontend/src/locales/messages/ru-RU.json | 8 +++++--- frontend/src/locales/messages/sr-SP.json | 8 +++++--- frontend/src/locales/messages/sv-SE.json | 8 +++++--- frontend/src/locales/messages/tr-TR.json | 8 +++++--- frontend/src/locales/messages/uk-UA.json | 8 +++++--- frontend/src/locales/messages/vi-VN.json | 8 +++++--- frontend/src/locales/messages/zh-CN.json | 8 +++++--- frontend/src/locales/messages/zh-TW.json | 8 +++++--- 57 files changed, 397 insertions(+), 87 deletions(-) diff --git a/frontend/src/locales/dateTimeFormats/af-ZA.json b/frontend/src/locales/dateTimeFormats/af-ZA.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/af-ZA.json +++ b/frontend/src/locales/dateTimeFormats/af-ZA.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ar-SA.json b/frontend/src/locales/dateTimeFormats/ar-SA.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/ar-SA.json +++ b/frontend/src/locales/dateTimeFormats/ar-SA.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ca-ES.json b/frontend/src/locales/dateTimeFormats/ca-ES.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/ca-ES.json +++ b/frontend/src/locales/dateTimeFormats/ca-ES.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/cs-CZ.json b/frontend/src/locales/dateTimeFormats/cs-CZ.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/cs-CZ.json +++ b/frontend/src/locales/dateTimeFormats/cs-CZ.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/da-DK.json b/frontend/src/locales/dateTimeFormats/da-DK.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/da-DK.json +++ b/frontend/src/locales/dateTimeFormats/da-DK.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/de-DE.json b/frontend/src/locales/dateTimeFormats/de-DE.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/de-DE.json +++ b/frontend/src/locales/dateTimeFormats/de-DE.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/el-GR.json b/frontend/src/locales/dateTimeFormats/el-GR.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/el-GR.json +++ b/frontend/src/locales/dateTimeFormats/el-GR.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/fi-FI.json b/frontend/src/locales/dateTimeFormats/fi-FI.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/fi-FI.json +++ b/frontend/src/locales/dateTimeFormats/fi-FI.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/fr-FR.json b/frontend/src/locales/dateTimeFormats/fr-FR.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/fr-FR.json +++ b/frontend/src/locales/dateTimeFormats/fr-FR.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/he-IL.json b/frontend/src/locales/dateTimeFormats/he-IL.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/he-IL.json +++ b/frontend/src/locales/dateTimeFormats/he-IL.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/hu-HU.json b/frontend/src/locales/dateTimeFormats/hu-HU.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/hu-HU.json +++ b/frontend/src/locales/dateTimeFormats/hu-HU.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/it-IT.json b/frontend/src/locales/dateTimeFormats/it-IT.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/it-IT.json +++ b/frontend/src/locales/dateTimeFormats/it-IT.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ja-JP.json b/frontend/src/locales/dateTimeFormats/ja-JP.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/ja-JP.json +++ b/frontend/src/locales/dateTimeFormats/ja-JP.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ko-KR.json b/frontend/src/locales/dateTimeFormats/ko-KR.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/ko-KR.json +++ b/frontend/src/locales/dateTimeFormats/ko-KR.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/nl-NL.json b/frontend/src/locales/dateTimeFormats/nl-NL.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/nl-NL.json +++ b/frontend/src/locales/dateTimeFormats/nl-NL.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/no-NO.json b/frontend/src/locales/dateTimeFormats/no-NO.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/no-NO.json +++ b/frontend/src/locales/dateTimeFormats/no-NO.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/pl-PL.json b/frontend/src/locales/dateTimeFormats/pl-PL.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/pl-PL.json +++ b/frontend/src/locales/dateTimeFormats/pl-PL.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/pt-BR.json b/frontend/src/locales/dateTimeFormats/pt-BR.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/pt-BR.json +++ b/frontend/src/locales/dateTimeFormats/pt-BR.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/pt-PT.json b/frontend/src/locales/dateTimeFormats/pt-PT.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/pt-PT.json +++ b/frontend/src/locales/dateTimeFormats/pt-PT.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ro-RO.json b/frontend/src/locales/dateTimeFormats/ro-RO.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/ro-RO.json +++ b/frontend/src/locales/dateTimeFormats/ro-RO.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ru-RU.json b/frontend/src/locales/dateTimeFormats/ru-RU.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/ru-RU.json +++ b/frontend/src/locales/dateTimeFormats/ru-RU.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/sr-SP.json b/frontend/src/locales/dateTimeFormats/sr-SP.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/sr-SP.json +++ b/frontend/src/locales/dateTimeFormats/sr-SP.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/sv-SE.json b/frontend/src/locales/dateTimeFormats/sv-SE.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/sv-SE.json +++ b/frontend/src/locales/dateTimeFormats/sv-SE.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/tr-TR.json b/frontend/src/locales/dateTimeFormats/tr-TR.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/tr-TR.json +++ b/frontend/src/locales/dateTimeFormats/tr-TR.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/uk-UA.json b/frontend/src/locales/dateTimeFormats/uk-UA.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/uk-UA.json +++ b/frontend/src/locales/dateTimeFormats/uk-UA.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/vi-VN.json b/frontend/src/locales/dateTimeFormats/vi-VN.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/vi-VN.json +++ b/frontend/src/locales/dateTimeFormats/vi-VN.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/zh-CN.json b/frontend/src/locales/dateTimeFormats/zh-CN.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/zh-CN.json +++ b/frontend/src/locales/dateTimeFormats/zh-CN.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/zh-TW.json b/frontend/src/locales/dateTimeFormats/zh-TW.json index 485c132d5979..9e0240f38cf8 100644 --- a/frontend/src/locales/dateTimeFormats/zh-TW.json +++ b/frontend/src/locales/dateTimeFormats/zh-TW.json @@ -9,5 +9,14 @@ "day": "numeric", "weekday": "long", "year": "numeric" + }, + "long": { + "year": "numeric", + "month": "long", + "day": "numeric", + "weekday": "long", + "hour": "numeric", + "minute": "numeric", + "hour12": true } } \ No newline at end of file diff --git a/frontend/src/locales/messages/af-ZA.json b/frontend/src/locales/messages/af-ZA.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/af-ZA.json +++ b/frontend/src/locales/messages/af-ZA.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/ar-SA.json b/frontend/src/locales/messages/ar-SA.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/ar-SA.json +++ b/frontend/src/locales/messages/ar-SA.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/ca-ES.json b/frontend/src/locales/messages/ca-ES.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/ca-ES.json +++ b/frontend/src/locales/messages/ca-ES.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/cs-CZ.json b/frontend/src/locales/messages/cs-CZ.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/cs-CZ.json +++ b/frontend/src/locales/messages/cs-CZ.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/da-DK.json b/frontend/src/locales/messages/da-DK.json index b63b879986f9..399693982560 100644 --- a/frontend/src/locales/messages/da-DK.json +++ b/frontend/src/locales/messages/da-DK.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Anvend", @@ -36,6 +37,7 @@ "confirm": "Bekræft", "create": "Opret", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Slet", "disabled": "Disabled", "download": "Hent", diff --git a/frontend/src/locales/messages/de-DE.json b/frontend/src/locales/messages/de-DE.json index acd1f0370485..0712e2bf52a2 100644 --- a/frontend/src/locales/messages/de-DE.json +++ b/frontend/src/locales/messages/de-DE.json @@ -14,10 +14,10 @@ "demo-status": "Demostatus", "development": "Entwicklung", "download-log": "Protokoll herunterladen", - "download-recipe-json": "Rezept JSON herunterladen", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Keine Demo", "production": "Production", - "sqlite-file": "SQLite Datei", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Entfernen der Kategorie fehlgeschlagen", "category-filter": "Kategoriefilter", "category-update-failed": "Aktualisieren der Kategorie fehlgeschlagen", - "category-updated": "Kategorie aktualisiert" + "category-updated": "Kategorie aktualisiert", + "category": "Category" }, "general": { "apply": "Anwenden", @@ -36,6 +37,7 @@ "confirm": "Bestätigen", "create": "Erstellen", "current-parenthesis": "(Neueste)", + "dashboard": "Dashboard", "delete": "Löschen", "disabled": "Deaktiviert", "download": "Herunterladen", diff --git a/frontend/src/locales/messages/el-GR.json b/frontend/src/locales/messages/el-GR.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/el-GR.json +++ b/frontend/src/locales/messages/el-GR.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/es-ES.json b/frontend/src/locales/messages/es-ES.json index 1fd0f815c081..2d19e72adafc 100644 --- a/frontend/src/locales/messages/es-ES.json +++ b/frontend/src/locales/messages/es-ES.json @@ -14,10 +14,10 @@ "demo-status": "Estado Demo", "development": "Desarrollo", "download-log": "Descargar Log", - "download-recipe-json": "Descargar Receta JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "No Demo", "production": "Producción", - "sqlite-file": "Archivo SQLite", + "database-url": "Database URL", "version": "Versión" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Error al eliminar categoría", "category-filter": "Filtros de Categorías", "category-update-failed": "Error al actualizar categoría", - "category-updated": "Categoría actualizada" + "category-updated": "Categoría actualizada", + "category": "Category" }, "general": { "apply": "Aplicar", @@ -36,6 +37,7 @@ "confirm": "Confirmar", "create": "Crear", "current-parenthesis": "(Actual)", + "dashboard": "Dashboard", "delete": "Eliminar", "disabled": "Deshabilitado", "download": "Descargar", diff --git a/frontend/src/locales/messages/fi-FI.json b/frontend/src/locales/messages/fi-FI.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/fi-FI.json +++ b/frontend/src/locales/messages/fi-FI.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/fr-FR.json b/frontend/src/locales/messages/fr-FR.json index 17f27227a09a..0a617f1963eb 100644 --- a/frontend/src/locales/messages/fr-FR.json +++ b/frontend/src/locales/messages/fr-FR.json @@ -14,10 +14,10 @@ "demo-status": "Mode démo", "development": "Développement", "download-log": "Télécharger les logs", - "download-recipe-json": "Télécharger le JSON de la recette", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Non", "production": "Production", - "sqlite-file": "Fichier SQLite", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "La suppression de la catégorie a échoué", "category-filter": "Filtre par catégories", "category-update-failed": "La mise à jour de la catégorie a échoué", - "category-updated": "Catégorie mise à jour" + "category-updated": "Catégorie mise à jour", + "category": "Category" }, "general": { "apply": "Appliquer", @@ -36,6 +37,7 @@ "confirm": "Confirmer", "create": "Créer", "current-parenthesis": "(Actuel)", + "dashboard": "Dashboard", "delete": "Supprimer", "disabled": "Désactivé", "download": "Télécharger", diff --git a/frontend/src/locales/messages/he-IL.json b/frontend/src/locales/messages/he-IL.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/he-IL.json +++ b/frontend/src/locales/messages/he-IL.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/hu-HU.json b/frontend/src/locales/messages/hu-HU.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/hu-HU.json +++ b/frontend/src/locales/messages/hu-HU.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/it-IT.json b/frontend/src/locales/messages/it-IT.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/it-IT.json +++ b/frontend/src/locales/messages/it-IT.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/ja-JP.json b/frontend/src/locales/messages/ja-JP.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/ja-JP.json +++ b/frontend/src/locales/messages/ja-JP.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/ko-KR.json b/frontend/src/locales/messages/ko-KR.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/ko-KR.json +++ b/frontend/src/locales/messages/ko-KR.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/nl-NL.json b/frontend/src/locales/messages/nl-NL.json index 18e258ace095..f19426757437 100644 --- a/frontend/src/locales/messages/nl-NL.json +++ b/frontend/src/locales/messages/nl-NL.json @@ -14,10 +14,10 @@ "demo-status": "Demo status", "development": "Versies in ontwikkeling", "download-log": "Logbestand downloaden", - "download-recipe-json": "Download Recept JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Niet Demo", "production": "Productie", - "sqlite-file": "SQLite bestand", + "database-url": "Database URL", "version": "Versie" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Categorie verwijderen mislukt", "category-filter": "Categorie Filter", "category-update-failed": "Categorie bijwerken mislukt", - "category-updated": "Categorie bijgewerkt" + "category-updated": "Categorie bijgewerkt", + "category": "Category" }, "general": { "apply": "Toepassen", @@ -36,6 +37,7 @@ "confirm": "Bevestigen", "create": "Maak", "current-parenthesis": "(Huidig)", + "dashboard": "Dashboard", "delete": "Verwijderen", "disabled": "Uitgeschakeld", "download": "Download", diff --git a/frontend/src/locales/messages/no-NO.json b/frontend/src/locales/messages/no-NO.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/no-NO.json +++ b/frontend/src/locales/messages/no-NO.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/pl-PL.json b/frontend/src/locales/messages/pl-PL.json index 23fc17fb9186..73479c31f083 100644 --- a/frontend/src/locales/messages/pl-PL.json +++ b/frontend/src/locales/messages/pl-PL.json @@ -14,10 +14,10 @@ "demo-status": "Status demo", "development": "Wersja testowa", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Nie demo", "production": "Produkcyjna", - "sqlite-file": "Plik SQLite'a", + "database-url": "Database URL", "version": "Wersja" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Zastosuj", @@ -36,6 +37,7 @@ "confirm": "Potwierdź", "create": "Utwórz", "current-parenthesis": "(Bieżący)", + "dashboard": "Dashboard", "delete": "Usuń", "disabled": "Wyłączone", "download": "Pobierz", diff --git a/frontend/src/locales/messages/pt-BR.json b/frontend/src/locales/messages/pt-BR.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/pt-BR.json +++ b/frontend/src/locales/messages/pt-BR.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/pt-PT.json b/frontend/src/locales/messages/pt-PT.json index 37a42cd90cc2..68f5be09d07b 100644 --- a/frontend/src/locales/messages/pt-PT.json +++ b/frontend/src/locales/messages/pt-PT.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirmar", "create": "Criar", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Eliminar", "disabled": "Disabled", "download": "Transferir", diff --git a/frontend/src/locales/messages/ro-RO.json b/frontend/src/locales/messages/ro-RO.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/ro-RO.json +++ b/frontend/src/locales/messages/ro-RO.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/ru-RU.json b/frontend/src/locales/messages/ru-RU.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/ru-RU.json +++ b/frontend/src/locales/messages/ru-RU.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/sr-SP.json b/frontend/src/locales/messages/sr-SP.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/sr-SP.json +++ b/frontend/src/locales/messages/sr-SP.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/sv-SE.json b/frontend/src/locales/messages/sv-SE.json index 54534e5aaf8d..7c5bd7aa45da 100644 --- a/frontend/src/locales/messages/sv-SE.json +++ b/frontend/src/locales/messages/sv-SE.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Skapa", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Ta bort", "disabled": "Disabled", "download": "Ladda ner", diff --git a/frontend/src/locales/messages/tr-TR.json b/frontend/src/locales/messages/tr-TR.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/tr-TR.json +++ b/frontend/src/locales/messages/tr-TR.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/uk-UA.json b/frontend/src/locales/messages/uk-UA.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/uk-UA.json +++ b/frontend/src/locales/messages/uk-UA.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/vi-VN.json b/frontend/src/locales/messages/vi-VN.json index 0ca827e7fb88..5aab6900f2f5 100644 --- a/frontend/src/locales/messages/vi-VN.json +++ b/frontend/src/locales/messages/vi-VN.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "Confirm", "create": "Create", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "Delete", "disabled": "Disabled", "download": "Download", diff --git a/frontend/src/locales/messages/zh-CN.json b/frontend/src/locales/messages/zh-CN.json index 827cc157ae20..863e86463faf 100644 --- a/frontend/src/locales/messages/zh-CN.json +++ b/frontend/src/locales/messages/zh-CN.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "应用", @@ -36,6 +37,7 @@ "confirm": "确定", "create": "创建", "current-parenthesis": "(当前)", + "dashboard": "Dashboard", "delete": "删除", "disabled": "Disabled", "download": "下载", diff --git a/frontend/src/locales/messages/zh-TW.json b/frontend/src/locales/messages/zh-TW.json index facd4c1e8a8e..6553dc6b2c4e 100644 --- a/frontend/src/locales/messages/zh-TW.json +++ b/frontend/src/locales/messages/zh-TW.json @@ -14,10 +14,10 @@ "demo-status": "Demo Status", "development": "Development", "download-log": "Download Log", - "download-recipe-json": "Download Recipe JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "Not Demo", "production": "Production", - "sqlite-file": "SQLite File", + "database-url": "Database URL", "version": "Version" }, "category": { @@ -27,7 +27,8 @@ "category-deletion-failed": "Category deletion failed", "category-filter": "Category Filter", "category-update-failed": "Category update failed", - "category-updated": "Category updated" + "category-updated": "Category updated", + "category": "Category" }, "general": { "apply": "Apply", @@ -36,6 +37,7 @@ "confirm": "確定", "create": "創建", "current-parenthesis": "(Current)", + "dashboard": "Dashboard", "delete": "删除", "disabled": "Disabled", "download": "下载",