Merge branch 'mealie-next' into mealie-next

This commit is contained in:
boc-the-git 2023-12-12 21:32:26 +11:00 committed by GitHub
commit b13d66108d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 1312 additions and 1300 deletions

View File

@ -41,7 +41,8 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [ "forwardPorts": [
3000, 3000,
9000 9000,
24678 // used by nuxt when hot-reloading using polling
], ],
// Use 'onCreateCommand' to run commands at the end of container creation. // Use 'onCreateCommand' to run commands at the end of container creation.
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.

View File

@ -1,7 +1,7 @@
--- ---
name: Bug Report name: Bug Report
description: "Submit a bug for the latest version of Mealie" description: "Submit a bug for the latest version of Mealie"
title: "[BUG] - YOUR TITLE" title: "[BUG] - YOUR DESCRIPTIVE TITLE GOES HERE"
labels: ["bug", "triage"] labels: ["bug", "triage"]
body: body:
- type: checkboxes - type: checkboxes
@ -14,7 +14,7 @@ body:
options: options:
- label: This is not a feature request. - label: This is not a feature request.
required: true required: true
- label: I added a very descriptive title to this issue. - label: I added a very descriptive title to this issue (title field is above this).
required: true required: true
- label: I used the GitHub search to find a similar issue and didn't find it. - label: I used the GitHub search to find a similar issue and didn't find it.
required: true required: true
@ -61,7 +61,8 @@ body:
- Docker (Windows) - Docker (Windows)
- Docker (Synology) - Docker (Synology)
- Unraid - Unraid
- Other - TrueNAS
- Other (please specify below)
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@ -36,7 +36,7 @@ jobs:
# Steps # Steps
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up python - name: Set up python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@ -60,7 +60,7 @@ jobs:
id: cache-validate id: cache-validate
if: steps.cached-poetry-dependencies.outputs.cache-hit == 'true' if: steps.cached-poetry-dependencies.outputs.cache-hit == 'true'
run: | run: |
echo "import black;print('venv good?')" > test.py && poetry run python test.py && echo ::set-output name=cache-hit-success::true echo "import black;print('venv good?')" > test.py && poetry run python test.py && echo "cache-hit-success=true" >> $GITHUB_OUTPUT
rm test.py rm test.py
continue-on-error: true continue-on-error: true

View File

@ -6,6 +6,9 @@ on:
tag: tag:
required: true required: true
type: string type: string
tags:
required: false
type: string
secrets: secrets:
DOCKERHUB_USERNAME: DOCKERHUB_USERNAME:
required: true required: true
@ -17,33 +20,42 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Log in to the Container registry - name: Log in to the Container registry (ghcr.io)
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (dockerhub)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Override __init__.py - name: Override __init__.py
run: | run: |
echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
with: with:
file: ./docker/Dockerfile file: ./docker/Dockerfile
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ghcr.io/${{ github.repository }}:${{ inputs.tag }} tags: |
hkotel/mealie:${{ inputs.tag }}
ghcr.io/${{ github.repository }}:${{ inputs.tag }}
${{ inputs.tags }}
build-args: | build-args: |
COMMIT=${{ github.sha }} COMMIT=${{ github.sha }}
# https://docs.docker.com/build/ci/github-actions/cache/#github-cache # https://docs.docker.com/build/ci/github-actions/cache/#github-cache

View File

@ -9,20 +9,20 @@ jobs:
steps: steps:
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@master uses: actions/checkout@v4
- name: Setup node env 🏗 - name: Setup node env 🏗
uses: actions/setup-node@v3.7.0 uses: actions/setup-node@v4.0.0
with: with:
node-version: 16 node-version: 16
check-latest: true check-latest: true
- name: Get yarn cache directory path 🛠 - name: Get yarn cache directory path 🛠
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache node_modules 📦 - name: Cache node_modules 📦
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@ -47,20 +47,20 @@ jobs:
steps: steps:
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@master uses: actions/checkout@v4
- name: Setup node env 🏗 - name: Setup node env 🏗
uses: actions/setup-node@v3.7.0 uses: actions/setup-node@v4.0.0
with: with:
node-version: 16 node-version: 16
check-latest: true check-latest: true
- name: Get yarn cache directory path 🛠 - name: Get yarn cache directory path 🛠
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache node_modules 📦 - name: Cache node_modules 📦
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@ -11,7 +11,7 @@ jobs:
fail-fast: true fail-fast: true
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Build Dockerfile - name: Build Dockerfile
run: | run: |

View File

@ -24,6 +24,9 @@ jobs:
- frontend-tests - frontend-tests
with: with:
tag: ${{ github.event.release.tag_name }} tag: ${{ github.event.release.tag_name }}
tags: |
hkotel/mealie:latest
ghcr.io/${{ github.repository }}:latest
secrets: secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@ -14,8 +14,8 @@
"webp" "webp"
], ],
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.organizeImports": false "source.organizeImports": "never"
}, },
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.workingDirectories": [ "eslint.workingDirectories": [

View File

@ -9,7 +9,7 @@
<!-- PROJECT LOGO --> <!-- PROJECT LOGO -->
<br /> <br />
<p align="center"> <p align="center">
<a href="https://github.com/hay-kot/mealie"> <a href="https://github.com/mealie-recipes/mealie">
<svg style="width:100px;height:100px" viewBox="0 0 24 24"> <svg style="width:100px;height:100px" viewBox="0 0 24 24">
<path fill="currentColor" d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z" /> <path fill="currentColor" d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z" />
</svg> </svg>
@ -21,12 +21,12 @@
A Place for All Your Recipes A Place for All Your Recipes
<br /> <br />
<a href="https://nightly.mealie.io"><strong>Explore the docs »</strong></a> <a href="https://nightly.mealie.io"><strong>Explore the docs »</strong></a>
<a href="https://github.com/hay-kot/mealie"> <a href="https://github.com/mealie-recipes/mealie">
</a> </a>
<br /> <br />
<a href="https://demo.mealie.io/">View Demo</a> <a href="https://demo.mealie.io/">View Demo</a>
· ·
<a href="https://github.com/hay-kot/mealie/issues">Report Bug</a> <a href="https://github.com/mealie-recipes/mealie/issues">Report Bug</a>
· ·
<a href="https://github.com/mealie-recipes/mealie/pkgs/container/mealie">GitHub Container Registry</a> <a href="https://github.com/mealie-recipes/mealie/pkgs/container/mealie">GitHub Container Registry</a>
</p> </p>
@ -56,6 +56,12 @@ If you are not a coder, you can still contribute financially. Financial contribu
<a href="https://www.buymeacoffee.com/haykot" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 107px !important;" ></a> <a href="https://www.buymeacoffee.com/haykot" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 107px !important;" ></a>
### Translations
Translations can be a great way for **non-coders** to contribute to project. We use [Crowdin](https://crowdin.com/project/mealie) to allow several contributors to work on translating Mealie. You can simply help by voting for your preferred translations, or even by completely translating Mealie into a new language.
For more information, check out the translation page on the [contributor's guide](https://nightly.mealie.io/contributors/translating/).
<!-- LICENSE --> <!-- LICENSE -->
## License ## License
Distributed under the AGPL License. See `LICENSE` for more information. Distributed under the AGPL License. See `LICENSE` for more information.
@ -76,17 +82,17 @@ Thanks to Linode for providing Hosting for the Demo, Beta, and Documentation sit
<!-- MARKDOWN LINKS & IMAGES --> <!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links --> <!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
[contributors-shield]: https://img.shields.io/github/contributors/hay-kot/mealie.svg?style=flat-square [contributors-shield]: https://img.shields.io/github/contributors/mealie-recipes/mealie.svg?style=flat-square
[docker-pull]: https://img.shields.io/docker/pulls/hkotel/mealie [docker-pull]: https://img.shields.io/docker/pulls/hkotel/mealie
[contributors-url]: https://github.com/hay-kot/mealie/graphs/contributors [contributors-url]: https://github.com/mealie-recipes/mealie/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/hay-kot/mealie.svg?style=flat-square [forks-shield]: https://img.shields.io/github/forks/mealie-recipes/mealie.svg?style=flat-square
[forks-url]: https://github.com/hay-kot/mealie/network/members [forks-url]: https://github.com/mealie-recipes/mealie/network/members
[stars-shield]: https://img.shields.io/github/stars/hay-kot/mealie.svg?style=flat-square [stars-shield]: https://img.shields.io/github/stars/mealie-recipes/mealie.svg?style=flat-square
[stars-url]: https://github.com/hay-kot/mealie/stargazers [stars-url]: https://github.com/mealie-recipes/mealie/stargazers
[issues-shield]: https://img.shields.io/github/issues/hay-kot/mealie.svg?style=flat-square [issues-shield]: https://img.shields.io/github/issues/mealie-recipes/mealie.svg?style=flat-square
[issues-url]: https://github.com/hay-kot/mealie/issues [issues-url]: https://github.com/mealie-recipes/mealie/issues
[license-shield]: https://img.shields.io/github/license/hay-kot/mealie.svg?style=flat-square [license-shield]: https://img.shields.io/github/license/mealie-recipes/mealie.svg?style=flat-square
[license-url]: https://github.com/hay-kot/mealie/blob/mealie-next/LICENSE [license-url]: https://github.com/mealie-recipes/mealie/blob/mealie-next/LICENSE
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
[linkedin-url]: https://linkedin.com/in/hay-kot [linkedin-url]: https://linkedin.com/in/hay-kot
[product-screenshot]: docs/docs/assets/img/home_screenshot.png [product-screenshot]: docs/docs/assets/img/home_screenshot.png

View File

@ -33,21 +33,29 @@ def populate_normalized_fields():
) )
for unit in units: for unit in units:
if unit.name is not None: 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: if unit.abbreviation is not None:
unit.abbreviation_normalized = IngredientUnitModel.normalize(unit.abbreviation) session.execute(
sa.text(
session.add(unit) f"UPDATE {IngredientUnitModel.__tablename__} SET abbreviation_normalized=:abbreviation_normalized WHERE id=:id"
).bindparams(abbreviation_normalized=IngredientUnitModel.normalize(unit.abbreviation), id=unit.id)
)
foods = ( foods = (
session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all() session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all()
) )
for food in foods: for food in foods:
if food.name is not None: if food.name is not None:
food.name_normalized = IngredientFoodModel.normalize(food.name) session.execute(
sa.text(
session.add(food) f"UPDATE {IngredientFoodModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id"
).bindparams(name_normalized=IngredientFoodModel.normalize(food.name), id=food.id)
)
session.commit() session.commit()

View File

