From 46cae07f2c91f64d3a32404c55c53aca41f9be45 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:44:31 -0600 Subject: [PATCH 01/74] New Crowdin updates (#2774) * New translations en-us.json (Catalan) * New translations en-us.json (Portuguese) --- frontend/lang/messages/ca-ES.json | 198 +++++++++++++++--------------- frontend/lang/messages/pt-PT.json | 94 +++++++------- 2 files changed, 146 insertions(+), 146 deletions(-) diff --git a/frontend/lang/messages/ca-ES.json b/frontend/lang/messages/ca-ES.json index 1b048a88130e..fb321dee89d4 100644 --- a/frontend/lang/messages/ca-ES.json +++ b/frontend/lang/messages/ca-ES.json @@ -115,8 +115,8 @@ "keyword": "Paraula clau", "link-copied": "S'ha copiat l'enllaç", "loading-events": "Carregant esdeveniments", - "loading-recipe": "Loading recipe...", - "loading-ocr-data": "Loading OCR data...", + "loading-recipe": "Carregant la recepta...", + "loading-ocr-data": "Carregant les dades OCR...", "loading-recipes": "Carregant les receptes", "message": "Missatge", "monday": "Dilluns", @@ -127,7 +127,7 @@ "no-recipe-found": "No s'han trobat receptes", "ok": "D'acord", "options": "Opcions:", - "plural-name": "Plural Name", + "plural-name": "Nom en plural", "print": "Imprimiu", "print-preferences": "Imprimiu les preferències", "random": "Aleatori", @@ -197,7 +197,7 @@ "refresh": "Actualitza", "upload-file": "Puja un fitxer", "created-on-date": "Creat el: {0}", - "unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes." + "unsaved-changes": "Tens canvis que no estan guardats. Vols guardar-los abans de sortir? Clica d'acord per guardar-los o cancel·lar per descartar els canvis." }, "group": { "are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup {groupName}?", @@ -212,7 +212,7 @@ "group-id-with-value": "Identificador del grup: {groupID}", "group-name": "Nom del grup", "group-not-found": "No s'ha trobat el grup", - "group-token": "Group Token", + "group-token": "Token del grup", "group-with-value": "Grup: {groupID}", "groups": "Grups", "manage-groups": "Gestiona els grups", @@ -246,13 +246,13 @@ "disable-organizing-recipe-ingredients-by-units-and-food": "Desactiva l'organització dels ingredients de la recepta per unitats i aliments", "disable-organizing-recipe-ingredients-by-units-and-food-description": "Amaga els camps Aliment, Unitat i Quantitat dels ingredients i tracta els ingredients com a camps de text sense format.", "general-preferences": "Preferències generals", - "group-recipe-preferences": "Group Recipe Preferences", - "report": "Report", - "report-with-id": "Report ID: {id}", + "group-recipe-preferences": "Preferències del grup de receptes", + "report": "Informe", + "report-with-id": "ID de l'informe: {id}", "group-management": "Gestió de grups", "admin-group-management": "Gestió del grup d'administradors", - "admin-group-management-text": "Changes to this group will be reflected immediately.", - "group-id-value": "Group Id: {0}" + "admin-group-management-text": "Els canvis en aquest grup s'actualitzaran immediatament.", + "group-id-value": "ID del grup: {0}" }, "meal-plan": { "create-a-new-meal-plan": "Crea un nou menú", @@ -291,68 +291,68 @@ "editor": "Editor", "meal-recipe": "Recepta del menú", "meal-title": "Títol del menú", - "meal-note": "Meal Note", + "meal-note": "Notes del menú", "note-only": "Note Only", "random-meal": "Menú aleatori", "random-dinner": "Principal aleatori", "random-side": "Guarnició aleatòria", - "this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.", + "this-rule-will-apply": "Aquesta regla s'aplicarà {dayCriteria} {mealTypeCriteria}.", "to-all-days": "a tots els dies", - "on-days": "on {0}s", + "on-days": "en {0}s", "for-all-meal-types": "per a tots els tipus de menús", - "for-type-meal-types": "for {0} meal types", - "meal-plan-rules": "Meal Plan Rules", - "new-rule": "New Rule", + "for-type-meal-types": "per {0} tipus de menús", + "meal-plan-rules": "Normes del planificador de menús", + "new-rule": "Nova norma", "meal-plan-rules-description": "You can create rules for auto selecting recipes for your meal plans. These rules are used by the server to determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same day/type constraints then the categories of the rules will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.", "new-rule-description": "When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to \"Any\" which will apply it to all the possible values for the day and/or meal type.", - "recipe-rules": "Recipe Rules", + "recipe-rules": "Normes per la recepta", "applies-to-all-days": "Aplica a tots els dies", - "applies-on-days": "Applies on {0}s", - "meal-plan-settings": "Meal Plan Settings" + "applies-on-days": "S'aplicarà en {0}s", + "meal-plan-settings": "Opcions de planificació de menús" }, "migration": { "migration-data-removed": "S'han suprimit les dades migrades", - "new-migration": "New Migration", + "new-migration": "Nova migració", "no-file-selected": "Cap fitxer seleccionat", "no-migration-data-available": "No hi han dades disponibles", - "previous-migrations": "Previous Migrations", + "previous-migrations": "Migracions prèvies", "recipe-migration": "Migració de receptes", "chowdown": { "description": "Migreu les dades de Chowdown", - "description-long": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.", + "description-long": "Mealie suporta de forma nativa el format de Chowdown. Descarrega el codi del repositori com a .zip i carrega'l a sota.", "title": "Chowdown" }, "nextcloud": { "description": "Migreu les dades des d'una instància de Nextcloud Cookbook", - "description-long": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.", + "description-long": "Les receptes de Nextcloud es poden importar des d'un zip que contingui les dades guardades a Nextcloud. Com a exemple, observa l'estructura de carpetes de sota per assegurar-te que les teves receptes es poden importar.", "title": "Nextcloud Cookbook" }, "copymethat": { "description-long": "Mealie pot importar receptes de Copy Me That. Exporta les teves receptes en format HTML i llavors puja el fitxer .zip aquí sota.", - "title": "Copy Me That Recipe Manager" + "title": "Copia aquest gestor de receptes" }, "paprika": { - "description-long": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.", + "description-long": "Mealie pot importar receptes de l'aplicació Paprika. Exporta les teves receptes de Paprika, reanomena l'arxiu a format .zip i carrega'l a sota.", "title": "Paprika Recipe Manager" }, "mealie-pre-v1": { - "description-long": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.", + "description-long": "Mealie pot importar les receptes des de versions prèvies a la v1.0 d'aquesta aplicació. Exporta les teves receptes des de la teva antiga instància i carrega l'arxiu. zip a sota. Només s'importaran les receptes, i cap dada més.", "title": "Mealie Pre v1.0" }, "tandoor": { - "description-long": "Mealie can import recipes from Tandoor. Export your data in the \"Default\" format, then upload the .zip below.", + "description-long": "Mealie pot importar les receptes de Tandoor. Exporta les dades en format \"Default\", i carrega el .zip a sota.", "title": "Tandoor Recipes" }, - "recipe-data-migrations": "Recipe Data Migrations", - "recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.", + "recipe-data-migrations": "Migració de receptes", + "recipe-data-migrations-explanation": "Les receptes es poden migrar des d'una altra aplicació suportada cap a Mealie. És una manera genial de començar a utilitzar el Mealie.", "choose-migration-type": "Elegeix un tipus de migració", "tag-all-recipes": "Etiqueta totes les receptes amb {tag-name}", "nextcloud-text": "Les receptes de Nextcloud poden ser importades d'un fitxer ZIP que contingui les dades emmagatzemades en Nextcloud. Segueix l'exemple d'estructura de directori de sota per assegurar que les receptes podran ser importades.", - "chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below", + "chowdown-text": "Mealie suporta de forma nativa el format de Chowdown. Descarrega el codi del repositori com a .zip i carrega'l a sota", "recipe-1": "Recepta 1", "recipe-2": "Recepta 2", "paprika-text": "Mealie pot importar receptes des de l'aplicació Paprika. Exporta les teves receptes de Paprika, reanomena l'extensió de l'arxiu a .zip i penja'l aquí sota.", - "mealie-text": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.", + "mealie-text": "Mealie pot importar les receptes des de versions prèvies a la v1.0 d'aquesta aplicació. Exporta les teves receptes des de la teva antiga instància i carrega l'arxiu. zip a sota. Només s'importaran les receptes, i cap dada més.", "plantoeat": { "title": "Plan to Eat", "description-long": "Mealie pot importar receptes de Plan to Eat." @@ -469,9 +469,9 @@ "add-to-plan": "Afegiu al menú", "add-to-timeline": "Afegir a la cronologia", "recipe-added-to-list": "Recepta afegida a la llista", - "recipes-added-to-list": "Recipes added to list", + "recipes-added-to-list": "Receptes afegides a la llista", "recipe-added-to-mealplan": "Recepta afegida al menú", - "failed-to-add-recipes-to-list": "Failed to add recipe to list", + "failed-to-add-recipes-to-list": "S'ha produït un error al intentar afegir la recepta a la llista", "failed-to-add-recipe-to-mealplan": "S'ha produït un error afegint la recepta al menú", "yield": "Racions", "quantity": "Quantitat", @@ -508,50 +508,50 @@ "made-this": "Ho he fet", "how-did-it-turn-out": "Com ha sortit?", "user-made-this": "{user} ha fet això", - "last-made-date": "Last Made {date}", + "last-made-date": "Última vegada feta {date}", "api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.", "message-key": "Message Key", - "parse": "Parse", - "attach-images-hint": "Attach images by dragging & dropping them into the editor", - "drop-image": "Drop image", - "enable-ingredient-amounts-to-use-this-feature": "Enable ingredient amounts to use this feature", - "recipes-with-units-or-foods-defined-cannot-be-parsed": "Recipes with units or foods defined cannot be parsed.", - "parse-ingredients": "Parse ingredients", + "parse": "Analitzar", + "attach-images-hint": "Afegeix imatges arrossegant i deixant anar la imatge a l'editor", + "drop-image": "Deixa anar la imatge", + "enable-ingredient-amounts-to-use-this-feature": "Habilita les quantitats d'ingredients per a poder fer servir aquesta característica", + "recipes-with-units-or-foods-defined-cannot-be-parsed": "Les receptes amb unitats o aliments definits no es poden analitzar.", + "parse-ingredients": "Analitzar ingredients", "edit-markdown": "Editar Markdown", "recipe-creation": "Creació d'una recepta", "select-one-of-the-various-ways-to-create-a-recipe": "Selecciona una de les diverses formes de crear una recepta", - "looking-for-migrations": "Looking For Migrations?", + "looking-for-migrations": "Estàs buscant migracions?", "import-with-url": "Importar amb l'URL", "create-recipe": "Crea la recepta", "import-with-zip": "Importar amb un .zip", "create-recipe-from-an-image": "Crea la recepta a partir d'una imatge", "bulk-url-import": "Importació d'URL en massa", - "debug-scraper": "Debug Scraper", + "debug-scraper": "Rastrejador de depuració", "create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Crea la recepta proporcionant-ne un nom. Totes les receptes han de tenir un nom únic.", - "new-recipe-names-must-be-unique": "New recipe names must be unique", - "scrape-recipe": "Scrape Recipe", - "scrape-recipe-description": "Scrape a recipe by url. Provide the url for the site you want to scrape, and Mealie will attempt to scrape the recipe from that site and add it to your collection.", - "import-original-keywords-as-tags": "Import original keywords as tags", + "new-recipe-names-must-be-unique": "Els noms de les noves receptes han de ser únics", + "scrape-recipe": "Rastrejar recepta", + "scrape-recipe-description": "Rastrejar recepta des de l'Url. Proporciona un Url del lloc que vols rastrejar i Mealie intentarà analitzar la recepta del lloc web i afegir-la a la teva col·lecció.", + "import-original-keywords-as-tags": "Importa les paraules clau originals com a tags", "stay-in-edit-mode": "Segueix en el mode d'edició", "import-from-zip": "Importa des d'un ZIP", - "import-from-zip-description": "Import a single recipe that was exported from another Mealie instance.", + "import-from-zip-description": "Importa una sola recepta que ha estat importada d'una altra instància de Mealie.", "zip-files-must-have-been-exported-from-mealie": "Els fitxers .zip han d'haver sigut exportats des de Mealie", "create-a-recipe-by-uploading-a-scan": "Crea la recepta pujant-ne un escaneig.", "upload-a-png-image-from-a-recipe-book": "Puja una imatge PNG d'un llibre de receptes", "recipe-bulk-importer": "Importador de receptes en massa", - "recipe-bulk-importer-description": "The Bulk recipe importer allows you to import multiple recipes at once by queueing the sites on the backend and running the task in the background. This can be useful when initially migrating to Mealie, or when you want to import a large number of recipes.", - "set-categories-and-tags": "Set Categories and Tags", - "bulk-imports": "Bulk Imports", - "bulk-import-process-has-started": "Bulk Import process has started", - "bulk-import-process-has-failed": "Bulk import process has failed", - "report-deletion-failed": "Report deletion failed", - "recipe-debugger": "Recipe Debugger", - "recipe-debugger-description": "Grab the URL of the recipe you want to debug and paste it here. The URL will be scraped by the recipe scraper and the results will be displayed. If you don't see any data returned, the site you are trying to scrape is not supported by Mealie or its scraper library.", - "debug": "Debug", - "tree-view": "Tree View", - "recipe-yield": "Recipe Yield", - "unit": "Unit", - "upload-image": "Upload image", + "recipe-bulk-importer-description": "L'importador de receptes a granel, permet que importis múltiples receptes a la vegada posant a la cua els llocs al backend i executant la tasca en segon pla. Això pot ser útil si es migra inicialment a Mealie o quan es vol importar un gran nombre de receptes.", + "set-categories-and-tags": "Estableix Categories i Etiquetes", + "bulk-imports": "Importacions a granel", + "bulk-import-process-has-started": "El procés d'importació a granel ha començat", + "bulk-import-process-has-failed": "El procés d'importació a granel ha fallat", + "report-deletion-failed": "No s'ha pogut suprimir l'informe", + "recipe-debugger": "Depuradora de receptes", + "recipe-debugger-description": "Agafa l'URL de la recepta que vols depurar i enganxa-la aquí. L'URL serà reastrejada pel rastrejador de receptes i es mostraran els resultats. Si no veieu cap dada retornada, el lloc que esteu provant de rastrejar no és compatible amb Mealie ni la seva biblioteca de rastreig.", + "debug": "Depuració", + "tree-view": "Vista en arbre", + "recipe-yield": "Rendiment de la recepta", + "unit": "Unitat", + "upload-image": "Puja una imatge", "screen-awake": "Mantenir la pantalla encesa", "remove-image": "Esborrar la imatge" }, @@ -562,26 +562,26 @@ "include": "Inclou", "max-results": "No mostreu més de", "or": "O", - "has-any": "Has Any", - "has-all": "Has All", + "has-any": "Conté qualsevol", + "has-all": "Ho conté tot", "results": "Resultats", "search": "Cerca", "search-mealie": "Cerca a Melie (prem /)", "search-placeholder": "Cerca...", "tag-filter": "Filtra per etiqueta", "search-hint": "Prem '/'", - "advanced": "Advanced", - "auto-search": "Auto Search", - "no-results": "No results found" + "advanced": "Avançat", + "auto-search": "Cerca automàtica", + "no-results": "No s'han trobat resultats" }, "settings": { "add-a-new-theme": "Afegiu un nou tema", "admin-settings": "Opcions de l'administrador", "backup": { - "backup-created": "Backup created successfully", + "backup-created": "La còpia de seguretat s'ha creat correctament", "backup-created-at-response-export_path": "S'ha creat una còpia de seguretat a {path}", "backup-deleted": "Còpia de seguretat suprimida", - "restore-success": "Restore successful", + "restore-success": "La restauració s'ha dut a terme correctament", "backup-tag": "Etiqueta de la còpia de seguretat", "create-heading": "Crea una còpia de seguretat", "delete-backup": "Esborra la còpia de seguretat", @@ -591,13 +591,13 @@ "partial-backup": "Còpia de seguretat parcial", "unable-to-delete-backup": "No s'ha pogut suprimir la còpia.", "experimental-description": "Backups a total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time. Currently, {not-crossed-version} (data migrations are not done automatically). These serve as a database agnostic way to export and import data or backup the site to an external location.", - "backup-restore": "Backup Restore", - "back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.", - "cannot-be-undone": "This action cannot be undone - use with caution.", - "postgresql-note": "If you are using PostGreSQL, please review the {backup-restore-process} prior to restoring.", - "backup-restore-process-in-the-documentation": "backup/restore process in the documentation", - "irreversible-acknowledgment": "I understand that this action is irreversible, destructive and may cause data loss", - "restore-backup": "Restore Backup" + "backup-restore": "Restaura la còpia de seguretat", + "back-restore-description": "Restaurar aquesta còpia de seguretat sobreescriurà totes les dades actuals de la teva base de dades i qualsevol directori i els substituirà amb el contingut d'aquesta còpia de seguretat. {cannot-be-undone} Si la restauració es duu a terme correctament, se us tancarà la sessió.", + "cannot-be-undone": "Aquesta acció no es pot desfer. Utilitza-la amb precaució.", + "postgresql-note": "Si estàs fent servir PostGresSQL, si us plau, revisa el {backup-restore-process} abans de fer la restauració.", + "backup-restore-process-in-the-documentation": "el procés de còpia de seguretat i restauració es troba a la documentació", + "irreversible-acknowledgment": "Entenc que aquesta acció és irreversible, destructiva i pot ocasionar la pèrdua de dades", + "restore-backup": "Restaura la còpia de seguretat" }, "backup-and-exports": "Còpies de seguretat", "change-password": "Canvia la contrasenya", @@ -661,8 +661,8 @@ "copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Còpia aquest token per a utilitzar-lo en una aplicació externa. Aquest token, no es tornarà a mostrar.", "create-an-api-token": "Crea un token d'API", "token-name": "Nom del token", - "generate": "Generate", - "you-have-token-count": "You have no active tokens.|You have one active token.|You have {count} active tokens." + "generate": "Genera", + "you-have-token-count": "No tens fitxes actives.|Tens una fitxa activa.|Tens {count} fitxes actives." }, "toolbox": { "assign-all": "Asigna tots", @@ -682,7 +682,7 @@ "webhooks-caps": "WEBHOOKS", "webhooks": "Webhooks", "webhook-name": "Nom del Webhook", - "description": "The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the webhooks will be sent with the data from the recipe that is scheduled for the day. Note that webhook execution is not exact. The webhooks are executed on a 5 minutes interval so the webhooks will be executed within 5 +/- minutes of the scheduled." + "description": "Els webhooks definits a sota s'executaran quan es defineixi un menú pel dia. En un temps estipulat, els webhooks s'enviaran amb les dades de la recepta que estigui programada pel dia. L'execució dels webhooks no és exacta. Els webhooks s'executen en un interval de 5 minuts, és a dir, que s'executaran en un interval de +/- 5 minuts del temps estipulat." }, "bug-report": "Bug Report", "bug-report-information": "Use this information to report a bug. Providing details of your instance to developers is the best way to get your issues resolved quickly.", @@ -742,8 +742,8 @@ "check-all-items": "Check All Items", "linked-recipes-count": "No Linked Recipes|One Linked Recipe|{count} Linked Recipes", "items-checked-count": "No items checked|One item checked|{count} items checked", - "no-label": "No Label", - "completed-on": "Completed on {date}" + "no-label": "Sense etiqueta", + "completed-on": "Completat el {date}" }, "sidebar": { "all-recipes": "Receptes", @@ -796,13 +796,13 @@ "tool-name": "Nom de l'estri", "create-new-tool": "Crea un nou estri", "on-hand-checkbox-label": "Mostra com a disponible (marcat)", - "required-tools": "Required Tools" + "required-tools": "Eines necessàries" }, "user": { "admin": "Administrador/a", "are-you-sure-you-want-to-delete-the-link": "Esteu segur de voler suprimir l'enllaç {link}?", "are-you-sure-you-want-to-delete-the-user": "Esteu segur de voler suprimir l'usuari {activeName} ID: {activeId}?", - "auth-method": "Auth Method", + "auth-method": "Mètode d'autenticació", "confirm-link-deletion": "Confirmeu l'eliminació de l'enllaç", "confirm-password": "Confirmeu la contrasenya", "confirm-user-deletion": "Confirmeu l'eliminació de l'usuari", @@ -816,7 +816,7 @@ "error-cannot-delete-super-user": "S'ha produït un error. El Super usuari no es pot suprimir!", "existing-password-does-not-match": "La contrasenya actual no coincideix", "full-name": "Nom sencer", - "generate-password-reset-link": "Generate Password Reset Link", + "generate-password-reset-link": "Genera un enllaç per reiniciar la contrasenya", "invite-only": "Només per invitació", "link-id": "Id de l'enllaç", "link-name": "Nom de l'enllaç", @@ -831,7 +831,7 @@ "password-updated": "S'ha actualitzat la contrasenya", "password": "Contrasenya", "password-strength": "Fortalesa de la contrasenya: {strength}", - "please-enter-password": "Please enter your new password.", + "please-enter-password": "Si us plau, entra la teva nova contrasenya.", "register": "Registreu-vos", "reset-password": "Restableix la contrasenya", "sign-in": "Inicia sessió", @@ -852,7 +852,7 @@ "username": "Nom d'usuari", "users-header": "USUARIS", "users": "Usuaris", - "user-not-found": "User not found", + "user-not-found": "No s'ha trobat l'usuari", "webhook-time": "Hora del Webhook", "webhooks-enabled": "Webhooks habilitats", "you-are-not-allowed-to-create-a-user": "Vostè no està autoritzat per a crear un usuari", @@ -860,24 +860,24 @@ "enable-advanced-content": "Habilita el contingut avançat", "enable-advanced-content-description": "Habilita les funcions avançades com ara multiplicar els ingredients, claus API, Webhooks i la gestió de les dades. Pots tornar a canviar aquesta configuració més tard", "favorite-recipes": "Receptes preferides", - "email-or-username": "Email or Username", - "remember-me": "Remember Me", - "please-enter-your-email-and-password": "Please enter your email and password", - "invalid-credentials": "Invalid Credentials", - "account-locked-please-try-again-later": "Account Locked. Please try again later", - "user-favorites": "User Favorites", + "email-or-username": "Correu electrònic o nom d'usuari", + "remember-me": "Recorda'm", + "please-enter-your-email-and-password": "Si us plau, introdueix el teu correu electrònic i la teva contrasenya", + "invalid-credentials": "Credencials no vàlides", + "account-locked-please-try-again-later": "Compte bloquejat, Si us plau, prova-ho més tard", + "user-favorites": "Favorits de l'usuari", "password-strength-values": { - "weak": "Weak", - "good": "Good", - "strong": "Strong", - "very-strong": "Very Strong" + "weak": "Dèbil", + "good": "Bona", + "strong": "Forta", + "very-strong": "Molt forta" }, - "user-management": "User Management", - "reset-locked-users": "Reset Locked Users", - "admin-user-creation": "Admin User Creation", + "user-management": "Gestió d'usuaris", + "reset-locked-users": "Reinicia els usuaris bloquejats", + "admin-user-creation": "Creació d'un usuari administrador", "admin-user-management": "Admin User Management", - "user-details": "User Details", - "user-name": "User Name", + "user-details": "Detalls de l'usuari", + "user-name": "Nom de l'usuari", "authentication-method": "Authentication Method", "authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie", "permissions": "Permissions", diff --git a/frontend/lang/messages/pt-PT.json b/frontend/lang/messages/pt-PT.json index c6a1ae66cc8d..ac59c05a6d6b 100644 --- a/frontend/lang/messages/pt-PT.json +++ b/frontend/lang/messages/pt-PT.json @@ -3,7 +3,7 @@ "about": "Sobre", "about-mealie": "Sobre Mealie", "api-docs": "Documentação de API", - "api-port": "Porta de API", + "api-port": "Porta da API", "application-mode": "Modo de aplicação", "database-type": "Tipo de Base de Dados", "database-url": "Endereço da Base de Dados", @@ -77,7 +77,7 @@ "tag-events": "Eventos de Etiquetagem", "category-events": "Eventos de Categoria", "when-a-new-user-joins-your-group": "Quando um novo utilizador entra no seu grupo", - "recipe-events": "Recipe Events" + "recipe-events": "Eventos de receita" }, "general": { "cancel": "Cancelar", @@ -115,8 +115,8 @@ "keyword": "Palavra-chave", "link-copied": "Ligação copiada", "loading-events": "A carregar Eventos", - "loading-recipe": "Loading recipe...", - "loading-ocr-data": "Loading OCR data...", + "loading-recipe": "A carregar receita...", + "loading-ocr-data": "A carregar dados OCR...", "loading-recipes": "A carregar receitas", "message": "Mensagem", "monday": "Segunda-feira", @@ -127,7 +127,7 @@ "no-recipe-found": "Nenhuma Receita Encontrada", "ok": "OK", "options": "Opções:", - "plural-name": "Plural Name", + "plural-name": "Nome no Plural", "print": "Imprimir", "print-preferences": "Preferências de impressão", "random": "Aleatório", @@ -197,7 +197,7 @@ "refresh": "Atualizar", "upload-file": "Carregar ficheiro", "created-on-date": "Criado em: {0}", - "unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes." + "unsaved-changes": "Tem alterações por gravar. Quer gravar antes de sair? OK para gravar, Cancelar para descartar alterações." }, "group": { "are-you-sure-you-want-to-delete-the-group": "Tem a certeza que quer eliminar {groupName}?", @@ -212,7 +212,7 @@ "group-id-with-value": "ID do Grupo: {groupID}", "group-name": "Nome do grupo", "group-not-found": "Grupo não encontrado", - "group-token": "Group Token", + "group-token": "Token do Grupo", "group-with-value": "Grupo: {groupID}", "groups": "Grupos", "manage-groups": "Gerir Grupos", @@ -248,7 +248,7 @@ "general-preferences": "Preferências Gerais", "group-recipe-preferences": "Agrupar preferências de receita", "report": "Relatório", - "report-with-id": "Report ID: {id}", + "report-with-id": "ID do relatório: {id}", "group-management": "Gestão de Grupos", "admin-group-management": "Gestão do Grupo Admin", "admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.", @@ -469,9 +469,9 @@ "add-to-plan": "Adicionar ao plano", "add-to-timeline": "Adicionar à Linha Temporal", "recipe-added-to-list": "Receita adicionada à lista", - "recipes-added-to-list": "Recipes added to list", + "recipes-added-to-list": "Receitas adicionadas à lista", "recipe-added-to-mealplan": "Receita adicionada ao plano de refeições", - "failed-to-add-recipes-to-list": "Failed to add recipe to list", + "failed-to-add-recipes-to-list": "Erro ao adicionar a receita à lista", "failed-to-add-recipe-to-mealplan": "Erro ao adicionar receita ao plano de refeições", "yield": "Rendimento", "quantity": "Quantidade", @@ -513,7 +513,7 @@ "message-key": "Chave de Mensagem", "parse": "Interpretar", "attach-images-hint": "Anexe imagens arrastando e soltando-as no editor", - "drop-image": "Drop image", + "drop-image": "Remover imagem", "enable-ingredient-amounts-to-use-this-feature": "Ativar para usar esta funcionalidade nas quantidades de ingredientes", "recipes-with-units-or-foods-defined-cannot-be-parsed": "Receitas com unidades ou alimentos definidos não podem ser interpretadas.", "parse-ingredients": "Interpretar ingredientes", @@ -572,16 +572,16 @@ "search-hint": "Prima '/'", "advanced": "Avançado", "auto-search": "Pesquisa Automática", - "no-results": "No results found" + "no-results": "Nenhum resultado encontrado" }, "settings": { "add-a-new-theme": "Adicionar novo tema", "admin-settings": "Definições de Administrador", "backup": { - "backup-created": "Backup created successfully", + "backup-created": "Backup realizado com sucesso", "backup-created-at-response-export_path": "Backup criado em {path}", "backup-deleted": "Backup eliminado", - "restore-success": "Restore successful", + "restore-success": "Restauro bem-sucedido", "backup-tag": "Cópia de segurança de Etiqueta", "create-heading": "Criar um Backup", "delete-backup": "Eliminar Backup", @@ -690,13 +690,13 @@ "configuration": "Configuração", "docker-volume": "Volume do Docker", "docker-volume-help": "O Mealie requer que o contentor do frontend e do backend partilhem o mesmo volume ou armazenamento do docker. Isso garante que o contentor do frontend pode aceder corretamente às imagens e recursos armazenados no disco.", - "volumes-are-misconfigured": "Volumes are misconfigured.", + "volumes-are-misconfigured": "Os volumes estão mal configurados.", "volumes-are-configured-correctly": "Os volumes estão configurados corretamente.", "status-unknown-try-running-a-validation": "Estado desconhecido. Tente executar uma validação.", "validate": "Validar", "email-configuration-status": "Estado de configuração do correio eletrónico", - "email-configured": "Email Configured", - "email-test-results": "Email Test Results", + "email-configured": "Email configurado", + "email-test-results": "Resultados do Teste de Email", "ready": "Pronto", "not-ready": "Não Pronto — Verificar Variáveis de Ambiente", "succeeded": "Sucesso", @@ -831,7 +831,7 @@ "password-updated": "Palavra-passe atualizada", "password": "Palavra-passe", "password-strength": "A palavra-passe é {strength}", - "please-enter-password": "Please enter your new password.", + "please-enter-password": "Por favor, introduza a sua nova palavra-passe.", "register": "Registar", "reset-password": "Repor Palavra-passe", "sign-in": "Inscreva-se", @@ -852,7 +852,7 @@ "username": "Nome de utilizador", "users-header": "UTILIZADORES", "users": "Utilizadores", - "user-not-found": "User not found", + "user-not-found": "Utilizador não encontrado", "webhook-time": "Hora do Webhook", "webhooks-enabled": "Webhooks ativados", "you-are-not-allowed-to-create-a-user": "Não tem permissão para criar um utilizador", @@ -875,7 +875,7 @@ "user-management": "Gestão de utilizadores", "reset-locked-users": "Reiniciar utilizadores bloqueados", "admin-user-creation": "Criação do Utilizador Administrador", - "admin-user-management": "Admin User Management", + "admin-user-management": "Gestão do Grupo Admin", "user-details": "Detalhes do Utilizador", "user-name": "Nome do Utilizador", "authentication-method": "Método de Autenticação", @@ -886,11 +886,11 @@ "user-can-manage-group": "O utilizador pode gerir o grupo", "user-can-organize-group-data": "O utilizador pode organizar dados do grupo", "enable-advanced-features": "Habilitar recursos avançados", - "it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.", - "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!", - "forgot-password": "Forgot Password", - "forgot-password-text": "Please enter your email address and we will send you a link to reset your password.", - "changes-reflected-immediately": "Changes to this user will be reflected immediately." + "it-looks-like-this-is-your-first-time-logging-in": "Parece que este é o seu primeiro login.", + "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Não quer voltar a ver isto? Não se esqueça de alterar o seu email nas suas definições de utilizador!", + "forgot-password": "Esqueceu-se da palavra-passe", + "forgot-password-text": "Por favor, digite o seu endereço de email para enviarmos um link para redefinir a sua palavra-passe.", + "changes-reflected-immediately": "As alterações a este utilizador serão aplicadas imediatamente." }, "language-dialog": { "translated": "traduzido", @@ -912,8 +912,8 @@ "food-label": "Rótulo de Alimento", "edit-food": "Editar Alimento", "food-data": "Dados do Alimento", - "example-food-singular": "ex: Onion", - "example-food-plural": "ex: Onions" + "example-food-singular": "ex: Cebola", + "example-food-plural": "ex: Cebolas" }, "units": { "seed-dialog-text": "Popule a base de dados com unidades comuns no seu idioma.", @@ -924,7 +924,7 @@ "merging-unit-into-unit": "A juntar {0} com {1}", "create-unit": "Criar Unidade", "abbreviation": "Abreviatura", - "plural-abbreviation": "Plural Abbreviation", + "plural-abbreviation": "Abreviatura no Plural", "description": "Descrição", "display-as-fraction": "Mostrar como fração", "use-abbreviation": "Usar abreviatura", @@ -932,10 +932,10 @@ "unit-data": "Dados da Unidade", "use-abbv": "Usar Abrev.", "fraction": "Fração", - "example-unit-singular": "ex: Tablespoon", - "example-unit-plural": "ex: Tablespoons", - "example-unit-abbreviation-singular": "ex: Tbsp", - "example-unit-abbreviation-plural": "ex: Tbsps" + "example-unit-singular": "ex: Colher de Sopa", + "example-unit-plural": "ex: Colheres de Sopa", + "example-unit-abbreviation-singular": "ex: Cdsp", + "example-unit-abbreviation-plural": "ex: Cdsps" }, "labels": { "seed-dialog-text": "Adicionar à base de dados rótulos comuns no seu idioma local.", @@ -964,8 +964,8 @@ "delete-recipes": "Eliminar Receitas", "source-unit-will-be-deleted": "Unidade de origem será eliminada" }, - "create-alias": "Create Alias", - "manage-aliases": "Manage Aliases", + "create-alias": "Criar Pseudónimo", + "manage-aliases": "Gerir Pseudónimos", "seed-data": "Gerar dados", "seed": "Gerar", "data-management": "Gestão de dados", @@ -975,24 +975,24 @@ "columns": "Colunas", "combine": "Combinar", "categories": { - "edit-category": "Edit Category", - "new-category": "New Category", - "category-data": "Category Data" + "edit-category": "Editar Categoria", + "new-category": "Nova Categoria", + "category-data": "Dados de Categoria" }, "tags": { - "new-tag": "New Tag", - "edit-tag": "Edit Tag", - "tag-data": "Tag Data" + "new-tag": "Nova etiqueta", + "edit-tag": "Editar Etiqueta", + "tag-data": "Dados de Etiqueta" }, "tools": { - "new-tool": "New Tool", - "edit-tool": "Edit Tool", - "tool-data": "Tool Data" + "new-tool": "Novo Utensílio", + "edit-tool": "Editar Utensílio", + "tool-data": "Dados do Utensílio" } }, "user-registration": { "user-registration": "Registo de Utilizador", - "registration-success": "Registration Success", + "registration-success": "Registo Bem-sucedido", "join-a-group": "Juntar-se a um grupo", "create-a-new-group": "Criar um Novo Grupo", "provide-registration-token-description": "Por favor, forneça o token de registo associado ao grupo a que gostaria de aderir. Terá de o obter de um membro atual do grupo.", @@ -1039,7 +1039,7 @@ }, "ocr-editor": { "ocr-editor": "Editor OCR", - "toolbar": "Toolbar", + "toolbar": "Barra de ferramentas", "selection-mode": "Modo de seleção", "pan-and-zoom-picture": "Deslocar e ampliar imagem", "split-text": "Dividir texto", @@ -1047,8 +1047,8 @@ "split-by-block": "Dividir por bloco de texto", "flatten": "Nivelar independentemente da formatação original", "help": { - "help": "Help", - "mouse-modes": "Mouse modes", + "help": "Ajuda", + "mouse-modes": "Modos do rato", "selection-mode": "Modo de Seleção (padrão)", "selection-mode-desc": "O modo de seleção é o modo principal disponível para inserir dados:", "selection-mode-steps": { From a5fecbc48a1c25cd87dc37ce89d87c677369f709 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:00:39 -0600 Subject: [PATCH 02/74] New translations en-us.json (Norwegian) (#2776) --- frontend/lang/messages/no-NO.json | 50 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/frontend/lang/messages/no-NO.json b/frontend/lang/messages/no-NO.json index 9800b1a4f55f..60fc80724ef8 100644 --- a/frontend/lang/messages/no-NO.json +++ b/frontend/lang/messages/no-NO.json @@ -77,7 +77,7 @@ "tag-events": "Tagg hendelser", "category-events": "Kategori hendelser", "when-a-new-user-joins-your-group": "Når en ny bruker blir med i gruppen din", - "recipe-events": "Recipe Events" + "recipe-events": "Oppskrift hendelser" }, "general": { "cancel": "Avbryt", @@ -115,8 +115,8 @@ "keyword": "Nøkkelord", "link-copied": "Lenke kopiert", "loading-events": "Laster hendelser", - "loading-recipe": "Loading recipe...", - "loading-ocr-data": "Loading OCR data...", + "loading-recipe": "Laster oppskrift...", + "loading-ocr-data": "Laster OCR data...", "loading-recipes": "Laster Oppskrifter", "message": "Melding", "monday": "Mandag", @@ -197,7 +197,7 @@ "refresh": "Oppdater", "upload-file": "Last opp fil", "created-on-date": "Opprettet: {0}", - "unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes." + "unsaved-changes": "Du har ulagrede endringer. Vil du lagre før du forlater den? Okay å lagre, Avbryt for å forkaste endringer." }, "group": { "are-you-sure-you-want-to-delete-the-group": "Er du sikker på at du vil slette {groupName}?", @@ -248,7 +248,7 @@ "general-preferences": "Generelle innstillinger", "group-recipe-preferences": "Innstillinger for gruppe-oppskrift", "report": "Rapport", - "report-with-id": "Report ID: {id}", + "report-with-id": "Rapport-ID: {id}", "group-management": "Gruppeadministrasjon", "admin-group-management": "Admin gruppe-administrasjon", "admin-group-management-text": "Endringer i denne gruppen vil umiddelbart bli reflektert.", @@ -572,7 +572,7 @@ "search-hint": "Trykk på '/'", "advanced": "Avansert", "auto-search": "Autosøk", - "no-results": "No results found" + "no-results": "Ingen resultater funnet" }, "settings": { "add-a-new-theme": "Legg til nytt tema", @@ -581,7 +581,7 @@ "backup-created": "Backup created successfully", "backup-created-at-response-export_path": "Sikkerhetskopi opprettet på {path}", "backup-deleted": "Sikkerhetskopi slettet", - "restore-success": "Restore successful", + "restore-success": "Gjenopprettingen var vellykket", "backup-tag": "Sikkerhetskopi-merke", "create-heading": "Opprett sikkerhetskopi", "delete-backup": "Slett Sikkerhetskopi", @@ -690,12 +690,12 @@ "configuration": "Konfigurasjon", "docker-volume": "Docker volum", "docker-volume-help": "Mealie krever at frontend og backend konteinerene deler samme docker volum/lagring. Dette sikrer at frontend får tilgang til bilder og ressurser lagret på harddisken.", - "volumes-are-misconfigured": "Volumes are misconfigured.", + "volumes-are-misconfigured": "Volumene er feilkonfigurert.", "volumes-are-configured-correctly": "Volumene er riktig konfigurert.", "status-unknown-try-running-a-validation": "Statusen er ukjent. Prøv å validere.", "validate": "Valider", "email-configuration-status": "Epost konfigurasjons status", - "email-configured": "Email Configured", + "email-configured": "E-post konfigurert", "email-test-results": "Email Test Results", "ready": "Klar", "not-ready": "Ikke klar - sjekk miljøvariabler", @@ -852,7 +852,7 @@ "username": "Brukernavn", "users-header": "BRUKERE", "users": "Brukere", - "user-not-found": "User not found", + "user-not-found": "Bruker ikke funnet", "webhook-time": "Webhooks Tidsbruk", "webhooks-enabled": "Webhooks aktivert", "you-are-not-allowed-to-create-a-user": "Du har ikke rettigheter til å opprette en bruker", @@ -958,13 +958,13 @@ "tag": "Etikett", "categorize": "Kategoriser", "update-settings": "Oppdater innstillinger", - "tag-recipes": "Tag Recipes", - "categorize-recipes": "Categorize Recipes", + "tag-recipes": "Tagg oppskrifter", + "categorize-recipes": "Kategoriser oppskrifter", "export-recipes": "Eksporter oppskrift", "delete-recipes": "Slett oppskrifter", "source-unit-will-be-deleted": "Kildeenheten vil bli slettet" }, - "create-alias": "Create Alias", + "create-alias": "Opprett alias", "manage-aliases": "Manage Aliases", "seed-data": "Frødata", "seed": "Seed", @@ -975,18 +975,18 @@ "columns": "Kolonner", "combine": "Kombiner", "categories": { - "edit-category": "Edit Category", - "new-category": "New Category", - "category-data": "Category Data" + "edit-category": "Rediger kategori", + "new-category": "Ny Kategori", + "category-data": "Kategoridata" }, "tags": { - "new-tag": "New Tag", - "edit-tag": "Edit Tag", - "tag-data": "Tag Data" + "new-tag": "Ny tagg", + "edit-tag": "Rediger Tag", + "tag-data": "Tagg data" }, "tools": { - "new-tool": "New Tool", - "edit-tool": "Edit Tool", + "new-tool": "Nytt verktøy", + "edit-tool": "Rediger verktøy", "tool-data": "Tool Data" } }, @@ -1047,8 +1047,8 @@ "split-by-block": "Del på tekstblokk", "flatten": "Flat ut, uavhengig av orginalformatering.", "help": { - "help": "Help", - "mouse-modes": "Mouse modes", + "help": "Hjelp", + "mouse-modes": "Musemodus", "selection-mode": "Valgmodus (standard)", "selection-mode-desc": "Utvalgsmodus er hovedmodus som kan brukes til å legge inn data:", "selection-mode-steps": { @@ -1081,7 +1081,7 @@ "info-description-cleanable-directories": "Cleanable Directories", "info-description-cleanable-images": "Rydbare bilder", "storage": { - "title-temporary-directory": "Temporary Directory (.temp)", + "title-temporary-directory": "Midlertidig mappe (.temp)", "title-backups-directory": "Backups Directory (backups)", "title-groups-directory": "Groups Directory (groups)", "title-recipes-directory": "Recipes Directory (recipes)", @@ -1094,7 +1094,7 @@ "action-clean-temporary-files-name": "Fjern midlertidige filer", "action-clean-temporary-files-description": "Fjerner alle filer og mapper i .temp-mappen", "action-clean-images-name": "Fjern bilder", - "action-clean-images-description": "Removes all the images that don't end with .webp", + "action-clean-images-description": "Fjerner alle bildene som ikke slutter med .webp", "actions-description": "Maintenance actions are {destructive_in_bold} and should be used with caution. Performing any of these actions is {irreversible_in_bold}.", "actions-description-destructive": "destruktiv", "actions-description-irreversible": "irreversibel", From 79b4342690096706d18568617d1582bb2af6c62c Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:59:15 -0600 Subject: [PATCH 03/74] New translations en-us.json (Japanese) --- frontend/lang/messages/ja-JP.json | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frontend/lang/messages/ja-JP.json b/frontend/lang/messages/ja-JP.json index 87e6c2500054..5f5d63e390db 100644 --- a/frontend/lang/messages/ja-JP.json +++ b/frontend/lang/messages/ja-JP.json @@ -10,7 +10,7 @@ "default-group": "デフォルトグループ", "demo": "体験版", "demo-status": "体験版ステータス", - "development": "Development", + "development": "開発", "docs": "ドキュメント", "download-log": "ログをダウンロード", "download-recipe-json": "Last Scraped JSON", @@ -42,8 +42,8 @@ "category-deleted": "カテゴリを削除しました。", "category-deletion-failed": "カテゴリの削除に失敗しました。", "category-filter": "カテゴリフィルタ", - "category-update-failed": "Category update failed", - "category-updated": "Category updated", + "category-update-failed": "カテゴリの更新に失敗しました", + "category-updated": "カテゴリーを更新しました", "uncategorized-count": "カテゴリなし {count}", "create-a-category": "カテゴリを作成", "category-name": "カテゴリ名", @@ -96,8 +96,8 @@ "duplicate": "複製", "edit": "編集", "enabled": "有効", - "exception": "Exception", - "failed-count": "Failed: {count}", + "exception": "例外", + "failed-count": "失敗: {count}", "failure-uploading-file": "Failure uploading file", "favorites": "お気に入り", "field-required": "Field Required", @@ -172,7 +172,7 @@ "id": "Id", "owner": "Owner", "date-added": "追加日", - "none": "None", + "none": "なし", "run": "Run", "menu": "メニュー", "a-name-is-required": "名前は必須です。", @@ -200,9 +200,9 @@ "unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes." }, "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", + "are-you-sure-you-want-to-delete-the-group": "{groupName} を削除しますか?", + "cannot-delete-default-group": "デフォルトのルグループは削除できません", + "cannot-delete-group-with-users": "ユーザがあるグループを削除できません", "confirm-group-deletion": "Confirm Group Deletion", "create-group": "Create Group", "error-updating-group": "Error updating group", @@ -237,7 +237,7 @@ "allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link", "show-nutrition-information": "Show nutrition information", "show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown", - "show-recipe-assets": "Show recipe assets", + "show-recipe-assets": "レシピアセットを表示", "show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available", "default-to-landscape-view": "Default to landscape view", "default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view", @@ -263,16 +263,16 @@ "end-date": "End Date", "group": "Group (Beta)", "main": "主菜", - "meal-planner": " 献立表", + "meal-planner": "献立表", "meal-plans": "献立", "mealplan-categories": "MEALPLAN CATEGORIES", - "mealplan-created": "Mealplan created", - "mealplan-creation-failed": "Mealplan creation failed", - "mealplan-deleted": "Mealplan Deleted", - "mealplan-deletion-failed": "Mealplan deletion failed", + "mealplan-created": "献立に作成しました", + "mealplan-creation-failed": "献立の作成に失敗しました", + "mealplan-deleted": "献立を削除しました", + "mealplan-deletion-failed": "献立の削除に失敗しました", "mealplan-settings": "献立設定", - "mealplan-update-failed": "Mealplan update failed", - "mealplan-updated": "Mealplan Updated", + "mealplan-update-failed": "献立の更新に失敗しました", + "mealplan-updated": "献立を更新しました", "no-meal-plan-defined-yet": "No meal plan defined yet", "no-meal-planned-for-today": "No meal planned for today", "only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans", From 5639047167c8950df7b57141cf8dc6abf9d461bf Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:59:16 -0600 Subject: [PATCH 04/74] New translations en-us.json (Japanese) --- mealie/lang/messages/ja-JP.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mealie/lang/messages/ja-JP.json b/mealie/lang/messages/ja-JP.json index d89050973be6..25ad979ccdc0 100644 --- a/mealie/lang/messages/ja-JP.json +++ b/mealie/lang/messages/ja-JP.json @@ -6,7 +6,7 @@ "unique-name-error": "Recipe names must be unique" }, "mealplan": { - "no-recipes-match-your-rules": "No recipes match your rules" + "no-recipes-match-your-rules": "ルールに一致するレシピはありません" }, "user": { "user-updated": "ユーザを更新しました。", From c1393a6b0c6bc5d62d8773ecf6b05709f19d73e1 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:59:17 -0600 Subject: [PATCH 05/74] New translations en-us.json (Japanese) --- .../seed/resources/foods/locales/ja-JP.json | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mealie/repos/seed/resources/foods/locales/ja-JP.json b/mealie/repos/seed/resources/foods/locales/ja-JP.json index 44cb4ffb16b7..7dca749e4a36 100644 --- a/mealie/repos/seed/resources/foods/locales/ja-JP.json +++ b/mealie/repos/seed/resources/foods/locales/ja-JP.json @@ -1,20 +1,20 @@ { "acorn-squash": "acorn squash", "alfalfa-sprouts": "alfalfa sprouts", - "anchovies": "anchovies", + "anchovies": "アンチョビ", "apples": "りんご", - "artichoke": "artichoke", - "arugula": "arugula", - "asparagus": "asparagus", + "artichoke": "アーティチョーク", + "arugula": "ルッコラ", + "asparagus": "アスパラガス", "aubergine": "茄子", "avocado": "アボカド", "bacon": "ベーコン", - "baking-powder": "baking powder", - "baking-soda": "baking soda", + "baking-powder": "ベーキングパウダー", + "baking-soda": "重曹", "baking-sugar": "baking sugar", "bar-sugar": "bar sugar", "basil": "バジル", - "bell-peppers": "bell peppers", + "bell-peppers": "ピーマン", "blackberries": "ブラックベリー", "brassicas": "brassicas", "bok-choy": "bok choy", @@ -64,7 +64,7 @@ "corn": "トウモロコシ", "corn-syrup": "corn syrup", "cottonseed-oil": "cottonseed oil", - "courgette": "courgette", + "courgette": "ズッキーニ", "cream-of-tartar": "cream of tartar", "cucumber": "きゅうり", "cumin": "cumin", @@ -157,12 +157,12 @@ "onion-family": "onion family", "onion": "玉ねぎ", "scallion": "scallion", - "shallot": "shallot", - "spring-onion": "spring onion", - "orange-blossom-water": "orange blossom water", + "shallot": "エシャロット", + "spring-onion": "ネギ", + "orange-blossom-water": "オレンジの花の水", "oysters": "oysters", "panch-puran": "panch puran", - "paprika": "paprika", + "paprika": "パプリカ", "parsnip": "parsnip", "pepper": "pepper", "peppers": "peppers", @@ -179,9 +179,9 @@ "refined-sugar": "refined sugar", "rice-flour": "rice flour", "rock-sugar": "rock sugar", - "rum": "rum", + "rum": "ラム酒", "salt": "塩", - "seafood": "seafood", + "seafood": "シーフード", "seeds": "seeds", "sesame-seeds": "sesame seeds", "sunflower-seeds": "sunflower seeds", From 27c4d92517ac1d11d4dd5a3526d6fa772ca720c4 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 2 Dec 2023 13:59:19 -0600 Subject: [PATCH 06/74] New translations en-us.json (Catalan) --- frontend/lang/messages/ca-ES.json | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/lang/messages/ca-ES.json b/frontend/lang/messages/ca-ES.json index fb321dee89d4..8548108da3ee 100644 --- a/frontend/lang/messages/ca-ES.json +++ b/frontend/lang/messages/ca-ES.json @@ -77,7 +77,7 @@ "tag-events": "Esdeveniments de les etiquetes", "category-events": "Esdeveniments de les categories", "when-a-new-user-joins-your-group": "Quan un nou usuari s'afegeix al grup", - "recipe-events": "Recipe Events" + "recipe-events": "Esdeveniments de receptes" }, "general": { "cancel": "Anuŀla", @@ -699,15 +699,15 @@ "email-test-results": "Email Test Results", "ready": "Ready", "not-ready": "Not Ready - Check Environmental Variables", - "succeeded": "Succeeded", - "failed": "Failed", - "general-about": "General About", - "application-version": "Application Version", - "application-version-error-text": "Your current version ({0}) does not match the latest release. Considering updating to the latest version ({1}).", - "mealie-is-up-to-date": "Mealie is up to date", - "secure-site": "Secure Site", - "secure-site-error-text": "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.", - "secure-site-success-text": "Site is accessed by localhost or https", + "succeeded": "Va tenir èxit", + "failed": "Ha fallat", + "general-about": "Informació General", + "application-version": "Versió de l'Aplicació", + "application-version-error-text": "La teva versió actual ({0}) no correspon amb l'última publicada. Considera actualitzar a l'última versió ({1}).", + "mealie-is-up-to-date": "Mealie està actualitzat", + "secure-site": "Web Segur", + "secure-site-error-text": "Allotja mitjançant localhost o assegura amb https. És possible que el porta-retalls i API addicionals del navegador no funcionin.", + "secure-site-success-text": "El web és accedit amb localhost o https", "server-side-base-url": "Server Side Base URL", "server-side-base-url-error-text": "`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.", "server-side-base-url-success-text": "Server Side URL does not match the default", @@ -731,15 +731,15 @@ "label": "Etiqueta", "linked-item-warning": "Aquest element està enllaçat amb una o més receptes. Modificar les unitats o els aliments pot provocar resultats inesperats en afegir o elimina la recepta del llistat.", "toggle-food": "Mostra el nom de l'aliment", - "manage-labels": "Manage Labels", - "are-you-sure-you-want-to-delete-this-item": "Are you sure you want to delete this item?", - "copy-as-text": "Copy as Text", - "copy-as-markdown": "Copy as Markdown", - "delete-checked": "Delete Checked", - "toggle-label-sort": "Toggle Label Sort", - "reorder-labels": "Reorder Labels", - "uncheck-all-items": "Uncheck All Items", - "check-all-items": "Check All Items", + "manage-labels": "Gestiona etiquetes", + "are-you-sure-you-want-to-delete-this-item": "Estàs segur/a que vols eliminar aquest ítem?", + "copy-as-text": "Copia com a text", + "copy-as-markdown": "Copia com a Markdown", + "delete-checked": "Suprimeix la selecció", + "toggle-label-sort": "Activa/Desactiva l'ordre per etiquetes", + "reorder-labels": "Canvia d'ordre les etiquetes", + "uncheck-all-items": "Desselecciona tots els ítems", + "check-all-items": "Selecciona tots els ítems", "linked-recipes-count": "No Linked Recipes|One Linked Recipe|{count} Linked Recipes", "items-checked-count": "No items checked|One item checked|{count} items checked", "no-label": "Sense etiqueta", From f32444b91dcb920e3a1960d99fd9204f1395f107 Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Sun, 3 Dec 2023 06:15:44 +0100 Subject: [PATCH 07/74] fix: handle Recipe Times as dicts and lists (#2764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * handle dicts * 🧹 * handle arrays * change default case & add warning logger * lint * typo * update dict case * update list case * add timedelta to cases * remove timedelta so mypy is happy --- mealie/services/scraper/cleaner.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mealie/services/scraper/cleaner.py b/mealie/services/scraper/cleaner.py index e5cdf6d94c30..f4bfc0ad7083 100644 --- a/mealie/services/scraper/cleaner.py +++ b/mealie/services/scraper/cleaner.py @@ -9,6 +9,11 @@ from datetime import datetime, timedelta from slugify import slugify +from mealie.core.root_logger import get_logger + +logger = get_logger("recipe-scraper") + + MATCH_DIGITS = re.compile(r"\d+([.,]\d+)?") """ Allow for commas as decimals (common in Europe) """ @@ -335,6 +340,7 @@ def clean_time(time_entry: str | timedelta | None) -> None | str: - `"PT1H"` - returns "1 hour" - `"PT1H30M"` - returns "1 hour 30 minutes" - `timedelta(hours=1, minutes=30)` - returns "1 hour 30 minutes" + - `{"minValue": "PT1H30M"}` - returns "1 hour 30 minutes" Raises: TypeError: if the type is not supported a TypeError is raised @@ -357,11 +363,16 @@ def clean_time(time_entry: str | timedelta | None) -> None | str: return str(time_entry) case timedelta(): return pretty_print_timedelta(time_entry) + case {"minValue": str(value)}: + return clean_time(value) + case [str(), *_]: + return clean_time(time_entry[0]) case datetime(): # TODO: Not sure what to do here return str(time_entry) case _: - raise TypeError(f"Unexpected type for time: {type(time_entry)}, {time_entry}") + logger.warning("[SCRAPER] Unexpected type or structure for time_entrys") + return None def parse_duration(iso_duration: str) -> timedelta: From 84b2519320879fef6c6266f1f92de95712c45d30 Mon Sep 17 00:00:00 2001 From: Dany Marcoux Date: Sun, 3 Dec 2023 12:39:51 +0100 Subject: [PATCH 08/74] Link to GitHub registry instead of DockerHub in the docs --- .../getting-started/installation/installation-checklist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/documentation/getting-started/installation/installation-checklist.md b/docs/docs/documentation/getting-started/installation/installation-checklist.md index c43fb3f52aa6..5df357cc3278 100644 --- a/docs/docs/documentation/getting-started/installation/installation-checklist.md +++ b/docs/docs/documentation/getting-started/installation/installation-checklist.md @@ -17,7 +17,7 @@ To deploy mealie on your local network it is highly recommended to use docker to [Get Docker Compose](https://docs.docker.com/compose/install/) -[Mealie on Dockerhub](https://hub.docker.com/r/hkotel/mealie) +[Mealie on GitHub registry](https://github.com/mealie-recipes/mealie/pkgs/container/mealie) - linux/amd64 - linux/arm64 From 0e45b962b2f2294f0234857282bdb0ef4717fa8f Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Sun, 3 Dec 2023 23:05:34 +0100 Subject: [PATCH 09/74] docs: remove from installation checklist (#2780) --- .../getting-started/installation/installation-checklist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/documentation/getting-started/installation/installation-checklist.md b/docs/docs/documentation/getting-started/installation/installation-checklist.md index 5df357cc3278..12f767bd860e 100644 --- a/docs/docs/documentation/getting-started/installation/installation-checklist.md +++ b/docs/docs/documentation/getting-started/installation/installation-checklist.md @@ -11,7 +11,7 @@ To install Mealie on your server there are a few steps for proper configuration. ## Pre-work -To deploy mealie on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose templates provided, you should be able to get a stack up and running easily by changing a few default values and deploying. You can deploy with either SQLite (default) or Postgres. SQLite is sufficient for most use cases. Additionally, with Mealie's automated backup and restore functionality, you can easily move between SQLite and Postgres as you wish. +To deploy mealie on your local network it is highly recommended to use docker to deploy the image straight from the GitHub registry. Using the docker-compose templates provided, you should be able to get a stack up and running easily by changing a few default values and deploying. You can deploy with either SQLite (default) or Postgres. SQLite is sufficient for most use cases. Additionally, with Mealie's automated backup and restore functionality, you can easily move between SQLite and Postgres as you wish. [Get Docker](https://docs.docker.com/get-docker/) From 1ac6e651d19f3697111a7b781a70954504ff3044 Mon Sep 17 00:00:00 2001 From: Dany Marcoux Date: Sun, 3 Dec 2023 23:06:24 +0100 Subject: [PATCH 10/74] docs: use github.com/mealie-recipes/mealie instead of older github.com/hay-kot/mealie (#2781) This was done with the following command: sed -i -e 's|github\.com/hay-kot/mealie|github.com/mealie-recipes/mealie|g' docs/docs/documentation/**/*.md --- docs/docs/documentation/community-guide/ios.md | 4 ++-- docs/docs/documentation/getting-started/introduction.md | 2 +- .../documentation/getting-started/migrating-to-mealie-v1.md | 2 +- .../getting-started/usage/backups-and-restoring.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/documentation/community-guide/ios.md b/docs/docs/documentation/community-guide/ios.md index 86d5c6bec8b2..dfdb0127e1ec 100644 --- a/docs/docs/documentation/community-guide/ios.md +++ b/docs/docs/documentation/community-guide/ios.md @@ -5,8 +5,8 @@ ![Image from apple site](https://help.apple.com/assets/5E8CEA35094622DF10489984/5E8CEA42094622DF1048998D/en_US/ed1f9c157cdefc13e0161e0f70015455.png) -User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/hay-kot/mealie/issues/103) for interested users. -This original method broke after the transition to version 1.X and an issue was raised on [Github](https://github.com/hay-kot/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X. +User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/mealie-recipes/mealie/issues/103) for interested users. +This original method broke after the transition to version 1.X and an issue was raised on [Github](https://github.com/mealie-recipes/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X. This is a useful utility for iOS users who browse for recipes in their web browser from their devices. diff --git a/docs/docs/documentation/getting-started/introduction.md b/docs/docs/documentation/getting-started/introduction.md index a3091b6e55e4..776d9312043f 100644 --- a/docs/docs/documentation/getting-started/introduction.md +++ b/docs/docs/documentation/getting-started/introduction.md @@ -4,7 +4,7 @@ This documentation is for the Mealie v1 Beta release and is not final. As such, it may contain incomplete or incorrect information. You should understand that installing Mealie v1 Beta is a work in progress and while we've committed to maintaining the database schema and provided migrations, we are still in the process of adding new features, and robust testing to ensure the application works as expected. - You should likely find bugs, errors, and unfinished pages within the application. To find the current status of the release you can checkout the [project on github](https://github.com/hay-kot/mealie/projects/7) or reach out on discord. + You should likely find bugs, errors, and unfinished pages within the application. To find the current status of the release you can checkout the [project on github](https://github.com/mealie-recipes/mealie/projects/7) or reach out on discord. Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and Mealie will automatically import the relevant data or add a family recipe with the UI editor. Mealie also provides an API for interactions from 3rd party applications. diff --git a/docs/docs/documentation/getting-started/migrating-to-mealie-v1.md b/docs/docs/documentation/getting-started/migrating-to-mealie-v1.md index 974d51c026f8..26a3c573370c 100644 --- a/docs/docs/documentation/getting-started/migrating-to-mealie-v1.md +++ b/docs/docs/documentation/getting-started/migrating-to-mealie-v1.md @@ -54,4 +54,4 @@ In most cases, it's faster to manually migrate the recipes that didn't take inst v1 Comes with a whole host of new features and improvements. Checkout the changelog to get a sense for what's new. -- [Github releases changelog](https://github.com/hay-kot/mealie/releases) +- [Github releases changelog](https://github.com/mealie-recipes/mealie/releases) diff --git a/docs/docs/documentation/getting-started/usage/backups-and-restoring.md b/docs/docs/documentation/getting-started/usage/backups-and-restoring.md index 7c160427b25e..84b9317b2f76 100644 --- a/docs/docs/documentation/getting-started/usage/backups-and-restoring.md +++ b/docs/docs/documentation/getting-started/usage/backups-and-restoring.md @@ -34,4 +34,4 @@ ALTER USER mealie WITH SUPERUSER; ALTER USER mealie WITH NOSUPERUSER; ``` -For more information see [GitHub Issue #1500](https://github.com/hay-kot/mealie/issues/1500) +For more information see [GitHub Issue #1500](https://github.com/mealie-recipes/mealie/issues/1500) From b369417690b6ecd7b8f09a988f5f978667d3be70 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:58:46 -0600 Subject: [PATCH 11/74] chore: restore latest tag (#2784) * restore dockerhub publishing * restore latest publishing tag * fix tag/tags inputs --- .github/workflows/partial-builder.yml | 18 +++++++++++++++--- .github/workflows/release.yml | 3 +++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/partial-builder.yml b/.github/workflows/partial-builder.yml index 583b84354a21..848d19dd7e86 100644 --- a/.github/workflows/partial-builder.yml +++ b/.github/workflows/partial-builder.yml @@ -6,6 +6,9 @@ on: tag: required: true type: string + tags: + required: false + type: string secrets: DOCKERHUB_USERNAME: required: true @@ -19,13 +22,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Log in to the Container registry + - name: Log in to the Container registry (ghcr.io) uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Log in to the Container registry (dockerhub) + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -37,13 +46,16 @@ jobs: echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py - name: Build and push Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: file: ./docker/Dockerfile context: . platforms: linux/amd64,linux/arm64 push: true - tags: ghcr.io/${{ github.repository }}:${{ inputs.tag }} + tags: | + hkotel/mealie:${{ inputs.tag }} + ghcr.io/${{ github.repository }}:${{ inputs.tag }} + ${{ inputs.tags }} build-args: | COMMIT=${{ github.sha }} # https://docs.docker.com/build/ci/github-actions/cache/#github-cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1dff7eddd20..8b1b6fe6574e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,9 @@ jobs: - frontend-tests with: tag: ${{ github.event.release.tag_name }} + tags: | + hkotel/mealie:latest + ghcr.io/${{ github.repository }}:latest secrets: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} From 2751e8318a89df4e016a3bafb7e0ee851ae2b57d Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:18:21 +0100 Subject: [PATCH 12/74] cleanup: Update makefile (#2789) * Update makefile * change from docker to prod --- makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makefile b/makefile index aa63259a65ea..7bbdfbe81316 100644 --- a/makefile +++ b/makefile @@ -122,8 +122,8 @@ frontend-lint: ## 🧺 Run yarn lint # ----------------------------------------------------------------------------- # Docker makefile -docker/prod: ## 🐳 Build and Start Docker Production Stack - cd docker && docker-compose -f docker-compose.yml -p mealie up --build +prod: ## 🐳 Build and Start Docker Production Stack + cd docker && docker compose -f docker-compose.yml -p mealie up --build generate: poetry run python dev/code-generation/main.py From 1d1d61df77f4c0e3619050e941e78a603ff81842 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 6 Dec 2023 09:01:48 -0600 Subject: [PATCH 13/74] fix: Missing Title and Metadata (#2770) * add document title to server spa meta * removed conflicting useMeta * replaced head with useMeta * formalized metadata injection * small injection refactor * added tests * added missing global tag * fixed setting tab title for logged-in users * simplified metadata update * remove duplicate tag and fix for foreign users * add metadata for shared recipes * added default recipe image * fixed shared URL --------- Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> --- frontend/pages/g/_groupSlug/r/_slug/index.vue | 27 +++--- mealie/routes/spa/__init__.py | 94 ++++++++++++++++--- tests/data/__init__.py | 2 + tests/data/html/mealie-recipe.html | 41 ++++++++ tests/integration_tests/test_spa.py | 70 ++++++++++++++ 5 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 tests/data/html/mealie-recipe.html create mode 100644 tests/integration_tests/test_spa.py diff --git a/frontend/pages/g/_groupSlug/r/_slug/index.vue b/frontend/pages/g/_groupSlug/r/_slug/index.vue index ffdb262ed40e..2e6abf18af8a 100644 --- a/frontend/pages/g/_groupSlug/r/_slug/index.vue +++ b/frontend/pages/g/_groupSlug/r/_slug/index.vue @@ -6,7 +6,9 @@ diff --git a/mealie/routes/spa/__init__.py b/mealie/routes/spa/__init__.py index 357fea085018..abf59d5142cb 100644 --- a/mealie/routes/spa/__init__.py +++ b/mealie/routes/spa/__init__.py @@ -1,6 +1,8 @@ import json import pathlib +from dataclasses import dataclass +from bs4 import BeautifulSoup from fastapi import Depends, FastAPI, Response from fastapi.encoders import jsonable_encoder from fastapi.staticfiles import StaticFiles @@ -16,6 +18,13 @@ from mealie.schema.recipe.recipe import Recipe from mealie.schema.user.user import PrivateUser +@dataclass +class MetaTag: + hid: str + property_name: str + content: str + + class SPAStaticFiles(StaticFiles): async def get_response(self, path: str, scope): try: @@ -33,10 +42,51 @@ __app_settings = get_app_settings() __contents = "" +def inject_meta(contents: str, tags: list[MetaTag]) -> str: + soup = BeautifulSoup(contents, "lxml") + scraped_meta_tags = soup.find_all("meta") + + tags_by_hid = {tag.hid: tag for tag in tags} + for scraped_meta_tag in scraped_meta_tags: + try: + scraped_hid = scraped_meta_tag["data-hid"] + except KeyError: + continue + + if not (matched_tag := tags_by_hid.pop(scraped_hid, None)): + continue + + scraped_meta_tag["property"] = matched_tag.property_name + scraped_meta_tag["content"] = matched_tag.content + + # add any tags we didn't find + if soup.html and soup.html.head: + for tag in tags_by_hid.values(): + html_tag = soup.new_tag( + "meta", + **{"data-n-head": "1", "data-hid": tag.hid, "property": tag.property_name, "content": tag.content}, + ) + soup.html.head.append(html_tag) + + return str(soup) + + +def inject_recipe_json(contents: str, schema: dict) -> str: + schema_as_html_tag = f"""""" + return contents.replace("", schema_as_html_tag + "\n", 1) + + def content_with_meta(group_slug: str, recipe: Recipe) -> str: # Inject meta tags recipe_url = f"{__app_settings.BASE_URL}/g/{group_slug}/r/{recipe.slug}" - image_url = f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}" + if recipe.image: + image_url = ( + f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}" + ) + else: + image_url = ( + "https://raw.githubusercontent.com/hay-kot/mealie/dev/frontend/public/img/icons/android-chrome-512x512.png" + ) ingredients: list[str] = [] if recipe.settings.disable_amount: # type: ignore @@ -84,20 +134,22 @@ def content_with_meta(group_slug: str, recipe: Recipe) -> str: "nutrition": nutrition, } - tags = [ - f'', - f'', - f'', - f'', - '', - f'', - f'', - f'', - f'', - f"""""", + meta_tags = [ + MetaTag(hid="og:title", property_name="og:title", content=recipe.name or ""), + MetaTag(hid="og:description", property_name="og:description", content=recipe.description or ""), + MetaTag(hid="og:image", property_name="og:image", content=image_url), + MetaTag(hid="og:url", property_name="og:url", content=recipe_url), + MetaTag(hid="twitter:card", property_name="twitter:card", content="summary_large_image"), + MetaTag(hid="twitter:title", property_name="twitter:title", content=recipe.name or ""), + MetaTag(hid="twitter:description", property_name="twitter:description", content=recipe.description or ""), + MetaTag(hid="twitter:image", property_name="twitter:image", content=image_url), + MetaTag(hid="twitter:url", property_name="twitter:url", content=recipe_url), ] - return __contents.replace("", "\n".join(tags) + "\n", 1) + global __contents + __contents = inject_recipe_json(__contents, as_schema_org) + __contents = inject_meta(__contents, meta_tags) + return __contents def response_404(): @@ -133,7 +185,7 @@ async def serve_recipe_with_meta( user: PrivateUser | None = Depends(try_get_current_user), session: Session = Depends(generate_session), ): - if not user: + if not user or user.group_slug != group_slug: return serve_recipe_with_meta_public(group_slug, recipe_slug, session) try: @@ -149,6 +201,19 @@ async def serve_recipe_with_meta( return response_404() +async def serve_shared_recipe_with_meta(group_slug: str, token_id: str, session: Session = Depends(generate_session)): + try: + repos = AllRepositories(session) + token_summary = repos.recipe_share_tokens.get_one(token_id) + if token_summary is None: + raise Exception("Token Not Found") + + return Response(content_with_meta(group_slug, token_summary.recipe), media_type="text/html") + + except Exception: + return response_404() + + def mount_spa(app: FastAPI): if not os.path.exists(__app_settings.STATIC_FILES): return @@ -157,4 +222,5 @@ def mount_spa(app: FastAPI): __contents = pathlib.Path(__app_settings.STATIC_FILES).joinpath("index.html").read_text() app.get("/g/{group_slug}/r/{recipe_slug}")(serve_recipe_with_meta) + app.get("/g/{group_slug}/shared/r/{token_id}")(serve_shared_recipe_with_meta) app.mount("/", SPAStaticFiles(directory=__app_settings.STATIC_FILES, html=True), name="spa") diff --git a/tests/data/__init__.py b/tests/data/__init__.py index 445426def3ee..588d2c854197 100644 --- a/tests/data/__init__.py +++ b/tests/data/__init__.py @@ -22,6 +22,8 @@ images_test_image_1 = CWD / "images/test-image-1.jpg" images_test_image_2 = CWD / "images/test-image-2.png" +html_mealie_recipe = CWD / "html/mealie-recipe.html" + html_sous_vide_smoked_beef_ribs = CWD / "html/sous-vide-smoked-beef-ribs.html" html_sous_vide_shrimp = CWD / "html/sous-vide-shrimp.html" diff --git a/tests/data/html/mealie-recipe.html b/tests/data/html/mealie-recipe.html new file mode 100644 index 000000000000..4df529d1f82c --- /dev/null +++ b/tests/data/html/mealie-recipe.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + Mealie + + + + + + + + + + + + + + +
+ + +
+
Loading...
+
+
+ + + + diff --git a/tests/integration_tests/test_spa.py b/tests/integration_tests/test_spa.py new file mode 100644 index 000000000000..f4c3dc190c8a --- /dev/null +++ b/tests/integration_tests/test_spa.py @@ -0,0 +1,70 @@ +from bs4 import BeautifulSoup + +from mealie.routes.spa import MetaTag, inject_meta, inject_recipe_json +from tests import data as test_data +from tests.utils.factories import random_string + + +def test_spa_metadata_injection(): + fp = test_data.html_mealie_recipe + with open(fp) as f: + soup = BeautifulSoup(f, "lxml") + assert soup.html and soup.html.head + + tags = soup.find_all("meta") + assert tags + + title_tag = None + for tag in tags: + if tag.get("data-hid") == "og:title": + title_tag = tag + break + + assert title_tag and title_tag["content"] + + new_title_tag = MetaTag(hid="og:title", property_name="og:title", content=random_string()) + new_arbitrary_tag = MetaTag(hid=random_string(), property_name=random_string(), content=random_string()) + new_html = inject_meta(str(soup), [new_title_tag, new_arbitrary_tag]) + + # verify changes were injected + soup = BeautifulSoup(new_html, "lxml") + assert soup.html and soup.html.head + + tags = soup.find_all("meta") + assert tags + + title_tag = None + for tag in tags: + if tag.get("data-hid") == "og:title": + title_tag = tag + break + + assert title_tag and title_tag["content"] == new_title_tag.content + + arbitrary_tag = None + for tag in tags: + if tag.get("data-hid") == new_arbitrary_tag.hid: + arbitrary_tag = tag + break + + assert arbitrary_tag and arbitrary_tag["content"] == new_arbitrary_tag.content + + +def test_spa_recipe_json_injection(): + recipe_name = random_string() + schema = { + "@context": "https://schema.org", + "@type": "Recipe", + "name": recipe_name, + } + + fp = test_data.html_mealie_recipe + with open(fp) as f: + soup = BeautifulSoup(f, "lxml") + assert "https://schema.org" not in str(soup) + + html = inject_recipe_json(str(soup), schema) + + assert "@context" in html + assert "https://schema.org" in html + assert recipe_name in html From 310069a7e9b8c87053deb1dac762c7ccc2ac2441 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:37:19 -0600 Subject: [PATCH 14/74] fix: various alembic migration issues with queries (#2773) * set expire_on_commit false to avoid refresh * converted deletes to raw SQL statements * call update statements directly in sql * parameterized text queries * replace orm with raw sql to avoid db differences --- ...9a_added_normalized_unit_and_food_names.py | 22 ++++-- ...6_dded3119c1fe_added_unique_constraints.py | 74 +++++++------------ 2 files changed, 41 insertions(+), 55 deletions(-) diff --git a/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py b/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py index 44efe74faf84..f77fe6fbc21e 100644 --- a/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py +++ b/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py @@ -33,21 +33,29 @@ def populate_normalized_fields(): ) for unit in units: if unit.name is not None: - unit.name_normalized = IngredientUnitModel.normalize(unit.name) + session.execute( + sa.text( + f"UPDATE {IngredientUnitModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id" + ).bindparams(name_normalized=IngredientUnitModel.normalize(unit.name), id=unit.id) + ) if unit.abbreviation is not None: - unit.abbreviation_normalized = IngredientUnitModel.normalize(unit.abbreviation) - - session.add(unit) + session.execute( + sa.text( + f"UPDATE {IngredientUnitModel.__tablename__} SET abbreviation_normalized=:abbreviation_normalized WHERE id=:id" + ).bindparams(abbreviation_normalized=IngredientUnitModel.normalize(unit.abbreviation), id=unit.id) + ) foods = ( session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all() ) for food in foods: if food.name is not None: - food.name_normalized = IngredientFoodModel.normalize(food.name) - - session.add(food) + session.execute( + sa.text( + f"UPDATE {IngredientFoodModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id" + ).bindparams(name_normalized=IngredientFoodModel.normalize(food.name), id=food.id) + ) session.commit() diff --git a/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py b/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py index 850404713a2d..885e619f8199 100644 --- a/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py +++ b/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py @@ -13,10 +13,8 @@ import sqlalchemy as sa from pydantic import UUID4 from sqlalchemy.orm import Session, load_only -import mealie.db.migration_types from alembic import op from mealie.db.models._model_base import SqlAlchemyBase -from mealie.db.models._model_utils.guid import GUID from mealie.db.models.group.shopping_list import ShoppingListItem from mealie.db.models.labels import MultiPurposeLabel from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel @@ -43,26 +41,25 @@ def _is_postgres(): return op.get_context().dialect.name == "postgresql" -def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list[str]]: - duplicate_map: defaultdict[str, list[str]] = defaultdict(list) - for obj in session.query(model).options(load_only(model.id, model.group_id, model.name)).all(): - key = f"{obj.group_id}$${obj.name}" - duplicate_map[key].append(str(obj.id)) +def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list]: + duplicate_map: defaultdict[str, list] = defaultdict(list) + + query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}")) + for row in query.all(): + id, group_id, name = row + key = f"{group_id}$${name}" + duplicate_map[key].append(id) return duplicate_map def _resolve_duplicate_food( session: Session, - keep_food: IngredientFoodModel, keep_food_id: UUID4, dupe_food_id: UUID4, ): for shopping_list_item in session.query(ShoppingListItem).filter_by(food_id=dupe_food_id).all(): shopping_list_item.food_id = keep_food_id - shopping_list_item.food = keep_food - - session.commit() for recipe_ingredient in ( session.query(RecipeIngredientModel) @@ -71,62 +68,43 @@ def _resolve_duplicate_food( .all() ): recipe_ingredient.food_id = keep_food_id - recipe_ingredient.food = keep_food - session.commit() - - session.query(IngredientFoodModel).options(load_only(IngredientFoodModel.id)).filter_by(id=dupe_food_id).delete() - session.commit() + session.execute( + sa.text(f"DELETE FROM {IngredientFoodModel.__tablename__} WHERE id=:id").bindparams(id=dupe_food_id) + ) def _resolve_duplicate_unit( session: Session, - keep_unit: IngredientUnitModel, keep_unit_id: UUID4, dupe_unit_id: UUID4, ): for shopping_list_item in session.query(ShoppingListItem).filter_by(unit_id=dupe_unit_id).all(): shopping_list_item.unit_id = keep_unit_id - shopping_list_item.unit = keep_unit - - session.commit() for recipe_ingredient in session.query(RecipeIngredientModel).filter_by(unit_id=dupe_unit_id).all(): recipe_ingredient.unit_id = keep_unit_id - recipe_ingredient.unit = keep_unit - session.commit() - - session.query(IngredientUnitModel).options(load_only(IngredientUnitModel.id)).filter_by(id=dupe_unit_id).delete() - session.commit() + session.execute( + sa.text(f"DELETE FROM {IngredientUnitModel.__tablename__} WHERE id=:id").bindparams(id=dupe_unit_id) + ) def _resolve_duplicate_label( session: Session, - keep_label: MultiPurposeLabel, keep_label_id: UUID4, dupe_label_id: UUID4, ): for shopping_list_item in session.query(ShoppingListItem).filter_by(label_id=dupe_label_id).all(): shopping_list_item.label_id = keep_label_id - shopping_list_item.label = keep_label - - session.commit() for ingredient_food in session.query(IngredientFoodModel).filter_by(label_id=dupe_label_id).all(): ingredient_food.label_id = keep_label_id - ingredient_food.label = keep_label - session.commit() - - session.query(MultiPurposeLabel).options(load_only(MultiPurposeLabel.id)).filter_by(id=dupe_label_id).delete() - session.commit() + session.execute(sa.text(f"DELETE FROM {MultiPurposeLabel.__tablename__} WHERE id=:id").bindparams(id=dupe_label_id)) -def _resolve_duplicate_foods_units_labels(): - bind = op.get_bind() - session = Session(bind=bind) - +def _resolve_duplicate_foods_units_labels(session: Session): for model, resolve_func in [ (IngredientFoodModel, _resolve_duplicate_food), (IngredientUnitModel, _resolve_duplicate_unit), @@ -138,9 +116,8 @@ def _resolve_duplicate_foods_units_labels(): continue keep_id = ids[0] - keep_obj = session.query(model).options(load_only(model.id)).filter_by(id=keep_id).first() for dupe_id in ids[1:]: - resolve_func(session, keep_obj, keep_id, dupe_id) + resolve_func(session, keep_id, dupe_id) def _remove_duplicates_from_m2m_table(session: Session, table_meta: TableMeta): @@ -163,20 +140,20 @@ def _remove_duplicates_from_m2m_table(session: Session, table_meta: TableMeta): ) session.execute(query) - session.commit() -def _remove_duplicates_from_m2m_tables(table_metas: list[TableMeta]): - bind = op.get_bind() - session = Session(bind=bind) - +def _remove_duplicates_from_m2m_tables(session: Session, table_metas: list[TableMeta]): for table_meta in table_metas: _remove_duplicates_from_m2m_table(session, table_meta) def upgrade(): - _resolve_duplicate_foods_units_labels() + bind = op.get_bind() + session = Session(bind=bind) + + _resolve_duplicate_foods_units_labels(session) _remove_duplicates_from_m2m_tables( + session, [ TableMeta("cookbooks_to_categories", "cookbook_id", "category_id"), TableMeta("cookbooks_to_tags", "cookbook_id", "tag_id"), @@ -189,12 +166,13 @@ def upgrade(): TableMeta("recipes_to_tools", "recipe_id", "tool_id"), TableMeta("users_to_favorites", "user_id", "recipe_id"), TableMeta("shopping_lists_multi_purpose_labels", "shopping_list_id", "label_id"), - ] + ], ) + session.commit() + # ### commands auto generated by Alembic - please adjust! ### # we use batch_alter_table here because otherwise this fails on sqlite - # M2M with op.batch_alter_table("cookbooks_to_categories") as batch_op: batch_op.create_unique_constraint("cookbook_id_category_id_key", ["cookbook_id", "category_id"]) From b28aa828469f898b936337b679cbfdf75fa7ffad Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:47:35 -0600 Subject: [PATCH 15/74] add logs to help debug backup issues (#2795) --- mealie/routes/admin/admin_backups.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mealie/routes/admin/admin_backups.py b/mealie/routes/admin/admin_backups.py index 46c21f581226..7f426ebecdac 100644 --- a/mealie/routes/admin/admin_backups.py +++ b/mealie/routes/admin/admin_backups.py @@ -5,6 +5,7 @@ from pathlib import Path from fastapi import APIRouter, File, HTTPException, UploadFile, status from mealie.core.config import get_app_dirs +from mealie.core.root_logger import get_logger from mealie.core.security import create_file_token from mealie.pkgs.stats.fs_stats import pretty_size from mealie.routes._base import BaseAdminController, controller @@ -12,6 +13,7 @@ from mealie.schema.admin.backup import AllBackups, BackupFile from mealie.schema.response.responses import ErrorResponse, FileTokenResponse, SuccessResponse from mealie.services.backups_v2.backup_v2 import BackupSchemaMismatch, BackupV2 +logger = get_logger() router = APIRouter(prefix="/backups") @@ -42,6 +44,7 @@ class AdminBackupController(BaseAdminController): try: backup.backup() except Exception as e: + logger.exception(e) raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e return SuccessResponse.respond("Backup created successfully") @@ -106,6 +109,7 @@ class AdminBackupController(BaseAdminController): ErrorResponse.respond("database backup schema version does not match current database"), ) from e except Exception as e: + logger.exception(e) raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e return SuccessResponse.respond("Restore successful") From 415afbfae4c9f6403f7b3384e1911ab8e8403185 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:33:42 -0600 Subject: [PATCH 16/74] New translations en-us.json (Dutch) (#2797) --- frontend/lang/messages/nl-NL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lang/messages/nl-NL.json b/frontend/lang/messages/nl-NL.json index 2526ce0411cd..36621481a41f 100644 --- a/frontend/lang/messages/nl-NL.json +++ b/frontend/lang/messages/nl-NL.json @@ -831,7 +831,7 @@ "password-updated": "Wachtwoord bijgewerkt", "password": "Wachtwoord", "password-strength": "Wachtwoord is {strength}", - "please-enter-password": "Please enter your new password.", + "please-enter-password": "Voeg uw nieuwe wachtwoord in.", "register": "Registreren", "reset-password": "Wachtwoord herstellen", "sign-in": "Inloggen", From 449bb6f0ce550fdf7beac8a8897d77f2862e2822 Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:45:52 +0100 Subject: [PATCH 17/74] fix: remove group storage capacity from profile page (#2798) --- frontend/pages/user/profile/index.vue | 43 +-------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/frontend/pages/user/profile/index.vue b/frontend/pages/user/profile/index.vue index 8f9f23c951fe..ddb011550d1a 100644 --- a/frontend/pages/user/profile/index.vue +++ b/frontend/pages/user/profile/index.vue @@ -55,7 +55,7 @@

{{ $t('profile.account-summary-description') }}

- + {{ $t('profile.group-statistics') }} @@ -75,22 +75,6 @@ - - - {{ $t('profile.storage-capacity') }} - - {{ $t('profile.storage-capacity-description') }} - {{ $t('general.this-feature-is-currently-inactive') }} - - - - - - - - @@ -344,33 +328,8 @@ export default defineComponent({ return statsTo.value[key] ?? "unknown"; } - const storage = useAsync(async () => { - const { data } = await api.groups.storage(); - - if (data) { - return data; - } - }, useAsyncKey()); - - const storageUsedPercentage = computed(() => { - if (!storage.value) { - return 0; - } - - return (storage.value?.usedStorageBytes / storage.value?.totalStorageBytes) * 100 ?? 0; - }); - - const storageText = computed(() => { - if (!storage.value) { - return "Loading..."; - } - return `${storage.value.usedStorageStr} / ${storage.value.totalStorageStr}`; - }); - return { groupSlug, - storageText, - storageUsedPercentage, getStatsTitle, getStatsIcon, getStatsTo, From f8ad72ec3147320c191b1bb1284f06e345a708b0 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:08:47 -0600 Subject: [PATCH 18/74] fix: Bulk URL Import Fixes (#2796) * allow expections when fetching content * removed extra bracket on import text * added more fault tolerance and limited concurrency * fix entries not being saved to report * disable clicking into in-proress import * conditionally render expansion --- frontend/components/global/ReportTable.vue | 4 ++ frontend/pages/group/data/recipes.vue | 2 +- frontend/pages/group/reports/_id.vue | 2 +- mealie/services/recipe/recipe_data_service.py | 9 +++-- .../services/scraper/recipe_bulk_scraper.py | 37 ++++++++++++------- mealie/services/scraper/scraper_strategies.py | 2 +- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/frontend/components/global/ReportTable.vue b/frontend/components/global/ReportTable.vue index a4eaabdf3488..aede7b1c4b71 100644 --- a/frontend/components/global/ReportTable.vue +++ b/frontend/components/global/ReportTable.vue @@ -49,6 +49,10 @@ export default defineComponent({ ]; function handleRowClick(item: ReportSummary) { + if (item.status === "in-progress") { + return; + } + router.push(`/group/reports/${item.id}`); } diff --git a/frontend/pages/group/data/recipes.vue b/frontend/pages/group/data/recipes.vue index 0895620bf3a2..0803f285badc 100644 --- a/frontend/pages/group/data/recipes.vue +++ b/frontend/pages/group/data/recipes.vue @@ -121,7 +121,7 @@ - {{ $t('general.import') }}} + {{ $t('general.import') }} diff --git a/mealie/services/recipe/recipe_data_service.py b/mealie/services/recipe/recipe_data_service.py index 59d42feb9b44..1f5bc759fab7 100644 --- a/mealie/services/recipe/recipe_data_service.py +++ b/mealie/services/recipe/recipe_data_service.py @@ -12,14 +12,17 @@ from mealie.services._base_service import BaseService _FIREFOX_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" -async def gather_with_concurrency(n, *coros): +async def gather_with_concurrency(n, *coros, ignore_exceptions=False): semaphore = asyncio.Semaphore(n) async def sem_coro(coro): async with semaphore: return await coro - return await asyncio.gather(*(sem_coro(c) for c in coros)) + results = await asyncio.gather(*(sem_coro(c) for c in coros), return_exceptions=ignore_exceptions) + if ignore_exceptions: + results = [r for r in results if not isinstance(r, Exception)] + return results async def largest_content_len(urls: list[str]) -> tuple[str, int]: @@ -31,7 +34,7 @@ async def largest_content_len(urls: list[str]) -> tuple[str, int]: async with AsyncClient() as client: tasks = [do(client, url) for url in urls] - responses: list[Response] = await gather_with_concurrency(10, *tasks) + responses: list[Response] = await gather_with_concurrency(10, *tasks, ignore_exceptions=True) for response in responses: len_int = int(response.headers.get("Content-Length", 0)) if len_int > largest_len: diff --git a/mealie/services/scraper/recipe_bulk_scraper.py b/mealie/services/scraper/recipe_bulk_scraper.py index 947e8b688d64..ed701ecc3886 100644 --- a/mealie/services/scraper/recipe_bulk_scraper.py +++ b/mealie/services/scraper/recipe_bulk_scraper.py @@ -1,10 +1,16 @@ -from asyncio import gather +import asyncio from pydantic import UUID4 from mealie.repos.repository_factory import AllRepositories from mealie.schema.recipe.recipe import CreateRecipeByUrlBulk, Recipe -from mealie.schema.reports.reports import ReportCategory, ReportCreate, ReportEntryCreate, ReportSummaryStatus +from mealie.schema.reports.reports import ( + ReportCategory, + ReportCreate, + ReportEntryCreate, + ReportEntryOut, + ReportSummaryStatus, +) from mealie.schema.user.user import GroupInDB from mealie.services._base_service import BaseService from mealie.services.recipe.recipe_service import RecipeService @@ -47,6 +53,7 @@ class RecipeBulkScraperService(BaseService): is_success = True is_failure = True + new_entries: list[ReportEntryOut] = [] for entry in self.report_entries: if is_failure and entry.success: is_failure = False @@ -54,7 +61,7 @@ class RecipeBulkScraperService(BaseService): if is_success and not entry.success: is_success = False - self.repos.group_report_entries.create(entry) + new_entries.append(self.repos.group_report_entries.create(entry)) if is_success: self.report.status = ReportSummaryStatus.success @@ -65,25 +72,29 @@ class RecipeBulkScraperService(BaseService): if not is_success and not is_failure: self.report.status = ReportSummaryStatus.partial + self.report.entries = new_entries self.repos.group_reports.update(self.report.id, self.report) async def scrape(self, urls: CreateRecipeByUrlBulk) -> None: + sem = asyncio.Semaphore(3) + async def _do(url: str) -> Recipe | None: - try: - recipe, _ = await create_from_url(url) - return recipe - except Exception as e: - self.service.logger.error(f"failed to scrape url during bulk url import {b.url}") - self.service.logger.exception(e) - self._add_error_entry(f"failed to scrape url {url}", str(e)) - return None + async with sem: + try: + recipe, _ = await create_from_url(url) + return recipe + except Exception as e: + self.service.logger.error(f"failed to scrape url during bulk url import {url}") + self.service.logger.exception(e) + self._add_error_entry(f"failed to scrape url {url}", str(e)) + return None if self.report is None: self.get_report_id() tasks = [_do(b.url) for b in urls.imports] - results = await gather(*tasks) + results = await asyncio.gather(*tasks, return_exceptions=True) for b, recipe in zip(urls.imports, results, strict=True): - if not recipe: + if not recipe or isinstance(recipe, Exception): continue if b.tags: diff --git a/mealie/services/scraper/scraper_strategies.py b/mealie/services/scraper/scraper_strategies.py index cd2435192366..fd51498023a4 100644 --- a/mealie/services/scraper/scraper_strategies.py +++ b/mealie/services/scraper/scraper_strategies.py @@ -172,7 +172,7 @@ class RecipeScraperPackage(ABCScraperStrategy): try: scraped_schema = scrape_html(recipe_html, org_url=self.url) except (NoSchemaFoundInWildMode, AttributeError): - self.logger.error("Recipe Scraper was unable to extract a recipe.") + self.logger.error(f"Recipe Scraper was unable to extract a recipe from {self.url}") return None except ConnectionError as e: From 1c1e50dbdafe61d98e81d42575c96b4c8329aca5 Mon Sep 17 00:00:00 2001 From: Brendan Date: Sat, 9 Dec 2023 14:41:26 +0000 Subject: [PATCH 19/74] Rearrange defineComponent to remove warnings --- frontend/pages/admin/site-settings.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/admin/site-settings.vue b/frontend/pages/admin/site-settings.vue index 433674c69da6..0c71d6c3e11b 100644 --- a/frontend/pages/admin/site-settings.vue +++ b/frontend/pages/admin/site-settings.vue @@ -216,6 +216,7 @@ interface CheckApp extends CheckAppConfig { } export default defineComponent({ + components: { AppLoader }, layout: "admin", setup() { // ========================================================== @@ -465,8 +466,7 @@ export default defineComponent({ return { title: this.$t("settings.site-settings") as string, }; - }, - components: { AppLoader } + } }); From 2cba2d5fd294c4529a78a8b9d12b186f49eedec6 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 9 Dec 2023 15:22:52 -0600 Subject: [PATCH 20/74] New Crowdin updates (#2809) * New translations en-us.json (Polish) * New translations en-us.json (Polish) --- frontend/lang/messages/pl-PL.json | 22 +++++++++---------- .../seed/resources/foods/locales/pl-PL.json | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/lang/messages/pl-PL.json b/frontend/lang/messages/pl-PL.json index 168ec4effda4..019e607dfd2b 100644 --- a/frontend/lang/messages/pl-PL.json +++ b/frontend/lang/messages/pl-PL.json @@ -469,7 +469,7 @@ "add-to-plan": "Dodaj do planu", "add-to-timeline": "Add to Timeline", "recipe-added-to-list": "Przepis dodany do listy", - "recipes-added-to-list": "Recipes added to list", + "recipes-added-to-list": "Przepisy dodane do listy", "recipe-added-to-mealplan": "Przepis dodany do planu posiłków", "failed-to-add-recipes-to-list": "Failed to add recipe to list", "failed-to-add-recipe-to-mealplan": "Nie udało się dodać przepisu do planu posiłków", @@ -495,10 +495,10 @@ "public-link": "Link publiczny", "timer": { "kitchen-timer": "Kitchen Timer", - "start-timer": "Start Timer", + "start-timer": "Włącz minutnik", "pause-timer": "Pause Timer", "resume-timer": "Resume Timer", - "stop-timer": "Stop Timer" + "stop-timer": "Zatrzymaj minutnik" }, "edit-timeline-event": "Edytuj zdarzenie osi czasu", "timeline": "Oś czasu", @@ -553,7 +553,7 @@ "unit": "Jednostka", "upload-image": "Prześlij obraz", "screen-awake": "Keep Screen Awake", - "remove-image": "Remove image" + "remove-image": "Usuń obraz" }, "search": { "advanced-search": "Wyszukiwanie zaawansowane", @@ -578,7 +578,7 @@ "add-a-new-theme": "Dodaj nowy motyw", "admin-settings": "Ustawienia administratora", "backup": { - "backup-created": "Backup created successfully", + "backup-created": "Kopia zapasowa utworzona pomyślnie", "backup-created-at-response-export_path": "Kopia zapasowa została utworzona w {path}", "backup-deleted": "Kopia zapasowa została usunięta", "restore-success": "Restore successful", @@ -831,7 +831,7 @@ "password-updated": "Hasło zostało zaktualizowane", "password": "Hasło", "password-strength": "Hasło jest {strength}", - "please-enter-password": "Please enter your new password.", + "please-enter-password": "Wpisz nowe hasło.", "register": "Zarejestruj się", "reset-password": "Zresetuj hasło", "sign-in": "Zaloguj się", @@ -852,7 +852,7 @@ "username": "Nazwa użytkownika", "users-header": "UŻYTKOWNICY", "users": "Użytkownicy", - "user-not-found": "User not found", + "user-not-found": "Nie znaleziono użytkownika", "webhook-time": "Czas webhooka", "webhooks-enabled": "Webhooki włączone", "you-are-not-allowed-to-create-a-user": "Nie masz uprawnień do tworzenia użytkowników", @@ -975,12 +975,12 @@ "columns": "Kolumny", "combine": "Połącz", "categories": { - "edit-category": "Edit Category", - "new-category": "New Category", - "category-data": "Category Data" + "edit-category": "Edytuj kategorię", + "new-category": "Nowa kategoria", + "category-data": "Dane kategorii" }, "tags": { - "new-tag": "New Tag", + "new-tag": "Nowy Tag", "edit-tag": "Edit Tag", "tag-data": "Tag Data" }, diff --git a/mealie/repos/seed/resources/foods/locales/pl-PL.json b/mealie/repos/seed/resources/foods/locales/pl-PL.json index ad4b9990de70..a71a3a6045b0 100644 --- a/mealie/repos/seed/resources/foods/locales/pl-PL.json +++ b/mealie/repos/seed/resources/foods/locales/pl-PL.json @@ -104,7 +104,7 @@ "fruit-sugar": "cukier z owoców", "garam-masala": "garam masala", "garlic": "czosnek", - "gem-squash": "gem squash", + "gem-squash": "dynia zielona", "ginger": "imbir", "giblets": "podroby", "grains": "zboże", From 7aac82bff36e20887a66586b8dc434d26e1c1fb1 Mon Sep 17 00:00:00 2001 From: boc-the-git <3479092+boc-the-git@users.noreply.github.com> Date: Mon, 11 Dec 2023 03:56:22 +1100 Subject: [PATCH 21/74] fix: Add 'loading' message to settings page (#2806) * Add 'loading' message to settings page * Fix loading message in site settings page * Refactor code to use AppLoader --- frontend/lang/messages/en-US.json | 1 + frontend/pages/admin/site-settings.vue | 605 ++++++++++++------------- 2 files changed, 289 insertions(+), 317 deletions(-) diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index 07b577d6f700..dc817971e663 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -114,6 +114,7 @@ "json": "JSON", "keyword": "Keyword", "link-copied": "Link Copied", + "loading": "Loading", "loading-events": "Loading Events", "loading-recipe": "Loading recipe...", "loading-ocr-data": "Loading OCR data...", diff --git a/frontend/pages/admin/site-settings.vue b/frontend/pages/admin/site-settings.vue index c4d64ad93b9f..433674c69da6 100644 --- a/frontend/pages/admin/site-settings.vue +++ b/frontend/pages/admin/site-settings.vue @@ -130,40 +130,47 @@
- {{ $t('data-pages.data-management-description') }} -