@ -13,10 +13,8 @@ import sqlalchemy as sa
from pydantic import UUID4 from pydantic import UUID4
from sqlalchemy.orm import Session, load_only from sqlalchemy.orm import Session, load_only
import mealie.db.migration_types
from alembic import op from alembic import op
from mealie.db.models._model_base import SqlAlchemyBase 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.group.shopping_list import ShoppingListItem
from mealie.db.models.labels import MultiPurposeLabel from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel
@ -43,26 +41,25 @@ def _is_postgres():
return op.get_context().dialect.name == "postgresql" return op.get_context().dialect.name == "postgresql"
def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list[str]]: def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list]:
duplicate_map: defaultdict[str, list[str]] = defaultdict(list) duplicate_map: defaultdict[str, list] = 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}" query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}"))
duplicate_map[key].append(str(obj.id)) for row in query.all():
id, group_id, name = row
key = f"{group_id}$${name}"
duplicate_map[key].append(id)
return duplicate_map return duplicate_map
def _resolve_duplicate_food( def _resolve_duplicate_food(
session: Session, session: Session,
keep_food: IngredientFoodModel,
keep_food_id: UUID4, keep_food_id: UUID4,
dupe_food_id: UUID4, dupe_food_id: UUID4,
): ):
for shopping_list_item in session.query(ShoppingListItem).filter_by(food_id=dupe_food_id).all(): 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_id = keep_food_id
shopping_list_item.food = keep_food
session.commit()
for recipe_ingredient in ( for recipe_ingredient in (
session.query(RecipeIngredientModel) session.query(RecipeIngredientModel)
@ -71,62 +68,43 @@ def _resolve_duplicate_food(
.all() .all()
): ):
recipe_ingredient.food_id = keep_food_id recipe_ingredient.food_id = keep_food_id
recipe_ingredient.food = keep_food
session.commit() session.execute(
sa.text(f"DELETE FROM {IngredientFoodModel.__tablename__} WHERE id=:id").bindparams(id=dupe_food_id)
session.query(IngredientFoodModel).options(load_only(IngredientFoodModel.id)).filter_by(id=dupe_food_id).delete() )
session.commit()
def _resolve_duplicate_unit( def _resolve_duplicate_unit(
session: Session, session: Session,
keep_unit: IngredientUnitModel,
keep_unit_id: UUID4, keep_unit_id: UUID4,
dupe_unit_id: UUID4, dupe_unit_id: UUID4,
): ):
for shopping_list_item in session.query(ShoppingListItem).filter_by(unit_id=dupe_unit_id).all(): 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_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(): 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_id = keep_unit_id
recipe_ingredient.unit = keep_unit
session.commit() session.execute(
sa.text(f"DELETE FROM {IngredientUnitModel.__tablename__} WHERE id=:id").bindparams(id=dupe_unit_id)
session.query(IngredientUnitModel).options(load_only(IngredientUnitModel.id)).filter_by(id=dupe_unit_id).delete() )
session.commit()
def _resolve_duplicate_label( def _resolve_duplicate_label(
session: Session, session: Session,
keep_label: MultiPurposeLabel,
keep_label_id: UUID4, keep_label_id: UUID4,
dupe_label_id: UUID4, dupe_label_id: UUID4,
): ):
for shopping_list_item in session.query(ShoppingListItem).filter_by(label_id=dupe_label_id).all(): 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_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(): 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_id = keep_label_id
ingredient_food.label = keep_label
session.commit() session.execute(sa.text(f"DELETE FROM {MultiPurposeLabel.__tablename__} WHERE id=:id").bindparams(id=dupe_label_id))
session.query(MultiPurposeLabel).options(load_only(MultiPurposeLabel.id)).filter_by(id=dupe_label_id).delete()
session.commit()
def _resolve_duplicate_foods_units_labels(): def _resolve_duplicate_foods_units_labels(session: Session):
bind = op.get_bind()
session = Session(bind=bind)
for model, resolve_func in [ for model, resolve_func in [
(IngredientFoodModel, _resolve_duplicate_food), (IngredientFoodModel, _resolve_duplicate_food),
(IngredientUnitModel, _resolve_duplicate_unit), (IngredientUnitModel, _resolve_duplicate_unit),
@ -138,9 +116,8 @@ def _resolve_duplicate_foods_units_labels():
continue continue
keep_id = ids[0] 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:]: 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): 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.execute(query)
session.commit()
def _remove_duplicates_from_m2m_tables(table_metas: list[TableMeta]): def _remove_duplicates_from_m2m_tables(session: Session, table_metas: list[TableMeta]):
bind = op.get_bind()
session = Session(bind=bind)
for table_meta in table_metas: for table_meta in table_metas:
_remove_duplicates_from_m2m_table(session, table_meta) _remove_duplicates_from_m2m_table(session, table_meta)
def upgrade(): 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( _remove_duplicates_from_m2m_tables(
session,
[ [
TableMeta("cookbooks_to_categories", "cookbook_id", "category_id"), TableMeta("cookbooks_to_categories", "cookbook_id", "category_id"),
TableMeta("cookbooks_to_tags", "cookbook_id", "tag_id"), TableMeta("cookbooks_to_tags", "cookbook_id", "tag_id"),
@ -189,12 +166,13 @@ def upgrade():
TableMeta("recipes_to_tools", "recipe_id", "tool_id"), TableMeta("recipes_to_tools", "recipe_id", "tool_id"),
TableMeta("users_to_favorites", "user_id", "recipe_id"), TableMeta("users_to_favorites", "user_id", "recipe_id"),
TableMeta("shopping_lists_multi_purpose_labels", "shopping_list_id", "label_id"), TableMeta("shopping_lists_multi_purpose_labels", "shopping_list_id", "label_id"),
] ],
) )
session.commit()
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
# we use batch_alter_table here because otherwise this fails on sqlite # we use batch_alter_table here because otherwise this fails on sqlite
# M2M # M2M
with op.batch_alter_table("cookbooks_to_categories") as batch_op: 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"]) batch_op.create_unique_constraint("cookbook_id_category_id_key", ["cookbook_id", "category_id"])

View File

@ -62,7 +62,7 @@ A quality update with major props to [zackbcom](https://github.com/zackbcom) for
### Recipes ### Recipes
- Added user feedback on bad URL - Added user feedback on bad URL
- Better backend data validation for updating recipes, avoid small syntax errors corrupting database entry. [Closes #8](https://github.com/hay-kot/mealie/issues/8) - Better backend data validation for updating recipes, avoid small syntax errors corrupting database entry. [Closes #8](https://github.com/mealie-recipes/mealie/issues/8)
- Fixed spacing Closes while editing new recipes in JSON - Fixed spacing Closes while editing new recipes in JSON
## v0.0.0 - Initial Pre-release ## v0.0.0 - Initial Pre-release

View File

@ -5,7 +5,7 @@
**Database Version: v0.4.0** **Database Version: v0.4.0**
!!! error "Breaking Changes" !!! error "Breaking Changes"
1. With a recent refactor some users been experiencing issues with an environmental variable not being set correct. If you are experiencing issues, please provide your comments [Here](https://github.com/hay-kot/mealie/issues/281). 1. With a recent refactor some users been experiencing issues with an environmental variable not being set correct. If you are experiencing issues, please provide your comments [Here](https://github.com/mealie-recipes/mealie/issues/281).
2. If you are a developer, you may experience issues with development as a new environmental variable has been introduced. Setting `PRODUCTION=false` will allow you to develop as normal. 2. If you are a developer, you may experience issues with development as a new environmental variable has been introduced. Setting `PRODUCTION=false` will allow you to develop as normal.
@ -31,4 +31,4 @@
- Unify Logger across the backend - Unify Logger across the backend
- mealie.log and last_recipe.json are now downloadable from the frontend from the /admin/about - mealie.log and last_recipe.json are now downloadable from the frontend from the /admin/about
- New download schema where you request a token and then use that token to hit a single endpoint to download a file. This is a notable change if you are using the API to download backups. - New download schema where you request a token and then use that token to hit a single endpoint to download a file. This is a notable change if you are using the API to download backups.
- Recipe images can now be added directly from a URL - [See #117 for details](https://github.com/hay-kot/mealie/issues/117) - Recipe images can now be added directly from a URL - [See #117 for details](https://github.com/mealie-recipes/mealie/issues/117)

View File

@ -8,7 +8,7 @@
- Fixed #617 - Section behavior when adding a step - Fixed #617 - Section behavior when adding a step
- Fixed #615 - Recipe Settings are not available when creating new recipe - Fixed #615 - Recipe Settings are not available when creating new recipe
- Fixed #625 - API of today's image returns strange characters - Fixed #625 - API of today's image returns strange characters
- Fixed [#590](https://github.com/hay-kot/mealie/issues/590) - Duplicate Events when using Gunicorn Workers - Fixed [#590](https://github.com/mealie-recipes/mealie/issues/590) - Duplicate Events when using Gunicorn Workers
## Features and Improvements ## Features and Improvements

View File

@ -6,14 +6,14 @@
- FastAPI to 0.78.0 - FastAPI to 0.78.0
- Recipe Ingredient Editor - Recipe Ingredient Editor
- [#1140](https://github.com/hay-kot/mealie/issues/1140) - Error in processing the quantity of ingredients #1140 - UI Now prevents entering not-allowed characters in quantity field - [#1140](https://github.com/mealie-recipes/mealie/issues/1140) - Error in processing the quantity of ingredients #1140 - UI Now prevents entering not-allowed characters in quantity field
- UI now allows no value to be set in addition to a zero (0) value. - UI now allows no value to be set in addition to a zero (0) value.
- [#1237](https://github.com/hay-kot/mealie/issues/1237) - UI: Saving a 0 quantity ingredient displays 0 until the page is refreshed #1237 - UI Now properly reacts to changes in the quantity field. - [#1237](https://github.com/mealie-recipes/mealie/issues/1237) - UI: Saving a 0 quantity ingredient displays 0 until the page is refreshed #1237 - UI Now properly reacts to changes in the quantity field.
- Fix Mealie v0.5.x migration issue [#1183](https://github.com/hay-kot/mealie/issues/1183) - Fix Mealie v0.5.x migration issue [#1183](https://github.com/mealie-recipes/mealie/issues/1183)
- Consolidated Frontend Types thanks to [@PFischbeck](https://github.com/Fischbeck) - Consolidated Frontend Types thanks to [@PFischbeck](https://github.com/Fischbeck)
- Added support for SSL/No Auth Email [@nkringle](https://github.com/nkringle) - Added support for SSL/No Auth Email [@nkringle](https://github.com/nkringle)
- [Implement several notifications for server actions ](https://github.com/hay-kot/mealie/pull/1234)[@miroito](https://github.com/Miroito) - [Implement several notifications for server actions ](https://github.com/mealie-recipes/mealie/pull/1234)[@miroito](https://github.com/Miroito)
- Fix display issue for shared recipe rendering on server [@PFischbeck](https://github.com/Fischbeck) - Fix display issue for shared recipe rendering on server [@PFischbeck](https://github.com/Fischbeck)
## v1.0.0b - 2022-05-09 ## v1.0.0b - 2022-05-09
@ -36,7 +36,7 @@
- Mealie now stores the original text from parsed ingredients, with the ability to peak at the original text from a recipe. [@miroito](https://github.com/Miroito) - Mealie now stores the original text from parsed ingredients, with the ability to peak at the original text from a recipe. [@miroito](https://github.com/Miroito)
- Added some management / utility functions for administrators to manage data and cleanup artifacts from the file system. - Added some management / utility functions for administrators to manage data and cleanup artifacts from the file system.
- Fix clear url action in recipe creation [#1101](https://github.com/hay-kot/mealie/pull/1101) [@miroito](https://github.com/Miroito) - Fix clear url action in recipe creation [#1101](https://github.com/mealie-recipes/mealie/pull/1101) [@miroito](https://github.com/Miroito)
- Add group statistics calculations and data storage measurements - Add group statistics calculations and data storage measurements
- No hard limits are currently imposed on groups - though this may be implemented in the future. - No hard limits are currently imposed on groups - though this may be implemented in the future.

View File

@ -1,29 +1,29 @@
### Bug Fixes ### Bug Fixes
- Bump isomorphic-dompurify from 0.18.0 to 0.19.0 in /frontend ([#1257](https://github.com/hay-kot/mealie/issues/1257)) - Bump isomorphic-dompurify from 0.18.0 to 0.19.0 in /frontend ([#1257](https://github.com/mealie-recipes/mealie/issues/1257))
- Bump @nuxtjs/auth-next in /frontend ([#1265](https://github.com/hay-kot/mealie/issues/1265)) - Bump @nuxtjs/auth-next in /frontend ([#1265](https://github.com/mealie-recipes/mealie/issues/1265))
- Bad dev dependency ([#1281](https://github.com/hay-kot/mealie/issues/1281)) - Bad dev dependency ([#1281](https://github.com/mealie-recipes/mealie/issues/1281))
- Add touch support for mealplanner delete ([#1298](https://github.com/hay-kot/mealie/issues/1298)) - Add touch support for mealplanner delete ([#1298](https://github.com/mealie-recipes/mealie/issues/1298))
### Documentation ### Documentation
- Add references for VSCode dev containers ([#1299](https://github.com/hay-kot/mealie/issues/1299)) - Add references for VSCode dev containers ([#1299](https://github.com/mealie-recipes/mealie/issues/1299))
- Docker-compose.dev.yml is currently not functional ([#1300](https://github.com/hay-kot/mealie/issues/1300)) - Docker-compose.dev.yml is currently not functional ([#1300](https://github.com/mealie-recipes/mealie/issues/1300))
### Features ### Features
- Add reports to bulk recipe import (url) ([#1294](https://github.com/hay-kot/mealie/issues/1294)) - Add reports to bulk recipe import (url) ([#1294](https://github.com/mealie-recipes/mealie/issues/1294))
- Rewrite print implementation to support new ing ([#1305](https://github.com/hay-kot/mealie/issues/1305)) - Rewrite print implementation to support new ing ([#1305](https://github.com/mealie-recipes/mealie/issues/1305))
### Miscellaneous Tasks ### Miscellaneous Tasks
- Github stalebot changes ([#1271](https://github.com/hay-kot/mealie/issues/1271)) - Github stalebot changes ([#1271](https://github.com/mealie-recipes/mealie/issues/1271))
- Bump eslint-plugin-nuxt in /frontend ([#1258](https://github.com/hay-kot/mealie/issues/1258)) - Bump eslint-plugin-nuxt in /frontend ([#1258](https://github.com/mealie-recipes/mealie/issues/1258))
- Bump @vue/runtime-dom in /frontend ([#1259](https://github.com/hay-kot/mealie/issues/1259)) - Bump @vue/runtime-dom in /frontend ([#1259](https://github.com/mealie-recipes/mealie/issues/1259))
- Bump nuxt-vite from 0.1.3 to 0.3.5 in /frontend ([#1260](https://github.com/hay-kot/mealie/issues/1260)) - Bump nuxt-vite from 0.1.3 to 0.3.5 in /frontend ([#1260](https://github.com/mealie-recipes/mealie/issues/1260))
- Bump vue2-script-setup-transform in /frontend ([#1263](https://github.com/hay-kot/mealie/issues/1263)) - Bump vue2-script-setup-transform in /frontend ([#1263](https://github.com/mealie-recipes/mealie/issues/1263))
- Update dev dependencies ([#1282](https://github.com/hay-kot/mealie/issues/1282)) - Update dev dependencies ([#1282](https://github.com/mealie-recipes/mealie/issues/1282))
### Refactor ### Refactor
- Split up recipe create page ([#1283](https://github.com/hay-kot/mealie/issues/1283)) - Split up recipe create page ([#1283](https://github.com/mealie-recipes/mealie/issues/1283))

View File

@ -1,36 +1,36 @@
### Bug Fixes ### Bug Fixes
- Update issue links in v1.0.0beta-2 changelog ([#1312](https://github.com/hay-kot/mealie/issues/1312)) - Update issue links in v1.0.0beta-2 changelog ([#1312](https://github.com/mealie-recipes/mealie/issues/1312))
- Bad import path ([#1313](https://github.com/hay-kot/mealie/issues/1313)) - Bad import path ([#1313](https://github.com/mealie-recipes/mealie/issues/1313))
- Printer page refs ([#1314](https://github.com/hay-kot/mealie/issues/1314)) - Printer page refs ([#1314](https://github.com/mealie-recipes/mealie/issues/1314))
- Consolidate stores to fix mismatched state - Consolidate stores to fix mismatched state
- Bump @vue/composition-api from 1.6.1 to 1.6.2 in /frontend ([#1275](https://github.com/hay-kot/mealie/issues/1275)) - Bump @vue/composition-api from 1.6.1 to 1.6.2 in /frontend ([#1275](https://github.com/mealie-recipes/mealie/issues/1275))
- Shopping list label editor ([#1333](https://github.com/hay-kot/mealie/issues/1333)) - Shopping list label editor ([#1333](https://github.com/mealie-recipes/mealie/issues/1333))
### Features ### Features
- Default unit fractions to True - Default unit fractions to True
- Add unit abbreviation support ([#1332](https://github.com/hay-kot/mealie/issues/1332)) - Add unit abbreviation support ([#1332](https://github.com/mealie-recipes/mealie/issues/1332))
- Attached images by drag and drop for recipe steps ([#1341](https://github.com/hay-kot/mealie/issues/1341)) - Attached images by drag and drop for recipe steps ([#1341](https://github.com/mealie-recipes/mealie/issues/1341))
### Docs ### Docs
- Render homepage social media link images at 32x32 size ([#1310](https://github.com/hay-kot/mealie/issues/1310)) - Render homepage social media link images at 32x32 size ([#1310](https://github.com/mealie-recipes/mealie/issues/1310))
### Miscellaneous Tasks ### Miscellaneous Tasks
- Init git-cliff config - Init git-cliff config
- Bump @types/sortablejs in /frontend ([#1287](https://github.com/hay-kot/mealie/issues/1287)) - Bump @types/sortablejs in /frontend ([#1287](https://github.com/mealie-recipes/mealie/issues/1287))
- Bump @babel/eslint-parser in /frontend ([#1290](https://github.com/hay-kot/mealie/issues/1290)) - Bump @babel/eslint-parser in /frontend ([#1290](https://github.com/mealie-recipes/mealie/issues/1290))
### Refactor ### Refactor
- Unify recipe-organizer components ([#1340](https://github.com/hay-kot/mealie/issues/1340)) - Unify recipe-organizer components ([#1340](https://github.com/mealie-recipes/mealie/issues/1340))
### Security ### Security
- Delay server response whenever username is non existing ([#1338](https://github.com/hay-kot/mealie/issues/1338)) - Delay server response whenever username is non existing ([#1338](https://github.com/mealie-recipes/mealie/issues/1338))
### Wip ### Wip
- Pagination-repository ([#1316](https://github.com/hay-kot/mealie/issues/1316)) - Pagination-repository ([#1316](https://github.com/mealie-recipes/mealie/issues/1316))

View File

@ -63,57 +63,57 @@ If either of the above actions prevent the user from uploading images, the appli
### Bug Fixes ### Bug Fixes
- For erroneously-translated datetime config ([#1362](https://github.com/hay-kot/mealie/issues/1362)) - For erroneously-translated datetime config ([#1362](https://github.com/mealie-recipes/mealie/issues/1362))
- Fixed text color on RecipeCard in RecipePrintView and implemented ingredient sections ([#1351](https://github.com/hay-kot/mealie/issues/1351)) - Fixed text color on RecipeCard in RecipePrintView and implemented ingredient sections ([#1351](https://github.com/mealie-recipes/mealie/issues/1351))
- Ingredient sections lost after parsing ([#1368](https://github.com/hay-kot/mealie/issues/1368)) - Ingredient sections lost after parsing ([#1368](https://github.com/mealie-recipes/mealie/issues/1368))
- Increased float rounding precision for CRF parser ([#1369](https://github.com/hay-kot/mealie/issues/1369)) - Increased float rounding precision for CRF parser ([#1369](https://github.com/mealie-recipes/mealie/issues/1369))
- Infinite scroll bug on all recipes page ([#1393](https://github.com/hay-kot/mealie/issues/1393)) - Infinite scroll bug on all recipes page ([#1393](https://github.com/mealie-recipes/mealie/issues/1393))
- Fast fail of bulk importer ([#1394](https://github.com/hay-kot/mealie/issues/1394)) - Fast fail of bulk importer ([#1394](https://github.com/mealie-recipes/mealie/issues/1394))
- Bump @mdi/js from 5.9.55 to 6.7.96 in /frontend ([#1279](https://github.com/hay-kot/mealie/issues/1279)) - Bump @mdi/js from 5.9.55 to 6.7.96 in /frontend ([#1279](https://github.com/mealie-recipes/mealie/issues/1279))
- Bump @nuxtjs/i18n from 7.0.3 to 7.2.2 in /frontend ([#1288](https://github.com/hay-kot/mealie/issues/1288)) - Bump @nuxtjs/i18n from 7.0.3 to 7.2.2 in /frontend ([#1288](https://github.com/mealie-recipes/mealie/issues/1288))
- Bump date-fns from 2.23.0 to 2.28.0 in /frontend ([#1293](https://github.com/hay-kot/mealie/issues/1293)) - Bump date-fns from 2.23.0 to 2.28.0 in /frontend ([#1293](https://github.com/mealie-recipes/mealie/issues/1293))
- Bump fuse.js from 6.5.3 to 6.6.2 in /frontend ([#1325](https://github.com/hay-kot/mealie/issues/1325)) - Bump fuse.js from 6.5.3 to 6.6.2 in /frontend ([#1325](https://github.com/mealie-recipes/mealie/issues/1325))
- Bump core-js from 3.17.2 to 3.23.1 in /frontend ([#1383](https://github.com/hay-kot/mealie/issues/1383)) - Bump core-js from 3.17.2 to 3.23.1 in /frontend ([#1383](https://github.com/mealie-recipes/mealie/issues/1383))
- All-recipes page now sorts alphabetically ([#1405](https://github.com/hay-kot/mealie/issues/1405)) - All-recipes page now sorts alphabetically ([#1405](https://github.com/mealie-recipes/mealie/issues/1405))
- Sort recent recipes by created_at instead of date_added ([#1417](https://github.com/hay-kot/mealie/issues/1417)) - Sort recent recipes by created_at instead of date_added ([#1417](https://github.com/mealie-recipes/mealie/issues/1417))
- Only show scaler when ingredients amounts enabled ([#1426](https://github.com/hay-kot/mealie/issues/1426)) - Only show scaler when ingredients amounts enabled ([#1426](https://github.com/mealie-recipes/mealie/issues/1426))
- Add missing types for API token deletion ([#1428](https://github.com/hay-kot/mealie/issues/1428)) - Add missing types for API token deletion ([#1428](https://github.com/mealie-recipes/mealie/issues/1428))
- Entry nutrition checker ([#1448](https://github.com/hay-kot/mealie/issues/1448)) - Entry nutrition checker ([#1448](https://github.com/mealie-recipes/mealie/issues/1448))
- Use == operator instead of is_ for sql queries ([#1453](https://github.com/hay-kot/mealie/issues/1453)) - Use == operator instead of is_ for sql queries ([#1453](https://github.com/mealie-recipes/mealie/issues/1453))
- Use `mtime` instead of `ctime` for backup dates ([#1461](https://github.com/hay-kot/mealie/issues/1461)) - Use `mtime` instead of `ctime` for backup dates ([#1461](https://github.com/mealie-recipes/mealie/issues/1461))
- Mealplan pagination ([#1464](https://github.com/hay-kot/mealie/issues/1464)) - Mealplan pagination ([#1464](https://github.com/mealie-recipes/mealie/issues/1464))
- Properly use pagination for group event notifies ([#1512](https://github.com/hay-kot/mealie/pull/1512)) - Properly use pagination for group event notifies ([#1512](https://github.com/mealie-recipes/mealie/pull/1512))
### Documentation ### Documentation
- Add go bulk import example ([#1388](https://github.com/hay-kot/mealie/issues/1388)) - Add go bulk import example ([#1388](https://github.com/mealie-recipes/mealie/issues/1388))
- Fix old link - Fix old link
- Pagination and filtering, and fixed a few broken links ([#1488](https://github.com/hay-kot/mealie/issues/1488)) - Pagination and filtering, and fixed a few broken links ([#1488](https://github.com/mealie-recipes/mealie/issues/1488))
### Features ### Features
- Toggle display of ingredient references in recipe instructions ([#1268](https://github.com/hay-kot/mealie/issues/1268)) - Toggle display of ingredient references in recipe instructions ([#1268](https://github.com/mealie-recipes/mealie/issues/1268))
- Add custom scaling option ([#1345](https://github.com/hay-kot/mealie/issues/1345)) - Add custom scaling option ([#1345](https://github.com/mealie-recipes/mealie/issues/1345))
- Implemented "order by" API parameters for recipe, food, and unit queries ([#1356](https://github.com/hay-kot/mealie/issues/1356)) - Implemented "order by" API parameters for recipe, food, and unit queries ([#1356](https://github.com/mealie-recipes/mealie/issues/1356))
- Implement user favorites page ([#1376](https://github.com/hay-kot/mealie/issues/1376)) - Implement user favorites page ([#1376](https://github.com/mealie-recipes/mealie/issues/1376))
- Extend Apprise JSON notification functionality with programmatic data ([#1355](https://github.com/hay-kot/mealie/issues/1355)) - Extend Apprise JSON notification functionality with programmatic data ([#1355](https://github.com/mealie-recipes/mealie/issues/1355))
- Mealplan-webhooks ([#1403](https://github.com/hay-kot/mealie/issues/1403)) - Mealplan-webhooks ([#1403](https://github.com/mealie-recipes/mealie/issues/1403))
- Added "last-modified" header to supported record types ([#1379](https://github.com/hay-kot/mealie/issues/1379)) - Added "last-modified" header to supported record types ([#1379](https://github.com/mealie-recipes/mealie/issues/1379))
- Re-write get all routes to use pagination ([#1424](https://github.com/hay-kot/mealie/issues/1424)) - Re-write get all routes to use pagination ([#1424](https://github.com/mealie-recipes/mealie/issues/1424))
- Advanced filtering API ([#1468](https://github.com/hay-kot/mealie/issues/1468)) - Advanced filtering API ([#1468](https://github.com/mealie-recipes/mealie/issues/1468))
- Restore frontend sorting for all recipes ([#1497](https://github.com/hay-kot/mealie/issues/1497)) - Restore frontend sorting for all recipes ([#1497](https://github.com/mealie-recipes/mealie/issues/1497))
- Implemented local storage for sorting and dynamic sort icons on the new recipe sort card ([1506](https://github.com/hay-kot/mealie/pull/1506)) - Implemented local storage for sorting and dynamic sort icons on the new recipe sort card ([1506](https://github.com/mealie-recipes/mealie/pull/1506))
- create new foods and units from their Data Management pages ([#1511](https://github.com/hay-kot/mealie/pull/1511)) - create new foods and units from their Data Management pages ([#1511](https://github.com/mealie-recipes/mealie/pull/1511))
### Miscellaneous Tasks ### Miscellaneous Tasks
- Bump dev deps ([#1418](https://github.com/hay-kot/mealie/issues/1418)) - Bump dev deps ([#1418](https://github.com/mealie-recipes/mealie/issues/1418))
- Bump @vue/runtime-dom in /frontend ([#1423](https://github.com/hay-kot/mealie/issues/1423)) - Bump @vue/runtime-dom in /frontend ([#1423](https://github.com/mealie-recipes/mealie/issues/1423))
- Backend page_all route cleanup ([#1483](https://github.com/hay-kot/mealie/issues/1483)) - Backend page_all route cleanup ([#1483](https://github.com/mealie-recipes/mealie/issues/1483))
### Refactor ### Refactor
- Remove depreciated repo call ([#1370](https://github.com/hay-kot/mealie/issues/1370)) - Remove depreciated repo call ([#1370](https://github.com/mealie-recipes/mealie/issues/1370))
### Hotfix ### Hotfix
@ -121,6 +121,6 @@ If either of the above actions prevent the user from uploading images, the appli
### UI ### UI
- Improve parser ui text display ([#1437](https://github.com/hay-kot/mealie/issues/1437)) - Improve parser ui text display ([#1437](https://github.com/mealie-recipes/mealie/issues/1437))
<!-- generated by git-cliff --> <!-- generated by git-cliff -->

View File

@ -20,8 +20,8 @@ Pull requests are the best way to propose changes to the codebase (we use [Githu
## Any contributions you make will be under the AGPL Software License ## Any contributions you make will be under the AGPL Software License
In short, when you submit code changes, your submissions are understood to be under the same [AGPL License](https://choosealicense.com/licenses/agpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern. In short, when you submit code changes, your submissions are understood to be under the same [AGPL License](https://choosealicense.com/licenses/agpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
## Report bugs using Github's [issues](https://github.com/hay-kot/mealie/issues) ## Report bugs using Github's [issues](https://github.com/mealie-recipes/mealie/issues)
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/hay-kot/mealie/issues/new); it's that easy! We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/mealie-recipes/mealie/issues/new); it's that easy!
## Write bug reports with detail, background, and sample code ## Write bug reports with detail, background, and sample code
**Great Bug Reports** tend to have: **Great Bug Reports** tend to have:

View File

@ -124,9 +124,9 @@ docker-prod 🐳 Build and Start Docker Production Stack
``` ```
## Internationalization ## Internationalization
### Frontend ### Frontend
We use vue-i18n package for internationalization. Translations are stored in json format located in [frontend/lang/messages](https://github.com/hay-kot/mealie/tree/mealie-next/frontend/lang/messages). We use vue-i18n package for internationalization. Translations are stored in json format located in [frontend/lang/messages](https://github.com/mealie-recipes/mealie/tree/mealie-next/frontend/lang/messages).
### Backend ### Backend
Translations are stored in json format located in [mealie/lang/messages](https://github.com/hay-kot/mealie/tree/mealie-next/mealie/lang/messages). Translations are stored in json format located in [mealie/lang/messages](https://github.com/mealie-recipes/mealie/tree/mealie-next/mealie/lang/messages).
### Quick frontend localization with VS Code ### Quick frontend localization with VS Code
[i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) is helpful for generating new strings to translate using Code Actions. It also has a nice feature, which shows translations in-place when editing code. [i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) is helpful for generating new strings to translate using Code Actions. It also has a nice feature, which shows translations in-place when editing code.

View File

@ -15,6 +15,6 @@ Alternatively, you can register a new parser by fulfilling the `ABCIngredientPar
## Links ## Links
- [Pretrained Model](https://github.com/hay-kot/mealie-nlp-model) - [Pretrained Model](https://github.com/mealie-recipes/mealie-nlp-model)
- [CRF++ (Forked)](https://github.com/hay-kot/crfpp) - [CRF++ (Forked)](https://github.com/hay-kot/crfpp)

View File

@ -5,8 +5,8 @@
![Image from apple site](https://help.apple.com/assets/5E8CEA35094622DF10489984/5E8CEA42094622DF1048998D/en_US/ed1f9c157cdefc13e0161e0f70015455.png) ![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. 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/hay-kot/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X. 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. This is a useful utility for iOS users who browse for recipes in their web browser from their devices.

View File

@ -11,13 +11,13 @@ To install Mealie on your server there are a few steps for proper configuration.
## Pre-work ## 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/) [Get Docker](https://docs.docker.com/get-docker/)
[Get Docker Compose](https://docs.docker.com/compose/install/) [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/amd64
- linux/arm64 - linux/arm64

View File

@ -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. 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. 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.

View File

@ -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. 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)

View File

@ -34,4 +34,4 @@ ALTER USER mealie WITH SUPERUSER;
ALTER USER mealie WITH NOSUPERUSER; 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)

File diff suppressed because one or more lines are too long

View File

@ -330,9 +330,6 @@ export default defineComponent({
.list-group { .list-group {
min-height: 38px; min-height: 38px;
} }
.list-group-item {
cursor: move;
}
.list-group-item i { .list-group-item i {
cursor: pointer; cursor: pointer;
} }

View File

@ -117,7 +117,7 @@
@click="toggleDisabled(index)" @click="toggleDisabled(index)"
> >
<v-card-title :class="{ 'pb-0': !isChecked(index) }"> <v-card-title :class="{ 'pb-0': !isChecked(index) }">
<span class="handle"> <span :class="isEditForm ? 'handle' : ''">
<v-icon v-if="isEditForm" size="26" class="pb-1">{{ $globals.icons.arrowUpDown }}</v-icon> <v-icon v-if="isEditForm" size="26" class="pb-1">{{ $globals.icons.arrowUpDown }}</v-icon>
{{ $t("recipe.step-index", { step: index + 1 }) }} {{ $t("recipe.step-index", { step: index + 1 }) }}
</span> </span>
@ -671,9 +671,6 @@ export default defineComponent({
.list-group { .list-group {
min-height: 38px; min-height: 38px;
} }
.list-group-item {
cursor: move;
}
.list-group-item i { .list-group-item i {
cursor: pointer; cursor: pointer;
} }

View File

@ -49,6 +49,10 @@ export default defineComponent({
]; ];
function handleRowClick(item: ReportSummary) { function handleRowClick(item: ReportSummary) {
if (item.status === "in-progress") {
return;
}
router.push(`/group/reports/${item.id}`); router.push(`/group/reports/${item.id}`);
} }

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Sleutelwoord", "keyword": "Sleutelwoord",
"link-copied": "Skakel gekopieer", "link-copied": "Skakel gekopieer",
"loading": "Loading",
"loading-events": "Besig om gebeurtenisse te laai", "loading-events": "Besig om gebeurtenisse te laai",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,9 +114,10 @@
"json": "JSON", "json": "JSON",
"keyword": "كلمة مفتاحية", "keyword": "كلمة مفتاحية",
"link-copied": "تمّ نسْخ الرّابط", "link-copied": "تمّ نسْخ الرّابط",
"loading": "جار التحميل",
"loading-events": "جاري تحميل الأحداث", "loading-events": "جاري تحميل الأحداث",
"loading-recipe": "Loading recipe...", "loading-recipe": "جار تحميل الوصفات...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "جاري تحميل بيانات OCR...",
"loading-recipes": "جار تحميل الوصفات", "loading-recipes": "جار تحميل الوصفات",
"message": "الرسائل النصية Sms", "message": "الرسائل النصية Sms",
"monday": "الإثنين", "monday": "الإثنين",
@ -226,28 +227,28 @@
"manage-members": "إدارة الأعضاء", "manage-members": "إدارة الأعضاء",
"manage-members-description": "Manage the permissions of the members in your groups. {manage} allows the user to access the data-management page {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.", "manage-members-description": "Manage the permissions of the members in your groups. {manage} allows the user to access the data-management page {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.",
"manage": "Manage", "manage": "Manage",
"invite": "Invite", "invite": "دعوة",
"looking-to-update-your-profile": "Looking to Update Your Profile?", "looking-to-update-your-profile": "هل ترغب في تحديث ملفك الشخصي؟",
"default-recipe-preferences-description": "These are the default settings when a new recipe is created in your group. These can be changed for individual recipes in the recipe settings menu.", "default-recipe-preferences-description": "هذه هي الإعدادات الافتراضية عند إنشاء وصفة جديدة في مجموعتك. يمكن تغيير هذه الوصفات الفردية في قائمة إعدادات الوصفات.",
"default-recipe-preferences": "Default Recipe Preferences", "default-recipe-preferences": "Default Recipe Preferences",
"group-preferences": "Group Preferences", "group-preferences": "إعدادات المجموعة",
"private-group": "Private Group", "private-group": "مجموعة خاصة",
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.", "private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes", "allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
"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", "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": "عرض معلومات التغذية",
"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-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",
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available", "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": "Default to landscape view",
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view", "default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
"disable-users-from-commenting-on-recipes": "Disable users from commenting on recipes", "disable-users-from-commenting-on-recipes": "إيقاف المستخدمين من التعليق على الوصفات",
"disable-users-from-commenting-on-recipes-description": "Hides the comment section on the recipe page and disables commenting", "disable-users-from-commenting-on-recipes-description": "Hides the comment section on the recipe page and disables commenting",
"disable-organizing-recipe-ingredients-by-units-and-food": "Disable organizing recipe ingredients by units and food", "disable-organizing-recipe-ingredients-by-units-and-food": "Disable organizing recipe ingredients by units and food",
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields.", "disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields.",
"general-preferences": "General Preferences", "general-preferences": "General Preferences",
"group-recipe-preferences": "Group Recipe Preferences", "group-recipe-preferences": "Group Recipe Preferences",
"report": "Report", "report": "تقرير",
"report-with-id": "Report ID: {id}", "report-with-id": "Report ID: {id}",
"group-management": "Group Management", "group-management": "Group Management",
"admin-group-management": "Admin Group Management", "admin-group-management": "Admin Group Management",
@ -293,16 +294,16 @@
"meal-title": "Meal Title", "meal-title": "Meal Title",
"meal-note": "Meal Note", "meal-note": "Meal Note",
"note-only": "Note Only", "note-only": "Note Only",
"random-meal": "Random Meal", "random-meal": "وجبة عشوائية",
"random-dinner": "Random Dinner", "random-dinner": "عشاء عشوائي",
"random-side": "Random Side", "random-side": "Random Side",
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.", "this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
"to-all-days": "to all days", "to-all-days": "إلى جميع الأيام",
"on-days": "on {0}s", "on-days": "on {0}s",
"for-all-meal-types": "for all meal types", "for-all-meal-types": "for all meal types",
"for-type-meal-types": "for {0} meal types", "for-type-meal-types": "for {0} meal types",
"meal-plan-rules": "Meal Plan Rules", "meal-plan-rules": "Meal Plan Rules",
"new-rule": "New Rule", "new-rule": "قاعدة جديدة",
"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.", "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.", "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": "قواعد الوصفات",
@ -313,10 +314,10 @@
"migration": { "migration": {
"migration-data-removed": "Migration data removed", "migration-data-removed": "Migration data removed",
"new-migration": "New Migration", "new-migration": "New Migration",
"no-file-selected": "No File Selected", "no-file-selected": "لم يتمّ اختيار أيّ ملفّ",
"no-migration-data-available": "No Migration Data Available", "no-migration-data-available": "No Migration Data Available",
"previous-migrations": "Previous Migrations", "previous-migrations": "Previous Migrations",
"recipe-migration": "Recipe Migration", "recipe-migration": "نقل الوصفة",
"chowdown": { "chowdown": {
"description": "Migrate data from Chowdown", "description": "Migrate data from 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 natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
@ -384,12 +385,12 @@
"404-page-not-found": "404 Page not found", "404-page-not-found": "404 Page not found",
"all-recipes": "All Recipes", "all-recipes": "All Recipes",
"new-page-created": "New page created", "new-page-created": "New page created",
"page": "Page", "page": "الصفحة",
"page-creation-failed": "Page creation failed", "page-creation-failed": "Page creation failed",
"page-deleted": "Page deleted", "page-deleted": "تم حذف الصفحة",
"page-deletion-failed": "Page deletion failed", "page-deletion-failed": "حذف الصفحة فشل",
"page-update-failed": "Page update failed", "page-update-failed": "تحديث الصفحة فشل",
"page-updated": "Page updated", "page-updated": "تم تحديث صفحة",
"pages-update-failed": "Pages update failed", "pages-update-failed": "Pages update failed",
"pages-updated": "Pages updated", "pages-updated": "Pages updated",
"404-not-found": "لم يتم العثور على الصفحة. خطأ 404", "404-not-found": "لم يتم العثور على الصفحة. خطأ 404",

View File

@ -77,7 +77,7 @@
"tag-events": "Събития за таг", "tag-events": "Събития за таг",
"category-events": "Събития за категория", "category-events": "Събития за категория",
"when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група", "when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група",
"recipe-events": "Recipe Events" "recipe-events": "Събития на рецептата"
}, },
"general": { "general": {
"cancel": "Откажи", "cancel": "Откажи",
@ -114,9 +114,10 @@
"json": "JSON", "json": "JSON",
"keyword": "Ключова дума", "keyword": "Ключова дума",
"link-copied": "Линкът е копиран", "link-copied": "Линкът е копиран",
"loading": "Зареждане",
"loading-events": "Зареждане на събития", "loading-events": "Зареждане на събития",
"loading-recipe": "Loading recipe...", "loading-recipe": "Зареждане на рецептата...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Зареждане на OCR данните...",
"loading-recipes": "Рецептите се зареждат", "loading-recipes": "Рецептите се зареждат",
"message": "Съобщение", "message": "Съобщение",
"monday": "Понеделник", "monday": "Понеделник",
@ -127,7 +128,7 @@
"no-recipe-found": "Няма намерени рецепти", "no-recipe-found": "Няма намерени рецепти",
"ok": "Добре", "ok": "Добре",
"options": "Опции:", "options": "Опции:",
"plural-name": "Plural Name", "plural-name": "Име в множествено число",
"print": "Принтирай", "print": "Принтирай",
"print-preferences": "Настройки на принтиране", "print-preferences": "Настройки на принтиране",
"random": "Произволно", "random": "Произволно",
@ -197,7 +198,7 @@
"refresh": "Опресни", "refresh": "Опресни",
"upload-file": "Качване на файл", "upload-file": "Качване на файл",
"created-on-date": "Създадено на {0}", "created-on-date": "Създадено на {0}",
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes." "unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените."
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
@ -212,7 +213,7 @@
"group-id-with-value": "ID на Групата: {groupID}", "group-id-with-value": "ID на Групата: {groupID}",
"group-name": "Име на групата", "group-name": "Име на групата",
"group-not-found": "Групата не е намерена", "group-not-found": "Групата не е намерена",
"group-token": "Group Token", "group-token": "Токен на групата",
"group-with-value": "Група: {groupID}", "group-with-value": "Група: {groupID}",
"groups": "Групи", "groups": "Групи",
"manage-groups": "Управление на групи", "manage-groups": "Управление на групи",
@ -248,7 +249,7 @@
"general-preferences": "Общи предпочитания", "general-preferences": "Общи предпочитания",
"group-recipe-preferences": "Предпочитания за рецепта по група", "group-recipe-preferences": "Предпочитания за рецепта по група",
"report": "Сигнал", "report": "Сигнал",
"report-with-id": "Report ID: {id}", "report-with-id": "Номер на сигнала: {id}",
"group-management": "Управление на групите", "group-management": "Управление на групите",
"admin-group-management": "Административно управление на групите", "admin-group-management": "Административно управление на групите",
"admin-group-management-text": "Промените по тази група ще бъдат отразени моментално.", "admin-group-management-text": "Промените по тази група ще бъдат отразени моментално.",
@ -469,9 +470,9 @@
"add-to-plan": "Добави към план", "add-to-plan": "Добави към план",
"add-to-timeline": "Добави към времевата линия", "add-to-timeline": "Добави към времевата линия",
"recipe-added-to-list": "Рецептата е добавена към списъка", "recipe-added-to-list": "Рецептата е добавена към списъка",
"recipes-added-to-list": "Recipes added to list", "recipes-added-to-list": "Рецептите са добавени към списъка",
"recipe-added-to-mealplan": "Рецептата е добавена към хранителния план", "recipe-added-to-mealplan": "Рецептата е добавена към хранителния план",
"failed-to-add-recipes-to-list": "Failed to add recipe to list", "failed-to-add-recipes-to-list": "Неуспешно добавяне на рецепта към списъка",
"failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план", "failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план",
"yield": "Добив", "yield": "Добив",
"quantity": "Количество", "quantity": "Количество",
@ -513,7 +514,7 @@
"message-key": "Ключ на съобщението", "message-key": "Ключ на съобщението",
"parse": "Анализирай", "parse": "Анализирай",
"attach-images-hint": "Прикачете снимки като ги влачете и пуснете в редактора", "attach-images-hint": "Прикачете снимки като ги влачете и пуснете в редактора",
"drop-image": "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": "Анализирай съставките",
@ -572,16 +573,16 @@
"search-hint": "Натисни '/'", "search-hint": "Натисни '/'",
"advanced": "Разширени", "advanced": "Разширени",
"auto-search": "Автоматично търсене", "auto-search": "Автоматично търсене",
"no-results": "No results found" "no-results": "Не са намерени резултати"
}, },
"settings": { "settings": {
"add-a-new-theme": "Добавяне на нова тема", "add-a-new-theme": "Добавяне на нова тема",
"admin-settings": "Административни настройки", "admin-settings": "Административни настройки",
"backup": { "backup": {
"backup-created": "Backup created successfully", "backup-created": "Архивът е създаден успешно",
"backup-created-at-response-export_path": "Резервно копие е създадено на {path}", "backup-created-at-response-export_path": "Резервно копие е създадено на {path}",
"backup-deleted": "Резервното копие е изтрито", "backup-deleted": "Резервното копие е изтрито",
"restore-success": "Restore successful", "restore-success": "Успешно възстановяване",
"backup-tag": "Таг на резервното копие", "backup-tag": "Таг на резервното копие",
"create-heading": "Създай резервно копие", "create-heading": "Създай резервно копие",
"delete-backup": "Изтрий резервно копие", "delete-backup": "Изтрий резервно копие",
@ -690,13 +691,13 @@
"configuration": "Конфигурация", "configuration": "Конфигурация",
"docker-volume": "Docker том", "docker-volume": "Docker том",
"docker-volume-help": "Mealie изисква контейнерът на frontend и backend да споделят един и същ том на docker или място за съхранение. Това гарантира, че frontend контейнера може да има правилен достъп до изображенията и активите, съхранени на диска.", "docker-volume-help": "Mealie изисква контейнерът на frontend и backend да споделят един и същ том на docker или място за съхранение. Това гарантира, че frontend контейнера може да има правилен достъп до изображенията и активите, съхранени на диска.",
"volumes-are-misconfigured": "Volumes are misconfigured.", "volumes-are-misconfigured": "Томовете са конфигурирани неправилно.",
"volumes-are-configured-correctly": "Томовете са конфигурирани правилно.", "volumes-are-configured-correctly": "Томовете са конфигурирани правилно.",
"status-unknown-try-running-a-validation": "Статус Неизвестен. Опитайте да стартирате проверка.", "status-unknown-try-running-a-validation": "Статус Неизвестен. Опитайте да стартирате проверка.",
"validate": "Валидирайте", "validate": "Валидирайте",
"email-configuration-status": "Статус на имейл конфигурация", "email-configuration-status": "Статус на имейл конфигурация",
"email-configured": "Email Configured", "email-configured": "Email е конфигуриран",
"email-test-results": "Email Test Results", "email-test-results": "Резултати от тест на email",
"ready": "Готов", "ready": "Готов",
"not-ready": "Не е готово - Проверете променливите на средата", "not-ready": "Не е готово - Проверете променливите на средата",
"succeeded": "Успешно", "succeeded": "Успешно",
@ -831,7 +832,7 @@
"password-updated": "Паролата е актуализирана", "password-updated": "Паролата е актуализирана",
"password": "Парола", "password": "Парола",
"password-strength": "Сигурността на паролата е {strength}", "password-strength": "Сигурността на паролата е {strength}",
"please-enter-password": "Please enter your new password.", "please-enter-password": "Моля, въведете новата си парола.",
"register": "Регистриране", "register": "Регистриране",
"reset-password": "Нулиране на паролата", "reset-password": "Нулиране на паролата",
"sign-in": "Влизане", "sign-in": "Влизане",
@ -852,7 +853,7 @@
"username": "Потребителско име", "username": "Потребителско име",
"users-header": "Потребители", "users-header": "Потребители",
"users": "Потребители", "users": "Потребители",
"user-not-found": "User not found", "user-not-found": "Потребителят не е намерен",
"webhook-time": "Webhook време", "webhook-time": "Webhook време",
"webhooks-enabled": "Webhooks са пуснати", "webhooks-enabled": "Webhooks са пуснати",
"you-are-not-allowed-to-create-a-user": "Нямате право да създавате потребител", "you-are-not-allowed-to-create-a-user": "Нямате право да създавате потребител",
@ -875,7 +876,7 @@
"user-management": "Управление на потребителя", "user-management": "Управление на потребителя",
"reset-locked-users": "Нулиране на заключените потребители", "reset-locked-users": "Нулиране на заключените потребители",
"admin-user-creation": "Създаване на администратор", "admin-user-creation": "Създаване на администратор",
"admin-user-management": "Admin User Management", "admin-user-management": "Управление на администраторите",
"user-details": "Детайли за потребителя", "user-details": "Детайли за потребителя",
"user-name": "Потребителско име", "user-name": "Потребителско име",
"authentication-method": "Метод за автентикация", "authentication-method": "Метод за автентикация",
@ -886,11 +887,11 @@
"user-can-manage-group": "Потребителя може да управлява групата", "user-can-manage-group": "Потребителя може да управлява групата",
"user-can-organize-group-data": "Потребителя може да организира данните на групата", "user-can-organize-group-data": "Потребителя може да организира данните на групата",
"enable-advanced-features": "Включване на разширени функции", "enable-advanced-features": "Включване на разширени функции",
"it-looks-like-this-is-your-first-time-logging-in": "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!", "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Искате ли да виждате това по-често? Уверете се, че сте конфигурирали настройките си за email известяване правилно!",
"forgot-password": "Forgot Password", "forgot-password": "Забравена Парола",
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.", "forgot-password-text": "Въведете Вашият имейл адрес и ние ще ви изпратим линк, с който да промените Вашата парола.",
"changes-reflected-immediately": "Changes to this user will be reflected immediately." "changes-reflected-immediately": "Промените по този потребител ще бъдат отразени моментално."
}, },
"language-dialog": { "language-dialog": {
"translated": "преведено", "translated": "преведено",
@ -912,8 +913,8 @@
"food-label": "Заглавие на храната", "food-label": "Заглавие на храната",
"edit-food": "Редактирай храна", "edit-food": "Редактирай храна",
"food-data": "Данни за храните", "food-data": "Данни за храните",
"example-food-singular": "ex: Onion", "example-food-singular": "пример: Домат",
"example-food-plural": "ex: Onions" "example-food-plural": "пример: Домати"
}, },
"units": { "units": {
"seed-dialog-text": "Заредете базата данни с общи единици въз основа на Вашия местен език.", "seed-dialog-text": "Заредете базата данни с общи единици въз основа на Вашия местен език.",
@ -924,7 +925,7 @@
"merging-unit-into-unit": "Обединяване на {0} с {1}", "merging-unit-into-unit": "Обединяване на {0} с {1}",
"create-unit": "Създаване на мерна единица", "create-unit": "Създаване на мерна единица",
"abbreviation": "Абревиатура", "abbreviation": "Абревиатура",
"plural-abbreviation": "Plural Abbreviation", "plural-abbreviation": "Съкращение за множествено число",
"description": "Описание", "description": "Описание",
"display-as-fraction": "Показване като фракция", "display-as-fraction": "Показване като фракция",
"use-abbreviation": "Използвай съкращение", "use-abbreviation": "Използвай съкращение",
@ -932,10 +933,10 @@
"unit-data": "Данни на мерната единица", "unit-data": "Данни на мерната единица",
"use-abbv": "Използвай съкращение", "use-abbv": "Използвай съкращение",
"fraction": "Фракция", "fraction": "Фракция",
"example-unit-singular": "ex: Tablespoon", "example-unit-singular": "пример: Чаена лъжичка",
"example-unit-plural": "ex: Tablespoons", "example-unit-plural": "пример: Чаени лъжички",
"example-unit-abbreviation-singular": "ex: Tbsp", "example-unit-abbreviation-singular": "пример: ч.л.",
"example-unit-abbreviation-plural": "ex: Tbsps" "example-unit-abbreviation-plural": "пример: ч.л.-ки"
}, },
"labels": { "labels": {
"seed-dialog-text": "Заредете базата данни с общи етикети въз основа на Вашия местен език.", "seed-dialog-text": "Заредете базата данни с общи етикети въз основа на Вашия местен език.",
@ -964,8 +965,8 @@
"delete-recipes": "Изтрий рецепти", "delete-recipes": "Изтрий рецепти",
"source-unit-will-be-deleted": "Изходната мерна единица ще бъде изтрита" "source-unit-will-be-deleted": "Изходната мерна единица ще бъде изтрита"
}, },
"create-alias": "Create Alias", "create-alias": "Създаване на псевдоним",
"manage-aliases": "Manage Aliases", "manage-aliases": "Управление на псевдоними",
"seed-data": "Сийд на данни", "seed-data": "Сийд на данни",
"seed": "Сийд", "seed": "Сийд",
"data-management": "Управление на данни", "data-management": "Управление на данни",
@ -975,24 +976,24 @@
"columns": "Колони", "columns": "Колони",
"combine": "Обедини", "combine": "Обедини",
"categories": { "categories": {
"edit-category": "Edit Category", "edit-category": "Редактиране на категория",
"new-category": "New Category", "new-category": "Нова категория",
"category-data": "Category Data" "category-data": "Категория за данните"
}, },
"tags": { "tags": {
"new-tag": "New Tag", "new-tag": "Нов таг",
"edit-tag": "Edit Tag", "edit-tag": "Редакция на таг",
"tag-data": "Tag Data" "tag-data": "Данни на тага"
}, },
"tools": { "tools": {
"new-tool": "New Tool", "new-tool": "Нов инструмент",
"edit-tool": "Edit Tool", "edit-tool": "Редактирай инструмента",
"tool-data": "Tool Data" "tool-data": "Данни на инструмента"
} }
}, },
"user-registration": { "user-registration": {
"user-registration": "Регистрации на потребител", "user-registration": "Регистрации на потребител",
"registration-success": "Registration Success", "registration-success": "Успешна регистрация",
"join-a-group": "Присъединете се към групата", "join-a-group": "Присъединете се към групата",
"create-a-new-group": "Създай нова група", "create-a-new-group": "Създай нова група",
"provide-registration-token-description": "Моля, предоставете регистрационния маркер, свързан с групата, към която искате да се присъедините. Ще трябва да го получите от съществуващ член на групата.", "provide-registration-token-description": "Моля, предоставете регистрационния маркер, свързан с групата, към която искате да се присъедините. Ще трябва да го получите от съществуващ член на групата.",
@ -1039,7 +1040,7 @@
}, },
"ocr-editor": { "ocr-editor": {
"ocr-editor": "Ocr редактор", "ocr-editor": "Ocr редактор",
"toolbar": "Toolbar", "toolbar": "Лента с инструменти",
"selection-mode": "Режим на избиране", "selection-mode": "Режим на избиране",
"pan-and-zoom-picture": "Мащабиране на изображение", "pan-and-zoom-picture": "Мащабиране на изображение",
"split-text": "Раздели текст", "split-text": "Раздели текст",
@ -1047,8 +1048,8 @@
"split-by-block": "Раздели по текстов блок", "split-by-block": "Раздели по текстов блок",
"flatten": "Изравняване независимо от оригиналното форматиране", "flatten": "Изравняване независимо от оригиналното форматиране",
"help": { "help": {
"help": "Help", "help": "Помощ",
"mouse-modes": "Mouse modes", "mouse-modes": "Режими на мишката",
"selection-mode": "Режим на избиране (по подразбиране)", "selection-mode": "Режим на избиране (по подразбиране)",
"selection-mode-desc": "Режимът за избиране е основният режим, който може да се използва за въвеждане на данни:", "selection-mode-desc": "Режимът за избиране е основният режим, който може да се използва за въвеждане на данни:",
"selection-mode-steps": { "selection-mode-steps": {

View File

@ -77,7 +77,7 @@
"tag-events": "Esdeveniments de les etiquetes", "tag-events": "Esdeveniments de les etiquetes",
"category-events": "Esdeveniments de les categories", "category-events": "Esdeveniments de les categories",
"when-a-new-user-joins-your-group": "Quan un nou usuari s'afegeix al grup", "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": { "general": {
"cancel": "Anuŀla", "cancel": "Anuŀla",
@ -114,9 +114,10 @@
"json": "JSON", "json": "JSON",
"keyword": "Paraula clau", "keyword": "Paraula clau",
"link-copied": "S'ha copiat l'enllaç", "link-copied": "S'ha copiat l'enllaç",
"loading": "Loading",
"loading-events": "Carregant esdeveniments", "loading-events": "Carregant esdeveniments",
"loading-recipe": "Loading recipe...", "loading-recipe": "Carregant la recepta...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Carregant les dades OCR...",
"loading-recipes": "Carregant les receptes", "loading-recipes": "Carregant les receptes",
"message": "Missatge", "message": "Missatge",
"monday": "Dilluns", "monday": "Dilluns",
@ -127,7 +128,7 @@
"no-recipe-found": "No s'han trobat receptes", "no-recipe-found": "No s'han trobat receptes",
"ok": "D'acord", "ok": "D'acord",
"options": "Opcions:", "options": "Opcions:",
"plural-name": "Plural Name", "plural-name": "Nom en plural",
"print": "Imprimiu", "print": "Imprimiu",
"print-preferences": "Imprimiu les preferències", "print-preferences": "Imprimiu les preferències",
"random": "Aleatori", "random": "Aleatori",
@ -197,7 +198,7 @@
"refresh": "Actualitza", "refresh": "Actualitza",
"upload-file": "Puja un fitxer", "upload-file": "Puja un fitxer",
"created-on-date": "Creat el: {0}", "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": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup <b>{groupName}<b/>?",
@ -212,7 +213,7 @@
"group-id-with-value": "Identificador del grup: {groupID}", "group-id-with-value": "Identificador del grup: {groupID}",
"group-name": "Nom del grup", "group-name": "Nom del grup",
"group-not-found": "No s'ha trobat el grup", "group-not-found": "No s'ha trobat el grup",
"group-token": "Group Token", "group-token": "Token del grup",
"group-with-value": "Grup: {groupID}", "group-with-value": "Grup: {groupID}",
"groups": "Grups", "groups": "Grups",
"manage-groups": "Gestiona els grups", "manage-groups": "Gestiona els grups",
@ -246,13 +247,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": "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.", "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", "general-preferences": "Preferències generals",
"group-recipe-preferences": "Group Recipe Preferences", "group-recipe-preferences": "Preferències del grup de receptes",
"report": "Report", "report": "Informe",
"report-with-id": "Report ID: {id}", "report-with-id": "ID de l'informe: {id}",
"group-management": "Gestió de grups", "group-management": "Gestió de grups",
"admin-group-management": "Gestió del grup d'administradors", "admin-group-management": "Gestió del grup d'administradors",
"admin-group-management-text": "Changes to this group will be reflected immediately.", "admin-group-management-text": "Els canvis en aquest grup s'actualitzaran immediatament.",
"group-id-value": "Group Id: {0}" "group-id-value": "ID del grup: {0}"
}, },
"meal-plan": { "meal-plan": {
"create-a-new-meal-plan": "Crea un nou menú", "create-a-new-meal-plan": "Crea un nou menú",
@ -291,68 +292,68 @@
"editor": "Editor", "editor": "Editor",
"meal-recipe": "Recepta del menú", "meal-recipe": "Recepta del menú",
"meal-title": "Títol del menú", "meal-title": "Títol del menú",
"meal-note": "Meal Note", "meal-note": "Notes del menú",
"note-only": "Note Only", "note-only": "Note Only",
"random-meal": "Menú aleatori", "random-meal": "Menú aleatori",
"random-dinner": "Principal aleatori", "random-dinner": "Principal aleatori",
"random-side": "Guarnició aleatòria", "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", "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-all-meal-types": "per a tots els tipus de menús",
"for-type-meal-types": "for {0} meal types", "for-type-meal-types": "per {0} tipus de menús",
"meal-plan-rules": "Meal Plan Rules", "meal-plan-rules": "Normes del planificador de menús",
"new-rule": "New Rule", "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.", "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.", "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-to-all-days": "Aplica a tots els dies",
"applies-on-days": "Applies on {0}s", "applies-on-days": "S'aplicarà en {0}s",
"meal-plan-settings": "Meal Plan Settings" "meal-plan-settings": "Opcions de planificació de menús"
}, },
"migration": { "migration": {
"migration-data-removed": "S'han suprimit les dades migrades", "migration-data-removed": "S'han suprimit les dades migrades",
"new-migration": "New Migration", "new-migration": "Nova migració",
"no-file-selected": "Cap fitxer seleccionat", "no-file-selected": "Cap fitxer seleccionat",
"no-migration-data-available": "No hi han dades disponibles", "no-migration-data-available": "No hi han dades disponibles",
"previous-migrations": "Previous Migrations", "previous-migrations": "Migracions prèvies",
"recipe-migration": "Migració de receptes", "recipe-migration": "Migració de receptes",
"chowdown": { "chowdown": {
"description": "Migreu les dades de 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" "title": "Chowdown"
}, },
"nextcloud": { "nextcloud": {
"description": "Migreu les dades des d'una instància de Nextcloud Cookbook", "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" "title": "Nextcloud Cookbook"
}, },
"copymethat": { "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.", "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": { "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" "title": "Paprika Recipe Manager"
}, },
"mealie-pre-v1": { "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" "title": "Mealie Pre v1.0"
}, },
"tandoor": { "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" "title": "Tandoor Recipes"
}, },
"recipe-data-migrations": "Recipe Data Migrations", "recipe-data-migrations": "Migració de receptes",
"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-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ó", "choose-migration-type": "Elegeix un tipus de migració",
"tag-all-recipes": "Etiqueta totes les receptes amb {tag-name}", "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.", "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-1": "Recepta 1",
"recipe-2": "Recepta 2", "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.", "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": { "plantoeat": {
"title": "Plan to Eat", "title": "Plan to Eat",
"description-long": "Mealie pot importar receptes de Plan to Eat." "description-long": "Mealie pot importar receptes de Plan to Eat."
@ -469,9 +470,9 @@
"add-to-plan": "Afegiu al menú", "add-to-plan": "Afegiu al menú",
"add-to-timeline": "Afegir a la cronologia", "add-to-timeline": "Afegir a la cronologia",
"recipe-added-to-list": "Recepta afegida a la llista", "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ú", "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ú", "failed-to-add-recipe-to-mealplan": "S'ha produït un error afegint la recepta al menú",
"yield": "Racions", "yield": "Racions",
"quantity": "Quantitat", "quantity": "Quantitat",
@ -508,50 +509,50 @@
"made-this": "Ho he fet", "made-this": "Ho he fet",
"how-did-it-turn-out": "Com ha sortit?", "how-did-it-turn-out": "Com ha sortit?",
"user-made-this": "{user} ha fet això", "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.", "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", "message-key": "Message Key",
"parse": "Parse", "parse": "Analitzar",
"attach-images-hint": "Attach images by dragging & dropping them into the editor", "attach-images-hint": "Afegeix imatges arrossegant i deixant anar la imatge a l'editor",
"drop-image": "Drop image", "drop-image": "Deixa anar la imatge",
"enable-ingredient-amounts-to-use-this-feature": "Enable ingredient amounts to use this feature", "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": "Recipes with units or foods defined cannot be parsed.", "recipes-with-units-or-foods-defined-cannot-be-parsed": "Les receptes amb unitats o aliments definits no es poden analitzar.",
"parse-ingredients": "Parse ingredients", "parse-ingredients": "Analitzar ingredients",
"edit-markdown": "Editar Markdown", "edit-markdown": "Editar Markdown",
"recipe-creation": "Creació d'una recepta", "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", "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", "import-with-url": "Importar amb l'URL",
"create-recipe": "Crea la recepta", "create-recipe": "Crea la recepta",
"import-with-zip": "Importar amb un .zip", "import-with-zip": "Importar amb un .zip",
"create-recipe-from-an-image": "Crea la recepta a partir d'una imatge", "create-recipe-from-an-image": "Crea la recepta a partir d'una imatge",
"bulk-url-import": "Importació d'URL en massa", "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.", "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", "new-recipe-names-must-be-unique": "Els noms de les noves receptes han de ser únics",
"scrape-recipe": "Scrape Recipe", "scrape-recipe": "Rastrejar recepta",
"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.", "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": "Import original keywords as tags", "import-original-keywords-as-tags": "Importa les paraules clau originals com a tags",
"stay-in-edit-mode": "Segueix en el mode d'edició", "stay-in-edit-mode": "Segueix en el mode d'edició",
"import-from-zip": "Importa des d'un ZIP", "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", "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.", "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", "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": "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.", "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": "Set Categories and Tags", "set-categories-and-tags": "Estableix Categories i Etiquetes",
"bulk-imports": "Bulk Imports", "bulk-imports": "Importacions a granel",
"bulk-import-process-has-started": "Bulk Import process has started", "bulk-import-process-has-started": "El procés d'importació a granel ha començat",
"bulk-import-process-has-failed": "Bulk import process has failed", "bulk-import-process-has-failed": "El procés d'importació a granel ha fallat",
"report-deletion-failed": "Report deletion failed", "report-deletion-failed": "No s'ha pogut suprimir l'informe",
"recipe-debugger": "Recipe Debugger", "recipe-debugger": "Depuradora de receptes",
"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.", "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": "Debug", "debug": "Depuració",
"tree-view": "Tree View", "tree-view": "Vista en arbre",
"recipe-yield": "Recipe Yield", "recipe-yield": "Rendiment de la recepta",
"unit": "Unit", "unit": "Unitat",
"upload-image": "Upload image", "upload-image": "Puja una imatge",
"screen-awake": "Mantenir la pantalla encesa", "screen-awake": "Mantenir la pantalla encesa",
"remove-image": "Esborrar la imatge" "remove-image": "Esborrar la imatge"
}, },
@ -562,26 +563,26 @@
"include": "Inclou", "include": "Inclou",
"max-results": "No mostreu més de", "max-results": "No mostreu més de",
"or": "O", "or": "O",
"has-any": "Has Any", "has-any": "Conté qualsevol",
"has-all": "Has All", "has-all": "Ho conté tot",
"results": "Resultats", "results": "Resultats",
"search": "Cerca", "search": "Cerca",
"search-mealie": "Cerca a Melie (prem /)", "search-mealie": "Cerca a Melie (prem /)",
"search-placeholder": "Cerca...", "search-placeholder": "Cerca...",
"tag-filter": "Filtra per etiqueta", "tag-filter": "Filtra per etiqueta",
"search-hint": "Prem '/'", "search-hint": "Prem '/'",
"advanced": "Advanced", "advanced": "Avançat",
"auto-search": "Auto Search", "auto-search": "Cerca automàtica",
"no-results": "No results found" "no-results": "No s'han trobat resultats"
}, },
"settings": { "settings": {
"add-a-new-theme": "Afegiu un nou tema", "add-a-new-theme": "Afegiu un nou tema",
"admin-settings": "Opcions de l'administrador", "admin-settings": "Opcions de l'administrador",
"backup": { "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-created-at-response-export_path": "S'ha creat una còpia de seguretat a {path}",
"backup-deleted": "Còpia de seguretat suprimida", "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", "backup-tag": "Etiqueta de la còpia de seguretat",
"create-heading": "Crea una còpia de seguretat", "create-heading": "Crea una còpia de seguretat",
"delete-backup": "Esborra la còpia de seguretat", "delete-backup": "Esborra la còpia de seguretat",
@ -591,13 +592,13 @@
"partial-backup": "Còpia de seguretat parcial", "partial-backup": "Còpia de seguretat parcial",
"unable-to-delete-backup": "No s'ha pogut suprimir la còpia.", "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.", "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", "backup-restore": "Restaura la còpia de seguretat",
"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.", "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": "This action cannot be undone - use with caution.", "cannot-be-undone": "Aquesta acció no es pot desfer. Utilitza-la amb precaució.",
"postgresql-note": "If you are using PostGreSQL, please review the {backup-restore-process} prior to restoring.", "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": "backup/restore process in the documentation", "backup-restore-process-in-the-documentation": "el procés de còpia de seguretat i restauració es troba a la documentació",
"irreversible-acknowledgment": "I understand that this action is irreversible, destructive and may cause data loss", "irreversible-acknowledgment": "Entenc que aquesta acció és irreversible, destructiva i pot ocasionar la pèrdua de dades",
"restore-backup": "Restore Backup" "restore-backup": "Restaura la còpia de seguretat"
}, },
"backup-and-exports": "Còpies de seguretat", "backup-and-exports": "Còpies de seguretat",
"change-password": "Canvia la contrasenya", "change-password": "Canvia la contrasenya",
@ -661,8 +662,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.", "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", "create-an-api-token": "Crea un token d'API",
"token-name": "Nom del token", "token-name": "Nom del token",
"generate": "Generate", "generate": "Genera",
"you-have-token-count": "You have no active tokens.|You have one active token.|You have {count} active tokens." "you-have-token-count": "No tens fitxes actives.|Tens una fitxa activa.|Tens {count} fitxes actives."
}, },
"toolbox": { "toolbox": {
"assign-all": "Asigna tots", "assign-all": "Asigna tots",
@ -682,7 +683,7 @@
"webhooks-caps": "WEBHOOKS", "webhooks-caps": "WEBHOOKS",
"webhooks": "Webhooks", "webhooks": "Webhooks",
"webhook-name": "Nom del Webhook", "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": "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.", "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.",
@ -699,15 +700,15 @@
"email-test-results": "Email Test Results", "email-test-results": "Email Test Results",
"ready": "Ready", "ready": "Ready",
"not-ready": "Not Ready - Check Environmental Variables", "not-ready": "Not Ready - Check Environmental Variables",
"succeeded": "Succeeded", "succeeded": "Va tenir èxit",
"failed": "Failed", "failed": "Ha fallat",
"general-about": "General About", "general-about": "Informació General",
"application-version": "Application Version", "application-version": "Versió de l'Aplicació",
"application-version-error-text": "Your current version ({0}) does not match the latest release. Considering updating to the latest version ({1}).", "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 is up to date", "mealie-is-up-to-date": "Mealie està actualitzat",
"secure-site": "Secure Site", "secure-site": "Web Segur",
"secure-site-error-text": "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.", "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": "Site is accessed by localhost or https", "secure-site-success-text": "El web és accedit amb localhost o https",
"server-side-base-url": "Server Side Base URL", "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-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", "server-side-base-url-success-text": "Server Side URL does not match the default",
@ -731,19 +732,19 @@
"label": "Etiqueta", "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.", "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", "toggle-food": "Mostra el nom de l'aliment",
"manage-labels": "Manage Labels", "manage-labels": "Gestiona etiquetes",
"are-you-sure-you-want-to-delete-this-item": "Are you sure you want to delete this item?", "are-you-sure-you-want-to-delete-this-item": "Estàs segur/a que vols eliminar aquest ítem?",
"copy-as-text": "Copy as Text", "copy-as-text": "Copia com a text",
"copy-as-markdown": "Copy as Markdown", "copy-as-markdown": "Copia com a Markdown",
"delete-checked": "Delete Checked", "delete-checked": "Suprimeix la selecció",
"toggle-label-sort": "Toggle Label Sort", "toggle-label-sort": "Activa/Desactiva l'ordre per etiquetes",
"reorder-labels": "Reorder Labels", "reorder-labels": "Canvia d'ordre les etiquetes",
"uncheck-all-items": "Uncheck All Items", "uncheck-all-items": "Desselecciona tots els ítems",
"check-all-items": "Check All Items", "check-all-items": "Selecciona tots els ítems",
"linked-recipes-count": "No Linked Recipes|One Linked Recipe|{count} Linked Recipes", "linked-recipes-count": "No Linked Recipes|One Linked Recipe|{count} Linked Recipes",
"items-checked-count": "No items checked|One item checked|{count} items checked", "items-checked-count": "No items checked|One item checked|{count} items checked",
"no-label": "No Label", "no-label": "Sense etiqueta",
"completed-on": "Completed on {date}" "completed-on": "Completat el {date}"
}, },
"sidebar": { "sidebar": {
"all-recipes": "Receptes", "all-recipes": "Receptes",
@ -796,13 +797,13 @@
"tool-name": "Nom de l'estri", "tool-name": "Nom de l'estri",
"create-new-tool": "Crea un nou estri", "create-new-tool": "Crea un nou estri",
"on-hand-checkbox-label": "Mostra com a disponible (marcat)", "on-hand-checkbox-label": "Mostra com a disponible (marcat)",
"required-tools": "Required Tools" "required-tools": "Eines necessàries"
}, },
"user": { "user": {
"admin": "Administrador/a", "admin": "Administrador/a",
"are-you-sure-you-want-to-delete-the-link": "Esteu segur de voler suprimir l'enllaç <b>{link}<b/>?", "are-you-sure-you-want-to-delete-the-link": "Esteu segur de voler suprimir l'enllaç <b>{link}<b/>?",
"are-you-sure-you-want-to-delete-the-user": "Esteu segur de voler suprimir l'usuari <b>{activeName}<b/> ID: <b>{activeId}<b/>?", "are-you-sure-you-want-to-delete-the-user": "Esteu segur de voler suprimir l'usuari <b>{activeName}<b/> ID: <b>{activeId}<b/>?",
"auth-method": "Auth Method", "auth-method": "Mètode d'autenticació",
"confirm-link-deletion": "Confirmeu l'eliminació de l'enllaç", "confirm-link-deletion": "Confirmeu l'eliminació de l'enllaç",
"confirm-password": "Confirmeu la contrasenya", "confirm-password": "Confirmeu la contrasenya",
"confirm-user-deletion": "Confirmeu l'eliminació de l'usuari", "confirm-user-deletion": "Confirmeu l'eliminació de l'usuari",
@ -816,7 +817,7 @@
"error-cannot-delete-super-user": "S'ha produït un error. El Super usuari no es pot suprimir!", "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", "existing-password-does-not-match": "La contrasenya actual no coincideix",
"full-name": "Nom sencer", "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ó", "invite-only": "Només per invitació",
"link-id": "Id de l'enllaç", "link-id": "Id de l'enllaç",
"link-name": "Nom de l'enllaç", "link-name": "Nom de l'enllaç",
@ -831,7 +832,7 @@
"password-updated": "S'ha actualitzat la contrasenya", "password-updated": "S'ha actualitzat la contrasenya",
"password": "Contrasenya", "password": "Contrasenya",
"password-strength": "Fortalesa de la contrasenya: {strength}", "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", "register": "Registreu-vos",
"reset-password": "Restableix la contrasenya", "reset-password": "Restableix la contrasenya",
"sign-in": "Inicia sessió", "sign-in": "Inicia sessió",
@ -852,7 +853,7 @@
"username": "Nom d'usuari", "username": "Nom d'usuari",
"users-header": "USUARIS", "users-header": "USUARIS",
"users": "Usuaris", "users": "Usuaris",
"user-not-found": "User not found", "user-not-found": "No s'ha trobat l'usuari",
"webhook-time": "Hora del Webhook", "webhook-time": "Hora del Webhook",
"webhooks-enabled": "Webhooks habilitats", "webhooks-enabled": "Webhooks habilitats",
"you-are-not-allowed-to-create-a-user": "Vostè no està autoritzat per a crear un usuari", "you-are-not-allowed-to-create-a-user": "Vostè no està autoritzat per a crear un usuari",
@ -860,24 +861,24 @@
"enable-advanced-content": "Habilita el contingut avançat", "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", "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", "favorite-recipes": "Receptes preferides",
"email-or-username": "Email or Username", "email-or-username": "Correu electrònic o nom d'usuari",
"remember-me": "Remember Me", "remember-me": "Recorda'm",
"please-enter-your-email-and-password": "Please enter your email and password", "please-enter-your-email-and-password": "Si us plau, introdueix el teu correu electrònic i la teva contrasenya",
"invalid-credentials": "Invalid Credentials", "invalid-credentials": "Credencials no vàlides",
"account-locked-please-try-again-later": "Account Locked. Please try again later", "account-locked-please-try-again-later": "Compte bloquejat, Si us plau, prova-ho més tard",
"user-favorites": "User Favorites", "user-favorites": "Favorits de l'usuari",
"password-strength-values": { "password-strength-values": {
"weak": "Weak", "weak": "Dèbil",
"good": "Good", "good": "Bona",
"strong": "Strong", "strong": "Forta",
"very-strong": "Very Strong" "very-strong": "Molt forta"
}, },
"user-management": "User Management", "user-management": "Gestió d'usuaris",
"reset-locked-users": "Reset Locked Users", "reset-locked-users": "Reinicia els usuaris bloquejats",
"admin-user-creation": "Admin User Creation", "admin-user-creation": "Creació d'un usuari administrador",
"admin-user-management": "Admin User Management", "admin-user-management": "Admin User Management",
"user-details": "User Details", "user-details": "Detalls de l'usuari",
"user-name": "User Name", "user-name": "Nom de l'usuari",
"authentication-method": "Authentication Method", "authentication-method": "Authentication Method",
"authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie", "authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie",
"permissions": "Permissions", "permissions": "Permissions",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Klíčové slovo", "keyword": "Klíčové slovo",
"link-copied": "Odkaz zkopírován", "link-copied": "Odkaz zkopírován",
"loading": "Loading",
"loading-events": "Načítání událostí", "loading-events": "Načítání událostí",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Nøgleord", "keyword": "Nøgleord",
"link-copied": "Link kopieret", "link-copied": "Link kopieret",
"loading": "Indlæser",
"loading-events": "Indlæser hændelser", "loading-events": "Indlæser hændelser",
"loading-recipe": "Indlæser opskrift...", "loading-recipe": "Indlæser opskrift...",
"loading-ocr-data": "Indlæser OCR data...", "loading-ocr-data": "Indlæser OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Schlüsselwort", "keyword": "Schlüsselwort",
"link-copied": "Link kopiert", "link-copied": "Link kopiert",
"loading": "Wird geladen...",
"loading-events": "Ereignisse werden geladen", "loading-events": "Ereignisse werden geladen",
"loading-recipe": "Lade Rezept...", "loading-recipe": "Lade Rezept...",
"loading-ocr-data": "Lade OCR-Daten...", "loading-ocr-data": "Lade OCR-Daten...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Λέξη-κλειδί", "keyword": "Λέξη-κλειδί",
"link-copied": "Ο Σύνδεσμος Αντιγράφηκε", "link-copied": "Ο Σύνδεσμος Αντιγράφηκε",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Keyword", "keyword": "Keyword",
"link-copied": "Link Copied", "link-copied": "Link Copied",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Keyword", "keyword": "Keyword",
"link-copied": "Link Copied", "link-copied": "Link Copied",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Etiqueta", "keyword": "Etiqueta",
"link-copied": "Enlace copiado", "link-copied": "Enlace copiado",
"loading": "Loading",
"loading-events": "Cargando Eventos", "loading-events": "Cargando Eventos",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Hakusana", "keyword": "Hakusana",
"link-copied": "Linkki kopioitu", "link-copied": "Linkki kopioitu",
"loading": "Loading",
"loading-events": "Ladataan tapahtumia", "loading-events": "Ladataan tapahtumia",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Mot-clé", "keyword": "Mot-clé",
"link-copied": "Lien copié", "link-copied": "Lien copié",
"loading": "Chargement",
"loading-events": "Chargement des événements", "loading-events": "Chargement des événements",
"loading-recipe": "Chargement de la recette...", "loading-recipe": "Chargement de la recette...",
"loading-ocr-data": "Chargement des données OCR...", "loading-ocr-data": "Chargement des données OCR...",
@ -875,7 +876,7 @@
"user-management": "Gestion des utilisateurs", "user-management": "Gestion des utilisateurs",
"reset-locked-users": "Réinitialiser les utilisateurs verrouillés", "reset-locked-users": "Réinitialiser les utilisateurs verrouillés",
"admin-user-creation": "Création d'un utilisateur admin", "admin-user-creation": "Création d'un utilisateur admin",
"admin-user-management": "Admin User Management", "admin-user-management": "Administration des utilisateurs",
"user-details": "Détails de l'utilisateur", "user-details": "Détails de l'utilisateur",
"user-name": "Nom d'utilisateur", "user-name": "Nom d'utilisateur",
"authentication-method": "Méthode d'authentification", "authentication-method": "Méthode d'authentification",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Mot-clé", "keyword": "Mot-clé",
"link-copied": "Lien copié", "link-copied": "Lien copié",
"loading": "Chargement",
"loading-events": "Chargement des événements", "loading-events": "Chargement des événements",
"loading-recipe": "Chargement de la recette...", "loading-recipe": "Chargement de la recette...",
"loading-ocr-data": "Chargement des données OCR...", "loading-ocr-data": "Chargement des données OCR...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Keyword", "keyword": "Keyword",
"link-copied": "Link Copied", "link-copied": "Link Copied",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "מילת מפתח", "keyword": "מילת מפתח",
"link-copied": "קישור הועתק", "link-copied": "קישור הועתק",
"loading": "Loading",
"loading-events": "טוען", "loading-events": "טוען",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Ključna riječ", "keyword": "Ključna riječ",
"link-copied": "Poveznica kopirana", "link-copied": "Poveznica kopirana",
"loading": "Loading",
"loading-events": "Učitavanje događaja", "loading-events": "Učitavanje događaja",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -77,7 +77,7 @@
"tag-events": "Címke események", "tag-events": "Címke események",
"category-events": "Kategória események", "category-events": "Kategória események",
"when-a-new-user-joins-your-group": "Amikor egy új felhasználó csatlakozik a csoportodba", "when-a-new-user-joins-your-group": "Amikor egy új felhasználó csatlakozik a csoportodba",
"recipe-events": "Recipe Events" "recipe-events": "Recept esemény"
}, },
"general": { "general": {
"cancel": "Mégsem", "cancel": "Mégsem",
@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Kulcsszó", "keyword": "Kulcsszó",
"link-copied": "Hivatkozás másolva", "link-copied": "Hivatkozás másolva",
"loading": "Betöltés",
"loading-events": "Események betöltése", "loading-events": "Események betöltése",
"loading-recipe": "Recept betöltése...", "loading-recipe": "Recept betöltése...",
"loading-ocr-data": "OCR adatok betöltése...", "loading-ocr-data": "OCR adatok betöltése...",
@ -590,7 +591,7 @@
"import-summary": "Import összefoglaló", "import-summary": "Import összefoglaló",
"partial-backup": "Részleges biztonsági mentés", "partial-backup": "Részleges biztonsági mentés",
"unable-to-delete-backup": "Nem lehetett létrehozni a biztonsági mentést.", "unable-to-delete-backup": "Nem lehetett létrehozni a biztonsági mentést.",
"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.", "experimental-description": "A biztonsági mentések az oldal adatbázisának és adatkönyvtárának teljes pillanatfelvételei. Ez az összes adatot tartalmazza, és nem lehet beállítani, hogy az adatok részhalmazait kizárja. Ezt úgy is elképzelheti, mint a Mealie egy adott időpontban készült pillanatfelvételét. Ezek adatbázis-független módon szolgálnak az adatok exportálására és importálására, vagy a webhely külső helyre történő mentésére.",
"backup-restore": "Biztonsági Mentés/Visszaállítás", "backup-restore": "Biztonsági Mentés/Visszaállítás",
"back-restore-description": "A biztonsági mentés visszaállítása felülírja az adatbázisban és az adatkönyvtárban lévő összes aktuális adatot, és a biztonsági mentés tartalmával helyettesíti azokat. {cannot-be-undone} Ha a visszaállítás sikeres, akkor a rendszer kilépteti Önt.", "back-restore-description": "A biztonsági mentés visszaállítása felülírja az adatbázisban és az adatkönyvtárban lévő összes aktuális adatot, és a biztonsági mentés tartalmával helyettesíti azokat. {cannot-be-undone} Ha a visszaállítás sikeres, akkor a rendszer kilépteti Önt.",
"cannot-be-undone": "Ezt a műveletet visszavonható - óvatosan használja.", "cannot-be-undone": "Ezt a műveletet visszavonható - óvatosan használja.",
@ -1056,8 +1057,8 @@
"click": "Kattintson bármelyik mezőre a jobb oldalon, majd kattintson vissza a kép feletti téglalapra.", "click": "Kattintson bármelyik mezőre a jobb oldalon, majd kattintson vissza a kép feletti téglalapra.",
"result": "A kiválasztott szöveg a korábban kiválasztott mezőben jelenik meg." "result": "A kiválasztott szöveg a korábban kiválasztott mezőben jelenik meg."
}, },
"pan-and-zoom-mode": "Pan and Zoom Mode", "pan-and-zoom-mode": "Pásztázás és nagyítás mód",
"pan-and-zoom-desc": "Select pan and zoom by clicking the icon. This mode allows to zoom inside the image and move around to make using big images easier.", "pan-and-zoom-desc": "Válassza ki a pásztázást és a nagyítást az ikonra kattintva. Ez a mód lehetővé teszi a kép nagyítását és mozgását a nagy képek használatának megkönnyítése érdekében.",
"split-text-mode": "Szöveg felosztási módok", "split-text-mode": "Szöveg felosztási módok",
"split-modes": { "split-modes": {
"line-mode": "Vonal mód (alapértelmezett)", "line-mode": "Vonal mód (alapértelmezett)",
@ -1113,7 +1114,7 @@
"show-individual-confidence": "", "show-individual-confidence": "",
"ingredient-text": "Hozzávaló szöveg", "ingredient-text": "Hozzávaló szöveg",
"average-confident": "{0} Confident", "average-confident": "{0} Confident",
"try-an-example": "Próbáld ki", "try-an-example": "Próbáljon ki egy példát",
"parser": "Szintaxis elemző", "parser": "Szintaxis elemző",
"background-tasks": "Háttér folyamatok", "background-tasks": "Háttér folyamatok",
"background-tasks-description": "Itt megtekintheti az összes futó háttérfeladatot és azok állapotát", "background-tasks-description": "Itt megtekintheti az összes futó háttérfeladatot és azok állapotát",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Parola chiave", "keyword": "Parola chiave",
"link-copied": "Link Copiato", "link-copied": "Link Copiato",
"loading": "Loading",
"loading-events": "Caricamento eventi", "loading-events": "Caricamento eventi",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -10,7 +10,7 @@
"default-group": "デフォルトグループ", "default-group": "デフォルトグループ",
"demo": "体験版", "demo": "体験版",
"demo-status": "体験版ステータス", "demo-status": "体験版ステータス",
"development": "Development", "development": "開発",
"docs": "ドキュメント", "docs": "ドキュメント",
"download-log": "ログをダウンロード", "download-log": "ログをダウンロード",
"download-recipe-json": "Last Scraped JSON", "download-recipe-json": "Last Scraped JSON",
@ -42,8 +42,8 @@
"category-deleted": "カテゴリを削除しました。", "category-deleted": "カテゴリを削除しました。",
"category-deletion-failed": "カテゴリの削除に失敗しました。", "category-deletion-failed": "カテゴリの削除に失敗しました。",
"category-filter": "カテゴリフィルタ", "category-filter": "カテゴリフィルタ",
"category-update-failed": "Category update failed", "category-update-failed": "カテゴリの更新に失敗しました",
"category-updated": "Category updated", "category-updated": "カテゴリーを更新しました",
"uncategorized-count": "カテゴリなし {count}", "uncategorized-count": "カテゴリなし {count}",
"create-a-category": "カテゴリを作成", "create-a-category": "カテゴリを作成",
"category-name": "カテゴリ名", "category-name": "カテゴリ名",
@ -96,8 +96,8 @@
"duplicate": "複製", "duplicate": "複製",
"edit": "編集", "edit": "編集",
"enabled": "有効", "enabled": "有効",
"exception": "Exception", "exception": "例外",
"failed-count": "Failed: {count}", "failed-count": "失敗: {count}",
"failure-uploading-file": "Failure uploading file", "failure-uploading-file": "Failure uploading file",
"favorites": "お気に入り", "favorites": "お気に入り",
"field-required": "Field Required", "field-required": "Field Required",
@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "キーワード", "keyword": "キーワード",
"link-copied": "リンクをコピーしました。", "link-copied": "リンクをコピーしました。",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",
@ -172,7 +173,7 @@
"id": "Id", "id": "Id",
"owner": "Owner", "owner": "Owner",
"date-added": "追加日", "date-added": "追加日",
"none": "None", "none": "なし",
"run": "Run", "run": "Run",
"menu": "メニュー", "menu": "メニュー",
"a-name-is-required": "名前は必須です。", "a-name-is-required": "名前は必須です。",
@ -200,9 +201,9 @@
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes." "unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes."
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/> を削除しますか?",
"cannot-delete-default-group": "Cannot delete default group", "cannot-delete-default-group": "デフォルトのルグループは削除できません",
"cannot-delete-group-with-users": "Cannot delete group with users", "cannot-delete-group-with-users": "ユーザがあるグループを削除できません",
"confirm-group-deletion": "Confirm Group Deletion", "confirm-group-deletion": "Confirm Group Deletion",
"create-group": "Create Group", "create-group": "Create Group",
"error-updating-group": "Error updating group", "error-updating-group": "Error updating group",
@ -237,7 +238,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", "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": "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-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", "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": "Default to landscape view",
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view", "default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
@ -263,16 +264,16 @@
"end-date": "End Date", "end-date": "End Date",
"group": "Group (Beta)", "group": "Group (Beta)",
"main": "主菜", "main": "主菜",
"meal-planner": " 献立表", "meal-planner": "献立表",
"meal-plans": "献立", "meal-plans": "献立",
"mealplan-categories": "MEALPLAN CATEGORIES", "mealplan-categories": "MEALPLAN CATEGORIES",
"mealplan-created": "Mealplan created", "mealplan-created": "献立に作成しました",
"mealplan-creation-failed": "Mealplan creation failed", "mealplan-creation-failed": "献立の作成に失敗しました",
"mealplan-deleted": "Mealplan Deleted", "mealplan-deleted": "献立を削除しました",
"mealplan-deletion-failed": "Mealplan deletion failed", "mealplan-deletion-failed": "献立の削除に失敗しました",
"mealplan-settings": "献立設定", "mealplan-settings": "献立設定",
"mealplan-update-failed": "Mealplan update failed", "mealplan-update-failed": "献立の更新に失敗しました",
"mealplan-updated": "Mealplan Updated", "mealplan-updated": "献立を更新しました",
"no-meal-plan-defined-yet": "No meal plan defined yet", "no-meal-plan-defined-yet": "No meal plan defined yet",
"no-meal-planned-for-today": "No meal planned for today", "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", "only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "키워드", "keyword": "키워드",
"link-copied": "링크 복사됨", "link-copied": "링크 복사됨",
"loading": "Loading",
"loading-events": "이벤트를 불러오는 중", "loading-events": "이벤트를 불러오는 중",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Raktažodis", "keyword": "Raktažodis",
"link-copied": "Nuoroda nukopijuota", "link-copied": "Nuoroda nukopijuota",
"loading": "Loading",
"loading-events": "Užkrovimo įvykiai", "loading-events": "Užkrovimo įvykiai",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Keyword", "keyword": "Keyword",
"link-copied": "Link Copied", "link-copied": "Link Copied",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Trefwoord", "keyword": "Trefwoord",
"link-copied": "Link Gekopieerd", "link-copied": "Link Gekopieerd",
"loading": "Laden",
"loading-events": "Gebeurtenis laden", "loading-events": "Gebeurtenis laden",
"loading-recipe": "Recepten ophalen...", "loading-recipe": "Recepten ophalen...",
"loading-ocr-data": "OCR gegevens laden...", "loading-ocr-data": "OCR gegevens laden...",
@ -831,7 +832,7 @@
"password-updated": "Wachtwoord bijgewerkt", "password-updated": "Wachtwoord bijgewerkt",
"password": "Wachtwoord", "password": "Wachtwoord",
"password-strength": "Wachtwoord is {strength}", "password-strength": "Wachtwoord is {strength}",
"please-enter-password": "Please enter your new password.", "please-enter-password": "Voeg uw nieuwe wachtwoord in.",
"register": "Registreren", "register": "Registreren",
"reset-password": "Wachtwoord herstellen", "reset-password": "Wachtwoord herstellen",
"sign-in": "Inloggen", "sign-in": "Inloggen",

View File

@ -2,14 +2,14 @@
"about": { "about": {
"about": "Om", "about": "Om",
"about-mealie": "Om Mealie", "about-mealie": "Om Mealie",
"api-docs": "API Dokumentasjon", "api-docs": "API dokumentasjon",
"api-port": "API port", "api-port": "API port",
"application-mode": "Program Modus", "application-mode": "Programmodus",
"database-type": "Databasetype", "database-type": "Databasetype",
"database-url": "Databasens URL", "database-url": "Databasens URL",
"default-group": "Standardgruppe", "default-group": "Standardgruppe",
"demo": "Demo", "demo": "Demo",
"demo-status": "Demo status", "demo-status": "Demostatus",
"development": "Utvikling", "development": "Utvikling",
"docs": "Dokumentasjon", "docs": "Dokumentasjon",
"download-log": "Nedlastingslogg", "download-log": "Nedlastingslogg",
@ -38,7 +38,7 @@
"category": { "category": {
"categories": "Kategorier", "categories": "Kategorier",
"category-created": "Kategori opprettet", "category-created": "Kategori opprettet",
"category-creation-failed": "Kategori-opprettelse mislyktes", "category-creation-failed": "Kategoriopprettelse mislyktes",
"category-deleted": "Kategori slettet", "category-deleted": "Kategori slettet",
"category-deletion-failed": "Sletting av kategori mislyktes", "category-deletion-failed": "Sletting av kategori mislyktes",
"category-filter": "Kategori Filter", "category-filter": "Kategori Filter",
@ -64,7 +64,7 @@
"something-went-wrong": "Noe gikk galt!", "something-went-wrong": "Noe gikk galt!",
"subscribed-events": "Abonnerte hendelser", "subscribed-events": "Abonnerte hendelser",
"test-message-sent": "Testmelding sendt", "test-message-sent": "Testmelding sendt",
"new-notification": "Ny Varsel", "new-notification": "Ny varsel",
"event-notifiers": "Varsel for hendelse", "event-notifiers": "Varsel for hendelse",
"apprise-url-skipped-if-blank": "Bruk URL (hopp over hvis den er tom)", "apprise-url-skipped-if-blank": "Bruk URL (hopp over hvis den er tom)",
"enable-notifier": "Aktiver varsleren", "enable-notifier": "Aktiver varsleren",
@ -77,7 +77,7 @@
"tag-events": "Tagg hendelser", "tag-events": "Tagg hendelser",
"category-events": "Kategori hendelser", "category-events": "Kategori hendelser",
"when-a-new-user-joins-your-group": "Når en ny bruker blir med i gruppen din", "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": { "general": {
"cancel": "Avbryt", "cancel": "Avbryt",
@ -114,9 +114,10 @@
"json": "JSON", "json": "JSON",
"keyword": "Nøkkelord", "keyword": "Nøkkelord",
"link-copied": "Lenke kopiert", "link-copied": "Lenke kopiert",
"loading": "Loading",
"loading-events": "Laster hendelser", "loading-events": "Laster hendelser",
"loading-recipe": "Loading recipe...", "loading-recipe": "Laster oppskrift...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Laster OCR data...",
"loading-recipes": "Laster Oppskrifter", "loading-recipes": "Laster Oppskrifter",
"message": "Melding", "message": "Melding",
"monday": "Mandag", "monday": "Mandag",
@ -197,7 +198,7 @@
"refresh": "Oppdater", "refresh": "Oppdater",
"upload-file": "Last opp fil", "upload-file": "Last opp fil",
"created-on-date": "Opprettet: {0}", "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": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på at du vil slette <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "Er du sikker på at du vil slette <b>{groupName}<b/>?",
@ -230,7 +231,7 @@
"looking-to-update-your-profile": "Ønsker du å oppdatere profilen din?", "looking-to-update-your-profile": "Ønsker du å oppdatere profilen din?",
"default-recipe-preferences-description": "Dette er standardinnstillingene når en ny oppskrift blir opprettet i gruppen din. Disse kan endres for individuelle oppskrifter i oppskriftsmenyen.", "default-recipe-preferences-description": "Dette er standardinnstillingene når en ny oppskrift blir opprettet i gruppen din. Disse kan endres for individuelle oppskrifter i oppskriftsmenyen.",
"default-recipe-preferences": "Standard Oppskriftsinnstillinger", "default-recipe-preferences": "Standard Oppskriftsinnstillinger",
"group-preferences": "Gruppe Innstillinger", "group-preferences": "Gruppeinnstillinger",
"private-group": "Privat gruppe", "private-group": "Privat gruppe",
"private-group-description": "Hvis din gruppe settes til privat, vil alle instillinger setter til standard. Dette overstyrer en individuell oppskrifter offentlige innstillinger.", "private-group-description": "Hvis din gruppe settes til privat, vil alle instillinger setter til standard. Dette overstyrer en individuell oppskrifter offentlige innstillinger.",
"allow-users-outside-of-your-group-to-see-your-recipes": "Tillat brukere utenfor gruppen å se oppskriftene dine", "allow-users-outside-of-your-group-to-see-your-recipes": "Tillat brukere utenfor gruppen å se oppskriftene dine",
@ -246,11 +247,11 @@
"disable-organizing-recipe-ingredients-by-units-and-food": "Deaktiver organisering av oppskriftsingredienser av enheter og mat", "disable-organizing-recipe-ingredients-by-units-and-food": "Deaktiver organisering av oppskriftsingredienser av enheter og mat",
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Skjuler mat, enhet og beløp for ingredienser og behandler ingredienser som rene tekstfelt.", "disable-organizing-recipe-ingredients-by-units-and-food-description": "Skjuler mat, enhet og beløp for ingredienser og behandler ingredienser som rene tekstfelt.",
"general-preferences": "Generelle innstillinger", "general-preferences": "Generelle innstillinger",
"group-recipe-preferences": "Innstillinger for gruppe-oppskrift", "group-recipe-preferences": "Innstillinger for gruppeoppskrift",
"report": "Rapport", "report": "Rapport",
"report-with-id": "Report ID: {id}", "report-with-id": "Rapport-ID: {id}",
"group-management": "Gruppeadministrasjon", "group-management": "Gruppeadministrasjon",
"admin-group-management": "Admin gruppe-administrasjon", "admin-group-management": "Admin gruppeadministrasjon",
"admin-group-management-text": "Endringer i denne gruppen vil umiddelbart bli reflektert.", "admin-group-management-text": "Endringer i denne gruppen vil umiddelbart bli reflektert.",
"group-id-value": "Gruppe ID: {0}" "group-id-value": "Gruppe ID: {0}"
}, },
@ -263,7 +264,7 @@
"end-date": "Sluttdato", "end-date": "Sluttdato",
"group": "Gruppe (Beta)", "group": "Gruppe (Beta)",
"main": "Hovedrett", "main": "Hovedrett",
"meal-planner": "Planlegg Måltid", "meal-planner": "Planlegg måltid",
"meal-plans": "Måltidsplan", "meal-plans": "Måltidsplan",
"mealplan-categories": "MÅLTIDSPLAN KATEGORIER", "mealplan-categories": "MÅLTIDSPLAN KATEGORIER",
"mealplan-created": "Måltidsplan opprettet", "mealplan-created": "Måltidsplan opprettet",
@ -572,16 +573,16 @@
"search-hint": "Trykk på '/'", "search-hint": "Trykk på '/'",
"advanced": "Avansert", "advanced": "Avansert",
"auto-search": "Autosøk", "auto-search": "Autosøk",
"no-results": "No results found" "no-results": "Ingen resultater funnet"
}, },
"settings": { "settings": {
"add-a-new-theme": "Legg til nytt tema", "add-a-new-theme": "Legg til nytt tema",
"admin-settings": "Administrator innstillinger", "admin-settings": "Administrator innstillinger",
"backup": { "backup": {
"backup-created": "Backup created successfully", "backup-created": "Sikkerhetskopiering fullført",
"backup-created-at-response-export_path": "Sikkerhetskopi opprettet på {path}", "backup-created-at-response-export_path": "Sikkerhetskopi opprettet på {path}",
"backup-deleted": "Sikkerhetskopi slettet", "backup-deleted": "Sikkerhetskopi slettet",
"restore-success": "Restore successful", "restore-success": "Gjenopprettingen var vellykket",
"backup-tag": "Sikkerhetskopi-merke", "backup-tag": "Sikkerhetskopi-merke",
"create-heading": "Opprett sikkerhetskopi", "create-heading": "Opprett sikkerhetskopi",
"delete-backup": "Slett Sikkerhetskopi", "delete-backup": "Slett Sikkerhetskopi",
@ -690,12 +691,12 @@
"configuration": "Konfigurasjon", "configuration": "Konfigurasjon",
"docker-volume": "Docker volum", "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.", "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.", "volumes-are-configured-correctly": "Volumene er riktig konfigurert.",
"status-unknown-try-running-a-validation": "Statusen er ukjent. Prøv å validere.", "status-unknown-try-running-a-validation": "Statusen er ukjent. Prøv å validere.",
"validate": "Valider", "validate": "Valider",
"email-configuration-status": "Epost konfigurasjons status", "email-configuration-status": "E-postkonfigurasjonsstatus",
"email-configured": "Email Configured", "email-configured": "E-post konfigurert",
"email-test-results": "Email Test Results", "email-test-results": "Email Test Results",
"ready": "Klar", "ready": "Klar",
"not-ready": "Ikke klar - sjekk miljøvariabler", "not-ready": "Ikke klar - sjekk miljøvariabler",
@ -831,7 +832,7 @@
"password-updated": "Passord oppdatert", "password-updated": "Passord oppdatert",
"password": "Passord", "password": "Passord",
"password-strength": "Passordet er {strength}", "password-strength": "Passordet er {strength}",
"please-enter-password": "Please enter your new password.", "please-enter-password": "Angi nytt passord.",
"register": "Registrér", "register": "Registrér",
"reset-password": "Tilbakestill passord", "reset-password": "Tilbakestill passord",
"sign-in": "Logg inn", "sign-in": "Logg inn",
@ -852,20 +853,20 @@
"username": "Brukernavn", "username": "Brukernavn",
"users-header": "BRUKERE", "users-header": "BRUKERE",
"users": "Brukere", "users": "Brukere",
"user-not-found": "User not found", "user-not-found": "Bruker ikke funnet",
"webhook-time": "Webhooks Tidsbruk", "webhook-time": "Webhooks Tidsbruk",
"webhooks-enabled": "Webhooks aktivert", "webhooks-enabled": "Webhooks aktivert",
"you-are-not-allowed-to-create-a-user": "Du har ikke rettigheter til å opprette en bruker", "you-are-not-allowed-to-create-a-user": "Du har ikke rettigheter til å opprette en bruker",
"you-are-not-allowed-to-delete-this-user": "Du har ikke rettigheter til å slette denne brukeren", "you-are-not-allowed-to-delete-this-user": "Du har ikke rettigheter til å slette denne brukeren",
"enable-advanced-content": "Aktiver avanserte funksjoner", "enable-advanced-content": "Aktiver avanserte funksjoner",
"enable-advanced-content-description": "Aktiver avanserte funksjoner som skalering av oppskriffter, API-nøkler, Webhooks og Data styring. Du kan alltid endre dette senere", "enable-advanced-content-description": "Aktiver avanserte funksjoner som skalering av oppskriffter, API-nøkler, Webhooks og Data styring. Du kan alltid endre dette senere",
"favorite-recipes": "Favoritt oppskrifter", "favorite-recipes": "Favorittoppskrifter",
"email-or-username": "E-post eller brukernavn", "email-or-username": "E-post eller brukernavn",
"remember-me": "Husk meg", "remember-me": "Husk meg",
"please-enter-your-email-and-password": "Vennligst angi brukernavnet og passordet ditt", "please-enter-your-email-and-password": "Vennligst angi brukernavnet og passordet ditt",
"invalid-credentials": "Ugyldig brukerinformasjon", "invalid-credentials": "Ugyldig brukerinformasjon",
"account-locked-please-try-again-later": "Kontoen er låst. Prøv igjen senere", "account-locked-please-try-again-later": "Kontoen er låst. Prøv igjen senere",
"user-favorites": "Bruker favoritter", "user-favorites": "Brukerfavoritter",
"password-strength-values": { "password-strength-values": {
"weak": "Svak", "weak": "Svak",
"good": "God", "good": "God",
@ -888,9 +889,9 @@
"enable-advanced-features": "Aktiver avanserte funksjoner", "enable-advanced-features": "Aktiver avanserte funksjoner",
"it-looks-like-this-is-your-first-time-logging-in": "Det ser ut som dette er første gang du logger på.", "it-looks-like-this-is-your-first-time-logging-in": "Det ser ut som dette er første gang du logger på.",
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Ønsker du ikke å se dette lenger? Sørg for å endre e-posten din i brukerinnstillingene dine!", "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Ønsker du ikke å se dette lenger? Sørg for å endre e-posten din i brukerinnstillingene dine!",
"forgot-password": "Forgot Password", "forgot-password": "Glemt passord",
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.", "forgot-password-text": "Skriv inn e-postadressen din. Vi sender deg en e-post slik at du kan tilbakestille passordet ditt.",
"changes-reflected-immediately": "Changes to this user will be reflected immediately." "changes-reflected-immediately": "Endringer på denne brukeren gjenspeiles umiddelbart."
}, },
"language-dialog": { "language-dialog": {
"translated": "oversatt", "translated": "oversatt",
@ -953,21 +954,21 @@
"recipe-data": "Oppskriftsdata", "recipe-data": "Oppskriftsdata",
"recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.", "recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.",
"recipe-columns": "Recipe Columns", "recipe-columns": "Recipe Columns",
"data-exports-description": "This section provides links to available exports that are ready to download. These exports do expire, so be sure to grab them while they're still available.", "data-exports-description": "Dette avsnittet gir lenker til tilgjengelige eksportfiler som er klare til nedlasting. Disse eksportfilene utløper, så sørg for å laste dem ned mens de fortsatt er tilgjengelige.",
"data-exports": "Data eksport", "data-exports": "Data eksport",
"tag": "Etikett", "tag": "Etikett",
"categorize": "Kategoriser", "categorize": "Kategoriser",
"update-settings": "Oppdater innstillinger", "update-settings": "Oppdater innstillinger",
"tag-recipes": "Tag Recipes", "tag-recipes": "Tagg oppskrifter",
"categorize-recipes": "Categorize Recipes", "categorize-recipes": "Kategoriser oppskrifter",
"export-recipes": "Eksporter oppskrift", "export-recipes": "Eksporter oppskrift",
"delete-recipes": "Slett oppskrifter", "delete-recipes": "Slett oppskrifter",
"source-unit-will-be-deleted": "Kildeenheten vil bli slettet" "source-unit-will-be-deleted": "Kildeenheten vil bli slettet"
}, },
"create-alias": "Create Alias", "create-alias": "Opprett alias",
"manage-aliases": "Manage Aliases", "manage-aliases": "Administrer aliaser",
"seed-data": "Frødata", "seed-data": "Frødata",
"seed": "Seed", "seed": "Nøkkel",
"data-management": "Databehandling", "data-management": "Databehandling",
"data-management-description": "Velg hvilke data du vil gjøre endringer i.", "data-management-description": "Velg hvilke data du vil gjøre endringer i.",
"select-data": "Velg data", "select-data": "Velg data",
@ -975,31 +976,31 @@
"columns": "Kolonner", "columns": "Kolonner",
"combine": "Kombiner", "combine": "Kombiner",
"categories": { "categories": {
"edit-category": "Edit Category", "edit-category": "Rediger kategori",
"new-category": "New Category", "new-category": "Ny Kategori",
"category-data": "Category Data" "category-data": "Kategoridata"
}, },
"tags": { "tags": {
"new-tag": "New Tag", "new-tag": "Ny tagg",
"edit-tag": "Edit Tag", "edit-tag": "Rediger Tag",
"tag-data": "Tag Data" "tag-data": "Tagg data"
}, },
"tools": { "tools": {
"new-tool": "New Tool", "new-tool": "Nytt verktøy",
"edit-tool": "Edit Tool", "edit-tool": "Rediger verktøy",
"tool-data": "Tool Data" "tool-data": "Verktøydata"
} }
}, },
"user-registration": { "user-registration": {
"user-registration": "Brukerregistrering", "user-registration": "Brukerregistrering",
"registration-success": "Registration Success", "registration-success": "Registrering vellykket",
"join-a-group": "Bli med i en gruppe", "join-a-group": "Bli med i en gruppe",
"create-a-new-group": "Opprett en ny gruppe", "create-a-new-group": "Opprett en ny gruppe",
"provide-registration-token-description": "Vennligst oppgi registreringstoken knyttet til gruppen du ønsker å bli med. Du må skaffe dette fra et eksisterende gruppemedlem.", "provide-registration-token-description": "Vennligst oppgi registreringstoken knyttet til gruppen du ønsker å bli med. Du må skaffe dette fra et eksisterende gruppemedlem.",
"group-details": "Gruppeinfo", "group-details": "Gruppedetaljer",
"group-details-description": "Før du oppretter en konto må du lage en gruppe. Gruppen din vil bare inneholde deg, men du vil kunne invitere andre senere. Medlemmer i gruppen din kan dele måltider, handlelister, oppskrifter med mer!", "group-details-description": "Før du oppretter en konto må du lage en gruppe. Gruppen din vil bare inneholde deg, men du vil kunne invitere andre senere. Medlemmer i gruppen din kan dele måltider, handlelister, oppskrifter med mer!",
"use-seed-data": "Bruk hoveddata", "use-seed-data": "Bruk hoveddata",
"use-seed-data-description": "Møt kommer med en samling av mat, enheter og etiketter som kan brukes til å fylle gruppen din med nyttige data for å organisere oppskriftene dine.", "use-seed-data-description": "Mealie kommer med en samling av mat, enheter og etiketter som kan brukes til å fylle gruppen din med nyttige data for å organisere oppskriftene dine.",
"account-details": "Kontodetaljer" "account-details": "Kontodetaljer"
}, },
"validation": { "validation": {
@ -1039,7 +1040,7 @@
}, },
"ocr-editor": { "ocr-editor": {
"ocr-editor": "redigere OCR", "ocr-editor": "redigere OCR",
"toolbar": "Toolbar", "toolbar": "Verktøylinje",
"selection-mode": "Velg modus", "selection-mode": "Velg modus",
"pan-and-zoom-picture": "Panorer og zoom bilde", "pan-and-zoom-picture": "Panorer og zoom bilde",
"split-text": "Splitt tekst", "split-text": "Splitt tekst",
@ -1047,8 +1048,8 @@
"split-by-block": "Del på tekstblokk", "split-by-block": "Del på tekstblokk",
"flatten": "Flat ut, uavhengig av orginalformatering.", "flatten": "Flat ut, uavhengig av orginalformatering.",
"help": { "help": {
"help": "Help", "help": "Hjelp",
"mouse-modes": "Mouse modes", "mouse-modes": "Musemodus",
"selection-mode": "Valgmodus (standard)", "selection-mode": "Valgmodus (standard)",
"selection-mode-desc": "Utvalgsmodus er hovedmodus som kan brukes til å legge inn data:", "selection-mode-desc": "Utvalgsmodus er hovedmodus som kan brukes til å legge inn data:",
"selection-mode-steps": { "selection-mode-steps": {
@ -1081,7 +1082,7 @@
"info-description-cleanable-directories": "Cleanable Directories", "info-description-cleanable-directories": "Cleanable Directories",
"info-description-cleanable-images": "Rydbare bilder", "info-description-cleanable-images": "Rydbare bilder",
"storage": { "storage": {
"title-temporary-directory": "Temporary Directory (.temp)", "title-temporary-directory": "Midlertidig mappe (.temp)",
"title-backups-directory": "Backups Directory (backups)", "title-backups-directory": "Backups Directory (backups)",
"title-groups-directory": "Groups Directory (groups)", "title-groups-directory": "Groups Directory (groups)",
"title-recipes-directory": "Recipes Directory (recipes)", "title-recipes-directory": "Recipes Directory (recipes)",
@ -1094,7 +1095,7 @@
"action-clean-temporary-files-name": "Fjern midlertidige filer", "action-clean-temporary-files-name": "Fjern midlertidige filer",
"action-clean-temporary-files-description": "Fjerner alle filer og mapper i .temp-mappen", "action-clean-temporary-files-description": "Fjerner alle filer og mapper i .temp-mappen",
"action-clean-images-name": "Fjern bilder", "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": "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-destructive": "destruktiv",
"actions-description-irreversible": "irreversibel", "actions-description-irreversible": "irreversibel",
@ -1127,7 +1128,7 @@
"get-public-link": "Få offentlig lenke", "get-public-link": "Få offentlig lenke",
"account-summary": "Kontosammendrag", "account-summary": "Kontosammendrag",
"account-summary-description": "Her er en oppsummering av gruppens informasjon", "account-summary-description": "Her er en oppsummering av gruppens informasjon",
"group-statistics": "Gruppe Statistikk", "group-statistics": "Gruppestatistikk",
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.", "group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
"storage-capacity": "Lagringskapasitet", "storage-capacity": "Lagringskapasitet",
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.", "storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
@ -1141,14 +1142,14 @@
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.", "group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.", "cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
"members": "Medlemmer", "members": "Medlemmer",
"members-description": "See who's in your group and manage their permissions.", "members-description": "Se hvem som er i gruppen din og adminstrer deres tillatelser.",
"webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.", "webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.",
"notifiers": "Varslere", "notifiers": "Varslere",
"notifiers-description": "Setup email and push notifications that trigger on specific events.", "notifiers-description": "Setup email and push notifications that trigger on specific events.",
"manage-data": "Administrer data", "manage-data": "Administrer data",
"manage-data-description": "Administrer mat og enheter (flere alternativer kommer snart)", "manage-data-description": "Administrer mat og enheter (flere alternativer kommer snart)",
"data-migrations": "Datamigrering", "data-migrations": "Datamigrering",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown", "data-migrations-description": "Overfør eksisterende data fra andre programmer som Nextcloud Recipes og Chowdown",
"email-sent": "Epost sendt", "email-sent": "Epost sendt",
"error-sending-email": "Feil ved sending av e-post", "error-sending-email": "Feil ved sending av e-post",
"personal-information": "Personlig Informasjon", "personal-information": "Personlig Informasjon",
@ -1165,16 +1166,16 @@
"manage-data-migrations": "Manage Data Migrations" "manage-data-migrations": "Manage Data Migrations"
}, },
"cookbook": { "cookbook": {
"cookbooks": "Cookbooks", "cookbooks": "Kokebøker",
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes and tags. Creating a cookbook will add an entry to the side-bar and all the recipes with the tags and categories chosen will be displayed in the cookbook.", "description": "Cookbooks are another way to organize recipes by creating cross sections of recipes and tags. Creating a cookbook will add an entry to the side-bar and all the recipes with the tags and categories chosen will be displayed in the cookbook.",
"public-cookbook": "Public Cookbook", "public-cookbook": "Offentlig kokebok",
"public-cookbook-description": "Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.", "public-cookbook-description": "Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.",
"filter-options": "Filter Options", "filter-options": "Filter Options",
"filter-options-description": "When require all is selected the cookbook will only include recipes that have all of the items selected. This applies to each subset of selectors and not a cross section of the selected items.", "filter-options-description": "When require all is selected the cookbook will only include recipes that have all of the items selected. This applies to each subset of selectors and not a cross section of the selected items.",
"require-all-categories": "Require All Categories", "require-all-categories": "Require All Categories",
"require-all-tags": "Require All Tags", "require-all-tags": "Require All Tags",
"require-all-tools": "Require All Tools", "require-all-tools": "Require All Tools",
"cookbook-name": "Cookbook Name", "cookbook-name": "Navn på kokebok",
"cookbook-with-name": "Cookbook {0}" "cookbook-with-name": "Cookbook {0}"
} }
} }

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Słowo kluczowe", "keyword": "Słowo kluczowe",
"link-copied": "Odnośnik skopiowany", "link-copied": "Odnośnik skopiowany",
"loading": "Loading",
"loading-events": "Ładowanie wydarzeń", "loading-events": "Ładowanie wydarzeń",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",
@ -469,7 +470,7 @@
"add-to-plan": "Dodaj do planu", "add-to-plan": "Dodaj do planu",
"add-to-timeline": "Add to Timeline", "add-to-timeline": "Add to Timeline",
"recipe-added-to-list": "Przepis dodany do listy", "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", "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-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", "failed-to-add-recipe-to-mealplan": "Nie udało się dodać przepisu do planu posiłków",
@ -495,10 +496,10 @@
"public-link": "Link publiczny", "public-link": "Link publiczny",
"timer": { "timer": {
"kitchen-timer": "Kitchen Timer", "kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer", "start-timer": "Włącz minutnik",
"pause-timer": "Pause Timer", "pause-timer": "Pause Timer",
"resume-timer": "Resume Timer", "resume-timer": "Resume Timer",
"stop-timer": "Stop Timer" "stop-timer": "Zatrzymaj minutnik"
}, },
"edit-timeline-event": "Edytuj zdarzenie osi czasu", "edit-timeline-event": "Edytuj zdarzenie osi czasu",
"timeline": "Oś czasu", "timeline": "Oś czasu",
@ -553,7 +554,7 @@
"unit": "Jednostka", "unit": "Jednostka",
"upload-image": "Prześlij obraz", "upload-image": "Prześlij obraz",
"screen-awake": "Keep Screen Awake", "screen-awake": "Keep Screen Awake",
"remove-image": "Remove image" "remove-image": "Usuń obraz"
}, },
"search": { "search": {
"advanced-search": "Wyszukiwanie zaawansowane", "advanced-search": "Wyszukiwanie zaawansowane",
@ -578,7 +579,7 @@
"add-a-new-theme": "Dodaj nowy motyw", "add-a-new-theme": "Dodaj nowy motyw",
"admin-settings": "Ustawienia administratora", "admin-settings": "Ustawienia administratora",
"backup": { "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-created-at-response-export_path": "Kopia zapasowa została utworzona w {path}",
"backup-deleted": "Kopia zapasowa została usunięta", "backup-deleted": "Kopia zapasowa została usunięta",
"restore-success": "Restore successful", "restore-success": "Restore successful",
@ -831,7 +832,7 @@
"password-updated": "Hasło zostało zaktualizowane", "password-updated": "Hasło zostało zaktualizowane",
"password": "Hasło", "password": "Hasło",
"password-strength": "Hasło jest {strength}", "password-strength": "Hasło jest {strength}",
"please-enter-password": "Please enter your new password.", "please-enter-password": "Wpisz nowe hasło.",
"register": "Zarejestruj się", "register": "Zarejestruj się",
"reset-password": "Zresetuj hasło", "reset-password": "Zresetuj hasło",
"sign-in": "Zaloguj się", "sign-in": "Zaloguj się",
@ -852,7 +853,7 @@
"username": "Nazwa użytkownika", "username": "Nazwa użytkownika",
"users-header": "UŻYTKOWNICY", "users-header": "UŻYTKOWNICY",
"users": "Użytkownicy", "users": "Użytkownicy",
"user-not-found": "User not found", "user-not-found": "Nie znaleziono użytkownika",
"webhook-time": "Czas webhooka", "webhook-time": "Czas webhooka",
"webhooks-enabled": "Webhooki włączone", "webhooks-enabled": "Webhooki włączone",
"you-are-not-allowed-to-create-a-user": "Nie masz uprawnień do tworzenia użytkowników", "you-are-not-allowed-to-create-a-user": "Nie masz uprawnień do tworzenia użytkowników",
@ -975,12 +976,12 @@
"columns": "Kolumny", "columns": "Kolumny",
"combine": "Połącz", "combine": "Połącz",
"categories": { "categories": {
"edit-category": "Edit Category", "edit-category": "Edytuj kategorię",
"new-category": "New Category", "new-category": "Nowa kategoria",
"category-data": "Category Data" "category-data": "Dane kategorii"
}, },
"tags": { "tags": {
"new-tag": "New Tag", "new-tag": "Nowy Tag",
"edit-tag": "Edit Tag", "edit-tag": "Edit Tag",
"tag-data": "Tag Data" "tag-data": "Tag Data"
}, },

View File

@ -4,7 +4,7 @@
"about-mealie": "Sobre Mealie", "about-mealie": "Sobre Mealie",
"api-docs": "Documentação da API", "api-docs": "Documentação da API",
"api-port": "Porta da API", "api-port": "Porta da API",
"application-mode": "Modo do aplicativo", "application-mode": "Modo do Aplicativo",
"database-type": "Tipo do banco de dados", "database-type": "Tipo do banco de dados",
"database-url": "URL do servidor de banco de dados", "database-url": "URL do servidor de banco de dados",
"default-group": "Grupo padrão", "default-group": "Grupo padrão",
@ -77,7 +77,7 @@
"tag-events": "Eventos de Etiqueta", "tag-events": "Eventos de Etiqueta",
"category-events": "Eventos de Categoria", "category-events": "Eventos de Categoria",
"when-a-new-user-joins-your-group": "Quando um novo usuário entrar no seu grupo", "when-a-new-user-joins-your-group": "Quando um novo usuário entrar no seu grupo",
"recipe-events": "Recipe Events" "recipe-events": "Eventos da Receita"
}, },
"general": { "general": {
"cancel": "Cancelar", "cancel": "Cancelar",
@ -114,9 +114,10 @@
"json": "JSON", "json": "JSON",
"keyword": "Palavra chave", "keyword": "Palavra chave",
"link-copied": "Link Copiado", "link-copied": "Link Copiado",
"loading": "Carregando",
"loading-events": "Carregando eventos", "loading-events": "Carregando eventos",
"loading-recipe": "Loading recipe...", "loading-recipe": "Carregando receita...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Carregando dados de OCR...",
"loading-recipes": "Carregando Receitas", "loading-recipes": "Carregando Receitas",
"message": "Mensagem", "message": "Mensagem",
"monday": "Segunda-feira", "monday": "Segunda-feira",
@ -127,7 +128,7 @@
"no-recipe-found": "Nenhuma Receita Encontrada", "no-recipe-found": "Nenhuma Receita Encontrada",
"ok": "OK", "ok": "OK",
"options": "Opções:", "options": "Opções:",
"plural-name": "Plural Name", "plural-name": "Nome Plural",
"print": "Imprimir", "print": "Imprimir",
"print-preferences": "Preferências de impressão", "print-preferences": "Preferências de impressão",
"random": "Aleatório", "random": "Aleatório",
@ -197,7 +198,7 @@
"refresh": "Recarregar", "refresh": "Recarregar",
"upload-file": "Enviar arquivo", "upload-file": "Enviar arquivo",
"created-on-date": "Criado em {0}", "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": "Você possui alterações não salvas. Deseja salvar antes de sair? Ok para salvar, Cancelar para descartar alterações."
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Tem certeza que deseja excluir o grupo <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "Tem certeza que deseja excluir o grupo <b>{groupName}<b/>?",
@ -212,7 +213,7 @@
"group-id-with-value": "ID do grupo: {groupID}", "group-id-with-value": "ID do grupo: {groupID}",
"group-name": "Nome do Grupo", "group-name": "Nome do Grupo",
"group-not-found": "Grupo não encontrado", "group-not-found": "Grupo não encontrado",
"group-token": "Group Token", "group-token": "Token do Grupo",
"group-with-value": "Grupo: {groupID}", "group-with-value": "Grupo: {groupID}",
"groups": "Grupos", "groups": "Grupos",
"manage-groups": "Gerenciar Grupos", "manage-groups": "Gerenciar Grupos",
@ -248,7 +249,7 @@
"general-preferences": "Preferências Gerais", "general-preferences": "Preferências Gerais",
"group-recipe-preferences": "Preferências de Grupo de Receitas", "group-recipe-preferences": "Preferências de Grupo de Receitas",
"report": "Denunciar", "report": "Denunciar",
"report-with-id": "Report ID: {id}", "report-with-id": "ID do Relatório: {id}",
"group-management": "Gerenciamento de grupos", "group-management": "Gerenciamento de grupos",
"admin-group-management": "Gerenciamento de Grupos Administrativos", "admin-group-management": "Gerenciamento de Grupos Administrativos",
"admin-group-management-text": "As alterações a este grupo serão refletidas imediatamente.", "admin-group-management-text": "As alterações a este grupo serão refletidas imediatamente.",
@ -469,9 +470,9 @@
"add-to-plan": "Adicionar ao Plano", "add-to-plan": "Adicionar ao Plano",
"add-to-timeline": "Adicionar à linha do tempo", "add-to-timeline": "Adicionar à linha do tempo",
"recipe-added-to-list": "Receita adicionada à lista", "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", "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": "Falha ao adicionar receita à lista",
"failed-to-add-recipe-to-mealplan": "Falha ao adicionar a receita ao plano de refeições", "failed-to-add-recipe-to-mealplan": "Falha ao adicionar a receita ao plano de refeições",
"yield": "Rendimento", "yield": "Rendimento",
"quantity": "Quantidade", "quantity": "Quantidade",
@ -513,7 +514,7 @@
"message-key": "Chave de mensagem", "message-key": "Chave de mensagem",
"parse": "Analisar", "parse": "Analisar",
"attach-images-hint": "Anexe imagens arrastando e soltando-as no editor", "attach-images-hint": "Anexe imagens arrastando e soltando-as no editor",
"drop-image": "Drop image", "drop-image": "Arrastar imagem",
"enable-ingredient-amounts-to-use-this-feature": "Ative quantidades de ingredientes para usar esta funcionalidade", "enable-ingredient-amounts-to-use-this-feature": "Ative quantidades de ingredientes para usar esta funcionalidade",
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Receitas com unidades ou alimentos definidos não podem ser analisadas.", "recipes-with-units-or-foods-defined-cannot-be-parsed": "Receitas com unidades ou alimentos definidos não podem ser analisadas.",
"parse-ingredients": "Analisar ingredientes", "parse-ingredients": "Analisar ingredientes",
@ -572,16 +573,16 @@
"search-hint": "Pressione '/'", "search-hint": "Pressione '/'",
"advanced": "Avançado", "advanced": "Avançado",
"auto-search": "Pesquisa automática", "auto-search": "Pesquisa automática",
"no-results": "No results found" "no-results": "Nenhum resultado encontrado"
}, },
"settings": { "settings": {
"add-a-new-theme": "Adicionar um novo tema", "add-a-new-theme": "Adicionar um novo tema",
"admin-settings": "Configurações de Administrador", "admin-settings": "Configurações de Administrador",
"backup": { "backup": {
"backup-created": "Backup created successfully", "backup-created": "Backup criado com sucesso",
"backup-created-at-response-export_path": "Backup criado em {path}", "backup-created-at-response-export_path": "Backup criado em {path}",
"backup-deleted": "Backup excluído", "backup-deleted": "Backup excluído",
"restore-success": "Restore successful", "restore-success": "Restauração bem-sucedida",
"backup-tag": "Etiqueta de Backup", "backup-tag": "Etiqueta de Backup",
"create-heading": "Criar um Backup", "create-heading": "Criar um Backup",
"delete-backup": "Excluir Backup", "delete-backup": "Excluir Backup",
@ -690,13 +691,13 @@
"configuration": "Configuração", "configuration": "Configuração",
"docker-volume": "Volume do Docker", "docker-volume": "Volume do Docker",
"docker-volume-help": "Mealie requer que o contêiner frontend e o backend compartilhem o mesmo volume ou armazenamento do docker. Isto garante que o contêiner de frontend possa acessar corretamente as imagens e arquivos armazenados no disco.", "docker-volume-help": "Mealie requer que o contêiner frontend e o backend compartilhem o mesmo volume ou armazenamento do docker. Isto garante que o contêiner de frontend possa acessar corretamente as imagens e arquivos armazenados no disco.",
"volumes-are-misconfigured": "Volumes are misconfigured.", "volumes-are-misconfigured": "Volumes estão mal configurados.",
"volumes-are-configured-correctly": "Volumes estão configurados corretamente.", "volumes-are-configured-correctly": "Volumes estão configurados corretamente.",
"status-unknown-try-running-a-validation": "Status desconhecido. Tente executar uma validação.", "status-unknown-try-running-a-validation": "Status desconhecido. Tente executar uma validação.",
"validate": "Validar", "validate": "Validar",
"email-configuration-status": "Status da configuração do e-mail", "email-configuration-status": "Status da configuração do e-mail",
"email-configured": "Email Configured", "email-configured": "E-mail configurado",
"email-test-results": "Email Test Results", "email-test-results": "Resultados do teste de e-mail",
"ready": "Pronto", "ready": "Pronto",
"not-ready": "Não está Pronto - Verificar variáveis ambientais", "not-ready": "Não está Pronto - Verificar variáveis ambientais",
"succeeded": "Sucesso", "succeeded": "Sucesso",
@ -831,7 +832,7 @@
"password-updated": "Senha modificada", "password-updated": "Senha modificada",
"password": "Senha", "password": "Senha",
"password-strength": "Senha é {strength}", "password-strength": "Senha é {strength}",
"please-enter-password": "Please enter your new password.", "please-enter-password": "Por favor, digite sua nova senha.",
"register": "Registre-se", "register": "Registre-se",
"reset-password": "Alterar senha", "reset-password": "Alterar senha",
"sign-in": "Iniciar sessão", "sign-in": "Iniciar sessão",
@ -852,7 +853,7 @@
"username": "Nome de usuário", "username": "Nome de usuário",
"users-header": "USUÁRIOS", "users-header": "USUÁRIOS",
"users": "Usuários", "users": "Usuários",
"user-not-found": "User not found", "user-not-found": "Usuário não encontrado",
"webhook-time": "Hora do Webhook", "webhook-time": "Hora do Webhook",
"webhooks-enabled": "Webhooks ativados", "webhooks-enabled": "Webhooks ativados",
"you-are-not-allowed-to-create-a-user": "Você não tem permissão para criar um usuário", "you-are-not-allowed-to-create-a-user": "Você não tem permissão para criar um usuário",
@ -875,7 +876,7 @@
"user-management": "Gerenciamento de usuários", "user-management": "Gerenciamento de usuários",
"reset-locked-users": "Redefinir Usuários Bloqueados", "reset-locked-users": "Redefinir Usuários Bloqueados",
"admin-user-creation": "Criação de Usuário Administrativo", "admin-user-creation": "Criação de Usuário Administrativo",
"admin-user-management": "Admin User Management", "admin-user-management": "Gerenciamento de Usuário Administrativo",
"user-details": "Detalhes do Usuário", "user-details": "Detalhes do Usuário",
"user-name": "Nome do usuário", "user-name": "Nome do usuário",
"authentication-method": "Método de autenticação", "authentication-method": "Método de autenticação",
@ -886,11 +887,11 @@
"user-can-manage-group": "Usuário pode gerenciar o grupo", "user-can-manage-group": "Usuário pode gerenciar o grupo",
"user-can-organize-group-data": "Usuário pode organizar dados do grupo", "user-can-organize-group-data": "Usuário pode organizar dados do grupo",
"enable-advanced-features": "Ativar recursos avançados", "enable-advanced-features": "Ativar recursos avançados",
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.", "it-looks-like-this-is-your-first-time-logging-in": "Parece que este é seu primeiro login.",
"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!", "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Não quer mais ver isto? Não se esqueça de alterar seu e-mail nas configurações do seu usuário!",
"forgot-password": "Forgot Password", "forgot-password": "Esqueci minha senha",
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.", "forgot-password-text": "Digite seu endereço de e-mail e enviaremos um link para redefinir sua senha.",
"changes-reflected-immediately": "Changes to this user will be reflected immediately." "changes-reflected-immediately": "As alterações deste usuário serão refletidas imediatamente."
}, },
"language-dialog": { "language-dialog": {
"translated": "traduzido", "translated": "traduzido",
@ -912,8 +913,8 @@
"food-label": "Rótulo da Comida", "food-label": "Rótulo da Comida",
"edit-food": "Editar Comida", "edit-food": "Editar Comida",
"food-data": "Dados da Comida", "food-data": "Dados da Comida",
"example-food-singular": "ex: Onion", "example-food-singular": "ex: Cebola",
"example-food-plural": "ex: Onions" "example-food-plural": "ex: Cebolas"
}, },
"units": { "units": {
"seed-dialog-text": "Adicione a base de dados unidades comuns baseadas em seu idioma.", "seed-dialog-text": "Adicione a base de dados unidades comuns baseadas em seu idioma.",
@ -924,7 +925,7 @@
"merging-unit-into-unit": "Mesclando {0} em {1}", "merging-unit-into-unit": "Mesclando {0} em {1}",
"create-unit": "Criar unidade", "create-unit": "Criar unidade",
"abbreviation": "Abreviação", "abbreviation": "Abreviação",
"plural-abbreviation": "Plural Abbreviation", "plural-abbreviation": "Abreviação Plural",
"description": "Descrição", "description": "Descrição",
"display-as-fraction": "Exibir como fração", "display-as-fraction": "Exibir como fração",
"use-abbreviation": "Usar abreviação", "use-abbreviation": "Usar abreviação",
@ -932,10 +933,10 @@
"unit-data": "Dados da Unidade", "unit-data": "Dados da Unidade",
"use-abbv": "Usar abreviação", "use-abbv": "Usar abreviação",
"fraction": "Fração", "fraction": "Fração",
"example-unit-singular": "ex: Tablespoon", "example-unit-singular": "ex: Colher de Sopa",
"example-unit-plural": "ex: Tablespoons", "example-unit-plural": "ex: Colheres de Sopa",
"example-unit-abbreviation-singular": "ex: Tbsp", "example-unit-abbreviation-singular": "ex: Clsp",
"example-unit-abbreviation-plural": "ex: Tbsps" "example-unit-abbreviation-plural": "ex: Clssp"
}, },
"labels": { "labels": {
"seed-dialog-text": "Semente o banco de dados com rótulos comuns baseados no seu idioma local.", "seed-dialog-text": "Semente o banco de dados com rótulos comuns baseados no seu idioma local.",
@ -964,8 +965,8 @@
"delete-recipes": "Excluir Receitas", "delete-recipes": "Excluir Receitas",
"source-unit-will-be-deleted": "Unidade de origem será excluída" "source-unit-will-be-deleted": "Unidade de origem será excluída"
}, },
"create-alias": "Create Alias", "create-alias": "Criar Apelido",
"manage-aliases": "Manage Aliases", "manage-aliases": "Gerenciar apelidos",
"seed-data": "Semear dados", "seed-data": "Semear dados",
"seed": "Semear", "seed": "Semear",
"data-management": "Gerenciamento de dados", "data-management": "Gerenciamento de dados",
@ -975,24 +976,24 @@
"columns": "Colunas", "columns": "Colunas",
"combine": "Combinar", "combine": "Combinar",
"categories": { "categories": {
"edit-category": "Edit Category", "edit-category": "Editar Categoria",
"new-category": "New Category", "new-category": "Nova Categoria",
"category-data": "Category Data" "category-data": "Dados da Categoria"
}, },
"tags": { "tags": {
"new-tag": "New Tag", "new-tag": "Nova Tag",
"edit-tag": "Edit Tag", "edit-tag": "Editar Tag",
"tag-data": "Tag Data" "tag-data": "Dados da Tag"
}, },
"tools": { "tools": {
"new-tool": "New Tool", "new-tool": "Nova Ferramenta",
"edit-tool": "Edit Tool", "edit-tool": "Editar Ferramenta",
"tool-data": "Tool Data" "tool-data": "Dados da Ferramenta"
} }
}, },
"user-registration": { "user-registration": {
"user-registration": "Cadastro de usuário", "user-registration": "Cadastro de usuário",
"registration-success": "Registration Success", "registration-success": "Registrado com Sucesso",
"join-a-group": "Junte-se a um grupo", "join-a-group": "Junte-se a um grupo",
"create-a-new-group": "Criar Grupo", "create-a-new-group": "Criar Grupo",
"provide-registration-token-description": "Forneça o token de registro associado ao grupo que deseja aderir. Você precisará obter isso de um membro de grupo existente.", "provide-registration-token-description": "Forneça o token de registro associado ao grupo que deseja aderir. Você precisará obter isso de um membro de grupo existente.",
@ -1039,7 +1040,7 @@
}, },
"ocr-editor": { "ocr-editor": {
"ocr-editor": "Editor OCR", "ocr-editor": "Editor OCR",
"toolbar": "Toolbar", "toolbar": "Barra de Ferramentas",
"selection-mode": "Modo de seleção", "selection-mode": "Modo de seleção",
"pan-and-zoom-picture": "Imagem pan e zoom", "pan-and-zoom-picture": "Imagem pan e zoom",
"split-text": "Dividir texto", "split-text": "Dividir texto",
@ -1047,8 +1048,8 @@
"split-by-block": "Dividir por bloco de texto", "split-by-block": "Dividir por bloco de texto",
"flatten": "Achatar independentemente da formatação original", "flatten": "Achatar independentemente da formatação original",
"help": { "help": {
"help": "Help", "help": "Ajuda",
"mouse-modes": "Mouse modes", "mouse-modes": "Modos do mouse",
"selection-mode": "Modo de Seleção (Padrão)", "selection-mode": "Modo de Seleção (Padrão)",
"selection-mode-desc": "O modo de seleção é o modo principal que pode ser usado para inserir dados:", "selection-mode-desc": "O modo de seleção é o modo principal que pode ser usado para inserir dados:",
"selection-mode-steps": { "selection-mode-steps": {

View File

@ -3,7 +3,7 @@
"about": "Sobre", "about": "Sobre",
"about-mealie": "Sobre Mealie", "about-mealie": "Sobre Mealie",
"api-docs": "Documentação de API", "api-docs": "Documentação de API",
"api-port": "Porta de API", "api-port": "Porta da API",
"application-mode": "Modo de aplicação", "application-mode": "Modo de aplicação",
"database-type": "Tipo de Base de Dados", "database-type": "Tipo de Base de Dados",
"database-url": "Endereço da Base de Dados", "database-url": "Endereço da Base de Dados",
@ -77,7 +77,7 @@
"tag-events": "Eventos de Etiquetagem", "tag-events": "Eventos de Etiquetagem",
"category-events": "Eventos de Categoria", "category-events": "Eventos de Categoria",
"when-a-new-user-joins-your-group": "Quando um novo utilizador entra no seu grupo", "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": { "general": {
"cancel": "Cancelar", "cancel": "Cancelar",
@ -114,9 +114,10 @@
"json": "JSON", "json": "JSON",
"keyword": "Palavra-chave", "keyword": "Palavra-chave",
"link-copied": "Ligação copiada", "link-copied": "Ligação copiada",
"loading": "Loading",
"loading-events": "A carregar Eventos", "loading-events": "A carregar Eventos",
"loading-recipe": "Loading recipe...", "loading-recipe": "A carregar receita...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "A carregar dados OCR...",
"loading-recipes": "A carregar receitas", "loading-recipes": "A carregar receitas",
"message": "Mensagem", "message": "Mensagem",
"monday": "Segunda-feira", "monday": "Segunda-feira",
@ -127,7 +128,7 @@
"no-recipe-found": "Nenhuma Receita Encontrada", "no-recipe-found": "Nenhuma Receita Encontrada",
"ok": "OK", "ok": "OK",
"options": "Opções:", "options": "Opções:",
"plural-name": "Plural Name", "plural-name": "Nome no Plural",
"print": "Imprimir", "print": "Imprimir",
"print-preferences": "Preferências de impressão", "print-preferences": "Preferências de impressão",
"random": "Aleatório", "random": "Aleatório",
@ -197,7 +198,7 @@
"refresh": "Atualizar", "refresh": "Atualizar",
"upload-file": "Carregar ficheiro", "upload-file": "Carregar ficheiro",
"created-on-date": "Criado em: {0}", "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": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Tem a certeza que quer eliminar <b>{groupName}</b>?", "are-you-sure-you-want-to-delete-the-group": "Tem a certeza que quer eliminar <b>{groupName}</b>?",
@ -212,7 +213,7 @@
"group-id-with-value": "ID do Grupo: {groupID}", "group-id-with-value": "ID do Grupo: {groupID}",
"group-name": "Nome do grupo", "group-name": "Nome do grupo",
"group-not-found": "Grupo não encontrado", "group-not-found": "Grupo não encontrado",
"group-token": "Group Token", "group-token": "Token do Grupo",
"group-with-value": "Grupo: {groupID}", "group-with-value": "Grupo: {groupID}",
"groups": "Grupos", "groups": "Grupos",
"manage-groups": "Gerir Grupos", "manage-groups": "Gerir Grupos",
@ -248,7 +249,7 @@
"general-preferences": "Preferências Gerais", "general-preferences": "Preferências Gerais",
"group-recipe-preferences": "Agrupar preferências de receita", "group-recipe-preferences": "Agrupar preferências de receita",
"report": "Relatório", "report": "Relatório",
"report-with-id": "Report ID: {id}", "report-with-id": "ID do relatório: {id}",
"group-management": "Gestão de Grupos", "group-management": "Gestão de Grupos",
"admin-group-management": "Gestão do Grupo Admin", "admin-group-management": "Gestão do Grupo Admin",
"admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.", "admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.",
@ -469,9 +470,9 @@
"add-to-plan": "Adicionar ao plano", "add-to-plan": "Adicionar ao plano",
"add-to-timeline": "Adicionar à Linha Temporal", "add-to-timeline": "Adicionar à Linha Temporal",
"recipe-added-to-list": "Receita adicionada à lista", "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", "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", "failed-to-add-recipe-to-mealplan": "Erro ao adicionar receita ao plano de refeições",
"yield": "Rendimento", "yield": "Rendimento",
"quantity": "Quantidade", "quantity": "Quantidade",
@ -513,7 +514,7 @@
"message-key": "Chave de Mensagem", "message-key": "Chave de Mensagem",
"parse": "Interpretar", "parse": "Interpretar",
"attach-images-hint": "Anexe imagens arrastando e soltando-as no editor", "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", "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.", "recipes-with-units-or-foods-defined-cannot-be-parsed": "Receitas com unidades ou alimentos definidos não podem ser interpretadas.",
"parse-ingredients": "Interpretar ingredientes", "parse-ingredients": "Interpretar ingredientes",
@ -572,16 +573,16 @@
"search-hint": "Prima '/'", "search-hint": "Prima '/'",
"advanced": "Avançado", "advanced": "Avançado",
"auto-search": "Pesquisa Automática", "auto-search": "Pesquisa Automática",
"no-results": "No results found" "no-results": "Nenhum resultado encontrado"
}, },
"settings": { "settings": {
"add-a-new-theme": "Adicionar novo tema", "add-a-new-theme": "Adicionar novo tema",
"admin-settings": "Definições de Administrador", "admin-settings": "Definições de Administrador",
"backup": { "backup": {
"backup-created": "Backup created successfully", "backup-created": "Backup realizado com sucesso",
"backup-created-at-response-export_path": "Backup criado em {path}", "backup-created-at-response-export_path": "Backup criado em {path}",
"backup-deleted": "Backup eliminado", "backup-deleted": "Backup eliminado",
"restore-success": "Restore successful", "restore-success": "Restauro bem-sucedido",
"backup-tag": "Cópia de segurança de Etiqueta", "backup-tag": "Cópia de segurança de Etiqueta",
"create-heading": "Criar um Backup", "create-heading": "Criar um Backup",
"delete-backup": "Eliminar Backup", "delete-backup": "Eliminar Backup",
@ -690,13 +691,13 @@
"configuration": "Configuração", "configuration": "Configuração",
"docker-volume": "Volume do Docker", "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.", "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.", "volumes-are-configured-correctly": "Os volumes estão configurados corretamente.",
"status-unknown-try-running-a-validation": "Estado desconhecido. Tente executar uma validação.", "status-unknown-try-running-a-validation": "Estado desconhecido. Tente executar uma validação.",
"validate": "Validar", "validate": "Validar",
"email-configuration-status": "Estado de configuração do correio eletrónico", "email-configuration-status": "Estado de configuração do correio eletrónico",
"email-configured": "Email Configured", "email-configured": "Email configurado",
"email-test-results": "Email Test Results", "email-test-results": "Resultados do Teste de Email",
"ready": "Pronto", "ready": "Pronto",
"not-ready": "Não Pronto — Verificar Variáveis de Ambiente", "not-ready": "Não Pronto — Verificar Variáveis de Ambiente",
"succeeded": "Sucesso", "succeeded": "Sucesso",
@ -831,7 +832,7 @@
"password-updated": "Palavra-passe atualizada", "password-updated": "Palavra-passe atualizada",
"password": "Palavra-passe", "password": "Palavra-passe",
"password-strength": "A palavra-passe é {strength}", "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", "register": "Registar",
"reset-password": "Repor Palavra-passe", "reset-password": "Repor Palavra-passe",
"sign-in": "Inscreva-se", "sign-in": "Inscreva-se",
@ -852,7 +853,7 @@
"username": "Nome de utilizador", "username": "Nome de utilizador",
"users-header": "UTILIZADORES", "users-header": "UTILIZADORES",
"users": "Utilizadores", "users": "Utilizadores",
"user-not-found": "User not found", "user-not-found": "Utilizador não encontrado",
"webhook-time": "Hora do Webhook", "webhook-time": "Hora do Webhook",
"webhooks-enabled": "Webhooks ativados", "webhooks-enabled": "Webhooks ativados",
"you-are-not-allowed-to-create-a-user": "Não tem permissão para criar um utilizador", "you-are-not-allowed-to-create-a-user": "Não tem permissão para criar um utilizador",
@ -875,7 +876,7 @@
"user-management": "Gestão de utilizadores", "user-management": "Gestão de utilizadores",
"reset-locked-users": "Reiniciar utilizadores bloqueados", "reset-locked-users": "Reiniciar utilizadores bloqueados",
"admin-user-creation": "Criação do Utilizador Administrador", "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-details": "Detalhes do Utilizador",
"user-name": "Nome do Utilizador", "user-name": "Nome do Utilizador",
"authentication-method": "Método de Autenticação", "authentication-method": "Método de Autenticação",
@ -886,11 +887,11 @@
"user-can-manage-group": "O utilizador pode gerir o grupo", "user-can-manage-group": "O utilizador pode gerir o grupo",
"user-can-organize-group-data": "O utilizador pode organizar dados do grupo", "user-can-organize-group-data": "O utilizador pode organizar dados do grupo",
"enable-advanced-features": "Habilitar recursos avançados", "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.", "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": "Don't want to see this anymore? Be sure to change your email in your user settings!", "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": "Forgot Password", "forgot-password": "Esqueceu-se da palavra-passe",
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.", "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": "Changes to this user will be reflected immediately." "changes-reflected-immediately": "As alterações a este utilizador serão aplicadas imediatamente."
}, },
"language-dialog": { "language-dialog": {
"translated": "traduzido", "translated": "traduzido",
@ -912,8 +913,8 @@
"food-label": "Rótulo de Alimento", "food-label": "Rótulo de Alimento",
"edit-food": "Editar Alimento", "edit-food": "Editar Alimento",
"food-data": "Dados do Alimento", "food-data": "Dados do Alimento",
"example-food-singular": "ex: Onion", "example-food-singular": "ex: Cebola",
"example-food-plural": "ex: Onions" "example-food-plural": "ex: Cebolas"
}, },
"units": { "units": {
"seed-dialog-text": "Popule a base de dados com unidades comuns no seu idioma.", "seed-dialog-text": "Popule a base de dados com unidades comuns no seu idioma.",
@ -924,7 +925,7 @@
"merging-unit-into-unit": "A juntar {0} com {1}", "merging-unit-into-unit": "A juntar {0} com {1}",
"create-unit": "Criar Unidade", "create-unit": "Criar Unidade",
"abbreviation": "Abreviatura", "abbreviation": "Abreviatura",
"plural-abbreviation": "Plural Abbreviation", "plural-abbreviation": "Abreviatura no Plural",
"description": "Descrição", "description": "Descrição",
"display-as-fraction": "Mostrar como fração", "display-as-fraction": "Mostrar como fração",
"use-abbreviation": "Usar abreviatura", "use-abbreviation": "Usar abreviatura",
@ -932,10 +933,10 @@
"unit-data": "Dados da Unidade", "unit-data": "Dados da Unidade",
"use-abbv": "Usar Abrev.", "use-abbv": "Usar Abrev.",
"fraction": "Fração", "fraction": "Fração",
"example-unit-singular": "ex: Tablespoon", "example-unit-singular": "ex: Colher de Sopa",
"example-unit-plural": "ex: Tablespoons", "example-unit-plural": "ex: Colheres de Sopa",
"example-unit-abbreviation-singular": "ex: Tbsp", "example-unit-abbreviation-singular": "ex: Cdsp",
"example-unit-abbreviation-plural": "ex: Tbsps" "example-unit-abbreviation-plural": "ex: Cdsps"
}, },
"labels": { "labels": {
"seed-dialog-text": "Adicionar à base de dados rótulos comuns no seu idioma local.", "seed-dialog-text": "Adicionar à base de dados rótulos comuns no seu idioma local.",
@ -964,8 +965,8 @@
"delete-recipes": "Eliminar Receitas", "delete-recipes": "Eliminar Receitas",
"source-unit-will-be-deleted": "Unidade de origem será eliminada" "source-unit-will-be-deleted": "Unidade de origem será eliminada"
}, },
"create-alias": "Create Alias", "create-alias": "Criar Pseudónimo",
"manage-aliases": "Manage Aliases", "manage-aliases": "Gerir Pseudónimos",
"seed-data": "Gerar dados", "seed-data": "Gerar dados",
"seed": "Gerar", "seed": "Gerar",
"data-management": "Gestão de dados", "data-management": "Gestão de dados",
@ -975,24 +976,24 @@
"columns": "Colunas", "columns": "Colunas",
"combine": "Combinar", "combine": "Combinar",
"categories": { "categories": {
"edit-category": "Edit Category", "edit-category": "Editar Categoria",
"new-category": "New Category", "new-category": "Nova Categoria",
"category-data": "Category Data" "category-data": "Dados de Categoria"
}, },
"tags": { "tags": {
"new-tag": "New Tag", "new-tag": "Nova etiqueta",
"edit-tag": "Edit Tag", "edit-tag": "Editar Etiqueta",
"tag-data": "Tag Data" "tag-data": "Dados de Etiqueta"
}, },
"tools": { "tools": {
"new-tool": "New Tool", "new-tool": "Novo Utensílio",
"edit-tool": "Edit Tool", "edit-tool": "Editar Utensílio",
"tool-data": "Tool Data" "tool-data": "Dados do Utensílio"
} }
}, },
"user-registration": { "user-registration": {
"user-registration": "Registo de Utilizador", "user-registration": "Registo de Utilizador",
"registration-success": "Registration Success", "registration-success": "Registo Bem-sucedido",
"join-a-group": "Juntar-se a um grupo", "join-a-group": "Juntar-se a um grupo",
"create-a-new-group": "Criar um Novo 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.", "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 +1040,7 @@
}, },
"ocr-editor": { "ocr-editor": {
"ocr-editor": "Editor OCR", "ocr-editor": "Editor OCR",
"toolbar": "Toolbar", "toolbar": "Barra de ferramentas",
"selection-mode": "Modo de seleção", "selection-mode": "Modo de seleção",
"pan-and-zoom-picture": "Deslocar e ampliar imagem", "pan-and-zoom-picture": "Deslocar e ampliar imagem",
"split-text": "Dividir texto", "split-text": "Dividir texto",
@ -1047,8 +1048,8 @@
"split-by-block": "Dividir por bloco de texto", "split-by-block": "Dividir por bloco de texto",
"flatten": "Nivelar independentemente da formatação original", "flatten": "Nivelar independentemente da formatação original",
"help": { "help": {
"help": "Help", "help": "Ajuda",
"mouse-modes": "Mouse modes", "mouse-modes": "Modos do rato",
"selection-mode": "Modo de Seleção (padrão)", "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-desc": "O modo de seleção é o modo principal disponível para inserir dados:",
"selection-mode-steps": { "selection-mode-steps": {

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Cuvânt cheie", "keyword": "Cuvânt cheie",
"link-copied": "Link copiat", "link-copied": "Link copiat",
"loading": "Loading",
"loading-events": "Se încarcă evenimentele", "loading-events": "Se încarcă evenimentele",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Ключевое слово", "keyword": "Ключевое слово",
"link-copied": "Ссылка скопирована", "link-copied": "Ссылка скопирована",
"loading": "Loading",
"loading-events": "Загрузка событий", "loading-events": "Загрузка событий",
"loading-recipe": "Загрузка рецепта...", "loading-recipe": "Загрузка рецепта...",
"loading-ocr-data": "Загрузка данных OCR...", "loading-ocr-data": "Загрузка данных OCR...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Kľučové slovo", "keyword": "Kľučové slovo",
"link-copied": "Odkaz bol skopírovaný", "link-copied": "Odkaz bol skopírovaný",
"loading": "Loading",
"loading-events": "Načítanie udalostí", "loading-events": "Načítanie udalostí",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Ključna beseda", "keyword": "Ključna beseda",
"link-copied": "Povezava kopirana", "link-copied": "Povezava kopirana",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Ključna reč", "keyword": "Ključna reč",
"link-copied": "Линк је копиран", "link-copied": "Линк је копиран",
"loading": "Loading",
"loading-events": "Учитавање догађаја", "loading-events": "Учитавање догађаја",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Nyckelord", "keyword": "Nyckelord",
"link-copied": "Länk kopierad", "link-copied": "Länk kopierad",
"loading": "Loading",
"loading-events": "Laddar händelser", "loading-events": "Laddar händelser",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Anahtar Kelime", "keyword": "Anahtar Kelime",
"link-copied": "Bağlantı Kopyalandı", "link-copied": "Bağlantı Kopyalandı",
"loading": "Yükleniyor",
"loading-events": "Etkinlikler yükleniyor", "loading-events": "Etkinlikler yükleniyor",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "OCR verileri yükleniyor...", "loading-ocr-data": "OCR verileri yükleniyor...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Ключове слово", "keyword": "Ключове слово",
"link-copied": "Посилання скопійовано", "link-copied": "Посилання скопійовано",
"loading": "Завантаження",
"loading-events": "Завантаження подій", "loading-events": "Завантаження подій",
"loading-recipe": "Завантаження рецепта...", "loading-recipe": "Завантаження рецепта...",
"loading-ocr-data": "Завантаження даних OCR...", "loading-ocr-data": "Завантаження даних OCR...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "Keyword", "keyword": "Keyword",
"link-copied": "Link Copied", "link-copied": "Link Copied",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "关键字", "keyword": "关键字",
"link-copied": "链接已复制", "link-copied": "链接已复制",
"loading": "Loading",
"loading-events": "正在加载事件", "loading-events": "正在加载事件",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -114,6 +114,7 @@
"json": "JSON", "json": "JSON",
"keyword": "關鍵字", "keyword": "關鍵字",
"link-copied": "已複製連結", "link-copied": "已複製連結",
"loading": "Loading",
"loading-events": "Loading Events", "loading-events": "Loading Events",
"loading-recipe": "Loading recipe...", "loading-recipe": "Loading recipe...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",

View File

@ -1,137 +0,0 @@
<template>
<v-container fluid class="md-container">
<BannerExperimental></BannerExperimental>
<BaseCardSectionTitle title="Site Analytics">
Your instance of Mealie can send anonymous usage statistics to the Mealie project team. This is done to help us
gauge the usage of mealie, provide public statistics and to help us improve the user experience.
<p class="pt-4 pb-0 mb-0">
Your installation creates a UUID that is used to identify your installation,
<strong> this is randomly generated using the UUID4 implementation in python</strong>. This UUID is stored on
our analytics server and used to ensure your data is only counted once.
</p>
</BaseCardSectionTitle>
<section>
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.chart" title="Analytics Settings">
When you opt into analytics your install will register itself with the Analytics API to count the installation
and register your generated anonymous installation ID
</BaseCardSectionTitle>
<v-card-text>
<v-switch v-model="state.analyticsEnabled" label="Collect Anonymous Analytics" />
</v-card-text>
</section>
<section class="my-8">
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.chart" title="Analytics Data">
This is a list of all the data that is sent to the Mealie project team.
</BaseCardSectionTitle>
<v-card class="ma-2">
<template v-for="(value, idx) in data">
<v-list-item :key="`item-${idx}`">
<v-list-item-title class="py-2">
<div>{{ value.text }}</div>
<v-list-item-subtitle class="text-end"> {{ getValue(value.valueKey) }} </v-list-item-subtitle>
</v-list-item-title>
</v-list-item>
<v-divider :key="`divider-${idx}`" class="mx-2"></v-divider>
</template>
</v-card>
</section>
</v-container>
</template>
<script lang="ts">
import { defineComponent, reactive, useAsync } from "@nuxtjs/composition-api";
import { useAdminApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
import { MealieAnalytics } from "~/lib/api/types/analytics";
type DisplayData = {
text: string;
valueKey: keyof MealieAnalytics;
};
export default defineComponent({
layout: "admin",
setup() {
const adminApi = useAdminApi();
const state = reactive({
analyticsEnabled: false,
});
const analyticsData = useAsync(async () => {
const { data } = await adminApi.analytics.getAnalytics();
return data;
}, useAsyncKey());
function getValue(key: keyof MealieAnalytics) {
if (!analyticsData.value) {
return "";
}
return analyticsData.value[key];
}
const data: DisplayData[] = [
{
text: "Installation Id",
valueKey: "installationId",
},
{
text: "Version",
valueKey: "version",
},
{
text: "Database",
valueKey: "databaseType",
},
{
text: "Using Email",
valueKey: "usingEmail",
},
{
text: "Using LDAP",
valueKey: "usingLdap",
},
{
text: "API Tokens",
valueKey: "apiTokens",
},
{
text: "Users",
valueKey: "users",
},
{
text: "Recipes",
valueKey: "recipes",
},
{
text: "Groups",
valueKey: "groups",
},
{
text: "Shopping Lists",
valueKey: "shoppingLists",
},
{
text: "Cookbooks",
valueKey: "cookbooks",
},
];
return {
data,
state,
analyticsData,
getValue,
};
},
head() {
return {
title: "Analytics",
};
},
});
</script>
<style scoped></style>

View File

@ -1,5 +1,6 @@
<template> <template>
<v-container fluid class="narrow-container"> <v-container fluid class="narrow-container">
<!-- Image -->
<BasePageTitle divider> <BasePageTitle divider>
<template #header> <template #header>
<v-img max-height="200" max-width="150" :src="require('~/static/svgs/admin-site-settings.svg')"></v-img> <v-img max-height="200" max-width="150" :src="require('~/static/svgs/admin-site-settings.svg')"></v-img>
@ -7,6 +8,7 @@
<template #title> {{ $t("settings.site-settings") }} </template> <template #title> {{ $t("settings.site-settings") }} </template>
</BasePageTitle> </BasePageTitle>
<!-- Bug Report -->
<BaseDialog v-model="bugReportDialog" :title="$t('settings.bug-report')" :width="800" :icon="$globals.icons.github"> <BaseDialog v-model="bugReportDialog" :title="$t('settings.bug-report')" :width="800" :icon="$globals.icons.github">
<v-card-text> <v-card-text>
<div class="pb-4"> <div class="pb-4">
@ -27,7 +29,6 @@
<BaseButton <BaseButton
color="info" color="info"
@click=" @click="
dockerValidate();
bugReportDialog = true; bugReportDialog = true;
" "
> >
@ -36,6 +37,7 @@
</BaseButton> </BaseButton>
</div> </div>
<!-- Configuration -->
<section> <section>
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$tc('settings.configuration')"> </BaseCardSectionTitle> <BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$tc('settings.configuration')"> </BaseCardSectionTitle>
<v-card class="mb-4"> <v-card class="mb-4">
@ -60,40 +62,7 @@
</v-card> </v-card>
</section> </section>
<section> <!-- Email -->
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.docker" :title="$tc('settings.docker-volume')" />
<v-alert
border="left"
colored-border
:type="docker.state === DockerVolumeState.Error ? 'error' : 'info'"
:icon="$globals.icons.docker"
elevation="2"
:loading="docker.loading"
>
<div class="d-flex align-center font-weight-medium">
{{ $t('settings.docker-volume') }}
<HelpIcon small class="my-n3">
{{ $t('settings.docker-volume-help') }}
</HelpIcon>
</div>
<div>
<template v-if="docker.state === DockerVolumeState.Error"> {{ $t('settings.volumes-are-misconfigured') }} </template>
<template v-else-if="docker.state === DockerVolumeState.Success">
{{ $t('settings.volumes-are-configured-correctly') }}
</template>
<template v-else-if="docker.state === DockerVolumeState.Unknown">
{{ $t('settings.status-unknown-try-running-a-validation') }}
</template>
</div>
<div class="mt-4">
<BaseButton color="info" :loading="docker.loading" @click="dockerValidate">
<template #icon> {{ $globals.icons.checkboxMarkedCircle }} </template>
{{ $t('settings.validate') }}
</BaseButton>
</div>
</v-alert>
</section>
<section> <section>
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.email" :title="$tc('user.email')" /> <BaseCardSectionTitle class="pt-2" :icon="$globals.icons.email" :title="$tc('user.email')" />
<v-alert border="left" colored-border :type="appConfig.emailReady ? 'success' : 'error'" elevation="2"> <v-alert border="left" colored-border :type="appConfig.emailReady ? 'success' : 'error'" elevation="2">
@ -130,40 +99,47 @@
<section class="mt-4"> <section class="mt-4">
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$tc('settings.general-about')"> </BaseCardSectionTitle> <BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$tc('settings.general-about')"> </BaseCardSectionTitle>
<v-card class="mb-4"> <v-card class="mb-4">
<template v-for="(property, idx) in appInfo"> <template v-if="appInfo && appInfo.length">
<v-list-item :key="property.name"> <template v-for="(property, idx) in appInfo">
<v-list-item-icon> <v-list-item :key="property.name">
<v-icon> {{ property.icon || $globals.icons.user }} </v-icon> <v-list-item-icon>
</v-list-item-icon> <v-icon> {{ property.icon || $globals.icons.user }} </v-icon>
<v-list-item-content> </v-list-item-icon>
<v-list-item-title> <v-list-item-content>
<div>{{ property.name }}</div> <v-list-item-title>
</v-list-item-title> <div>{{ property.name }}</div>
<template v-if="property.slot === 'recipe-scraper'"> </v-list-item-title>
<v-list-item-subtitle> <template v-if="property.slot === 'recipe-scraper'">
<a <v-list-item-subtitle>
target="_blank" <a
:href="`https://github.com/hhursev/recipe-scrapers/releases/tag/${property.value}`" target="_blank"
> :href="`https://github.com/hhursev/recipe-scrapers/releases/tag/${property.value}`"
>
{{ property.value }}
</a>
</v-list-item-subtitle>
</template>
<template v-else-if="property.slot === 'build'">
<v-list-item-subtitle>
<a target="_blank" :href="`https://github.com/hay-kot/mealie/commit/${property.value}`">
{{ property.value }}
</a>
</v-list-item-subtitle>
</template>
<template v-else>
<v-list-item-subtitle>
{{ property.value }} {{ property.value }}
</a> </v-list-item-subtitle>
</v-list-item-subtitle> </template>
</template> </v-list-item-content>
<template v-else-if="property.slot === 'build'"> </v-list-item>
<v-list-item-subtitle> <v-divider v-if="appInfo && idx !== appInfo.length - 1" :key="`divider-${property.name}`"></v-divider>
<a target="_blank" :href="`https://github.com/hay-kot/mealie/commit/${property.value}`"> </template>
{{ property.value }} </template>
</a> <template v-else>
</v-list-item-subtitle> <div class="mb-3 text-center">
</template> <AppLoader :waiting-text="$tc('general.loading')" />
<template v-else> </div>
<v-list-item-subtitle>
{{ property.value }}
</v-list-item-subtitle>
</template>
</v-list-item-content>
</v-list-item>
<v-divider v-if="appInfo && idx !== appInfo.length - 1" :key="`divider-${property.name}`"></v-divider>
</template> </template>
</v-card> </v-card>
</section> </section>
@ -186,6 +162,7 @@ import { useAdminApi, useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators"; import { validators } from "~/composables/use-validators";
import { useAsyncKey } from "~/composables/use-utils"; import { useAsyncKey } from "~/composables/use-utils";
import { CheckAppConfig } from "~/lib/api/types/admin"; import { CheckAppConfig } from "~/lib/api/types/admin";
import AppLoader from "~/components/global/AppLoader.vue";
enum DockerVolumeState { enum DockerVolumeState {
Unknown = "unknown", Unknown = "unknown",
@ -208,294 +185,230 @@ interface CheckApp extends CheckAppConfig {
} }
export default defineComponent({ export default defineComponent({
layout: "admin", components: { AppLoader },
setup() { layout: "admin",
// ========================================================== setup() {
// Docker Volume Validation const state = reactive({
const docker = reactive({ loading: false,
loading: false, address: "",
state: DockerVolumeState.Unknown, success: false,
}); error: "",
tested: false,
async function dockerValidate() { });
docker.loading = true; const appConfig = ref<CheckApp>({
emailReady: true,
// Do API Check baseUrlSet: true,
const { data } = await adminApi.about.checkDocker(); isSiteSecure: true,
if (data == null) { isUpToDate: false,
docker.state = DockerVolumeState.Error; ldapReady: false,
return; });
} function isLocalHostOrHttps() {
return window.location.hostname === "localhost" || window.location.protocol === "https:";
// Get File Contents
const { data: fileContents } = await adminApi.about.getDockerValidateFileContents();
if (data.text === fileContents) {
docker.state = DockerVolumeState.Success;
} else {
docker.state = DockerVolumeState.Error;
}
docker.loading = false;
}
const state = reactive({
loading: false,
address: "",
success: false,
error: "",
tested: false,
});
const appConfig = ref<CheckApp>({
emailReady: true,
baseUrlSet: true,
isSiteSecure: true,
isUpToDate: false,
ldapReady: false,
});
function isLocalHostOrHttps() {
return window.location.hostname === "localhost" || window.location.protocol === "https:";
}
const api = useUserApi();
const adminApi = useAdminApi();
onMounted(async () => {
const { data } = await adminApi.about.checkApp();
if (data) {
appConfig.value = { ...data, isSiteSecure: false };
}
appConfig.value.isSiteSecure = isLocalHostOrHttps();
});
const simpleChecks = computed<SimpleCheck[]>(() => {
const goodIcon = $globals.icons.checkboxMarkedCircle;
const badIcon = $globals.icons.alert;
const warningIcon = $globals.icons.alertCircle;
const goodColor = "success";
const badColor = "error";
const warningColor = "warning";
const data: SimpleCheck[] = [
{
id: "application-version",
text: i18n.t("settings.application-version"),
status: appConfig.value.isUpToDate,
errorText: i18n.t("settings.application-version-error-text", [rawAppInfo.value.version, rawAppInfo.value.versionLatest]),
successText: i18n.t("settings.mealie-is-up-to-date"),
color: appConfig.value.isUpToDate ? goodColor : warningColor,
icon: appConfig.value.isUpToDate ? goodIcon : warningIcon,
},
{
id: "secure-site",
text: i18n.t("settings.secure-site"),
status: appConfig.value.isSiteSecure,
errorText: i18n.t("settings.secure-site-error-text"),
successText: i18n.t("settings.secure-site-success-text"),
color: appConfig.value.isSiteSecure ? goodColor : badColor,
icon: appConfig.value.isSiteSecure ? goodIcon : badIcon,
},
{
id: "server-side-base-url",
text: i18n.t("settings.server-side-base-url"),
status: appConfig.value.baseUrlSet,
errorText:
i18n.t("settings.server-side-base-url-error-text"),
successText: i18n.t("settings.server-side-base-url-success-text"),
color: appConfig.value.baseUrlSet ? goodColor : badColor,
icon: appConfig.value.baseUrlSet ? goodIcon : badIcon,
},
{
id: "ldap-ready",
text: i18n.t("settings.ldap-ready"),
status: appConfig.value.ldapReady,
errorText:
i18n.t("settings.ldap-ready-error-text"),
successText: i18n.t("settings.ldap-ready-success-text"),
color: appConfig.value.ldapReady ? goodColor : warningColor,
icon: appConfig.value.ldapReady ? goodIcon : warningIcon,
},
];
return data;
});
async function testEmail() {
state.loading = true;
state.tested = false;
const { data } = await api.email.test({ email: state.address });
if (data) {
if (data.success) {
state.success = true;
} else {
state.error = data.error ?? "";
state.success = false;
} }
} const api = useUserApi();
state.loading = false; const adminApi = useAdminApi();
state.tested = true; onMounted(async () => {
const { data } = await adminApi.about.checkApp();
if (data) {
appConfig.value = { ...data, isSiteSecure: false };
}
appConfig.value.isSiteSecure = isLocalHostOrHttps();
});
const simpleChecks = computed<SimpleCheck[]>(() => {
const goodIcon = $globals.icons.checkboxMarkedCircle;
const badIcon = $globals.icons.alert;
const warningIcon = $globals.icons.alertCircle;
const goodColor = "success";
const badColor = "error";
const warningColor = "warning";
const data: SimpleCheck[] = [
{
id: "application-version",
text: i18n.t("settings.application-version"),
status: appConfig.value.isUpToDate,
errorText: i18n.t("settings.application-version-error-text", [rawAppInfo.value.version, rawAppInfo.value.versionLatest]),
successText: i18n.t("settings.mealie-is-up-to-date"),
color: appConfig.value.isUpToDate ? goodColor : warningColor,
icon: appConfig.value.isUpToDate ? goodIcon : warningIcon,
},
{
id: "secure-site",
text: i18n.t("settings.secure-site"),
status: appConfig.value.isSiteSecure,
errorText: i18n.t("settings.secure-site-error-text"),
successText: i18n.t("settings.secure-site-success-text"),
color: appConfig.value.isSiteSecure ? goodColor : badColor,
icon: appConfig.value.isSiteSecure ? goodIcon : badIcon,
},
{
id: "server-side-base-url",
text: i18n.t("settings.server-side-base-url"),
status: appConfig.value.baseUrlSet,
errorText: i18n.t("settings.server-side-base-url-error-text"),
successText: i18n.t("settings.server-side-base-url-success-text"),
color: appConfig.value.baseUrlSet ? goodColor : badColor,
icon: appConfig.value.baseUrlSet ? goodIcon : badIcon,
},
{
id: "ldap-ready",
text: i18n.t("settings.ldap-ready"),
status: appConfig.value.ldapReady,
errorText: i18n.t("settings.ldap-ready-error-text"),
successText: i18n.t("settings.ldap-ready-success-text"),
color: appConfig.value.ldapReady ? goodColor : warningColor,
icon: appConfig.value.ldapReady ? goodIcon : warningIcon,
},
];
return data;
});
async function testEmail() {
state.loading = true;
state.tested = false;
const { data } = await api.email.test({ email: state.address });
if (data) {
if (data.success) {
state.success = true;
}
else {
state.error = data.error ?? "";
state.success = false;
}
}
state.loading = false;
state.tested = true;
}
const validEmail = computed(() => {
if (state.address === "") {
return false;
}
const valid = validators.email(state.address);
// Explicit bool check because validators.email sometimes returns a string
if (valid === true) {
return true;
}
return false;
});
// ============================================================
// General About Info
const { $globals, i18n } = useContext();
const rawAppInfo = ref({
version: "null",
versionLatest: "null",
});
function getAppInfo() {
const statistics = useAsync(async () => {
const { data } = await adminApi.about.about();
if (data) {
rawAppInfo.value.version = data.version;
rawAppInfo.value.versionLatest = data.versionLatest;
const prettyInfo = [
{
name: i18n.t("about.version"),
icon: $globals.icons.information,
value: data.version,
},
{
slot: "build",
name: i18n.t("settings.build"),
icon: $globals.icons.information,
value: data.buildId,
},
{
name: i18n.t("about.application-mode"),
icon: $globals.icons.devTo,
value: data.production ? i18n.t("about.production") : i18n.t("about.development"),
},
{
name: i18n.t("about.demo-status"),
icon: $globals.icons.testTube,
value: data.demoStatus ? i18n.t("about.demo") : i18n.t("about.not-demo"),
},
{
name: i18n.t("about.api-port"),
icon: $globals.icons.api,
value: data.apiPort,
},
{
name: i18n.t("about.api-docs"),
icon: $globals.icons.file,
value: data.apiDocs ? i18n.t("general.enabled") : i18n.t("general.disabled"),
},
{
name: i18n.t("about.database-type"),
icon: $globals.icons.database,
value: data.dbType,
},
{
name: i18n.t("about.database-url"),
icon: $globals.icons.database,
value: data.dbUrl,
},
{
name: i18n.t("about.default-group"),
icon: $globals.icons.group,
value: data.defaultGroup,
},
{
slot: "recipe-scraper",
name: i18n.t("settings.recipe-scraper-version"),
icon: $globals.icons.primary,
value: data.recipeScraperVersion,
},
];
return prettyInfo;
}
return data;
}, useAsyncKey());
return statistics;
}
const appInfo = getAppInfo();
const bugReportDialog = ref(false);
const bugReportText = computed(() => {
const ignore = {
[i18n.tc("about.database-url")]: true,
[i18n.tc("about.default-group")]: true,
};
let text = "**Details**\n";
appInfo.value?.forEach((item) => {
if (ignore[item.name as string]) {
return;
}
text += `${item.name as string}: ${item.value as string}\n`;
});
const ignoreChecks: {
[key: string]: boolean;
} = {
"application-version": true,
};
text += "\n**Checks**\n";
simpleChecks.value.forEach((item) => {
if (ignoreChecks[item.id]) {
return;
}
const status = item.status ? i18n.tc("general.yes") : i18n.tc("general.no");
text += `${item.text.toString()}: ${status}\n`;
});
text += `${i18n.tc("settings.email-configured")}: ${appConfig.value.emailReady ? i18n.tc("general.yes") : i18n.tc("general.no")}\n`;
return text;
});
return {
bugReportDialog,
bugReportText,
DockerVolumeState,
simpleChecks,
appConfig,
validEmail,
validators,
...toRefs(state),
testEmail,
appInfo,
};
},
head() {
return {
title: this.$t("settings.site-settings") as string,
};
} }
const validEmail = computed(() => {
if (state.address === "") {
return false;
}
const valid = validators.email(state.address);
// Explicit bool check because validators.email sometimes returns a string
if (valid === true) {
return true;
}
return false;
});
// ============================================================
// General About Info
const { $globals, i18n } = useContext();
const rawAppInfo = ref({
version: "null",
versionLatest: "null",
});
function getAppInfo() {
const statistics = useAsync(async () => {
const { data } = await adminApi.about.about();
if (data) {
rawAppInfo.value.version = data.version;
rawAppInfo.value.versionLatest = data.versionLatest;
const prettyInfo = [
{
name: i18n.t("about.version"),
icon: $globals.icons.information,
value: data.version,
},
{
slot: "build",
name: i18n.t("settings.build"),
icon: $globals.icons.information,
value: data.buildId,
},
{
name: i18n.t("about.application-mode"),
icon: $globals.icons.devTo,
value: data.production ? i18n.t("about.production") : i18n.t("about.development"),
},
{
name: i18n.t("about.demo-status"),
icon: $globals.icons.testTube,
value: data.demoStatus ? i18n.t("about.demo") : i18n.t("about.not-demo"),
},
{
name: i18n.t("about.api-port"),
icon: $globals.icons.api,
value: data.apiPort,
},
{
name: i18n.t("about.api-docs"),
icon: $globals.icons.file,
value: data.apiDocs ? i18n.t("general.enabled") : i18n.t("general.disabled"),
},
{
name: i18n.t("about.database-type"),
icon: $globals.icons.database,
value: data.dbType,
},
{
name: i18n.t("about.database-url"),
icon: $globals.icons.database,
value: data.dbUrl,
},
{
name: i18n.t("about.default-group"),
icon: $globals.icons.group,
value: data.defaultGroup,
},
{
slot: "recipe-scraper",
name: i18n.t("settings.recipe-scraper-version"),
icon: $globals.icons.primary,
value: data.recipeScraperVersion,
},
];
return prettyInfo;
}
return data;
}, useAsyncKey());
return statistics;
}
const appInfo = getAppInfo();
const bugReportDialog = ref(false);
const bugReportText = computed(() => {
const ignore = {
[i18n.tc("about.database-url")]: true,
[i18n.tc("about.default-group")]: true,
};
let text = "**Details**\n";
appInfo.value?.forEach((item) => {
if (ignore[item.name as string]) {
return;
}
text += `${item.name as string}: ${item.value as string}\n`;
});
const ignoreChecks: { [key: string]: boolean } = {
"application-version": true,
};
text += "\n**Checks**\n";
simpleChecks.value.forEach((item) => {
if (ignoreChecks[item.id]) {
return;
}
const status = item.status ? i18n.tc("general.yes") : i18n.tc("general.no");
text += `${item.text.toString()}: ${status}\n`;
});
text += `${i18n.tc("settings.email-configured")}: ${appConfig.value.emailReady ? i18n.tc("general.yes") : i18n.tc("general.no")}\n`;
text += `${i18n.tc("settings.docker-volume")}: ${docker.state}`;
return text;
});
return {
bugReportDialog,
bugReportText,
DockerVolumeState,
docker,
dockerValidate,
simpleChecks,
appConfig,
validEmail,
validators,
...toRefs(state),
testEmail,
appInfo,
};
},
head() {
return {
title: this.$t("settings.site-settings") as string,
};
},
}); });
</script> </script>

View File

@ -6,7 +6,9 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref, useAsync, useContext, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api"; import { computed, defineComponent, ref, useAsync, useContext, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api";
import { whenever } from "@vueuse/core";
import { useLoggedInState } from "~/composables/use-logged-in-state"; import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useAsyncKey } from "~/composables/use-utils";
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue"; import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
import { usePublicExploreApi } from "~/composables/api/api-client"; import { usePublicExploreApi } from "~/composables/api/api-client";
import { useRecipe } from "~/composables/recipes"; import { useRecipe } from "~/composables/recipes";
@ -15,14 +17,13 @@ import { Recipe } from "~/lib/api/types/recipe";
export default defineComponent({ export default defineComponent({
components: { RecipePage }, components: { RecipePage },
setup() { setup() {
const { $auth } = useContext(); const { $auth } = useContext();
const { isOwnGroup } = useLoggedInState(); const { isOwnGroup } = useLoggedInState();
const { title } = useMeta();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const slug = route.value.params.slug; const slug = route.value.params.slug;
const { title } = useMeta();
let recipe = ref<Recipe | null>(null); let recipe = ref<Recipe | null>(null);
if (isOwnGroup.value) { if (isOwnGroup.value) {
const { recipe: data } = useRecipe(slug); const { recipe: data } = useRecipe(slug);
@ -32,28 +33,28 @@ export default defineComponent({
const api = usePublicExploreApi(groupSlug.value); const api = usePublicExploreApi(groupSlug.value);
recipe = useAsync(async () => { recipe = useAsync(async () => {
const { data, error } = await api.explore.recipes.getOne(slug); const { data, error } = await api.explore.recipes.getOne(slug);
if (error) { if (error) {
console.error("error loading recipe -> ", error); console.error("error loading recipe -> ", error);
router.push(`/g/${groupSlug.value}`); router.push(`/g/${groupSlug.value}`);
} }
return data; return data;
}) }, useAsyncKey())
} }
title.value = recipe.value?.name || ""; whenever(
() => recipe.value,
() => {
if (recipe.value) {
title.value = recipe.value.name;
}
},
)
return { return {
recipe, recipe,
}; };
}, },
head() { head: {},
if (this.recipe) {
return {
title: this.recipe.name
}
}
}
}); });
</script> </script>

View File

@ -103,7 +103,11 @@ export default defineComponent({
if (refreshTags) { if (refreshTags) {
tags.actions.refresh(); tags.actions.refresh();
} }
router.push(`/g/${groupSlug.value}/r/${response.data}?edit=${edit.toString()}`);
// we clear the query params first so if the user hits back, they don't re-import the recipe
router.replace({ query: {} }).then(
() => router.push(`/g/${groupSlug.value}/r/${response.data}?edit=${edit.toString()}`)
);
} }
const recipeUrl = computed({ const recipeUrl = computed({

View File

@ -6,7 +6,6 @@
</template> </template>
<template #title> {{ $t('data-pages.data-management') }} </template> <template #title> {{ $t('data-pages.data-management') }} </template>
{{ $t('data-pages.data-management-description') }} {{ $t('data-pages.data-management-description') }}
<BannerExperimental class="mt-5"></BannerExperimental>
<template #content> <template #content>
<div> <div>
<BaseOverflowButton <BaseOverflowButton

View File

@ -121,7 +121,7 @@
<template #icon> <template #icon>
{{ $globals.icons.database }} {{ $globals.icons.database }}
</template> </template>
{{ $t('general.import') }}} {{ $t('general.import') }}
</BaseButton> </BaseButton>
<BaseButton <BaseButton
color="info" color="info"

View File

@ -24,8 +24,8 @@
</v-date-picker> </v-date-picker>
</v-menu> </v-menu>
<div class="d-flex align-center justify-space-between mb-2"> <div class="d-flex flex-wrap align-center justify-space-between mb-2">
<v-tabs> <v-tabs style="width: fit-content;">
<v-tab :to="`/group/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab> <v-tab :to="`/group/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab>
<v-tab :to="`/group/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab> <v-tab :to="`/group/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab>
</v-tabs> </v-tabs>

View File

@ -21,7 +21,7 @@
{{ $d(Date.parse(item.timestamp), "short") }} {{ $d(Date.parse(item.timestamp), "short") }}
</template> </template>
<template #expanded-item="{ headers, item }"> <template #expanded-item="{ headers, item }">
<td class="pa-6" :colspan="headers.length">{{ item.exception }}</td> <td v-if="item.exception" class="pa-6" :colspan="headers.length">{{ item.exception }}</td>
</template> </template>
</v-data-table> </v-data-table>
</v-container> </v-container>

View File

@ -55,7 +55,7 @@
<p>{{ $t('profile.account-summary-description') }}</p> <p>{{ $t('profile.account-summary-description') }}</p>
</div> </div>
<v-row tag="section"> <v-row tag="section">
<v-col cols="12" sm="12" md="6"> <v-col cols="12" sm="12" md="12">
<v-card outlined> <v-card outlined>
<v-card-title class="headline pb-0"> {{ $t('profile.group-statistics') }} </v-card-title> <v-card-title class="headline pb-0"> {{ $t('profile.group-statistics') }} </v-card-title>
<v-card-text class="py-0"> <v-card-text class="py-0">
@ -75,22 +75,6 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
<v-col cols="12" sm="12" md="6" class="d-flex align-strart">
<v-card outlined>
<v-card-title class="headline pb-0"> {{ $t('profile.storage-capacity') }} </v-card-title>
<v-card-text class="py-0">
{{ $t('profile.storage-capacity-description') }}
<strong> {{ $t('general.this-feature-is-currently-inactive') }}</strong>
</v-card-text>
<v-card-text>
<v-progress-linear :value="storageUsedPercentage" color="accent" class="rounded" height="30">
<template #default>
<strong> {{ storageText }} </strong>
</template>
</v-progress-linear>
</v-card-text>
</v-card>
</v-col>
</v-row> </v-row>
</section> </section>
<v-divider class="my-7"></v-divider> <v-divider class="my-7"></v-divider>
@ -344,33 +328,8 @@ export default defineComponent({
return statsTo.value[key] ?? "unknown"; 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 { return {
groupSlug, groupSlug,
storageText,
storageUsedPercentage,
getStatsTitle, getStatsTitle,
getStatsIcon, getStatsIcon,
getStatsTo, getStatsTo,

View File

@ -122,8 +122,8 @@ frontend-lint: ## 🧺 Run yarn lint
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Docker makefile # Docker makefile
docker/prod: ## 🐳 Build and Start Docker Production Stack prod: ## 🐳 Build and Start Docker Production Stack
cd docker && docker-compose -f docker-compose.yml -p mealie up --build cd docker && docker compose -f docker-compose.yml -p mealie up --build
generate: generate:
poetry run python dev/code-generation/main.py poetry run python dev/code-generation/main.py

View File

@ -0,0 +1,50 @@
from slugify import slugify
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from mealie.core import root_logger
from mealie.db.models.group import Group
logger = root_logger.get_logger("init_db")
def _do_fix(session: Session, group: Group, counter: int):
if counter:
new_name = f"{group.id} ({counter})"
else:
new_name = str(group.id)
group.name = new_name
group.slug = slugify(group.name)
session.commit()
def fix_group_with_no_name(session: Session):
groups = session.query(Group).filter(Group.name == "").all()
if not groups:
logger.debug("No group found with an empty name; skipping fix")
return
logger.info(
f'{len(groups)} {"group" if len(groups) == 1 else "groups"} found with a missing name; '
f"applying default name"
)
offset = 0
for i, group in enumerate(groups):
attempts = 0
while True:
if attempts >= 3:
raise Exception(
f'Unable to fix empty group name for group_id "{group.id}": too many attempts ({attempts})'
)
counter = i + offset
try:
_do_fix(session, group, counter)
break
except IntegrityError:
session.rollback()
attempts += 1
offset += 1
continue

View File

@ -13,7 +13,7 @@ def fix_slug_food_names(db: AllRepositories):
logger = root_logger.get_logger("init_db") logger = root_logger.get_logger("init_db")
if not food: if not food:
logger.info(f"No food found with slug: '{check_for_food}' skipping fix") logger.debug(f"No food found with slug: '{check_for_food}' skipping fix")
return return
all_foods = db.ingredient_foods.get_all() all_foods = db.ingredient_foods.get_all()

View File

@ -10,6 +10,7 @@ from alembic.runtime import migration
from mealie.core import root_logger from mealie.core import root_logger
from mealie.core.config import get_app_settings from mealie.core.config import get_app_settings
from mealie.db.db_setup import session_context from mealie.db.db_setup import session_context
from mealie.db.fixes.fix_group_with_no_name import fix_group_with_no_name
from mealie.db.fixes.fix_slug_foods import fix_slug_food_names from mealie.db.fixes.fix_slug_foods import fix_slug_food_names
from mealie.repos.all_repositories import get_repositories from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories from mealie.repos.repository_factory import AllRepositories
@ -104,6 +105,7 @@ def main():
init_db(db) init_db(db)
safe_try(lambda: fix_slug_food_names(db)) safe_try(lambda: fix_slug_food_names(db))
safe_try(lambda: fix_group_with_no_name(session))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -6,7 +6,7 @@
"unique-name-error": "Recipe names must be unique" "unique-name-error": "Recipe names must be unique"
}, },
"mealplan": { "mealplan": {
"no-recipes-match-your-rules": "No recipes match your rules" "no-recipes-match-your-rules": "ルールに一致するレシピはありません"
}, },
"user": { "user": {
"user-updated": "ユーザを更新しました。", "user-updated": "ユーザを更新しました。",

View File

@ -2,37 +2,37 @@
"acorn-squash": "acorn squash", "acorn-squash": "acorn squash",
"alfalfa-sprouts": "alfalfa sprouts", "alfalfa-sprouts": "alfalfa sprouts",
"anchovies": "anchovies", "anchovies": "anchovies",
"apples": "apples", "apples": "تفاح",
"artichoke": "artichoke", "artichoke": "خرشوف",
"arugula": "arugula", "arugula": "جرجير",
"asparagus": "asparagus", "asparagus": "asparagus",
"aubergine": "aubergine", "aubergine": "aubergine",
"avocado": "avocado", "avocado": "اﻷفوكادو",
"bacon": "bacon", "bacon": "bacon",
"baking-powder": "baking powder", "baking-powder": "مسحوق الخبز",
"baking-soda": "baking soda", "baking-soda": "baking soda",
"baking-sugar": "baking sugar", "baking-sugar": "سكر الخبز",
"bar-sugar": "bar sugar", "bar-sugar": "bar sugar",
"basil": "basil", "basil": "ريحان",
"bell-peppers": "bell peppers", "bell-peppers": "bell peppers",
"blackberries": "blackberries", "blackberries": "توت الأسود",
"brassicas": "brassicas", "brassicas": "brassicas",
"bok-choy": "bok choy", "bok-choy": "bok choy",
"broccoflower": "broccoflower", "broccoflower": "broccoflower",
"broccoli": "broccoli", "broccoli": "بروكلي",
"broccolini": "broccolini", "broccolini": "broccolini",
"broccoli-rabe": "broccoli rabe", "broccoli-rabe": "broccoli rabe",
"brussels-sprouts": "brussels sprouts", "brussels-sprouts": "brussels sprouts",
"cabbage": "cabbage", "cabbage": "كرنب",
"cauliflower": "cauliflower", "cauliflower": "قرنبيط",
"chinese-leaves": "chinese leaves", "chinese-leaves": "chinese leaves",
"collard-greens": "collard greens", "collard-greens": "collard greens",
"kohlrabi": "kohlrabi", "kohlrabi": "kohlrabi",
"bread": "bread", "bread": "خبز",
"breadfruit": "breadfruit", "breadfruit": "breadfruit",
"broad-beans": "broad beans", "broad-beans": "broad beans",
"brown-sugar": "brown sugar", "brown-sugar": "سكر بني",
"butter": "butter", "butter": "زبدة",
"butternut-pumpkin": "butternut pumpkin", "butternut-pumpkin": "butternut pumpkin",
"butternut-squash": "butternut squash", "butternut-squash": "butternut squash",
"cactus-edible": "cactus, edible", "cactus-edible": "cactus, edible",
@ -40,70 +40,70 @@
"cannabis": "cannabis", "cannabis": "cannabis",
"capsicum": "capsicum", "capsicum": "capsicum",
"caraway": "caraway", "caraway": "caraway",
"carrot": "carrot", "carrot": "جزر",
"castor-sugar": "castor sugar", "castor-sugar": "castor sugar",
"cayenne-pepper": "cayenne pepper", "cayenne-pepper": "فلفل الكايين",
"celeriac": "celeriac", "celeriac": "celeriac",
"celery": "celery", "celery": "كرفس",
"cereal-grains": "cereal grains", "cereal-grains": "cereal grains",
"rice": "rice", "rice": "أرز",
"chard": "chard", "chard": "chard",
"cheese": "cheese", "cheese": "جبن",
"chicory": "chicory", "chicory": "chicory",
"chilli-peppers": "chilli peppers", "chilli-peppers": "chilli peppers",
"chives": "chives", "chives": "chives",
"chocolate": "chocolate", "chocolate": "بالشوكولاتة",
"cilantro": "cilantro", "cilantro": "كزبرة",
"cinnamon": "cinnamon", "cinnamon": "قرفة",
"clarified-butter": "clarified butter", "clarified-butter": "clarified butter",
"coconut": "coconut", "coconut": "جوز الهند",
"coconut-milk": "coconut milk", "coconut-milk": "حليب جوز الهند",
"coffee": "coffee", "coffee": "قهوة",
"confectioners-sugar": "confectioners' sugar", "confectioners-sugar": "confectioners' sugar",
"coriander": "coriander", "coriander": "كزبرة",
"corn": "corn", "corn": "ذرة",
"corn-syrup": "corn syrup", "corn-syrup": "corn syrup",
"cottonseed-oil": "cottonseed oil", "cottonseed-oil": "cottonseed oil",
"courgette": "courgette", "courgette": "courgette",
"cream-of-tartar": "cream of tartar", "cream-of-tartar": "cream of tartar",
"cucumber": "cucumber", "cucumber": "خيار",
"cumin": "cumin", "cumin": "كمون",
"daikon": "daikon", "daikon": "daikon",
"dairy-products-and-dairy-substitutes": "dairy products and dairy substitutes", "dairy-products-and-dairy-substitutes": "dairy products and dairy substitutes",
"eggs": "eggs", "eggs": "بيض",
"ghee": "ghee", "ghee": "ghee",
"milk": "milk", "milk": "حليب",
"dandelion": "dandelion", "dandelion": "dandelion",
"demerara-sugar": "demerara sugar", "demerara-sugar": "demerara sugar",
"dough": "dough", "dough": "dough",
"edible-cactus": "edible cactus", "edible-cactus": "edible cactus",
"eggplant": "eggplant", "eggplant": "باذنجان",
"endive": "endive", "endive": "endive",
"fats": "fats", "fats": "fats",
"speck": "speck", "speck": "speck",
"fava-beans": "fava beans", "fava-beans": "fava beans",
"fiddlehead": "fiddlehead", "fiddlehead": "fiddlehead",
"fish": "fish", "fish": "سَمَكٌ",
"catfish": "catfish ", "catfish": "catfish ",
"cod": "cod", "cod": "cod",
"salt-cod": "salt cod", "salt-cod": "salt cod",
"salmon": "salmon", "salmon": "سمك السالمون",
"skate": "skate", "skate": "skate",
"stockfish": "stockfish", "stockfish": "stockfish",
"trout": "trout", "trout": "سمك السلمون المرقط",
"tuna": "tuna", "tuna": "تونة",
"five-spice-powder": "five spice powder", "five-spice-powder": "مسحوق التوابل 5",
"flour": "flour", "flour": "دقيق",
"frisee": "frisee", "frisee": "frisee",
"fructose": "fructose", "fructose": "fructose",
"fruit": "fruit", "fruit": "فاكهة",
"apple": "apple", "apple": "تفاح",
"oranges": "oranges", "oranges": "برتقال",
"pear": "pear", "pear": "كمثرى",
"tomato": "tomato ", "tomato": "طماطم ",
"fruit-sugar": "fruit sugar", "fruit-sugar": "fruit sugar",
"garam-masala": "garam masala", "garam-masala": "garam masala",
"garlic": "garlic", "garlic": "ثوم",
"gem-squash": "gem squash", "gem-squash": "gem squash",
"ginger": "ginger", "ginger": "ginger",
"giblets": "giblets", "giblets": "giblets",
@ -115,8 +115,8 @@
"green-onion": "green onion", "green-onion": "green onion",
"heart-of-palm": "heart of palm", "heart-of-palm": "heart of palm",
"hemp": "hemp", "hemp": "hemp",
"herbs": "herbs", "herbs": "أعشاب",
"oregano": "oregano", "oregano": "توابل اوريجانو",
"parsley": "parsley", "parsley": "parsley",
"honey": "honey", "honey": "honey",
"icing-sugar": "icing sugar", "icing-sugar": "icing sugar",
@ -136,55 +136,55 @@
"beans": "beans", "beans": "beans",
"lentils": "lentils", "lentils": "lentils",
"lemongrass": "lemongrass", "lemongrass": "lemongrass",
"lettuce": "lettuce", "lettuce": "خس",
"liver": "liver", "liver": "كبد",
"maple-syrup": "maple syrup", "maple-syrup": "maple syrup",
"meat": "meat", "meat": "لحم",
"mortadella": "mortadella", "mortadella": "الموتادلا",
"mushroom": "mushroom", "mushroom": "فطر",
"white-mushroom": "white mushroom", "white-mushroom": "الفطر الأبيض",
"mussels": "mussels", "mussels": "بلح البحر",
"nori": "nori", "nori": "nori",
"nutmeg": "nutmeg", "nutmeg": "nutmeg",
"nutritional-yeast-flakes": "nutritional yeast flakes", "nutritional-yeast-flakes": "nutritional yeast flakes",
"nuts": "nuts", "nuts": "nuts",
"nanaimo-bar-mix": "nanaimo bar mix", "nanaimo-bar-mix": "nanaimo bar mix",
"octopuses": "octopuses", "octopuses": "octopuses",
"oils": "oils", "oils": "زيوت",
"olive-oil": "olive oil", "olive-oil": "زيت الزيتون",
"okra": "okra", "okra": "okra",
"olive": "olive", "olive": "زيتون",
"onion-family": "onion family", "onion-family": "عائلة البصل",
"onion": "onion", "onion": "بصل",
"scallion": "scallion", "scallion": "scallion",
"shallot": "shallot", "shallot": "shallot",
"spring-onion": "spring onion", "spring-onion": "البصل الأخضر",
"orange-blossom-water": "orange blossom water", "orange-blossom-water": "orange blossom water",
"oysters": "oysters", "oysters": "محار",
"panch-puran": "panch puran", "panch-puran": "panch puran",
"paprika": "paprika", "paprika": "paprika",
"parsnip": "parsnip", "parsnip": "parsnip",
"pepper": "pepper", "pepper": "فلفل",
"peppers": "peppers", "peppers": "الفلفل",
"plantain": "plantain", "plantain": "plantain",
"pineapple": "pineapple", "pineapple": "أناناس",
"poppy-seeds": "poppy seeds", "poppy-seeds": "بذور الخشخاش",
"potatoes": "potatoes", "potatoes": "بطاطس",
"poultry": "poultry", "poultry": "دواجن",
"powdered-sugar": "powdered sugar", "powdered-sugar": "سكر مسحوق",
"pumpkin": "pumpkin", "pumpkin": "pumpkin",
"pumpkin-seeds": "pumpkin seeds", "pumpkin-seeds": "pumpkin seeds",
"radish": "radish", "radish": "radish",
"raw-sugar": "raw sugar", "raw-sugar": "السكر الخام",
"refined-sugar": "refined sugar", "refined-sugar": "refined sugar",
"rice-flour": "rice flour", "rice-flour": "دقيق الأرز",
"rock-sugar": "rock sugar", "rock-sugar": "rock sugar",
"rum": "rum", "rum": "rum",
"salt": "salt", "salt": "ملح",
"seafood": "seafood", "seafood": "المأكولات البحرية",
"seeds": "seeds", "seeds": "بذور",
"sesame-seeds": "sesame seeds", "sesame-seeds": "بذور السمسم",
"sunflower-seeds": "sunflower seeds", "sunflower-seeds": "بذور عباد الشمس",
"soda": "soda", "soda": "soda",
"soda-baking": "soda, baking", "soda-baking": "soda, baking",
"soybean": "soybean", "soybean": "soybean",

View File

@ -1,20 +1,20 @@
{ {
"acorn-squash": "acorn squash", "acorn-squash": "acorn squash",
"alfalfa-sprouts": "alfalfa sprouts", "alfalfa-sprouts": "alfalfa sprouts",
"anchovies": "anchovies", "anchovies": "アンチョビ",
"apples": "りんご", "apples": "りんご",
"artichoke": "artichoke", "artichoke": "アーティチョーク",
"arugula": "arugula", "arugula": "ルッコラ",
"asparagus": "asparagus", "asparagus": "アスパラガス",
"aubergine": "茄子", "aubergine": "茄子",
"avocado": "アボカド", "avocado": "アボカド",
"bacon": "ベーコン", "bacon": "ベーコン",
"baking-powder": "baking powder", "baking-powder": "ベーキングパウダー",
"baking-soda": "baking soda", "baking-soda": "重曹",
"baking-sugar": "baking sugar", "baking-sugar": "baking sugar",
"bar-sugar": "bar sugar", "bar-sugar": "bar sugar",
"basil": "バジル", "basil": "バジル",
"bell-peppers": "bell peppers", "bell-peppers": "ピーマン",
"blackberries": "ブラックベリー", "blackberries": "ブラックベリー",
"brassicas": "brassicas", "brassicas": "brassicas",
"bok-choy": "bok choy", "bok-choy": "bok choy",
@ -64,7 +64,7 @@
"corn": "トウモロコシ", "corn": "トウモロコシ",
"corn-syrup": "corn syrup", "corn-syrup": "corn syrup",
"cottonseed-oil": "cottonseed oil", "cottonseed-oil": "cottonseed oil",
"courgette": "courgette", "courgette": "ズッキーニ",
"cream-of-tartar": "cream of tartar", "cream-of-tartar": "cream of tartar",
"cucumber": "きゅうり", "cucumber": "きゅうり",
"cumin": "cumin", "cumin": "cumin",
@ -157,12 +157,12 @@
"onion-family": "onion family", "onion-family": "onion family",
"onion": "玉ねぎ", "onion": "玉ねぎ",
"scallion": "scallion", "scallion": "scallion",
"shallot": "shallot", "shallot": "エシャロット",
"spring-onion": "spring onion", "spring-onion": "ネギ",
"orange-blossom-water": "orange blossom water", "orange-blossom-water": "オレンジの花の水",
"oysters": "oysters", "oysters": "oysters",
"panch-puran": "panch puran", "panch-puran": "panch puran",
"paprika": "paprika", "paprika": "パプリカ",
"parsnip": "parsnip", "parsnip": "parsnip",
"pepper": "pepper", "pepper": "pepper",
"peppers": "peppers", "peppers": "peppers",
@ -179,9 +179,9 @@
"refined-sugar": "refined sugar", "refined-sugar": "refined sugar",
"rice-flour": "rice flour", "rice-flour": "rice flour",
"rock-sugar": "rock sugar", "rock-sugar": "rock sugar",
"rum": "rum", "rum": "ラム酒",
"salt": "塩", "salt": "塩",
"seafood": "seafood", "seafood": "シーフード",
"seeds": "seeds", "seeds": "seeds",
"sesame-seeds": "sesame seeds", "sesame-seeds": "sesame seeds",
"sunflower-seeds": "sunflower seeds", "sunflower-seeds": "sunflower seeds",

View File

@ -104,7 +104,7 @@
"fruit-sugar": "cukier z owoców", "fruit-sugar": "cukier z owoców",
"garam-masala": "garam masala", "garam-masala": "garam masala",
"garlic": "czosnek", "garlic": "czosnek",
"gem-squash": "gem squash", "gem-squash": "dynia zielona",
"ginger": "imbir", "ginger": "imbir",
"giblets": "podroby", "giblets": "podroby",
"grains": "zboże", "grains": "zboże",

View File

@ -39,7 +39,7 @@
"calabrese": "calabresa", "calabrese": "calabresa",
"cannabis": "cannabis", "cannabis": "cannabis",
"capsicum": "páprica", "capsicum": "páprica",
"caraway": "caraway", "caraway": "cominho",
"carrot": "cenoura", "carrot": "cenoura",
"castor-sugar": "açúcar de confeiteiro", "castor-sugar": "açúcar de confeiteiro",
"cayenne-pepper": "pimenta caiena", "cayenne-pepper": "pimenta caiena",
@ -51,7 +51,7 @@
"cheese": "queijo", "cheese": "queijo",
"chicory": "chicória", "chicory": "chicória",
"chilli-peppers": "pimenta picante", "chilli-peppers": "pimenta picante",
"chives": "chives", "chives": "cebolinha",
"chocolate": "chocolate", "chocolate": "chocolate",
"cilantro": "coentro", "cilantro": "coentro",
"cinnamon": "canela", "cinnamon": "canela",
@ -68,7 +68,7 @@
"cream-of-tartar": "creme de tartar", "cream-of-tartar": "creme de tartar",
"cucumber": "pepino", "cucumber": "pepino",
"cumin": "cominho", "cumin": "cominho",
"daikon": "daikon", "daikon": "rabanete",
"dairy-products-and-dairy-substitutes": "produtos lácteos e substitutos de leite", "dairy-products-and-dairy-substitutes": "produtos lácteos e substitutos de leite",
"eggs": "ovos", "eggs": "ovos",
"ghee": "ghee", "ghee": "ghee",

View File

@ -3,28 +3,28 @@
"name": "Produce" "name": "Produce"
}, },
{ {
"name": "Grains" "name": "الحبوب"
}, },
{ {
"name": "Fruits" "name": "الفواكة"
}, },
{ {
"name": "Vegetables" "name": "الخضراوات"
}, },
{ {
"name": "Meat" "name": "اللحوم"
}, },
{ {
"name": "Seafood" "name": "المأكولات البحرية"
}, },
{ {
"name": "Beverages" "name": "المشروبات"
}, },
{ {
"name": "Baked Goods" "name": "المخبوزات"
}, },
{ {
"name": "Canned Goods" "name": "المعلبات"
}, },
{ {
"name": "Condiments" "name": "Condiments"
@ -33,10 +33,10 @@
"name": "Confectionary" "name": "Confectionary"
}, },
{ {
"name": "Dairy Products" "name": "منتجات الألبان"
}, },
{ {
"name": "Frozen Foods" "name": "الأطعمة المجمدة"
}, },
{ {
"name": "Health Foods" "name": "Health Foods"
@ -45,21 +45,21 @@
"name": "Household" "name": "Household"
}, },
{ {
"name": "Meat Products" "name": "منتجات اللحوم"
}, },
{ {
"name": "Snacks" "name": "الوجبات الخفيفة"
}, },
{ {
"name": "Spices" "name": "التوابل"
}, },
{ {
"name": "Sweets" "name": "الحلويات"
}, },
{ {
"name": "Alcohol" "name": "الكحول"
}, },
{ {
"name": "Other" "name": "أخرى"
} }
] ]

View File

@ -1,15 +1,10 @@
import asyncio from fastapi import APIRouter
import random
import shutil
import string
from fastapi import APIRouter, BackgroundTasks
from recipe_scrapers import __version__ as recipe_scraper_version from recipe_scrapers import __version__ as recipe_scraper_version
from mealie.core.release_checker import get_latest_version from mealie.core.release_checker import get_latest_version
from mealie.core.settings.static import APP_VERSION from mealie.core.settings.static import APP_VERSION
from mealie.routes._base import BaseAdminController, controller from mealie.routes._base import BaseAdminController, controller
from mealie.schema.admin.about import AdminAboutInfo, AppStatistics, CheckAppConfig, DockerVolumeText from mealie.schema.admin.about import AdminAboutInfo, AppStatistics, CheckAppConfig
router = APIRouter(prefix="/about") router = APIRouter(prefix="/about")
@ -57,25 +52,3 @@ class AdminAboutController(BaseAdminController):
base_url_set=settings.BASE_URL != "http://localhost:8080", base_url_set=settings.BASE_URL != "http://localhost:8080",
is_up_to_date=APP_VERSION == "develop" or APP_VERSION == "nightly" or get_latest_version() == APP_VERSION, is_up_to_date=APP_VERSION == "develop" or APP_VERSION == "nightly" or get_latest_version() == APP_VERSION,
) )
@router.get("/docker/validate", response_model=DockerVolumeText)
def validate_docker_volume(self, bg: BackgroundTasks):
validation_dir = self.folders.DATA_DIR / "docker-validation"
validation_dir.mkdir(exist_ok=True)
random_string = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(100))
with validation_dir.joinpath("validate.txt").open("w") as f:
f.write(random_string)
async def cleanup():
await asyncio.sleep(60)
try:
shutil.rmtree(validation_dir)
except Exception as e:
self.logger.error(f"Failed to remove docker validation directory: {e}")
bg.add_task(cleanup)
return DockerVolumeText(text=random_string)

View File

@ -5,6 +5,7 @@ from pathlib import Path
from fastapi import APIRouter, File, HTTPException, UploadFile, status from fastapi import APIRouter, File, HTTPException, UploadFile, status
from mealie.core.config import get_app_dirs 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.core.security import create_file_token
from mealie.pkgs.stats.fs_stats import pretty_size from mealie.pkgs.stats.fs_stats import pretty_size
from mealie.routes._base import BaseAdminController, controller 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.schema.response.responses import ErrorResponse, FileTokenResponse, SuccessResponse
from mealie.services.backups_v2.backup_v2 import BackupSchemaMismatch, BackupV2 from mealie.services.backups_v2.backup_v2 import BackupSchemaMismatch, BackupV2
logger = get_logger()
router = APIRouter(prefix="/backups") router = APIRouter(prefix="/backups")
@ -42,6 +44,7 @@ class AdminBackupController(BaseAdminController):
try: try:
backup.backup() backup.backup()
except Exception as e: except Exception as e:
logger.exception(e)
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e
return SuccessResponse.respond("Backup created successfully") return SuccessResponse.respond("Backup created successfully")
@ -106,6 +109,7 @@ class AdminBackupController(BaseAdminController):
ErrorResponse.respond("database backup schema version does not match current database"), ErrorResponse.respond("database backup schema version does not match current database"),
) from e ) from e
except Exception as e: except Exception as e:
logger.exception(e)
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e
return SuccessResponse.respond("Restore successful") return SuccessResponse.respond("Restore successful")

View File

@ -1,6 +1,8 @@
import json import json
import pathlib import pathlib
from dataclasses import dataclass
from bs4 import BeautifulSoup
from fastapi import Depends, FastAPI, Response from fastapi import Depends, FastAPI, Response
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@ -16,6 +18,13 @@ from mealie.schema.recipe.recipe import Recipe
from mealie.schema.user.user import PrivateUser from mealie.schema.user.user import PrivateUser
@dataclass
class MetaTag:
hid: str
property_name: str
content: str
class SPAStaticFiles(StaticFiles): class SPAStaticFiles(StaticFiles):
async def get_response(self, path: str, scope): async def get_response(self, path: str, scope):
try: try:
@ -33,10 +42,51 @@ __app_settings = get_app_settings()
__contents = "" __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"""<script type="application/ld+json">{json.dumps(jsonable_encoder(schema))}</script>"""
return contents.replace("</head>", schema_as_html_tag + "\n</head>", 1)
def content_with_meta(group_slug: str, recipe: Recipe) -> str: def content_with_meta(group_slug: str, recipe: Recipe) -> str:
# Inject meta tags # Inject meta tags
recipe_url = f"{__app_settings.BASE_URL}/g/{group_slug}/r/{recipe.slug}" 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] = [] ingredients: list[str] = []
if recipe.settings.disable_amount: # type: ignore if recipe.settings.disable_amount: # type: ignore
@ -84,20 +134,22 @@ def content_with_meta(group_slug: str, recipe: Recipe) -> str:
"nutrition": nutrition, "nutrition": nutrition,
} }
tags = [ meta_tags = [
f'<meta property="og:title" content="{recipe.name}" />', MetaTag(hid="og:title", property_name="og:title", content=recipe.name or ""),
f'<meta property="og:description" content="{recipe.description}" />', MetaTag(hid="og:description", property_name="og:description", content=recipe.description or ""),
f'<meta property="og:image" content="{image_url}" />', MetaTag(hid="og:image", property_name="og:image", content=image_url),
f'<meta property="og:url" content="{recipe_url}" />', MetaTag(hid="og:url", property_name="og:url", content=recipe_url),
'<meta name="twitter:card" content="summary_large_image" />', MetaTag(hid="twitter:card", property_name="twitter:card", content="summary_large_image"),
f'<meta name="twitter:title" content="{recipe.name}" />', MetaTag(hid="twitter:title", property_name="twitter:title", content=recipe.name or ""),
f'<meta name="twitter:description" content="{recipe.description}" />', MetaTag(hid="twitter:description", property_name="twitter:description", content=recipe.description or ""),
f'<meta name="twitter:image" content="{image_url}" />', MetaTag(hid="twitter:image", property_name="twitter:image", content=image_url),
f'<meta name="twitter:url" content="{recipe_url}" />', MetaTag(hid="twitter:url", property_name="twitter:url", content=recipe_url),
f"""<script type="application/ld+json">{json.dumps(jsonable_encoder(as_schema_org))}</script>""",
] ]
return __contents.replace("</head>", "\n".join(tags) + "\n</head>", 1) global __contents
__contents = inject_recipe_json(__contents, as_schema_org)
__contents = inject_meta(__contents, meta_tags)
return __contents
def response_404(): def response_404():
@ -133,7 +185,7 @@ async def serve_recipe_with_meta(
user: PrivateUser | None = Depends(try_get_current_user), user: PrivateUser | None = Depends(try_get_current_user),
session: Session = Depends(generate_session), 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) return serve_recipe_with_meta_public(group_slug, recipe_slug, session)
try: try:
@ -149,6 +201,19 @@ async def serve_recipe_with_meta(
return response_404() 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): def mount_spa(app: FastAPI):
if not os.path.exists(__app_settings.STATIC_FILES): if not os.path.exists(__app_settings.STATIC_FILES):
return return
@ -157,4 +222,5 @@ def mount_spa(app: FastAPI):
__contents = pathlib.Path(__app_settings.STATIC_FILES).joinpath("index.html").read_text() __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}/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") app.mount("/", SPAStaticFiles(directory=__app_settings.STATIC_FILES, html=True), name="spa")

View File

@ -1,5 +1,5 @@
# This file is auto-generated by gen_schema_exports.py # This file is auto-generated by gen_schema_exports.py
from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, DockerVolumeText from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig
from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob
from .email import EmailReady, EmailSuccess, EmailTest from .email import EmailReady, EmailSuccess, EmailTest
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
@ -31,7 +31,6 @@ __all__ = [
"AppStatistics", "AppStatistics",
"AppTheme", "AppTheme",
"CheckAppConfig", "CheckAppConfig",
"DockerVolumeText",
"EmailReady", "EmailReady",
"EmailSuccess", "EmailSuccess",
"EmailTest", "EmailTest",

View File

@ -59,7 +59,3 @@ class CheckAppConfig(MealieModel):
ldap_ready: bool ldap_ready: bool
base_url_set: bool base_url_set: bool
is_up_to_date: bool is_up_to_date: bool
class DockerVolumeText(MealieModel):
text: str

View File

@ -61,7 +61,7 @@ class ChangePassword(MealieModel):
class GroupBase(MealieModel): class GroupBase(MealieModel):
name: str name: constr(strip_whitespace=True, min_length=1) # type: ignore
class Config: class Config:
orm_mode = True orm_mode = True

View File

@ -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" _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) semaphore = asyncio.Semaphore(n)
async def sem_coro(coro): async def sem_coro(coro):
async with semaphore: async with semaphore:
return await coro 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]: 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: async with AsyncClient() as client:
tasks = [do(client, url) for url in urls] 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: for response in responses:
len_int = int(response.headers.get("Content-Length", 0)) len_int = int(response.headers.get("Content-Length", 0))
if len_int > largest_len: if len_int > largest_len:

View File

@ -9,6 +9,11 @@ from datetime import datetime, timedelta
from slugify import slugify from slugify import slugify
from mealie.core.root_logger import get_logger
logger = get_logger("recipe-scraper")
MATCH_DIGITS = re.compile(r"\d+([.,]\d+)?") MATCH_DIGITS = re.compile(r"\d+([.,]\d+)?")
""" Allow for commas as decimals (common in Europe) """ """ Allow for commas as decimals (common in Europe) """
@ -84,10 +89,10 @@ def clean_image(image: str | list | dict | None = None, default: str = "no image
image attempts to parse the image field from a recipe and return a string. Currenty image attempts to parse the image field from a recipe and return a string. Currenty
Supported Structures: Supported Structures:
- `https://exmaple.com` - A string - `https://example.com` - A string
- `{ "url": "https://exmaple.com" }` - A dictionary with a `url` key - `{ "url": "https://example.com" }` - A dictionary with a `url` key
- `["https://exmaple.com"]` - A list of strings - `["https://example.com"]` - A list of strings
- `[{ "url": "https://exmaple.com" }]` - A list of dictionaries with a `url` key - `[{ "url": "https://example.com" }]` - A list of dictionaries with a `url` key
Raises: Raises:
TypeError: If the image field is not a supported type a TypeError is raised. TypeError: If the image field is not a supported type a TypeError is raised.
@ -107,8 +112,11 @@ def clean_image(image: str | list | dict | None = None, default: str = "no image
return [x["url"] for x in image] return [x["url"] for x in image]
case {"url": str(image)}: case {"url": str(image)}:
return [image] return [image]
case [{"@id": str(_)}, *_]:
return [x["@id"] for x in image]
case _: case _:
raise TypeError(f"Unexpected type for image: {type(image)}, {image}") logger.exception(f"Unexpected type for image: {type(image)}, {image}")
return [default]
def clean_instructions(steps_object: list | dict | str, default: list | None = None) -> list[dict]: def clean_instructions(steps_object: list | dict | str, default: list | None = None) -> list[dict]:
@ -335,6 +343,7 @@ def clean_time(time_entry: str | timedelta | None) -> None | str:
- `"PT1H"` - returns "1 hour" - `"PT1H"` - returns "1 hour"
- `"PT1H30M"` - returns "1 hour 30 minutes" - `"PT1H30M"` - returns "1 hour 30 minutes"
- `timedelta(hours=1, minutes=30)` - returns "1 hour 30 minutes" - `timedelta(hours=1, minutes=30)` - returns "1 hour 30 minutes"
- `{"minValue": "PT1H30M"}` - returns "1 hour 30 minutes"
Raises: Raises:
TypeError: if the type is not supported a TypeError is raised TypeError: if the type is not supported a TypeError is raised
@ -357,11 +366,16 @@ def clean_time(time_entry: str | timedelta | None) -> None | str:
return str(time_entry) return str(time_entry)
case timedelta(): case timedelta():
return pretty_print_timedelta(time_entry) return pretty_print_timedelta(time_entry)
case {"minValue": str(value)}:
return clean_time(value)
case [str(), *_]:
return clean_time(time_entry[0])
case datetime(): case datetime():
# TODO: Not sure what to do here # TODO: Not sure what to do here
return str(time_entry) return str(time_entry)
case _: 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: def parse_duration(iso_duration: str) -> timedelta:

View File

@ -1,10 +1,16 @@
from asyncio import gather import asyncio
from pydantic import UUID4 from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import CreateRecipeByUrlBulk, Recipe 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.schema.user.user import GroupInDB
from mealie.services._base_service import BaseService from mealie.services._base_service import BaseService
from mealie.services.recipe.recipe_service import RecipeService from mealie.services.recipe.recipe_service import RecipeService
@ -47,6 +53,7 @@ class RecipeBulkScraperService(BaseService):
is_success = True is_success = True
is_failure = True is_failure = True
new_entries: list[ReportEntryOut] = []
for entry in self.report_entries: for entry in self.report_entries:
if is_failure and entry.success: if is_failure and entry.success:
is_failure = False is_failure = False
@ -54,7 +61,7 @@ class RecipeBulkScraperService(BaseService):
if is_success and not entry.success: if is_success and not entry.success:
is_success = False is_success = False
self.repos.group_report_entries.create(entry) new_entries.append(self.repos.group_report_entries.create(entry))
if is_success: if is_success:
self.report.status = ReportSummaryStatus.success self.report.status = ReportSummaryStatus.success
@ -65,25 +72,29 @@ class RecipeBulkScraperService(BaseService):
if not is_success and not is_failure: if not is_success and not is_failure:
self.report.status = ReportSummaryStatus.partial self.report.status = ReportSummaryStatus.partial
self.report.entries = new_entries
self.repos.group_reports.update(self.report.id, self.report) self.repos.group_reports.update(self.report.id, self.report)
async def scrape(self, urls: CreateRecipeByUrlBulk) -> None: async def scrape(self, urls: CreateRecipeByUrlBulk) -> None:
sem = asyncio.Semaphore(3)
async def _do(url: str) -> Recipe | None: async def _do(url: str) -> Recipe | None:
try: async with sem:
recipe, _ = await create_from_url(url) try:
return recipe recipe, _ = await create_from_url(url)
except Exception as e: return recipe
self.service.logger.error(f"failed to scrape url during bulk url import {b.url}") except Exception as e:
self.service.logger.exception(e) self.service.logger.error(f"failed to scrape url during bulk url import {url}")
self._add_error_entry(f"failed to scrape url {url}", str(e)) self.service.logger.exception(e)
return None self._add_error_entry(f"failed to scrape url {url}", str(e))
return None
if self.report is None: if self.report is None:
self.get_report_id() self.get_report_id()
tasks = [_do(b.url) for b in urls.imports] 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): for b, recipe in zip(urls.imports, results, strict=True):
if not recipe: if not recipe or isinstance(recipe, Exception):
continue continue
if b.tags: if b.tags:

View File

@ -172,7 +172,7 @@ class RecipeScraperPackage(ABCScraperStrategy):
try: try:
scraped_schema = scrape_html(recipe_html, org_url=self.url) scraped_schema = scrape_html(recipe_html, org_url=self.url)
except (NoSchemaFoundInWildMode, AttributeError): 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 return None
except ConnectionError as e: except ConnectionError as e:
@ -208,10 +208,6 @@ class RecipeScraperPackage(ABCScraperStrategy):
class RecipeScraperOpenGraph(ABCScraperStrategy): class RecipeScraperOpenGraph(ABCScraperStrategy):
"""
Abstract class for all recipe parsers.
"""
async def get_html(self, url: str) -> str: async def get_html(self, url: str) -> str:
return await safe_scrape_html(url) return await safe_scrape_html(url)
@ -241,7 +237,7 @@ class RecipeScraperOpenGraph(ABCScraperStrategy):
"recipeIngredient": ["Could not detect ingredients"], "recipeIngredient": ["Could not detect ingredients"],
"recipeInstructions": [{"text": "Could not detect instructions"}], "recipeInstructions": [{"text": "Could not detect instructions"}],
"slug": slugify(og_field(properties, "og:title")), "slug": slugify(og_field(properties, "og:title")),
"orgURL": og_field(properties, "og:url"), "orgURL": self.url,
"categories": [], "categories": [],
"tags": og_fields(properties, "og:article:tag"), "tags": og_fields(properties, "og:article:tag"),
"dateAdded": None, "dateAdded": None,

View File

@ -22,6 +22,8 @@ images_test_image_1 = CWD / "images/test-image-1.jpg"
images_test_image_2 = CWD / "images/test-image-2.png" 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_smoked_beef_ribs = CWD / "html/sous-vide-smoked-beef-ribs.html"
html_sous_vide_shrimp = CWD / "html/sous-vide-shrimp.html" html_sous_vide_shrimp = CWD / "html/sous-vide-shrimp.html"

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en" data-n-head="%7B%22lang%22:%7B%221%22:%22en%22%7D%7D">
<head>
<meta data-n-head="1" data-hid="og:type" property="og:type" content="website">
<meta data-n-head="1" data-hid="og:title" property="og:title" content="Mealie">
<meta data-n-head="1" data-hid="og:site_name" property="og:site_name" content="Mealie">
<meta data-n-head="1" data-hid="og:description" property="og:description" content="Mealie is a recipe management app for your kitchen.">
<meta data-n-head="1" data-hid="og:image" property="og:image" content="https://raw.githubusercontent.com/hay-kot/mealie/dev/frontend/public/img/icons/android-chrome-512x512.png">
<meta data-n-head="1" charset="utf-8">
<meta data-n-head="1" name="viewport" content="width=device-width,initial-scale=1">
<meta data-n-head="1" data-hid="description" name="description" content="Mealie is a recipe management app for your kitchen.">
<meta data-n-head="1" data-hid="charset" charset="utf-8">
<meta data-n-head="1" data-hid="mobile-web-app-capable" name="mobile-web-app-capable" content="yes">
<meta data-n-head="1" data-hid="apple-mobile-web-app-title" name="apple-mobile-web-app-title" content="Mealie">
<meta data-n-head="1" data-hid="theme-color" name="theme-color" content="#E58325">
<title>Mealie</title>
<link data-n-head="1" data-hid="favicon" rel="icon" type="image/x-icon" href="/favicon.ico" data-n-head="ssr">
<link data-n-head="1" data-hid="shortcut icon" rel="shortcut icon" type="image/png" href="/icons/icon-x64.png" data-n-head="ssr">
<link data-n-head="1" data-hid="apple-touch-icon" rel="apple-touch-icon" type="image/png" href="/icons/apple-touch-icon.png" data-n-head="ssr">
<link data-n-head="1" data-hid="mask-icon" rel="mask-icon" href="/icons/safari-pinned-tab.svg" data-n-head="ssr">
<link data-n-head="1" rel="shortcut icon" href="/icons/android-chrome-192x192.png">
<link data-n-head="1" rel="apple-touch-icon" href="/icons/android-chrome-maskable-512x512.png" sizes="512x512">
<link data-n-head="1" rel="manifest" href="/_nuxt/manifest.260e8103.json" data-hid="manifest">
<base href="/">
<link rel="preload" href="/_nuxt/4134a9b.js" as="script">
<link rel="preload" href="/_nuxt/caa94a4.js" as="script">
<link rel="preload" href="/_nuxt/90b93a8.js" as="script">
<link rel="preload" href="/_nuxt/9da1d16.js" as="script">
</head>
<body>
<div id="__nuxt">
<style>#nuxt-loading{background:#fff;visibility:hidden;opacity:0;position:absolute;left:0;right:0;top:0;bottom:0;display:flex;justify-content:center;align-items:center;flex-direction:column;animation:nuxtLoadingIn 10s ease;-webkit-animation:nuxtLoadingIn 10s ease;animation-fill-mode:forwards;overflow:hidden}@keyframes nuxtLoadingIn{0%{visibility:hidden;opacity:0}20%{visibility:visible;opacity:0}100%{visibility:visible;opacity:1}}@-webkit-keyframes nuxtLoadingIn{0%{visibility:hidden;opacity:0}20%{visibility:visible;opacity:0}100%{visibility:visible;opacity:1}}#nuxt-loading>div,#nuxt-loading>div:after{border-radius:50%;width:5rem;height:5rem}#nuxt-loading>div{font-size:10px;position:relative;text-indent:-9999em;border:.5rem solid #f5f5f5;border-left:.5rem solid #000;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:nuxtLoading 1.1s infinite linear;animation:nuxtLoading 1.1s infinite linear}#nuxt-loading.error>div{border-left:.5rem solid #ff4500;animation-duration:5s}@-webkit-keyframes nuxtLoading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes nuxtLoading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}</style>
<script>window.addEventListener("error",function(){var e=document.getElementById("nuxt-loading");e&&(e.className+=" error")})</script>
<div id="nuxt-loading" aria-live="polite" role="status">
<div>Loading...</div>
</div>
</div>
<script>window.__NUXT__=function(r,n,a,s,e,c,o){return{config:{GLOBAL_MIDDLEWARE:null,SUB_PATH:"",axios:{browserBaseURL:""},useDark:!1,themes:{dark:{primary:r,accent:n,secondary:a,success:s,info:e,warning:c,error:o,background:"#1E1E1E"},light:{primary:r,accent:n,secondary:a,success:s,info:e,warning:c,error:o}},_app:{basePath:"/",assetsPath:"/_nuxt/",cdnURL:null}}}}("#E58325","#007A99","#973542","#43A047","#1976d2","#FF6D00","#EF5350")</script>
<script src="/_nuxt/4134a9b.js"></script><script src="/_nuxt/caa94a4.js"></script><script src="/_nuxt/90b93a8.js"></script><script src="/_nuxt/9da1d16.js"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More