mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04:00
Merge branch 'mealie-next' into mealie-next
This commit is contained in:
commit
b13d66108d
@ -41,7 +41,8 @@
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [
|
||||
3000,
|
||||
9000
|
||||
9000,
|
||||
24678 // used by nuxt when hot-reloading using polling
|
||||
],
|
||||
// Use 'onCreateCommand' to run commands at the end of container creation.
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
|
7
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
7
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Bug Report
|
||||
description: "Submit a bug for the latest version of Mealie"
|
||||
title: "[BUG] - YOUR TITLE"
|
||||
title: "[BUG] - YOUR DESCRIPTIVE TITLE GOES HERE"
|
||||
labels: ["bug", "triage"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
@ -14,7 +14,7 @@ body:
|
||||
options:
|
||||
- label: This is not a feature request.
|
||||
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
|
||||
- label: I used the GitHub search to find a similar issue and didn't find it.
|
||||
required: true
|
||||
@ -61,7 +61,8 @@ body:
|
||||
- Docker (Windows)
|
||||
- Docker (Synology)
|
||||
- Unraid
|
||||
- Other
|
||||
- TrueNAS
|
||||
- Other (please specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
4
.github/workflows/partial-backend.yml
vendored
4
.github/workflows/partial-backend.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
# Steps
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up python
|
||||
uses: actions/setup-python@v4
|
||||
@ -60,7 +60,7 @@ jobs:
|
||||
id: cache-validate
|
||||
if: steps.cached-poetry-dependencies.outputs.cache-hit == 'true'
|
||||
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
|
||||
continue-on-error: true
|
||||
|
||||
|
26
.github/workflows/partial-builder.yml
vendored
26
.github/workflows/partial-builder.yml
vendored
@ -6,6 +6,9 @@ on:
|
||||
tag:
|
||||
required: true
|
||||
type: string
|
||||
tags:
|
||||
required: false
|
||||
type: string
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: true
|
||||
@ -17,33 +20,42 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
- name: Log in to the Container registry (ghcr.io)
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
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
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Override __init__.py
|
||||
run: |
|
||||
echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
file: ./docker/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository }}:${{ inputs.tag }}
|
||||
tags: |
|
||||
hkotel/mealie:${{ inputs.tag }}
|
||||
ghcr.io/${{ github.repository }}:${{ inputs.tag }}
|
||||
${{ inputs.tags }}
|
||||
build-args: |
|
||||
COMMIT=${{ github.sha }}
|
||||
# https://docs.docker.com/build/ci/github-actions/cache/#github-cache
|
||||
|
16
.github/workflows/partial-frontend.yml
vendored
16
.github/workflows/partial-frontend.yml
vendored
@ -9,20 +9,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node env 🏗
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
check-latest: true
|
||||
|
||||
- name: Get yarn cache directory 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 📦
|
||||
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'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@ -47,20 +47,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node env 🏗
|
||||
uses: actions/setup-node@v3.7.0
|
||||
uses: actions/setup-node@v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
check-latest: true
|
||||
|
||||
- name: Get yarn cache directory 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 📦
|
||||
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'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
@ -11,7 +11,7 @@ jobs:
|
||||
fail-fast: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Dockerfile
|
||||
run: |
|
||||
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -24,6 +24,9 @@ jobs:
|
||||
- frontend-tests
|
||||
with:
|
||||
tag: ${{ github.event.release.tag_name }}
|
||||
tags: |
|
||||
hkotel/mealie:latest
|
||||
ghcr.io/${{ github.repository }}:latest
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -14,8 +14,8 @@
|
||||
"webp"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": false
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.workingDirectories": [
|
||||
|
32
README.md
32
README.md
@ -9,7 +9,7 @@
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<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">
|
||||
<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>
|
||||
@ -21,12 +21,12 @@
|
||||
A Place for All Your Recipes
|
||||
<br />
|
||||
<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>
|
||||
<br />
|
||||
<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>
|
||||
</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>
|
||||
|
||||
### 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
|
||||
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 -->
|
||||
<!-- 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
|
||||
[contributors-url]: https://github.com/hay-kot/mealie/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/hay-kot/mealie.svg?style=flat-square
|
||||
[forks-url]: https://github.com/hay-kot/mealie/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/hay-kot/mealie.svg?style=flat-square
|
||||
[stars-url]: https://github.com/hay-kot/mealie/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/hay-kot/mealie.svg?style=flat-square
|
||||
[issues-url]: https://github.com/hay-kot/mealie/issues
|
||||
[license-shield]: https://img.shields.io/github/license/hay-kot/mealie.svg?style=flat-square
|
||||
[license-url]: https://github.com/hay-kot/mealie/blob/mealie-next/LICENSE
|
||||
[contributors-url]: https://github.com/mealie-recipes/mealie/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/mealie-recipes/mealie.svg?style=flat-square
|
||||
[forks-url]: https://github.com/mealie-recipes/mealie/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/mealie-recipes/mealie.svg?style=flat-square
|
||||
[stars-url]: https://github.com/mealie-recipes/mealie/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/mealie-recipes/mealie.svg?style=flat-square
|
||||
[issues-url]: https://github.com/mealie-recipes/mealie/issues
|
||||
[license-shield]: https://img.shields.io/github/license/mealie-recipes/mealie.svg?style=flat-square
|
||||
[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-url]: https://linkedin.com/in/hay-kot
|
||||
[product-screenshot]: docs/docs/assets/img/home_screenshot.png
|
||||
|
@ -33,21 +33,29 @@ def populate_normalized_fields():
|
||||
)
|
||||
for unit in units:
|
||||
if unit.name is not None:
|
||||
unit.name_normalized = IngredientUnitModel.normalize(unit.name)
|
||||
session.execute(
|
||||
sa.text(
|
||||
f"UPDATE {IngredientUnitModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id"
|
||||
).bindparams(name_normalized=IngredientUnitModel.normalize(unit.name), id=unit.id)
|
||||
)
|
||||
|
||||
if unit.abbreviation is not None:
|
||||
unit.abbreviation_normalized = IngredientUnitModel.normalize(unit.abbreviation)
|
||||
|
||||
session.add(unit)
|
||||
session.execute(
|
||||
sa.text(
|
||||
f"UPDATE {IngredientUnitModel.__tablename__} SET abbreviation_normalized=:abbreviation_normalized WHERE id=:id"
|
||||
).bindparams(abbreviation_normalized=IngredientUnitModel.normalize(unit.abbreviation), id=unit.id)
|
||||
)
|
||||
|
||||
foods = (
|
||||
session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all()
|
||||
)
|
||||
for food in foods:
|
||||
if food.name is not None:
|
||||
food.name_normalized = IngredientFoodModel.normalize(food.name)
|
||||
|
||||
session.add(food)
|
||||
session.execute(
|
||||
sa.text(
|
||||
f"UPDATE {IngredientFoodModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id"
|
||||
).bindparams(name_normalized=IngredientFoodModel.normalize(food.name), id=food.id)
|
||||
)
|
||||
|
||||
session.commit()
|
||||
|
||||
|
@ -13,10 +13,8 @@ import sqlalchemy as sa
|
||||
from pydantic import UUID4
|
||||
from sqlalchemy.orm import Session, load_only
|
||||
|
||||
import mealie.db.migration_types
|
||||
from alembic import op
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
from mealie.db.models.group.shopping_list import ShoppingListItem
|
||||
from mealie.db.models.labels import MultiPurposeLabel
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel
|
||||
@ -43,26 +41,25 @@ def _is_postgres():
|
||||
return op.get_context().dialect.name == "postgresql"
|
||||
|
||||
|
||||
def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list[str]]:
|
||||
duplicate_map: defaultdict[str, list[str]] = defaultdict(list)
|
||||
for obj in session.query(model).options(load_only(model.id, model.group_id, model.name)).all():
|
||||
key = f"{obj.group_id}$${obj.name}"
|
||||
duplicate_map[key].append(str(obj.id))
|
||||
def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list]:
|
||||
duplicate_map: defaultdict[str, list] = defaultdict(list)
|
||||
|
||||
query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}"))
|
||||
for row in query.all():
|
||||
id, group_id, name = row
|
||||
key = f"{group_id}$${name}"
|
||||
duplicate_map[key].append(id)
|
||||
|
||||
return duplicate_map
|
||||
|
||||
|
||||
def _resolve_duplicate_food(
|
||||
session: Session,
|
||||
keep_food: IngredientFoodModel,
|
||||
keep_food_id: UUID4,
|
||||
dupe_food_id: UUID4,
|
||||
):
|
||||
for shopping_list_item in session.query(ShoppingListItem).filter_by(food_id=dupe_food_id).all():
|
||||
shopping_list_item.food_id = keep_food_id
|
||||
shopping_list_item.food = keep_food
|
||||
|
||||
session.commit()
|
||||
|
||||
for recipe_ingredient in (
|
||||
session.query(RecipeIngredientModel)
|
||||
@ -71,62 +68,43 @@ def _resolve_duplicate_food(
|
||||
.all()
|
||||
):
|
||||
recipe_ingredient.food_id = keep_food_id
|
||||
recipe_ingredient.food = keep_food
|
||||
|
||||
session.commit()
|
||||
|
||||
session.query(IngredientFoodModel).options(load_only(IngredientFoodModel.id)).filter_by(id=dupe_food_id).delete()
|
||||
session.commit()
|
||||
session.execute(
|
||||
sa.text(f"DELETE FROM {IngredientFoodModel.__tablename__} WHERE id=:id").bindparams(id=dupe_food_id)
|
||||
)
|
||||
|
||||
|
||||
def _resolve_duplicate_unit(
|
||||
session: Session,
|
||||
keep_unit: IngredientUnitModel,
|
||||
keep_unit_id: UUID4,
|
||||
dupe_unit_id: UUID4,
|
||||
):
|
||||
for shopping_list_item in session.query(ShoppingListItem).filter_by(unit_id=dupe_unit_id).all():
|
||||
shopping_list_item.unit_id = keep_unit_id
|
||||
shopping_list_item.unit = keep_unit
|
||||
|
||||
session.commit()
|
||||
|
||||
for recipe_ingredient in session.query(RecipeIngredientModel).filter_by(unit_id=dupe_unit_id).all():
|
||||
recipe_ingredient.unit_id = keep_unit_id
|
||||
recipe_ingredient.unit = keep_unit
|
||||
|
||||
session.commit()
|
||||
|
||||
session.query(IngredientUnitModel).options(load_only(IngredientUnitModel.id)).filter_by(id=dupe_unit_id).delete()
|
||||
session.commit()
|
||||
session.execute(
|
||||
sa.text(f"DELETE FROM {IngredientUnitModel.__tablename__} WHERE id=:id").bindparams(id=dupe_unit_id)
|
||||
)
|
||||
|
||||
|
||||
def _resolve_duplicate_label(
|
||||
session: Session,
|
||||
keep_label: MultiPurposeLabel,
|
||||
keep_label_id: UUID4,
|
||||
dupe_label_id: UUID4,
|
||||
):
|
||||
for shopping_list_item in session.query(ShoppingListItem).filter_by(label_id=dupe_label_id).all():
|
||||
shopping_list_item.label_id = keep_label_id
|
||||
shopping_list_item.label = keep_label
|
||||
|
||||
session.commit()
|
||||
|
||||
for ingredient_food in session.query(IngredientFoodModel).filter_by(label_id=dupe_label_id).all():
|
||||
ingredient_food.label_id = keep_label_id
|
||||
ingredient_food.label = keep_label
|
||||
|
||||
session.commit()
|
||||
|
||||
session.query(MultiPurposeLabel).options(load_only(MultiPurposeLabel.id)).filter_by(id=dupe_label_id).delete()
|
||||
session.commit()
|
||||
session.execute(sa.text(f"DELETE FROM {MultiPurposeLabel.__tablename__} WHERE id=:id").bindparams(id=dupe_label_id))
|
||||
|
||||
|
||||
def _resolve_duplicate_foods_units_labels():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
|
||||
def _resolve_duplicate_foods_units_labels(session: Session):
|
||||
for model, resolve_func in [
|
||||
(IngredientFoodModel, _resolve_duplicate_food),
|
||||
(IngredientUnitModel, _resolve_duplicate_unit),
|
||||
@ -138,9 +116,8 @@ def _resolve_duplicate_foods_units_labels():
|
||||
continue
|
||||
|
||||
keep_id = ids[0]
|
||||
keep_obj = session.query(model).options(load_only(model.id)).filter_by(id=keep_id).first()
|
||||
for dupe_id in ids[1:]:
|
||||
resolve_func(session, keep_obj, keep_id, dupe_id)
|
||||
resolve_func(session, keep_id, dupe_id)
|
||||
|
||||
|
||||
def _remove_duplicates_from_m2m_table(session: Session, table_meta: TableMeta):
|
||||
@ -163,20 +140,20 @@ def _remove_duplicates_from_m2m_table(session: Session, table_meta: TableMeta):
|
||||
)
|
||||
|
||||
session.execute(query)
|
||||
session.commit()
|
||||
|
||||
|
||||
def _remove_duplicates_from_m2m_tables(table_metas: list[TableMeta]):
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
|
||||
def _remove_duplicates_from_m2m_tables(session: Session, table_metas: list[TableMeta]):
|
||||
for table_meta in table_metas:
|
||||
_remove_duplicates_from_m2m_table(session, table_meta)
|
||||
|
||||
|
||||
def upgrade():
|
||||
_resolve_duplicate_foods_units_labels()
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
|
||||
_resolve_duplicate_foods_units_labels(session)
|
||||
_remove_duplicates_from_m2m_tables(
|
||||
session,
|
||||
[
|
||||
TableMeta("cookbooks_to_categories", "cookbook_id", "category_id"),
|
||||
TableMeta("cookbooks_to_tags", "cookbook_id", "tag_id"),
|
||||
@ -189,12 +166,13 @@ def upgrade():
|
||||
TableMeta("recipes_to_tools", "recipe_id", "tool_id"),
|
||||
TableMeta("users_to_favorites", "user_id", "recipe_id"),
|
||||
TableMeta("shopping_lists_multi_purpose_labels", "shopping_list_id", "label_id"),
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
session.commit()
|
||||
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# we use batch_alter_table here because otherwise this fails on sqlite
|
||||
|
||||
# M2M
|
||||
with op.batch_alter_table("cookbooks_to_categories") as batch_op:
|
||||
batch_op.create_unique_constraint("cookbook_id_category_id_key", ["cookbook_id", "category_id"])
|
||||
|
@ -62,7 +62,7 @@ A quality update with major props to [zackbcom](https://github.com/zackbcom) for
|
||||
|
||||
### Recipes
|
||||
- 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
|
||||
|
||||
## v0.0.0 - Initial Pre-release
|
||||
|
@ -5,7 +5,7 @@
|
||||
**Database Version: v0.4.0**
|
||||
|
||||
!!! 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.
|
||||
|
||||
@ -31,4 +31,4 @@
|
||||
- Unify Logger across the backend
|
||||
- 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.
|
||||
- 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)
|
@ -8,7 +8,7 @@
|
||||
- Fixed #617 - Section behavior when adding a step
|
||||
- Fixed #615 - Recipe Settings are not available when creating new recipe
|
||||
- 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
|
||||
|
||||
|
@ -6,14 +6,14 @@
|
||||
- FastAPI to 0.78.0
|
||||
|
||||
- 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.
|
||||
- [#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)
|
||||
- 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)
|
||||
|
||||
## 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)
|
||||
- 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
|
||||
- No hard limits are currently imposed on groups - though this may be implemented in the future.
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
### 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 @nuxtjs/auth-next in /frontend ([#1265](https://github.com/hay-kot/mealie/issues/1265))
|
||||
- Bad dev dependency ([#1281](https://github.com/hay-kot/mealie/issues/1281))
|
||||
- Add touch support for mealplanner delete ([#1298](https://github.com/hay-kot/mealie/issues/1298))
|
||||
- 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/mealie-recipes/mealie/issues/1265))
|
||||
- Bad dev dependency ([#1281](https://github.com/mealie-recipes/mealie/issues/1281))
|
||||
- Add touch support for mealplanner delete ([#1298](https://github.com/mealie-recipes/mealie/issues/1298))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add references for VSCode dev containers ([#1299](https://github.com/hay-kot/mealie/issues/1299))
|
||||
- Docker-compose.dev.yml is currently not functional ([#1300](https://github.com/hay-kot/mealie/issues/1300))
|
||||
- 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/mealie-recipes/mealie/issues/1300))
|
||||
|
||||
### Features
|
||||
|
||||
- Add reports to bulk recipe import (url) ([#1294](https://github.com/hay-kot/mealie/issues/1294))
|
||||
- Rewrite print implementation to support new ing ([#1305](https://github.com/hay-kot/mealie/issues/1305))
|
||||
- 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/mealie-recipes/mealie/issues/1305))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Github stalebot changes ([#1271](https://github.com/hay-kot/mealie/issues/1271))
|
||||
- Bump eslint-plugin-nuxt in /frontend ([#1258](https://github.com/hay-kot/mealie/issues/1258))
|
||||
- Bump @vue/runtime-dom in /frontend ([#1259](https://github.com/hay-kot/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 vue2-script-setup-transform in /frontend ([#1263](https://github.com/hay-kot/mealie/issues/1263))
|
||||
- Update dev dependencies ([#1282](https://github.com/hay-kot/mealie/issues/1282))
|
||||
- Github stalebot changes ([#1271](https://github.com/mealie-recipes/mealie/issues/1271))
|
||||
- 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/mealie-recipes/mealie/issues/1259))
|
||||
- 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/mealie-recipes/mealie/issues/1263))
|
||||
- Update dev dependencies ([#1282](https://github.com/mealie-recipes/mealie/issues/1282))
|
||||
|
||||
### 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))
|
||||
|
@ -1,36 +1,36 @@
|
||||
### Bug Fixes
|
||||
|
||||
- Update issue links in v1.0.0beta-2 changelog ([#1312](https://github.com/hay-kot/mealie/issues/1312))
|
||||
- Bad import path ([#1313](https://github.com/hay-kot/mealie/issues/1313))
|
||||
- Printer page refs ([#1314](https://github.com/hay-kot/mealie/issues/1314))
|
||||
- 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/mealie-recipes/mealie/issues/1313))
|
||||
- Printer page refs ([#1314](https://github.com/mealie-recipes/mealie/issues/1314))
|
||||
- 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))
|
||||
- Shopping list label editor ([#1333](https://github.com/hay-kot/mealie/issues/1333))
|
||||
- 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/mealie-recipes/mealie/issues/1333))
|
||||
|
||||
### Features
|
||||
|
||||
- Default unit fractions to True
|
||||
- Add unit abbreviation support ([#1332](https://github.com/hay-kot/mealie/issues/1332))
|
||||
- Attached images by drag and drop for recipe steps ([#1341](https://github.com/hay-kot/mealie/issues/1341))
|
||||
- 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/mealie-recipes/mealie/issues/1341))
|
||||
|
||||
### 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
|
||||
|
||||
- Init git-cliff config
|
||||
- Bump @types/sortablejs in /frontend ([#1287](https://github.com/hay-kot/mealie/issues/1287))
|
||||
- Bump @babel/eslint-parser in /frontend ([#1290](https://github.com/hay-kot/mealie/issues/1290))
|
||||
- Bump @types/sortablejs in /frontend ([#1287](https://github.com/mealie-recipes/mealie/issues/1287))
|
||||
- Bump @babel/eslint-parser in /frontend ([#1290](https://github.com/mealie-recipes/mealie/issues/1290))
|
||||
|
||||
### 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
|
||||
|
||||
- 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
|
||||
|
||||
- Pagination-repository ([#1316](https://github.com/hay-kot/mealie/issues/1316))
|
||||
- Pagination-repository ([#1316](https://github.com/mealie-recipes/mealie/issues/1316))
|
||||
|
@ -63,57 +63,57 @@ If either of the above actions prevent the user from uploading images, the appli
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- For erroneously-translated datetime config ([#1362](https://github.com/hay-kot/mealie/issues/1362))
|
||||
- Fixed text color on RecipeCard in RecipePrintView and implemented ingredient sections ([#1351](https://github.com/hay-kot/mealie/issues/1351))
|
||||
- Ingredient sections lost after parsing ([#1368](https://github.com/hay-kot/mealie/issues/1368))
|
||||
- Increased float rounding precision for CRF parser ([#1369](https://github.com/hay-kot/mealie/issues/1369))
|
||||
- Infinite scroll bug on all recipes page ([#1393](https://github.com/hay-kot/mealie/issues/1393))
|
||||
- Fast fail of bulk importer ([#1394](https://github.com/hay-kot/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 @nuxtjs/i18n from 7.0.3 to 7.2.2 in /frontend ([#1288](https://github.com/hay-kot/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 fuse.js from 6.5.3 to 6.6.2 in /frontend ([#1325](https://github.com/hay-kot/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))
|
||||
- All-recipes page now sorts alphabetically ([#1405](https://github.com/hay-kot/mealie/issues/1405))
|
||||
- Sort recent recipes by created_at instead of date_added ([#1417](https://github.com/hay-kot/mealie/issues/1417))
|
||||
- Only show scaler when ingredients amounts enabled ([#1426](https://github.com/hay-kot/mealie/issues/1426))
|
||||
- Add missing types for API token deletion ([#1428](https://github.com/hay-kot/mealie/issues/1428))
|
||||
- Entry nutrition checker ([#1448](https://github.com/hay-kot/mealie/issues/1448))
|
||||
- Use == operator instead of is_ for sql queries ([#1453](https://github.com/hay-kot/mealie/issues/1453))
|
||||
- Use `mtime` instead of `ctime` for backup dates ([#1461](https://github.com/hay-kot/mealie/issues/1461))
|
||||
- Mealplan pagination ([#1464](https://github.com/hay-kot/mealie/issues/1464))
|
||||
- Properly use pagination for group event notifies ([#1512](https://github.com/hay-kot/mealie/pull/1512))
|
||||
- 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/mealie-recipes/mealie/issues/1351))
|
||||
- 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/mealie-recipes/mealie/issues/1369))
|
||||
- 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/mealie-recipes/mealie/issues/1394))
|
||||
- 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/mealie-recipes/mealie/issues/1288))
|
||||
- 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/mealie-recipes/mealie/issues/1325))
|
||||
- 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/mealie-recipes/mealie/issues/1405))
|
||||
- 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/mealie-recipes/mealie/issues/1426))
|
||||
- Add missing types for API token deletion ([#1428](https://github.com/mealie-recipes/mealie/issues/1428))
|
||||
- Entry nutrition checker ([#1448](https://github.com/mealie-recipes/mealie/issues/1448))
|
||||
- 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/mealie-recipes/mealie/issues/1461))
|
||||
- Mealplan pagination ([#1464](https://github.com/mealie-recipes/mealie/issues/1464))
|
||||
- Properly use pagination for group event notifies ([#1512](https://github.com/mealie-recipes/mealie/pull/1512))
|
||||
|
||||
### 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
|
||||
- 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
|
||||
|
||||
- Toggle display of ingredient references in recipe instructions ([#1268](https://github.com/hay-kot/mealie/issues/1268))
|
||||
- Add custom scaling option ([#1345](https://github.com/hay-kot/mealie/issues/1345))
|
||||
- Implemented "order by" API parameters for recipe, food, and unit queries ([#1356](https://github.com/hay-kot/mealie/issues/1356))
|
||||
- Implement user favorites page ([#1376](https://github.com/hay-kot/mealie/issues/1376))
|
||||
- Extend Apprise JSON notification functionality with programmatic data ([#1355](https://github.com/hay-kot/mealie/issues/1355))
|
||||
- Mealplan-webhooks ([#1403](https://github.com/hay-kot/mealie/issues/1403))
|
||||
- Added "last-modified" header to supported record types ([#1379](https://github.com/hay-kot/mealie/issues/1379))
|
||||
- Re-write get all routes to use pagination ([#1424](https://github.com/hay-kot/mealie/issues/1424))
|
||||
- Advanced filtering API ([#1468](https://github.com/hay-kot/mealie/issues/1468))
|
||||
- Restore frontend sorting for all recipes ([#1497](https://github.com/hay-kot/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))
|
||||
- create new foods and units from their Data Management pages ([#1511](https://github.com/hay-kot/mealie/pull/1511))
|
||||
- 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/mealie-recipes/mealie/issues/1345))
|
||||
- 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/mealie-recipes/mealie/issues/1376))
|
||||
- Extend Apprise JSON notification functionality with programmatic data ([#1355](https://github.com/mealie-recipes/mealie/issues/1355))
|
||||
- Mealplan-webhooks ([#1403](https://github.com/mealie-recipes/mealie/issues/1403))
|
||||
- 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/mealie-recipes/mealie/issues/1424))
|
||||
- Advanced filtering API ([#1468](https://github.com/mealie-recipes/mealie/issues/1468))
|
||||
- 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/mealie-recipes/mealie/pull/1506))
|
||||
- create new foods and units from their Data Management pages ([#1511](https://github.com/mealie-recipes/mealie/pull/1511))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Bump dev deps ([#1418](https://github.com/hay-kot/mealie/issues/1418))
|
||||
- Bump @vue/runtime-dom in /frontend ([#1423](https://github.com/hay-kot/mealie/issues/1423))
|
||||
- Backend page_all route cleanup ([#1483](https://github.com/hay-kot/mealie/issues/1483))
|
||||
- Bump dev deps ([#1418](https://github.com/mealie-recipes/mealie/issues/1418))
|
||||
- Bump @vue/runtime-dom in /frontend ([#1423](https://github.com/mealie-recipes/mealie/issues/1423))
|
||||
- Backend page_all route cleanup ([#1483](https://github.com/mealie-recipes/mealie/issues/1483))
|
||||
|
||||
### 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
|
||||
|
||||
@ -121,6 +121,6 @@ If either of the above actions prevent the user from uploading images, the appli
|
||||
|
||||
### 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 -->
|
||||
|
@ -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
|
||||
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)
|
||||
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!
|
||||
## 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/mealie-recipes/mealie/issues/new); it's that easy!
|
||||
|
||||
## Write bug reports with detail, background, and sample code
|
||||
**Great Bug Reports** tend to have:
|
||||
|
@ -124,9 +124,9 @@ docker-prod 🐳 Build and Start Docker Production Stack
|
||||
```
|
||||
## Internationalization
|
||||
### 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
|
||||
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
|
||||
[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.
|
||||
|
@ -15,6 +15,6 @@ Alternatively, you can register a new parser by fulfilling the `ABCIngredientPar
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||

|
||||
|
||||
User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/hay-kot/mealie/issues/103) for interested users.
|
||||
This original method broke after the transition to version 1.X and an issue was raised on [Github](https://github.com/hay-kot/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X.
|
||||
User [brasilikum](https://github.com/brasilikum) opened an issue on the main repo about how they had created an [iOS shortcut](https://github.com/mealie-recipes/mealie/issues/103) for interested users.
|
||||
This original method broke after the transition to version 1.X and an issue was raised on [Github](https://github.com/mealie-recipes/mealie/issues/2092) GitHub user [Zippyy](https://github.com/zippyy) has helped to create a working shortcut for version 1.X.
|
||||
|
||||
This is a useful utility for iOS users who browse for recipes in their web browser from their devices.
|
||||
|
||||
|
@ -11,13 +11,13 @@ To install Mealie on your server there are a few steps for proper configuration.
|
||||
|
||||
## Pre-work
|
||||
|
||||
To deploy mealie on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose templates provided, you should be able to get a stack up and running easily by changing a few default values and deploying. You can deploy with either SQLite (default) or Postgres. SQLite is sufficient for most use cases. Additionally, with Mealie's automated backup and restore functionality, you can easily move between SQLite and Postgres as you wish.
|
||||
To deploy mealie on your local network it is highly recommended to use docker to deploy the image straight from the GitHub registry. Using the docker-compose templates provided, you should be able to get a stack up and running easily by changing a few default values and deploying. You can deploy with either SQLite (default) or Postgres. SQLite is sufficient for most use cases. Additionally, with Mealie's automated backup and restore functionality, you can easily move between SQLite and Postgres as you wish.
|
||||
|
||||
[Get Docker](https://docs.docker.com/get-docker/)
|
||||
|
||||
[Get Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
[Mealie on Dockerhub](https://hub.docker.com/r/hkotel/mealie)
|
||||
[Mealie on GitHub registry](https://github.com/mealie-recipes/mealie/pkgs/container/mealie)
|
||||
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
This documentation is for the Mealie v1 Beta release and is not final. As such, it may contain incomplete or incorrect information. You should understand that installing Mealie v1 Beta is a work in progress and while we've committed to maintaining the database schema and provided migrations, we are still in the process of adding new features, and robust testing to ensure the application works as expected.
|
||||
|
||||
You should likely find bugs, errors, and unfinished pages within the application. To find the current status of the release you can checkout the [project on github](https://github.com/hay-kot/mealie/projects/7) or reach out on discord.
|
||||
You should likely find bugs, errors, and unfinished pages within the application. To find the current status of the release you can checkout the [project on github](https://github.com/mealie-recipes/mealie/projects/7) or reach out on discord.
|
||||
|
||||
Mealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and Mealie will automatically import the relevant data or add a family recipe with the UI editor. Mealie also provides an API for interactions from 3rd party applications.
|
||||
|
||||
|
@ -54,4 +54,4 @@ In most cases, it's faster to manually migrate the recipes that didn't take inst
|
||||
|
||||
v1 Comes with a whole host of new features and improvements. Checkout the changelog to get a sense for what's new.
|
||||
|
||||
- [Github releases changelog](https://github.com/hay-kot/mealie/releases)
|
||||
- [Github releases changelog](https://github.com/mealie-recipes/mealie/releases)
|
||||
|
@ -34,4 +34,4 @@ ALTER USER mealie WITH SUPERUSER;
|
||||
ALTER USER mealie WITH NOSUPERUSER;
|
||||
```
|
||||
|
||||
For more information see [GitHub Issue #1500](https://github.com/hay-kot/mealie/issues/1500)
|
||||
For more information see [GitHub Issue #1500](https://github.com/mealie-recipes/mealie/issues/1500)
|
||||
|
File diff suppressed because one or more lines are too long
@ -330,9 +330,6 @@ export default defineComponent({
|
||||
.list-group {
|
||||
min-height: 38px;
|
||||
}
|
||||
.list-group-item {
|
||||
cursor: move;
|
||||
}
|
||||
.list-group-item i {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -117,7 +117,7 @@
|
||||
@click="toggleDisabled(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>
|
||||
{{ $t("recipe.step-index", { step: index + 1 }) }}
|
||||
</span>
|
||||
@ -671,9 +671,6 @@ export default defineComponent({
|
||||
.list-group {
|
||||
min-height: 38px;
|
||||
}
|
||||
.list-group-item {
|
||||
cursor: move;
|
||||
}
|
||||
.list-group-item i {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -49,6 +49,10 @@ export default defineComponent({
|
||||
];
|
||||
|
||||
function handleRowClick(item: ReportSummary) {
|
||||
if (item.status === "in-progress") {
|
||||
return;
|
||||
}
|
||||
|
||||
router.push(`/group/reports/${item.id}`);
|
||||
}
|
||||
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Sleutelwoord",
|
||||
"link-copied": "Skakel gekopieer",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Besig om gebeurtenisse te laai",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,9 +114,10 @@
|
||||
"json": "JSON",
|
||||
"keyword": "كلمة مفتاحية",
|
||||
"link-copied": "تمّ نسْخ الرّابط",
|
||||
"loading": "جار التحميل",
|
||||
"loading-events": "جاري تحميل الأحداث",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
"loading-recipe": "جار تحميل الوصفات...",
|
||||
"loading-ocr-data": "جاري تحميل بيانات OCR...",
|
||||
"loading-recipes": "جار تحميل الوصفات",
|
||||
"message": "الرسائل النصية Sms",
|
||||
"monday": "الإثنين",
|
||||
@ -226,28 +227,28 @@
|
||||
"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": "Manage",
|
||||
"invite": "Invite",
|
||||
"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.",
|
||||
"invite": "دعوة",
|
||||
"looking-to-update-your-profile": "هل ترغب في تحديث ملفك الشخصي؟",
|
||||
"default-recipe-preferences-description": "هذه هي الإعدادات الافتراضية عند إنشاء وصفة جديدة في مجموعتك. يمكن تغيير هذه الوصفات الفردية في قائمة إعدادات الوصفات.",
|
||||
"default-recipe-preferences": "Default Recipe Preferences",
|
||||
"group-preferences": "Group Preferences",
|
||||
"private-group": "Private Group",
|
||||
"group-preferences": "إعدادات المجموعة",
|
||||
"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.",
|
||||
"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",
|
||||
"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-recipe-assets": "Show recipe assets",
|
||||
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available",
|
||||
"default-to-landscape-view": "Default to landscape view",
|
||||
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
|
||||
"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-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.",
|
||||
"general-preferences": "General Preferences",
|
||||
"group-recipe-preferences": "Group Recipe Preferences",
|
||||
"report": "Report",
|
||||
"report": "تقرير",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"group-management": "Group Management",
|
||||
"admin-group-management": "Admin Group Management",
|
||||
@ -293,16 +294,16 @@
|
||||
"meal-title": "Meal Title",
|
||||
"meal-note": "Meal Note",
|
||||
"note-only": "Note Only",
|
||||
"random-meal": "Random Meal",
|
||||
"random-dinner": "Random Dinner",
|
||||
"random-meal": "وجبة عشوائية",
|
||||
"random-dinner": "عشاء عشوائي",
|
||||
"random-side": "Random Side",
|
||||
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
|
||||
"to-all-days": "to all days",
|
||||
"to-all-days": "إلى جميع الأيام",
|
||||
"on-days": "on {0}s",
|
||||
"for-all-meal-types": "for all meal types",
|
||||
"for-type-meal-types": "for {0} meal types",
|
||||
"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.",
|
||||
"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": "قواعد الوصفات",
|
||||
@ -313,10 +314,10 @@
|
||||
"migration": {
|
||||
"migration-data-removed": "Migration data removed",
|
||||
"new-migration": "New Migration",
|
||||
"no-file-selected": "No File Selected",
|
||||
"no-file-selected": "لم يتمّ اختيار أيّ ملفّ",
|
||||
"no-migration-data-available": "No Migration Data Available",
|
||||
"previous-migrations": "Previous Migrations",
|
||||
"recipe-migration": "Recipe Migration",
|
||||
"recipe-migration": "نقل الوصفة",
|
||||
"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.",
|
||||
@ -384,12 +385,12 @@
|
||||
"404-page-not-found": "404 Page not found",
|
||||
"all-recipes": "All Recipes",
|
||||
"new-page-created": "New page created",
|
||||
"page": "Page",
|
||||
"page": "الصفحة",
|
||||
"page-creation-failed": "Page creation failed",
|
||||
"page-deleted": "Page deleted",
|
||||
"page-deletion-failed": "Page deletion failed",
|
||||
"page-update-failed": "Page update failed",
|
||||
"page-updated": "Page updated",
|
||||
"page-deleted": "تم حذف الصفحة",
|
||||
"page-deletion-failed": "حذف الصفحة فشل",
|
||||
"page-update-failed": "تحديث الصفحة فشل",
|
||||
"page-updated": "تم تحديث صفحة",
|
||||
"pages-update-failed": "Pages update failed",
|
||||
"pages-updated": "Pages updated",
|
||||
"404-not-found": "لم يتم العثور على الصفحة. خطأ 404",
|
||||
|
@ -77,7 +77,7 @@
|
||||
"tag-events": "Събития за таг",
|
||||
"category-events": "Събития за категория",
|
||||
"when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група",
|
||||
"recipe-events": "Recipe Events"
|
||||
"recipe-events": "Събития на рецептата"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Откажи",
|
||||
@ -114,9 +114,10 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Ключова дума",
|
||||
"link-copied": "Линкът е копиран",
|
||||
"loading": "Зареждане",
|
||||
"loading-events": "Зареждане на събития",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
"loading-recipe": "Зареждане на рецептата...",
|
||||
"loading-ocr-data": "Зареждане на OCR данните...",
|
||||
"loading-recipes": "Рецептите се зареждат",
|
||||
"message": "Съобщение",
|
||||
"monday": "Понеделник",
|
||||
@ -127,7 +128,7 @@
|
||||
"no-recipe-found": "Няма намерени рецепти",
|
||||
"ok": "Добре",
|
||||
"options": "Опции:",
|
||||
"plural-name": "Plural Name",
|
||||
"plural-name": "Име в множествено число",
|
||||
"print": "Принтирай",
|
||||
"print-preferences": "Настройки на принтиране",
|
||||
"random": "Произволно",
|
||||
@ -197,7 +198,7 @@
|
||||
"refresh": "Опресни",
|
||||
"upload-file": "Качване на файл",
|
||||
"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": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
||||
@ -212,7 +213,7 @@
|
||||
"group-id-with-value": "ID на Групата: {groupID}",
|
||||
"group-name": "Име на групата",
|
||||
"group-not-found": "Групата не е намерена",
|
||||
"group-token": "Group Token",
|
||||
"group-token": "Токен на групата",
|
||||
"group-with-value": "Група: {groupID}",
|
||||
"groups": "Групи",
|
||||
"manage-groups": "Управление на групи",
|
||||
@ -248,7 +249,7 @@
|
||||
"general-preferences": "Общи предпочитания",
|
||||
"group-recipe-preferences": "Предпочитания за рецепта по група",
|
||||
"report": "Сигнал",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"report-with-id": "Номер на сигнала: {id}",
|
||||
"group-management": "Управление на групите",
|
||||
"admin-group-management": "Административно управление на групите",
|
||||
"admin-group-management-text": "Промените по тази група ще бъдат отразени моментално.",
|
||||
@ -469,9 +470,9 @@
|
||||
"add-to-plan": "Добави към план",
|
||||
"add-to-timeline": "Добави към времевата линия",
|
||||
"recipe-added-to-list": "Рецептата е добавена към списъка",
|
||||
"recipes-added-to-list": "Recipes added to list",
|
||||
"recipes-added-to-list": "Рецептите са добавени към списъка",
|
||||
"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": "Рецептата не беше добавена към хранителния план",
|
||||
"yield": "Добив",
|
||||
"quantity": "Количество",
|
||||
@ -513,7 +514,7 @@
|
||||
"message-key": "Ключ на съобщението",
|
||||
"parse": "Анализирай",
|
||||
"attach-images-hint": "Прикачете снимки като ги влачете и пуснете в редактора",
|
||||
"drop-image": "Drop image",
|
||||
"drop-image": "Влачете и пуснете снимка",
|
||||
"enable-ingredient-amounts-to-use-this-feature": "Пуснете количествата на съставките за да използвате функционалността",
|
||||
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Рецепти със зададени мерни единици и храни ме могат да бъдат анализирани.",
|
||||
"parse-ingredients": "Анализирай съставките",
|
||||
@ -572,16 +573,16 @@
|
||||
"search-hint": "Натисни '/'",
|
||||
"advanced": "Разширени",
|
||||
"auto-search": "Автоматично търсене",
|
||||
"no-results": "No results found"
|
||||
"no-results": "Не са намерени резултати"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Добавяне на нова тема",
|
||||
"admin-settings": "Административни настройки",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created": "Архивът е създаден успешно",
|
||||
"backup-created-at-response-export_path": "Резервно копие е създадено на {path}",
|
||||
"backup-deleted": "Резервното копие е изтрито",
|
||||
"restore-success": "Restore successful",
|
||||
"restore-success": "Успешно възстановяване",
|
||||
"backup-tag": "Таг на резервното копие",
|
||||
"create-heading": "Създай резервно копие",
|
||||
"delete-backup": "Изтрий резервно копие",
|
||||
@ -690,13 +691,13 @@
|
||||
"configuration": "Конфигурация",
|
||||
"docker-volume": "Docker том",
|
||||
"docker-volume-help": "Mealie изисква контейнерът на frontend и backend да споделят един и същ том на docker или място за съхранение. Това гарантира, че frontend контейнера може да има правилен достъп до изображенията и активите, съхранени на диска.",
|
||||
"volumes-are-misconfigured": "Volumes are misconfigured.",
|
||||
"volumes-are-misconfigured": "Томовете са конфигурирани неправилно.",
|
||||
"volumes-are-configured-correctly": "Томовете са конфигурирани правилно.",
|
||||
"status-unknown-try-running-a-validation": "Статус Неизвестен. Опитайте да стартирате проверка.",
|
||||
"validate": "Валидирайте",
|
||||
"email-configuration-status": "Статус на имейл конфигурация",
|
||||
"email-configured": "Email Configured",
|
||||
"email-test-results": "Email Test Results",
|
||||
"email-configured": "Email е конфигуриран",
|
||||
"email-test-results": "Резултати от тест на email",
|
||||
"ready": "Готов",
|
||||
"not-ready": "Не е готово - Проверете променливите на средата",
|
||||
"succeeded": "Успешно",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "Паролата е актуализирана",
|
||||
"password": "Парола",
|
||||
"password-strength": "Сигурността на паролата е {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Моля, въведете новата си парола.",
|
||||
"register": "Регистриране",
|
||||
"reset-password": "Нулиране на паролата",
|
||||
"sign-in": "Влизане",
|
||||
@ -852,7 +853,7 @@
|
||||
"username": "Потребителско име",
|
||||
"users-header": "Потребители",
|
||||
"users": "Потребители",
|
||||
"user-not-found": "User not found",
|
||||
"user-not-found": "Потребителят не е намерен",
|
||||
"webhook-time": "Webhook време",
|
||||
"webhooks-enabled": "Webhooks са пуснати",
|
||||
"you-are-not-allowed-to-create-a-user": "Нямате право да създавате потребител",
|
||||
@ -875,7 +876,7 @@
|
||||
"user-management": "Управление на потребителя",
|
||||
"reset-locked-users": "Нулиране на заключените потребители",
|
||||
"admin-user-creation": "Създаване на администратор",
|
||||
"admin-user-management": "Admin User Management",
|
||||
"admin-user-management": "Управление на администраторите",
|
||||
"user-details": "Детайли за потребителя",
|
||||
"user-name": "Потребителско име",
|
||||
"authentication-method": "Метод за автентикация",
|
||||
@ -886,11 +887,11 @@
|
||||
"user-can-manage-group": "Потребителя може да управлява групата",
|
||||
"user-can-organize-group-data": "Потребителя може да организира данните на групата",
|
||||
"enable-advanced-features": "Включване на разширени функции",
|
||||
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.",
|
||||
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!",
|
||||
"forgot-password": "Forgot Password",
|
||||
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.",
|
||||
"changes-reflected-immediately": "Changes to this user will be reflected immediately."
|
||||
"it-looks-like-this-is-your-first-time-logging-in": "Изглежда това е първият път, в който влизате.",
|
||||
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Искате ли да виждате това по-често? Уверете се, че сте конфигурирали настройките си за email известяване правилно!",
|
||||
"forgot-password": "Забравена Парола",
|
||||
"forgot-password-text": "Въведете Вашият имейл адрес и ние ще ви изпратим линк, с който да промените Вашата парола.",
|
||||
"changes-reflected-immediately": "Промените по този потребител ще бъдат отразени моментално."
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "преведено",
|
||||
@ -912,8 +913,8 @@
|
||||
"food-label": "Заглавие на храната",
|
||||
"edit-food": "Редактирай храна",
|
||||
"food-data": "Данни за храните",
|
||||
"example-food-singular": "ex: Onion",
|
||||
"example-food-plural": "ex: Onions"
|
||||
"example-food-singular": "пример: Домат",
|
||||
"example-food-plural": "пример: Домати"
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Заредете базата данни с общи единици въз основа на Вашия местен език.",
|
||||
@ -924,7 +925,7 @@
|
||||
"merging-unit-into-unit": "Обединяване на {0} с {1}",
|
||||
"create-unit": "Създаване на мерна единица",
|
||||
"abbreviation": "Абревиатура",
|
||||
"plural-abbreviation": "Plural Abbreviation",
|
||||
"plural-abbreviation": "Съкращение за множествено число",
|
||||
"description": "Описание",
|
||||
"display-as-fraction": "Показване като фракция",
|
||||
"use-abbreviation": "Използвай съкращение",
|
||||
@ -932,10 +933,10 @@
|
||||
"unit-data": "Данни на мерната единица",
|
||||
"use-abbv": "Използвай съкращение",
|
||||
"fraction": "Фракция",
|
||||
"example-unit-singular": "ex: Tablespoon",
|
||||
"example-unit-plural": "ex: Tablespoons",
|
||||
"example-unit-abbreviation-singular": "ex: Tbsp",
|
||||
"example-unit-abbreviation-plural": "ex: Tbsps"
|
||||
"example-unit-singular": "пример: Чаена лъжичка",
|
||||
"example-unit-plural": "пример: Чаени лъжички",
|
||||
"example-unit-abbreviation-singular": "пример: ч.л.",
|
||||
"example-unit-abbreviation-plural": "пример: ч.л.-ки"
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Заредете базата данни с общи етикети въз основа на Вашия местен език.",
|
||||
@ -964,8 +965,8 @@
|
||||
"delete-recipes": "Изтрий рецепти",
|
||||
"source-unit-will-be-deleted": "Изходната мерна единица ще бъде изтрита"
|
||||
},
|
||||
"create-alias": "Create Alias",
|
||||
"manage-aliases": "Manage Aliases",
|
||||
"create-alias": "Създаване на псевдоним",
|
||||
"manage-aliases": "Управление на псевдоними",
|
||||
"seed-data": "Сийд на данни",
|
||||
"seed": "Сийд",
|
||||
"data-management": "Управление на данни",
|
||||
@ -975,24 +976,24 @@
|
||||
"columns": "Колони",
|
||||
"combine": "Обедини",
|
||||
"categories": {
|
||||
"edit-category": "Edit Category",
|
||||
"new-category": "New Category",
|
||||
"category-data": "Category Data"
|
||||
"edit-category": "Редактиране на категория",
|
||||
"new-category": "Нова категория",
|
||||
"category-data": "Категория за данните"
|
||||
},
|
||||
"tags": {
|
||||
"new-tag": "New Tag",
|
||||
"edit-tag": "Edit Tag",
|
||||
"tag-data": "Tag Data"
|
||||
"new-tag": "Нов таг",
|
||||
"edit-tag": "Редакция на таг",
|
||||
"tag-data": "Данни на тага"
|
||||
},
|
||||
"tools": {
|
||||
"new-tool": "New Tool",
|
||||
"edit-tool": "Edit Tool",
|
||||
"tool-data": "Tool Data"
|
||||
"new-tool": "Нов инструмент",
|
||||
"edit-tool": "Редактирай инструмента",
|
||||
"tool-data": "Данни на инструмента"
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "Регистрации на потребител",
|
||||
"registration-success": "Registration Success",
|
||||
"registration-success": "Успешна регистрация",
|
||||
"join-a-group": "Присъединете се към групата",
|
||||
"create-a-new-group": "Създай нова група",
|
||||
"provide-registration-token-description": "Моля, предоставете регистрационния маркер, свързан с групата, към която искате да се присъедините. Ще трябва да го получите от съществуващ член на групата.",
|
||||
@ -1039,7 +1040,7 @@
|
||||
},
|
||||
"ocr-editor": {
|
||||
"ocr-editor": "Ocr редактор",
|
||||
"toolbar": "Toolbar",
|
||||
"toolbar": "Лента с инструменти",
|
||||
"selection-mode": "Режим на избиране",
|
||||
"pan-and-zoom-picture": "Мащабиране на изображение",
|
||||
"split-text": "Раздели текст",
|
||||
@ -1047,8 +1048,8 @@
|
||||
"split-by-block": "Раздели по текстов блок",
|
||||
"flatten": "Изравняване независимо от оригиналното форматиране",
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"mouse-modes": "Mouse modes",
|
||||
"help": "Помощ",
|
||||
"mouse-modes": "Режими на мишката",
|
||||
"selection-mode": "Режим на избиране (по подразбиране)",
|
||||
"selection-mode-desc": "Режимът за избиране е основният режим, който може да се използва за въвеждане на данни:",
|
||||
"selection-mode-steps": {
|
||||
|
@ -77,7 +77,7 @@
|
||||
"tag-events": "Esdeveniments de les etiquetes",
|
||||
"category-events": "Esdeveniments de les categories",
|
||||
"when-a-new-user-joins-your-group": "Quan un nou usuari s'afegeix al grup",
|
||||
"recipe-events": "Recipe Events"
|
||||
"recipe-events": "Esdeveniments de receptes"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Anuŀla",
|
||||
@ -114,9 +114,10 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Paraula clau",
|
||||
"link-copied": "S'ha copiat l'enllaç",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Carregant esdeveniments",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
"loading-recipe": "Carregant la recepta...",
|
||||
"loading-ocr-data": "Carregant les dades OCR...",
|
||||
"loading-recipes": "Carregant les receptes",
|
||||
"message": "Missatge",
|
||||
"monday": "Dilluns",
|
||||
@ -127,7 +128,7 @@
|
||||
"no-recipe-found": "No s'han trobat receptes",
|
||||
"ok": "D'acord",
|
||||
"options": "Opcions:",
|
||||
"plural-name": "Plural Name",
|
||||
"plural-name": "Nom en plural",
|
||||
"print": "Imprimiu",
|
||||
"print-preferences": "Imprimiu les preferències",
|
||||
"random": "Aleatori",
|
||||
@ -197,7 +198,7 @@
|
||||
"refresh": "Actualitza",
|
||||
"upload-file": "Puja un fitxer",
|
||||
"created-on-date": "Creat el: {0}",
|
||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes."
|
||||
"unsaved-changes": "Tens canvis que no estan guardats. Vols guardar-los abans de sortir? Clica d'acord per guardar-los o cancel·lar per descartar els canvis."
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Esteu segur de voler suprimir el grup <b>{groupName}<b/>?",
|
||||
@ -212,7 +213,7 @@
|
||||
"group-id-with-value": "Identificador del grup: {groupID}",
|
||||
"group-name": "Nom del grup",
|
||||
"group-not-found": "No s'ha trobat el grup",
|
||||
"group-token": "Group Token",
|
||||
"group-token": "Token del grup",
|
||||
"group-with-value": "Grup: {groupID}",
|
||||
"groups": "Grups",
|
||||
"manage-groups": "Gestiona els grups",
|
||||
@ -246,13 +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-description": "Amaga els camps Aliment, Unitat i Quantitat dels ingredients i tracta els ingredients com a camps de text sense format.",
|
||||
"general-preferences": "Preferències generals",
|
||||
"group-recipe-preferences": "Group Recipe Preferences",
|
||||
"report": "Report",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"group-recipe-preferences": "Preferències del grup de receptes",
|
||||
"report": "Informe",
|
||||
"report-with-id": "ID de l'informe: {id}",
|
||||
"group-management": "Gestió de grups",
|
||||
"admin-group-management": "Gestió del grup d'administradors",
|
||||
"admin-group-management-text": "Changes to this group will be reflected immediately.",
|
||||
"group-id-value": "Group Id: {0}"
|
||||
"admin-group-management-text": "Els canvis en aquest grup s'actualitzaran immediatament.",
|
||||
"group-id-value": "ID del grup: {0}"
|
||||
},
|
||||
"meal-plan": {
|
||||
"create-a-new-meal-plan": "Crea un nou menú",
|
||||
@ -291,68 +292,68 @@
|
||||
"editor": "Editor",
|
||||
"meal-recipe": "Recepta del menú",
|
||||
"meal-title": "Títol del menú",
|
||||
"meal-note": "Meal Note",
|
||||
"meal-note": "Notes del menú",
|
||||
"note-only": "Note Only",
|
||||
"random-meal": "Menú aleatori",
|
||||
"random-dinner": "Principal aleatori",
|
||||
"random-side": "Guarnició aleatòria",
|
||||
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
|
||||
"this-rule-will-apply": "Aquesta regla s'aplicarà {dayCriteria} {mealTypeCriteria}.",
|
||||
"to-all-days": "a tots els dies",
|
||||
"on-days": "on {0}s",
|
||||
"on-days": "en {0}s",
|
||||
"for-all-meal-types": "per a tots els tipus de menús",
|
||||
"for-type-meal-types": "for {0} meal types",
|
||||
"meal-plan-rules": "Meal Plan Rules",
|
||||
"new-rule": "New Rule",
|
||||
"for-type-meal-types": "per {0} tipus de menús",
|
||||
"meal-plan-rules": "Normes del planificador de menús",
|
||||
"new-rule": "Nova norma",
|
||||
"meal-plan-rules-description": "You can create rules for auto selecting recipes for your meal plans. These rules are used by the server to determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same day/type constraints then the categories of the rules will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.",
|
||||
"new-rule-description": "When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to \"Any\" which will apply it to all the possible values for the day and/or meal type.",
|
||||
"recipe-rules": "Recipe Rules",
|
||||
"recipe-rules": "Normes per la recepta",
|
||||
"applies-to-all-days": "Aplica a tots els dies",
|
||||
"applies-on-days": "Applies on {0}s",
|
||||
"meal-plan-settings": "Meal Plan Settings"
|
||||
"applies-on-days": "S'aplicarà en {0}s",
|
||||
"meal-plan-settings": "Opcions de planificació de menús"
|
||||
},
|
||||
"migration": {
|
||||
"migration-data-removed": "S'han suprimit les dades migrades",
|
||||
"new-migration": "New Migration",
|
||||
"new-migration": "Nova migració",
|
||||
"no-file-selected": "Cap fitxer seleccionat",
|
||||
"no-migration-data-available": "No hi han dades disponibles",
|
||||
"previous-migrations": "Previous Migrations",
|
||||
"previous-migrations": "Migracions prèvies",
|
||||
"recipe-migration": "Migració de receptes",
|
||||
"chowdown": {
|
||||
"description": "Migreu les dades de Chowdown",
|
||||
"description-long": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
|
||||
"description-long": "Mealie suporta de forma nativa el format de Chowdown. Descarrega el codi del repositori com a .zip i carrega'l a sota.",
|
||||
"title": "Chowdown"
|
||||
},
|
||||
"nextcloud": {
|
||||
"description": "Migreu les dades des d'una instància de Nextcloud Cookbook",
|
||||
"description-long": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
|
||||
"description-long": "Les receptes de Nextcloud es poden importar des d'un zip que contingui les dades guardades a Nextcloud. Com a exemple, observa l'estructura de carpetes de sota per assegurar-te que les teves receptes es poden importar.",
|
||||
"title": "Nextcloud Cookbook"
|
||||
},
|
||||
"copymethat": {
|
||||
"description-long": "Mealie pot importar receptes de Copy Me That. Exporta les teves receptes en format HTML i llavors puja el fitxer .zip aquí sota.",
|
||||
"title": "Copy Me That Recipe Manager"
|
||||
"title": "Copia aquest gestor de receptes"
|
||||
},
|
||||
"paprika": {
|
||||
"description-long": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
|
||||
"description-long": "Mealie pot importar receptes de l'aplicació Paprika. Exporta les teves receptes de Paprika, reanomena l'arxiu a format .zip i carrega'l a sota.",
|
||||
"title": "Paprika Recipe Manager"
|
||||
},
|
||||
"mealie-pre-v1": {
|
||||
"description-long": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.",
|
||||
"description-long": "Mealie pot importar les receptes des de versions prèvies a la v1.0 d'aquesta aplicació. Exporta les teves receptes des de la teva antiga instància i carrega l'arxiu. zip a sota. Només s'importaran les receptes, i cap dada més.",
|
||||
"title": "Mealie Pre v1.0"
|
||||
},
|
||||
"tandoor": {
|
||||
"description-long": "Mealie can import recipes from Tandoor. Export your data in the \"Default\" format, then upload the .zip below.",
|
||||
"description-long": "Mealie pot importar les receptes de Tandoor. Exporta les dades en format \"Default\", i carrega el .zip a sota.",
|
||||
"title": "Tandoor Recipes"
|
||||
},
|
||||
"recipe-data-migrations": "Recipe Data Migrations",
|
||||
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
|
||||
"recipe-data-migrations": "Migració de receptes",
|
||||
"recipe-data-migrations-explanation": "Les receptes es poden migrar des d'una altra aplicació suportada cap a Mealie. És una manera genial de començar a utilitzar el Mealie.",
|
||||
"choose-migration-type": "Elegeix un tipus de migració",
|
||||
"tag-all-recipes": "Etiqueta totes les receptes amb {tag-name}",
|
||||
"nextcloud-text": "Les receptes de Nextcloud poden ser importades d'un fitxer ZIP que contingui les dades emmagatzemades en Nextcloud. Segueix l'exemple d'estructura de directori de sota per assegurar que les receptes podran ser importades.",
|
||||
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below",
|
||||
"chowdown-text": "Mealie suporta de forma nativa el format de Chowdown. Descarrega el codi del repositori com a .zip i carrega'l a sota",
|
||||
"recipe-1": "Recepta 1",
|
||||
"recipe-2": "Recepta 2",
|
||||
"paprika-text": "Mealie pot importar receptes des de l'aplicació Paprika. Exporta les teves receptes de Paprika, reanomena l'extensió de l'arxiu a .zip i penja'l aquí sota.",
|
||||
"mealie-text": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.",
|
||||
"mealie-text": "Mealie pot importar les receptes des de versions prèvies a la v1.0 d'aquesta aplicació. Exporta les teves receptes des de la teva antiga instància i carrega l'arxiu. zip a sota. Només s'importaran les receptes, i cap dada més.",
|
||||
"plantoeat": {
|
||||
"title": "Plan to Eat",
|
||||
"description-long": "Mealie pot importar receptes de Plan to Eat."
|
||||
@ -469,9 +470,9 @@
|
||||
"add-to-plan": "Afegiu al menú",
|
||||
"add-to-timeline": "Afegir a la cronologia",
|
||||
"recipe-added-to-list": "Recepta afegida a la llista",
|
||||
"recipes-added-to-list": "Recipes added to list",
|
||||
"recipes-added-to-list": "Receptes afegides a la llista",
|
||||
"recipe-added-to-mealplan": "Recepta afegida al menú",
|
||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||
"failed-to-add-recipes-to-list": "S'ha produït un error al intentar afegir la recepta a la llista",
|
||||
"failed-to-add-recipe-to-mealplan": "S'ha produït un error afegint la recepta al menú",
|
||||
"yield": "Racions",
|
||||
"quantity": "Quantitat",
|
||||
@ -508,50 +509,50 @@
|
||||
"made-this": "Ho he fet",
|
||||
"how-did-it-turn-out": "Com ha sortit?",
|
||||
"user-made-this": "{user} ha fet això",
|
||||
"last-made-date": "Last Made {date}",
|
||||
"last-made-date": "Última vegada feta {date}",
|
||||
"api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.",
|
||||
"message-key": "Message Key",
|
||||
"parse": "Parse",
|
||||
"attach-images-hint": "Attach images by dragging & dropping them into the editor",
|
||||
"drop-image": "Drop image",
|
||||
"enable-ingredient-amounts-to-use-this-feature": "Enable ingredient amounts to use this feature",
|
||||
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Recipes with units or foods defined cannot be parsed.",
|
||||
"parse-ingredients": "Parse ingredients",
|
||||
"parse": "Analitzar",
|
||||
"attach-images-hint": "Afegeix imatges arrossegant i deixant anar la imatge a l'editor",
|
||||
"drop-image": "Deixa anar la imatge",
|
||||
"enable-ingredient-amounts-to-use-this-feature": "Habilita les quantitats d'ingredients per a poder fer servir aquesta característica",
|
||||
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Les receptes amb unitats o aliments definits no es poden analitzar.",
|
||||
"parse-ingredients": "Analitzar ingredients",
|
||||
"edit-markdown": "Editar Markdown",
|
||||
"recipe-creation": "Creació d'una recepta",
|
||||
"select-one-of-the-various-ways-to-create-a-recipe": "Selecciona una de les diverses formes de crear una recepta",
|
||||
"looking-for-migrations": "Looking For Migrations?",
|
||||
"looking-for-migrations": "Estàs buscant migracions?",
|
||||
"import-with-url": "Importar amb l'URL",
|
||||
"create-recipe": "Crea la recepta",
|
||||
"import-with-zip": "Importar amb un .zip",
|
||||
"create-recipe-from-an-image": "Crea la recepta a partir d'una imatge",
|
||||
"bulk-url-import": "Importació d'URL en massa",
|
||||
"debug-scraper": "Debug Scraper",
|
||||
"debug-scraper": "Rastrejador de depuració",
|
||||
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Crea la recepta proporcionant-ne un nom. Totes les receptes han de tenir un nom únic.",
|
||||
"new-recipe-names-must-be-unique": "New recipe names must be unique",
|
||||
"scrape-recipe": "Scrape Recipe",
|
||||
"scrape-recipe-description": "Scrape a recipe by url. Provide the url for the site you want to scrape, and Mealie will attempt to scrape the recipe from that site and add it to your collection.",
|
||||
"import-original-keywords-as-tags": "Import original keywords as tags",
|
||||
"new-recipe-names-must-be-unique": "Els noms de les noves receptes han de ser únics",
|
||||
"scrape-recipe": "Rastrejar recepta",
|
||||
"scrape-recipe-description": "Rastrejar recepta des de l'Url. Proporciona un Url del lloc que vols rastrejar i Mealie intentarà analitzar la recepta del lloc web i afegir-la a la teva col·lecció.",
|
||||
"import-original-keywords-as-tags": "Importa les paraules clau originals com a tags",
|
||||
"stay-in-edit-mode": "Segueix en el mode d'edició",
|
||||
"import-from-zip": "Importa des d'un ZIP",
|
||||
"import-from-zip-description": "Import a single recipe that was exported from another Mealie instance.",
|
||||
"import-from-zip-description": "Importa una sola recepta que ha estat importada d'una altra instància de Mealie.",
|
||||
"zip-files-must-have-been-exported-from-mealie": "Els fitxers .zip han d'haver sigut exportats des de Mealie",
|
||||
"create-a-recipe-by-uploading-a-scan": "Crea la recepta pujant-ne un escaneig.",
|
||||
"upload-a-png-image-from-a-recipe-book": "Puja una imatge PNG d'un llibre de receptes",
|
||||
"recipe-bulk-importer": "Importador de receptes en massa",
|
||||
"recipe-bulk-importer-description": "The Bulk recipe importer allows you to import multiple recipes at once by queueing the sites on the backend and running the task in the background. This can be useful when initially migrating to Mealie, or when you want to import a large number of recipes.",
|
||||
"set-categories-and-tags": "Set Categories and Tags",
|
||||
"bulk-imports": "Bulk Imports",
|
||||
"bulk-import-process-has-started": "Bulk Import process has started",
|
||||
"bulk-import-process-has-failed": "Bulk import process has failed",
|
||||
"report-deletion-failed": "Report deletion failed",
|
||||
"recipe-debugger": "Recipe Debugger",
|
||||
"recipe-debugger-description": "Grab the URL of the recipe you want to debug and paste it here. The URL will be scraped by the recipe scraper and the results will be displayed. If you don't see any data returned, the site you are trying to scrape is not supported by Mealie or its scraper library.",
|
||||
"debug": "Debug",
|
||||
"tree-view": "Tree View",
|
||||
"recipe-yield": "Recipe Yield",
|
||||
"unit": "Unit",
|
||||
"upload-image": "Upload image",
|
||||
"recipe-bulk-importer-description": "L'importador de receptes a granel, permet que importis múltiples receptes a la vegada posant a la cua els llocs al backend i executant la tasca en segon pla. Això pot ser útil si es migra inicialment a Mealie o quan es vol importar un gran nombre de receptes.",
|
||||
"set-categories-and-tags": "Estableix Categories i Etiquetes",
|
||||
"bulk-imports": "Importacions a granel",
|
||||
"bulk-import-process-has-started": "El procés d'importació a granel ha començat",
|
||||
"bulk-import-process-has-failed": "El procés d'importació a granel ha fallat",
|
||||
"report-deletion-failed": "No s'ha pogut suprimir l'informe",
|
||||
"recipe-debugger": "Depuradora de receptes",
|
||||
"recipe-debugger-description": "Agafa l'URL de la recepta que vols depurar i enganxa-la aquí. L'URL serà reastrejada pel rastrejador de receptes i es mostraran els resultats. Si no veieu cap dada retornada, el lloc que esteu provant de rastrejar no és compatible amb Mealie ni la seva biblioteca de rastreig.",
|
||||
"debug": "Depuració",
|
||||
"tree-view": "Vista en arbre",
|
||||
"recipe-yield": "Rendiment de la recepta",
|
||||
"unit": "Unitat",
|
||||
"upload-image": "Puja una imatge",
|
||||
"screen-awake": "Mantenir la pantalla encesa",
|
||||
"remove-image": "Esborrar la imatge"
|
||||
},
|
||||
@ -562,26 +563,26 @@
|
||||
"include": "Inclou",
|
||||
"max-results": "No mostreu més de",
|
||||
"or": "O",
|
||||
"has-any": "Has Any",
|
||||
"has-all": "Has All",
|
||||
"has-any": "Conté qualsevol",
|
||||
"has-all": "Ho conté tot",
|
||||
"results": "Resultats",
|
||||
"search": "Cerca",
|
||||
"search-mealie": "Cerca a Melie (prem /)",
|
||||
"search-placeholder": "Cerca...",
|
||||
"tag-filter": "Filtra per etiqueta",
|
||||
"search-hint": "Prem '/'",
|
||||
"advanced": "Advanced",
|
||||
"auto-search": "Auto Search",
|
||||
"no-results": "No results found"
|
||||
"advanced": "Avançat",
|
||||
"auto-search": "Cerca automàtica",
|
||||
"no-results": "No s'han trobat resultats"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Afegiu un nou tema",
|
||||
"admin-settings": "Opcions de l'administrador",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created": "La còpia de seguretat s'ha creat correctament",
|
||||
"backup-created-at-response-export_path": "S'ha creat una còpia de seguretat a {path}",
|
||||
"backup-deleted": "Còpia de seguretat suprimida",
|
||||
"restore-success": "Restore successful",
|
||||
"restore-success": "La restauració s'ha dut a terme correctament",
|
||||
"backup-tag": "Etiqueta de la còpia de seguretat",
|
||||
"create-heading": "Crea una còpia de seguretat",
|
||||
"delete-backup": "Esborra la còpia de seguretat",
|
||||
@ -591,13 +592,13 @@
|
||||
"partial-backup": "Còpia de seguretat parcial",
|
||||
"unable-to-delete-backup": "No s'ha pogut suprimir la còpia.",
|
||||
"experimental-description": "Backups a total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time. Currently, {not-crossed-version} (data migrations are not done automatically). These serve as a database agnostic way to export and import data or backup the site to an external location.",
|
||||
"backup-restore": "Backup Restore",
|
||||
"back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.",
|
||||
"cannot-be-undone": "This action cannot be undone - use with caution.",
|
||||
"postgresql-note": "If you are using PostGreSQL, please review the {backup-restore-process} prior to restoring.",
|
||||
"backup-restore-process-in-the-documentation": "backup/restore process in the documentation",
|
||||
"irreversible-acknowledgment": "I understand that this action is irreversible, destructive and may cause data loss",
|
||||
"restore-backup": "Restore Backup"
|
||||
"backup-restore": "Restaura la còpia de seguretat",
|
||||
"back-restore-description": "Restaurar aquesta còpia de seguretat sobreescriurà totes les dades actuals de la teva base de dades i qualsevol directori i els substituirà amb el contingut d'aquesta còpia de seguretat. {cannot-be-undone} Si la restauració es duu a terme correctament, se us tancarà la sessió.",
|
||||
"cannot-be-undone": "Aquesta acció no es pot desfer. Utilitza-la amb precaució.",
|
||||
"postgresql-note": "Si estàs fent servir PostGresSQL, si us plau, revisa el {backup-restore-process} abans de fer la restauració.",
|
||||
"backup-restore-process-in-the-documentation": "el procés de còpia de seguretat i restauració es troba a la documentació",
|
||||
"irreversible-acknowledgment": "Entenc que aquesta acció és irreversible, destructiva i pot ocasionar la pèrdua de dades",
|
||||
"restore-backup": "Restaura la còpia de seguretat"
|
||||
},
|
||||
"backup-and-exports": "Còpies de seguretat",
|
||||
"change-password": "Canvia la contrasenya",
|
||||
@ -661,8 +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.",
|
||||
"create-an-api-token": "Crea un token d'API",
|
||||
"token-name": "Nom del token",
|
||||
"generate": "Generate",
|
||||
"you-have-token-count": "You have no active tokens.|You have one active token.|You have {count} active tokens."
|
||||
"generate": "Genera",
|
||||
"you-have-token-count": "No tens fitxes actives.|Tens una fitxa activa.|Tens {count} fitxes actives."
|
||||
},
|
||||
"toolbox": {
|
||||
"assign-all": "Asigna tots",
|
||||
@ -682,7 +683,7 @@
|
||||
"webhooks-caps": "WEBHOOKS",
|
||||
"webhooks": "Webhooks",
|
||||
"webhook-name": "Nom del Webhook",
|
||||
"description": "The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the webhooks will be sent with the data from the recipe that is scheduled for the day. Note that webhook execution is not exact. The webhooks are executed on a 5 minutes interval so the webhooks will be executed within 5 +/- minutes of the scheduled."
|
||||
"description": "Els webhooks definits a sota s'executaran quan es defineixi un menú pel dia. En un temps estipulat, els webhooks s'enviaran amb les dades de la recepta que estigui programada pel dia. L'execució dels webhooks no és exacta. Els webhooks s'executen en un interval de 5 minuts, és a dir, que s'executaran en un interval de +/- 5 minuts del temps estipulat."
|
||||
},
|
||||
"bug-report": "Bug Report",
|
||||
"bug-report-information": "Use this information to report a bug. Providing details of your instance to developers is the best way to get your issues resolved quickly.",
|
||||
@ -699,15 +700,15 @@
|
||||
"email-test-results": "Email Test Results",
|
||||
"ready": "Ready",
|
||||
"not-ready": "Not Ready - Check Environmental Variables",
|
||||
"succeeded": "Succeeded",
|
||||
"failed": "Failed",
|
||||
"general-about": "General About",
|
||||
"application-version": "Application Version",
|
||||
"application-version-error-text": "Your current version ({0}) does not match the latest release. Considering updating to the latest version ({1}).",
|
||||
"mealie-is-up-to-date": "Mealie is up to date",
|
||||
"secure-site": "Secure Site",
|
||||
"secure-site-error-text": "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.",
|
||||
"secure-site-success-text": "Site is accessed by localhost or https",
|
||||
"succeeded": "Va tenir èxit",
|
||||
"failed": "Ha fallat",
|
||||
"general-about": "Informació General",
|
||||
"application-version": "Versió de l'Aplicació",
|
||||
"application-version-error-text": "La teva versió actual ({0}) no correspon amb l'última publicada. Considera actualitzar a l'última versió ({1}).",
|
||||
"mealie-is-up-to-date": "Mealie està actualitzat",
|
||||
"secure-site": "Web Segur",
|
||||
"secure-site-error-text": "Allotja mitjançant localhost o assegura amb https. És possible que el porta-retalls i API addicionals del navegador no funcionin.",
|
||||
"secure-site-success-text": "El web és accedit amb localhost o https",
|
||||
"server-side-base-url": "Server Side Base URL",
|
||||
"server-side-base-url-error-text": "`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.",
|
||||
"server-side-base-url-success-text": "Server Side URL does not match the default",
|
||||
@ -731,19 +732,19 @@
|
||||
"label": "Etiqueta",
|
||||
"linked-item-warning": "Aquest element està enllaçat amb una o més receptes. Modificar les unitats o els aliments pot provocar resultats inesperats en afegir o elimina la recepta del llistat.",
|
||||
"toggle-food": "Mostra el nom de l'aliment",
|
||||
"manage-labels": "Manage Labels",
|
||||
"are-you-sure-you-want-to-delete-this-item": "Are you sure you want to delete this item?",
|
||||
"copy-as-text": "Copy as Text",
|
||||
"copy-as-markdown": "Copy as Markdown",
|
||||
"delete-checked": "Delete Checked",
|
||||
"toggle-label-sort": "Toggle Label Sort",
|
||||
"reorder-labels": "Reorder Labels",
|
||||
"uncheck-all-items": "Uncheck All Items",
|
||||
"check-all-items": "Check All Items",
|
||||
"manage-labels": "Gestiona etiquetes",
|
||||
"are-you-sure-you-want-to-delete-this-item": "Estàs segur/a que vols eliminar aquest ítem?",
|
||||
"copy-as-text": "Copia com a text",
|
||||
"copy-as-markdown": "Copia com a Markdown",
|
||||
"delete-checked": "Suprimeix la selecció",
|
||||
"toggle-label-sort": "Activa/Desactiva l'ordre per etiquetes",
|
||||
"reorder-labels": "Canvia d'ordre les etiquetes",
|
||||
"uncheck-all-items": "Desselecciona tots els ítems",
|
||||
"check-all-items": "Selecciona tots els ítems",
|
||||
"linked-recipes-count": "No Linked Recipes|One Linked Recipe|{count} Linked Recipes",
|
||||
"items-checked-count": "No items checked|One item checked|{count} items checked",
|
||||
"no-label": "No Label",
|
||||
"completed-on": "Completed on {date}"
|
||||
"no-label": "Sense etiqueta",
|
||||
"completed-on": "Completat el {date}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Receptes",
|
||||
@ -796,13 +797,13 @@
|
||||
"tool-name": "Nom de l'estri",
|
||||
"create-new-tool": "Crea un nou estri",
|
||||
"on-hand-checkbox-label": "Mostra com a disponible (marcat)",
|
||||
"required-tools": "Required Tools"
|
||||
"required-tools": "Eines necessàries"
|
||||
},
|
||||
"user": {
|
||||
"admin": "Administrador/a",
|
||||
"are-you-sure-you-want-to-delete-the-link": "Esteu segur de voler suprimir l'enllaç <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/>?",
|
||||
"auth-method": "Auth Method",
|
||||
"auth-method": "Mètode d'autenticació",
|
||||
"confirm-link-deletion": "Confirmeu l'eliminació de l'enllaç",
|
||||
"confirm-password": "Confirmeu la contrasenya",
|
||||
"confirm-user-deletion": "Confirmeu l'eliminació de l'usuari",
|
||||
@ -816,7 +817,7 @@
|
||||
"error-cannot-delete-super-user": "S'ha produït un error. El Super usuari no es pot suprimir!",
|
||||
"existing-password-does-not-match": "La contrasenya actual no coincideix",
|
||||
"full-name": "Nom sencer",
|
||||
"generate-password-reset-link": "Generate Password Reset Link",
|
||||
"generate-password-reset-link": "Genera un enllaç per reiniciar la contrasenya",
|
||||
"invite-only": "Només per invitació",
|
||||
"link-id": "Id de l'enllaç",
|
||||
"link-name": "Nom de l'enllaç",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "S'ha actualitzat la contrasenya",
|
||||
"password": "Contrasenya",
|
||||
"password-strength": "Fortalesa de la contrasenya: {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Si us plau, entra la teva nova contrasenya.",
|
||||
"register": "Registreu-vos",
|
||||
"reset-password": "Restableix la contrasenya",
|
||||
"sign-in": "Inicia sessió",
|
||||
@ -852,7 +853,7 @@
|
||||
"username": "Nom d'usuari",
|
||||
"users-header": "USUARIS",
|
||||
"users": "Usuaris",
|
||||
"user-not-found": "User not found",
|
||||
"user-not-found": "No s'ha trobat l'usuari",
|
||||
"webhook-time": "Hora del Webhook",
|
||||
"webhooks-enabled": "Webhooks habilitats",
|
||||
"you-are-not-allowed-to-create-a-user": "Vostè no està autoritzat per a crear un usuari",
|
||||
@ -860,24 +861,24 @@
|
||||
"enable-advanced-content": "Habilita el contingut avançat",
|
||||
"enable-advanced-content-description": "Habilita les funcions avançades com ara multiplicar els ingredients, claus API, Webhooks i la gestió de les dades. Pots tornar a canviar aquesta configuració més tard",
|
||||
"favorite-recipes": "Receptes preferides",
|
||||
"email-or-username": "Email or Username",
|
||||
"remember-me": "Remember Me",
|
||||
"please-enter-your-email-and-password": "Please enter your email and password",
|
||||
"invalid-credentials": "Invalid Credentials",
|
||||
"account-locked-please-try-again-later": "Account Locked. Please try again later",
|
||||
"user-favorites": "User Favorites",
|
||||
"email-or-username": "Correu electrònic o nom d'usuari",
|
||||
"remember-me": "Recorda'm",
|
||||
"please-enter-your-email-and-password": "Si us plau, introdueix el teu correu electrònic i la teva contrasenya",
|
||||
"invalid-credentials": "Credencials no vàlides",
|
||||
"account-locked-please-try-again-later": "Compte bloquejat, Si us plau, prova-ho més tard",
|
||||
"user-favorites": "Favorits de l'usuari",
|
||||
"password-strength-values": {
|
||||
"weak": "Weak",
|
||||
"good": "Good",
|
||||
"strong": "Strong",
|
||||
"very-strong": "Very Strong"
|
||||
"weak": "Dèbil",
|
||||
"good": "Bona",
|
||||
"strong": "Forta",
|
||||
"very-strong": "Molt forta"
|
||||
},
|
||||
"user-management": "User Management",
|
||||
"reset-locked-users": "Reset Locked Users",
|
||||
"admin-user-creation": "Admin User Creation",
|
||||
"user-management": "Gestió d'usuaris",
|
||||
"reset-locked-users": "Reinicia els usuaris bloquejats",
|
||||
"admin-user-creation": "Creació d'un usuari administrador",
|
||||
"admin-user-management": "Admin User Management",
|
||||
"user-details": "User Details",
|
||||
"user-name": "User Name",
|
||||
"user-details": "Detalls de l'usuari",
|
||||
"user-name": "Nom de l'usuari",
|
||||
"authentication-method": "Authentication Method",
|
||||
"authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie",
|
||||
"permissions": "Permissions",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Klíčové slovo",
|
||||
"link-copied": "Odkaz zkopírován",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Načítání událostí",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Nøgleord",
|
||||
"link-copied": "Link kopieret",
|
||||
"loading": "Indlæser",
|
||||
"loading-events": "Indlæser hændelser",
|
||||
"loading-recipe": "Indlæser opskrift...",
|
||||
"loading-ocr-data": "Indlæser OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Schlüsselwort",
|
||||
"link-copied": "Link kopiert",
|
||||
"loading": "Wird geladen...",
|
||||
"loading-events": "Ereignisse werden geladen",
|
||||
"loading-recipe": "Lade Rezept...",
|
||||
"loading-ocr-data": "Lade OCR-Daten...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Λέξη-κλειδί",
|
||||
"link-copied": "Ο Σύνδεσμος Αντιγράφηκε",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Keyword",
|
||||
"link-copied": "Link Copied",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Keyword",
|
||||
"link-copied": "Link Copied",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Etiqueta",
|
||||
"link-copied": "Enlace copiado",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Cargando Eventos",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Hakusana",
|
||||
"link-copied": "Linkki kopioitu",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Ladataan tapahtumia",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Mot-clé",
|
||||
"link-copied": "Lien copié",
|
||||
"loading": "Chargement",
|
||||
"loading-events": "Chargement des événements",
|
||||
"loading-recipe": "Chargement de la recette...",
|
||||
"loading-ocr-data": "Chargement des données OCR...",
|
||||
@ -875,7 +876,7 @@
|
||||
"user-management": "Gestion des utilisateurs",
|
||||
"reset-locked-users": "Réinitialiser les utilisateurs verrouillés",
|
||||
"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-name": "Nom d'utilisateur",
|
||||
"authentication-method": "Méthode d'authentification",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Mot-clé",
|
||||
"link-copied": "Lien copié",
|
||||
"loading": "Chargement",
|
||||
"loading-events": "Chargement des événements",
|
||||
"loading-recipe": "Chargement de la recette...",
|
||||
"loading-ocr-data": "Chargement des données OCR...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Keyword",
|
||||
"link-copied": "Link Copied",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "מילת מפתח",
|
||||
"link-copied": "קישור הועתק",
|
||||
"loading": "Loading",
|
||||
"loading-events": "טוען",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Ključna riječ",
|
||||
"link-copied": "Poveznica kopirana",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Učitavanje događaja",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -77,7 +77,7 @@
|
||||
"tag-events": "Címke események",
|
||||
"category-events": "Kategória események",
|
||||
"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": {
|
||||
"cancel": "Mégsem",
|
||||
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Kulcsszó",
|
||||
"link-copied": "Hivatkozás másolva",
|
||||
"loading": "Betöltés",
|
||||
"loading-events": "Események betöltése",
|
||||
"loading-recipe": "Recept betöltése...",
|
||||
"loading-ocr-data": "OCR adatok betöltése...",
|
||||
@ -590,7 +591,7 @@
|
||||
"import-summary": "Import összefoglaló",
|
||||
"partial-backup": "Részleges biztonsági mentés",
|
||||
"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",
|
||||
"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.",
|
||||
@ -1056,8 +1057,8 @@
|
||||
"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."
|
||||
},
|
||||
"pan-and-zoom-mode": "Pan and Zoom Mode",
|
||||
"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-mode": "Pásztázás és nagyítás mód",
|
||||
"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-modes": {
|
||||
"line-mode": "Vonal mód (alapértelmezett)",
|
||||
@ -1113,7 +1114,7 @@
|
||||
"show-individual-confidence": "",
|
||||
"ingredient-text": "Hozzávaló szöveg",
|
||||
"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ő",
|
||||
"background-tasks": "Háttér folyamatok",
|
||||
"background-tasks-description": "Itt megtekintheti az összes futó háttérfeladatot és azok állapotát",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Parola chiave",
|
||||
"link-copied": "Link Copiato",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Caricamento eventi",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -10,7 +10,7 @@
|
||||
"default-group": "デフォルトグループ",
|
||||
"demo": "体験版",
|
||||
"demo-status": "体験版ステータス",
|
||||
"development": "Development",
|
||||
"development": "開発",
|
||||
"docs": "ドキュメント",
|
||||
"download-log": "ログをダウンロード",
|
||||
"download-recipe-json": "Last Scraped JSON",
|
||||
@ -42,8 +42,8 @@
|
||||
"category-deleted": "カテゴリを削除しました。",
|
||||
"category-deletion-failed": "カテゴリの削除に失敗しました。",
|
||||
"category-filter": "カテゴリフィルタ",
|
||||
"category-update-failed": "Category update failed",
|
||||
"category-updated": "Category updated",
|
||||
"category-update-failed": "カテゴリの更新に失敗しました",
|
||||
"category-updated": "カテゴリーを更新しました",
|
||||
"uncategorized-count": "カテゴリなし {count}",
|
||||
"create-a-category": "カテゴリを作成",
|
||||
"category-name": "カテゴリ名",
|
||||
@ -96,8 +96,8 @@
|
||||
"duplicate": "複製",
|
||||
"edit": "編集",
|
||||
"enabled": "有効",
|
||||
"exception": "Exception",
|
||||
"failed-count": "Failed: {count}",
|
||||
"exception": "例外",
|
||||
"failed-count": "失敗: {count}",
|
||||
"failure-uploading-file": "Failure uploading file",
|
||||
"favorites": "お気に入り",
|
||||
"field-required": "Field Required",
|
||||
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "キーワード",
|
||||
"link-copied": "リンクをコピーしました。",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
@ -172,7 +173,7 @@
|
||||
"id": "Id",
|
||||
"owner": "Owner",
|
||||
"date-added": "追加日",
|
||||
"none": "None",
|
||||
"none": "なし",
|
||||
"run": "Run",
|
||||
"menu": "メニュー",
|
||||
"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."
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
|
||||
"cannot-delete-default-group": "Cannot delete default group",
|
||||
"cannot-delete-group-with-users": "Cannot delete group with users",
|
||||
"are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/> を削除しますか?",
|
||||
"cannot-delete-default-group": "デフォルトのルグループは削除できません",
|
||||
"cannot-delete-group-with-users": "ユーザがあるグループを削除できません",
|
||||
"confirm-group-deletion": "Confirm Group Deletion",
|
||||
"create-group": "Create Group",
|
||||
"error-updating-group": "Error updating group",
|
||||
@ -237,7 +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",
|
||||
"show-nutrition-information": "Show nutrition information",
|
||||
"show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown",
|
||||
"show-recipe-assets": "Show recipe assets",
|
||||
"show-recipe-assets": "レシピアセットを表示",
|
||||
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available",
|
||||
"default-to-landscape-view": "Default to landscape view",
|
||||
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
|
||||
@ -263,16 +264,16 @@
|
||||
"end-date": "End Date",
|
||||
"group": "Group (Beta)",
|
||||
"main": "主菜",
|
||||
"meal-planner": " 献立表",
|
||||
"meal-planner": "献立表",
|
||||
"meal-plans": "献立",
|
||||
"mealplan-categories": "MEALPLAN CATEGORIES",
|
||||
"mealplan-created": "Mealplan created",
|
||||
"mealplan-creation-failed": "Mealplan creation failed",
|
||||
"mealplan-deleted": "Mealplan Deleted",
|
||||
"mealplan-deletion-failed": "Mealplan deletion failed",
|
||||
"mealplan-created": "献立に作成しました",
|
||||
"mealplan-creation-failed": "献立の作成に失敗しました",
|
||||
"mealplan-deleted": "献立を削除しました",
|
||||
"mealplan-deletion-failed": "献立の削除に失敗しました",
|
||||
"mealplan-settings": "献立設定",
|
||||
"mealplan-update-failed": "Mealplan update failed",
|
||||
"mealplan-updated": "Mealplan Updated",
|
||||
"mealplan-update-failed": "献立の更新に失敗しました",
|
||||
"mealplan-updated": "献立を更新しました",
|
||||
"no-meal-plan-defined-yet": "No meal plan defined yet",
|
||||
"no-meal-planned-for-today": "No meal planned for today",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "키워드",
|
||||
"link-copied": "링크 복사됨",
|
||||
"loading": "Loading",
|
||||
"loading-events": "이벤트를 불러오는 중",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Raktažodis",
|
||||
"link-copied": "Nuoroda nukopijuota",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Užkrovimo įvykiai",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Keyword",
|
||||
"link-copied": "Link Copied",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Trefwoord",
|
||||
"link-copied": "Link Gekopieerd",
|
||||
"loading": "Laden",
|
||||
"loading-events": "Gebeurtenis laden",
|
||||
"loading-recipe": "Recepten ophalen...",
|
||||
"loading-ocr-data": "OCR gegevens laden...",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "Wachtwoord bijgewerkt",
|
||||
"password": "Wachtwoord",
|
||||
"password-strength": "Wachtwoord is {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Voeg uw nieuwe wachtwoord in.",
|
||||
"register": "Registreren",
|
||||
"reset-password": "Wachtwoord herstellen",
|
||||
"sign-in": "Inloggen",
|
||||
|
@ -2,14 +2,14 @@
|
||||
"about": {
|
||||
"about": "Om",
|
||||
"about-mealie": "Om Mealie",
|
||||
"api-docs": "API Dokumentasjon",
|
||||
"api-docs": "API dokumentasjon",
|
||||
"api-port": "API port",
|
||||
"application-mode": "Program Modus",
|
||||
"application-mode": "Programmodus",
|
||||
"database-type": "Databasetype",
|
||||
"database-url": "Databasens URL",
|
||||
"default-group": "Standardgruppe",
|
||||
"demo": "Demo",
|
||||
"demo-status": "Demo status",
|
||||
"demo-status": "Demostatus",
|
||||
"development": "Utvikling",
|
||||
"docs": "Dokumentasjon",
|
||||
"download-log": "Nedlastingslogg",
|
||||
@ -38,7 +38,7 @@
|
||||
"category": {
|
||||
"categories": "Kategorier",
|
||||
"category-created": "Kategori opprettet",
|
||||
"category-creation-failed": "Kategori-opprettelse mislyktes",
|
||||
"category-creation-failed": "Kategoriopprettelse mislyktes",
|
||||
"category-deleted": "Kategori slettet",
|
||||
"category-deletion-failed": "Sletting av kategori mislyktes",
|
||||
"category-filter": "Kategori Filter",
|
||||
@ -64,7 +64,7 @@
|
||||
"something-went-wrong": "Noe gikk galt!",
|
||||
"subscribed-events": "Abonnerte hendelser",
|
||||
"test-message-sent": "Testmelding sendt",
|
||||
"new-notification": "Ny Varsel",
|
||||
"new-notification": "Ny varsel",
|
||||
"event-notifiers": "Varsel for hendelse",
|
||||
"apprise-url-skipped-if-blank": "Bruk URL (hopp over hvis den er tom)",
|
||||
"enable-notifier": "Aktiver varsleren",
|
||||
@ -77,7 +77,7 @@
|
||||
"tag-events": "Tagg hendelser",
|
||||
"category-events": "Kategori hendelser",
|
||||
"when-a-new-user-joins-your-group": "Når en ny bruker blir med i gruppen din",
|
||||
"recipe-events": "Recipe Events"
|
||||
"recipe-events": "Oppskrift hendelser"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Avbryt",
|
||||
@ -114,9 +114,10 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Nøkkelord",
|
||||
"link-copied": "Lenke kopiert",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Laster hendelser",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
"loading-recipe": "Laster oppskrift...",
|
||||
"loading-ocr-data": "Laster OCR data...",
|
||||
"loading-recipes": "Laster Oppskrifter",
|
||||
"message": "Melding",
|
||||
"monday": "Mandag",
|
||||
@ -197,7 +198,7 @@
|
||||
"refresh": "Oppdater",
|
||||
"upload-file": "Last opp fil",
|
||||
"created-on-date": "Opprettet: {0}",
|
||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes."
|
||||
"unsaved-changes": "Du har ulagrede endringer. Vil du lagre før du forlater den? Okay å lagre, Avbryt for å forkaste endringer."
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på at du vil slette <b>{groupName}<b/>?",
|
||||
@ -230,7 +231,7 @@
|
||||
"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": "Standard Oppskriftsinnstillinger",
|
||||
"group-preferences": "Gruppe Innstillinger",
|
||||
"group-preferences": "Gruppeinnstillinger",
|
||||
"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.",
|
||||
"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-description": "Skjuler mat, enhet og beløp for ingredienser og behandler ingredienser som rene tekstfelt.",
|
||||
"general-preferences": "Generelle innstillinger",
|
||||
"group-recipe-preferences": "Innstillinger for gruppe-oppskrift",
|
||||
"group-recipe-preferences": "Innstillinger for gruppeoppskrift",
|
||||
"report": "Rapport",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"report-with-id": "Rapport-ID: {id}",
|
||||
"group-management": "Gruppeadministrasjon",
|
||||
"admin-group-management": "Admin gruppe-administrasjon",
|
||||
"admin-group-management": "Admin gruppeadministrasjon",
|
||||
"admin-group-management-text": "Endringer i denne gruppen vil umiddelbart bli reflektert.",
|
||||
"group-id-value": "Gruppe ID: {0}"
|
||||
},
|
||||
@ -263,7 +264,7 @@
|
||||
"end-date": "Sluttdato",
|
||||
"group": "Gruppe (Beta)",
|
||||
"main": "Hovedrett",
|
||||
"meal-planner": "Planlegg Måltid",
|
||||
"meal-planner": "Planlegg måltid",
|
||||
"meal-plans": "Måltidsplan",
|
||||
"mealplan-categories": "MÅLTIDSPLAN KATEGORIER",
|
||||
"mealplan-created": "Måltidsplan opprettet",
|
||||
@ -572,16 +573,16 @@
|
||||
"search-hint": "Trykk på '/'",
|
||||
"advanced": "Avansert",
|
||||
"auto-search": "Autosøk",
|
||||
"no-results": "No results found"
|
||||
"no-results": "Ingen resultater funnet"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Legg til nytt tema",
|
||||
"admin-settings": "Administrator innstillinger",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created": "Sikkerhetskopiering fullført",
|
||||
"backup-created-at-response-export_path": "Sikkerhetskopi opprettet på {path}",
|
||||
"backup-deleted": "Sikkerhetskopi slettet",
|
||||
"restore-success": "Restore successful",
|
||||
"restore-success": "Gjenopprettingen var vellykket",
|
||||
"backup-tag": "Sikkerhetskopi-merke",
|
||||
"create-heading": "Opprett sikkerhetskopi",
|
||||
"delete-backup": "Slett Sikkerhetskopi",
|
||||
@ -690,12 +691,12 @@
|
||||
"configuration": "Konfigurasjon",
|
||||
"docker-volume": "Docker volum",
|
||||
"docker-volume-help": "Mealie krever at frontend og backend konteinerene deler samme docker volum/lagring. Dette sikrer at frontend får tilgang til bilder og ressurser lagret på harddisken.",
|
||||
"volumes-are-misconfigured": "Volumes are misconfigured.",
|
||||
"volumes-are-misconfigured": "Volumene er feilkonfigurert.",
|
||||
"volumes-are-configured-correctly": "Volumene er riktig konfigurert.",
|
||||
"status-unknown-try-running-a-validation": "Statusen er ukjent. Prøv å validere.",
|
||||
"validate": "Valider",
|
||||
"email-configuration-status": "Epost konfigurasjons status",
|
||||
"email-configured": "Email Configured",
|
||||
"email-configuration-status": "E-postkonfigurasjonsstatus",
|
||||
"email-configured": "E-post konfigurert",
|
||||
"email-test-results": "Email Test Results",
|
||||
"ready": "Klar",
|
||||
"not-ready": "Ikke klar - sjekk miljøvariabler",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "Passord oppdatert",
|
||||
"password": "Passord",
|
||||
"password-strength": "Passordet er {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Angi nytt passord.",
|
||||
"register": "Registrér",
|
||||
"reset-password": "Tilbakestill passord",
|
||||
"sign-in": "Logg inn",
|
||||
@ -852,20 +853,20 @@
|
||||
"username": "Brukernavn",
|
||||
"users-header": "BRUKERE",
|
||||
"users": "Brukere",
|
||||
"user-not-found": "User not found",
|
||||
"user-not-found": "Bruker ikke funnet",
|
||||
"webhook-time": "Webhooks Tidsbruk",
|
||||
"webhooks-enabled": "Webhooks aktivert",
|
||||
"you-are-not-allowed-to-create-a-user": "Du har ikke rettigheter til å opprette en bruker",
|
||||
"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-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",
|
||||
"remember-me": "Husk meg",
|
||||
"please-enter-your-email-and-password": "Vennligst angi brukernavnet og passordet ditt",
|
||||
"invalid-credentials": "Ugyldig brukerinformasjon",
|
||||
"account-locked-please-try-again-later": "Kontoen er låst. Prøv igjen senere",
|
||||
"user-favorites": "Bruker favoritter",
|
||||
"user-favorites": "Brukerfavoritter",
|
||||
"password-strength-values": {
|
||||
"weak": "Svak",
|
||||
"good": "God",
|
||||
@ -888,9 +889,9 @@
|
||||
"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å.",
|
||||
"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-text": "Please enter your email address and we will send you a link to reset your password.",
|
||||
"changes-reflected-immediately": "Changes to this user will be reflected immediately."
|
||||
"forgot-password": "Glemt passord",
|
||||
"forgot-password-text": "Skriv inn e-postadressen din. Vi sender deg en e-post slik at du kan tilbakestille passordet ditt.",
|
||||
"changes-reflected-immediately": "Endringer på denne brukeren gjenspeiles umiddelbart."
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "oversatt",
|
||||
@ -953,21 +954,21 @@
|
||||
"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-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",
|
||||
"tag": "Etikett",
|
||||
"categorize": "Kategoriser",
|
||||
"update-settings": "Oppdater innstillinger",
|
||||
"tag-recipes": "Tag Recipes",
|
||||
"categorize-recipes": "Categorize Recipes",
|
||||
"tag-recipes": "Tagg oppskrifter",
|
||||
"categorize-recipes": "Kategoriser oppskrifter",
|
||||
"export-recipes": "Eksporter oppskrift",
|
||||
"delete-recipes": "Slett oppskrifter",
|
||||
"source-unit-will-be-deleted": "Kildeenheten vil bli slettet"
|
||||
},
|
||||
"create-alias": "Create Alias",
|
||||
"manage-aliases": "Manage Aliases",
|
||||
"create-alias": "Opprett alias",
|
||||
"manage-aliases": "Administrer aliaser",
|
||||
"seed-data": "Frødata",
|
||||
"seed": "Seed",
|
||||
"seed": "Nøkkel",
|
||||
"data-management": "Databehandling",
|
||||
"data-management-description": "Velg hvilke data du vil gjøre endringer i.",
|
||||
"select-data": "Velg data",
|
||||
@ -975,31 +976,31 @@
|
||||
"columns": "Kolonner",
|
||||
"combine": "Kombiner",
|
||||
"categories": {
|
||||
"edit-category": "Edit Category",
|
||||
"new-category": "New Category",
|
||||
"category-data": "Category Data"
|
||||
"edit-category": "Rediger kategori",
|
||||
"new-category": "Ny Kategori",
|
||||
"category-data": "Kategoridata"
|
||||
},
|
||||
"tags": {
|
||||
"new-tag": "New Tag",
|
||||
"edit-tag": "Edit Tag",
|
||||
"tag-data": "Tag Data"
|
||||
"new-tag": "Ny tagg",
|
||||
"edit-tag": "Rediger Tag",
|
||||
"tag-data": "Tagg data"
|
||||
},
|
||||
"tools": {
|
||||
"new-tool": "New Tool",
|
||||
"edit-tool": "Edit Tool",
|
||||
"tool-data": "Tool Data"
|
||||
"new-tool": "Nytt verktøy",
|
||||
"edit-tool": "Rediger verktøy",
|
||||
"tool-data": "Verktøydata"
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "Brukerregistrering",
|
||||
"registration-success": "Registration Success",
|
||||
"registration-success": "Registrering vellykket",
|
||||
"join-a-group": "Bli med i en 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.",
|
||||
"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!",
|
||||
"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"
|
||||
},
|
||||
"validation": {
|
||||
@ -1039,7 +1040,7 @@
|
||||
},
|
||||
"ocr-editor": {
|
||||
"ocr-editor": "redigere OCR",
|
||||
"toolbar": "Toolbar",
|
||||
"toolbar": "Verktøylinje",
|
||||
"selection-mode": "Velg modus",
|
||||
"pan-and-zoom-picture": "Panorer og zoom bilde",
|
||||
"split-text": "Splitt tekst",
|
||||
@ -1047,8 +1048,8 @@
|
||||
"split-by-block": "Del på tekstblokk",
|
||||
"flatten": "Flat ut, uavhengig av orginalformatering.",
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"mouse-modes": "Mouse modes",
|
||||
"help": "Hjelp",
|
||||
"mouse-modes": "Musemodus",
|
||||
"selection-mode": "Valgmodus (standard)",
|
||||
"selection-mode-desc": "Utvalgsmodus er hovedmodus som kan brukes til å legge inn data:",
|
||||
"selection-mode-steps": {
|
||||
@ -1081,7 +1082,7 @@
|
||||
"info-description-cleanable-directories": "Cleanable Directories",
|
||||
"info-description-cleanable-images": "Rydbare bilder",
|
||||
"storage": {
|
||||
"title-temporary-directory": "Temporary Directory (.temp)",
|
||||
"title-temporary-directory": "Midlertidig mappe (.temp)",
|
||||
"title-backups-directory": "Backups Directory (backups)",
|
||||
"title-groups-directory": "Groups Directory (groups)",
|
||||
"title-recipes-directory": "Recipes Directory (recipes)",
|
||||
@ -1094,7 +1095,7 @@
|
||||
"action-clean-temporary-files-name": "Fjern midlertidige filer",
|
||||
"action-clean-temporary-files-description": "Fjerner alle filer og mapper i .temp-mappen",
|
||||
"action-clean-images-name": "Fjern bilder",
|
||||
"action-clean-images-description": "Removes all the images that don't end with .webp",
|
||||
"action-clean-images-description": "Fjerner alle bildene som ikke slutter med .webp",
|
||||
"actions-description": "Maintenance actions are {destructive_in_bold} and should be used with caution. Performing any of these actions is {irreversible_in_bold}.",
|
||||
"actions-description-destructive": "destruktiv",
|
||||
"actions-description-irreversible": "irreversibel",
|
||||
@ -1127,7 +1128,7 @@
|
||||
"get-public-link": "Få offentlig lenke",
|
||||
"account-summary": "Kontosammendrag",
|
||||
"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.",
|
||||
"storage-capacity": "Lagringskapasitet",
|
||||
"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.",
|
||||
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
|
||||
"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.",
|
||||
"notifiers": "Varslere",
|
||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||
"manage-data": "Administrer data",
|
||||
"manage-data-description": "Administrer mat og enheter (flere alternativer kommer snart)",
|
||||
"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",
|
||||
"error-sending-email": "Feil ved sending av e-post",
|
||||
"personal-information": "Personlig Informasjon",
|
||||
@ -1165,16 +1166,16 @@
|
||||
"manage-data-migrations": "Manage Data Migrations"
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"require-all-categories": "Require All Categories",
|
||||
"require-all-tags": "Require All Tags",
|
||||
"require-all-tools": "Require All Tools",
|
||||
"cookbook-name": "Cookbook Name",
|
||||
"cookbook-name": "Navn på kokebok",
|
||||
"cookbook-with-name": "Cookbook {0}"
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Słowo kluczowe",
|
||||
"link-copied": "Odnośnik skopiowany",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Ładowanie wydarzeń",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
@ -469,7 +470,7 @@
|
||||
"add-to-plan": "Dodaj do planu",
|
||||
"add-to-timeline": "Add to Timeline",
|
||||
"recipe-added-to-list": "Przepis dodany do listy",
|
||||
"recipes-added-to-list": "Recipes added to list",
|
||||
"recipes-added-to-list": "Przepisy dodane do listy",
|
||||
"recipe-added-to-mealplan": "Przepis dodany do planu posiłków",
|
||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||
"failed-to-add-recipe-to-mealplan": "Nie udało się dodać przepisu do planu posiłków",
|
||||
@ -495,10 +496,10 @@
|
||||
"public-link": "Link publiczny",
|
||||
"timer": {
|
||||
"kitchen-timer": "Kitchen Timer",
|
||||
"start-timer": "Start Timer",
|
||||
"start-timer": "Włącz minutnik",
|
||||
"pause-timer": "Pause Timer",
|
||||
"resume-timer": "Resume Timer",
|
||||
"stop-timer": "Stop Timer"
|
||||
"stop-timer": "Zatrzymaj minutnik"
|
||||
},
|
||||
"edit-timeline-event": "Edytuj zdarzenie osi czasu",
|
||||
"timeline": "Oś czasu",
|
||||
@ -553,7 +554,7 @@
|
||||
"unit": "Jednostka",
|
||||
"upload-image": "Prześlij obraz",
|
||||
"screen-awake": "Keep Screen Awake",
|
||||
"remove-image": "Remove image"
|
||||
"remove-image": "Usuń obraz"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Wyszukiwanie zaawansowane",
|
||||
@ -578,7 +579,7 @@
|
||||
"add-a-new-theme": "Dodaj nowy motyw",
|
||||
"admin-settings": "Ustawienia administratora",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created": "Kopia zapasowa utworzona pomyślnie",
|
||||
"backup-created-at-response-export_path": "Kopia zapasowa została utworzona w {path}",
|
||||
"backup-deleted": "Kopia zapasowa została usunięta",
|
||||
"restore-success": "Restore successful",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "Hasło zostało zaktualizowane",
|
||||
"password": "Hasło",
|
||||
"password-strength": "Hasło jest {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Wpisz nowe hasło.",
|
||||
"register": "Zarejestruj się",
|
||||
"reset-password": "Zresetuj hasło",
|
||||
"sign-in": "Zaloguj się",
|
||||
@ -852,7 +853,7 @@
|
||||
"username": "Nazwa użytkownika",
|
||||
"users-header": "UŻYTKOWNICY",
|
||||
"users": "Użytkownicy",
|
||||
"user-not-found": "User not found",
|
||||
"user-not-found": "Nie znaleziono użytkownika",
|
||||
"webhook-time": "Czas webhooka",
|
||||
"webhooks-enabled": "Webhooki włączone",
|
||||
"you-are-not-allowed-to-create-a-user": "Nie masz uprawnień do tworzenia użytkowników",
|
||||
@ -975,12 +976,12 @@
|
||||
"columns": "Kolumny",
|
||||
"combine": "Połącz",
|
||||
"categories": {
|
||||
"edit-category": "Edit Category",
|
||||
"new-category": "New Category",
|
||||
"category-data": "Category Data"
|
||||
"edit-category": "Edytuj kategorię",
|
||||
"new-category": "Nowa kategoria",
|
||||
"category-data": "Dane kategorii"
|
||||
},
|
||||
"tags": {
|
||||
"new-tag": "New Tag",
|
||||
"new-tag": "Nowy Tag",
|
||||
"edit-tag": "Edit Tag",
|
||||
"tag-data": "Tag Data"
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
"about-mealie": "Sobre Mealie",
|
||||
"api-docs": "Documentação 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-url": "URL do servidor de banco de dados",
|
||||
"default-group": "Grupo padrão",
|
||||
@ -77,7 +77,7 @@
|
||||
"tag-events": "Eventos de Etiqueta",
|
||||
"category-events": "Eventos de Categoria",
|
||||
"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": {
|
||||
"cancel": "Cancelar",
|
||||
@ -114,9 +114,10 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Palavra chave",
|
||||
"link-copied": "Link Copiado",
|
||||
"loading": "Carregando",
|
||||
"loading-events": "Carregando eventos",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
"loading-recipe": "Carregando receita...",
|
||||
"loading-ocr-data": "Carregando dados de OCR...",
|
||||
"loading-recipes": "Carregando Receitas",
|
||||
"message": "Mensagem",
|
||||
"monday": "Segunda-feira",
|
||||
@ -127,7 +128,7 @@
|
||||
"no-recipe-found": "Nenhuma Receita Encontrada",
|
||||
"ok": "OK",
|
||||
"options": "Opções:",
|
||||
"plural-name": "Plural Name",
|
||||
"plural-name": "Nome Plural",
|
||||
"print": "Imprimir",
|
||||
"print-preferences": "Preferências de impressão",
|
||||
"random": "Aleatório",
|
||||
@ -197,7 +198,7 @@
|
||||
"refresh": "Recarregar",
|
||||
"upload-file": "Enviar arquivo",
|
||||
"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": {
|
||||
"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-name": "Nome do Grupo",
|
||||
"group-not-found": "Grupo não encontrado",
|
||||
"group-token": "Group Token",
|
||||
"group-token": "Token do Grupo",
|
||||
"group-with-value": "Grupo: {groupID}",
|
||||
"groups": "Grupos",
|
||||
"manage-groups": "Gerenciar Grupos",
|
||||
@ -248,7 +249,7 @@
|
||||
"general-preferences": "Preferências Gerais",
|
||||
"group-recipe-preferences": "Preferências de Grupo de Receitas",
|
||||
"report": "Denunciar",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"report-with-id": "ID do Relatório: {id}",
|
||||
"group-management": "Gerenciamento de grupos",
|
||||
"admin-group-management": "Gerenciamento de Grupos Administrativos",
|
||||
"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-timeline": "Adicionar à linha do tempo",
|
||||
"recipe-added-to-list": "Receita adicionada à lista",
|
||||
"recipes-added-to-list": "Recipes added to list",
|
||||
"recipes-added-to-list": "Receitas adicionadas à lista",
|
||||
"recipe-added-to-mealplan": "Receita adicionada ao plano de refeições",
|
||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||
"failed-to-add-recipes-to-list": "Falha ao adicionar receita à lista",
|
||||
"failed-to-add-recipe-to-mealplan": "Falha ao adicionar a receita ao plano de refeições",
|
||||
"yield": "Rendimento",
|
||||
"quantity": "Quantidade",
|
||||
@ -513,7 +514,7 @@
|
||||
"message-key": "Chave de mensagem",
|
||||
"parse": "Analisar",
|
||||
"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",
|
||||
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Receitas com unidades ou alimentos definidos não podem ser analisadas.",
|
||||
"parse-ingredients": "Analisar ingredientes",
|
||||
@ -572,16 +573,16 @@
|
||||
"search-hint": "Pressione '/'",
|
||||
"advanced": "Avançado",
|
||||
"auto-search": "Pesquisa automática",
|
||||
"no-results": "No results found"
|
||||
"no-results": "Nenhum resultado encontrado"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Adicionar um novo tema",
|
||||
"admin-settings": "Configurações de Administrador",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created": "Backup criado com sucesso",
|
||||
"backup-created-at-response-export_path": "Backup criado em {path}",
|
||||
"backup-deleted": "Backup excluído",
|
||||
"restore-success": "Restore successful",
|
||||
"restore-success": "Restauração bem-sucedida",
|
||||
"backup-tag": "Etiqueta de Backup",
|
||||
"create-heading": "Criar um Backup",
|
||||
"delete-backup": "Excluir Backup",
|
||||
@ -690,13 +691,13 @@
|
||||
"configuration": "Configuração",
|
||||
"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.",
|
||||
"volumes-are-misconfigured": "Volumes are misconfigured.",
|
||||
"volumes-are-misconfigured": "Volumes estão mal configurados.",
|
||||
"volumes-are-configured-correctly": "Volumes estão configurados corretamente.",
|
||||
"status-unknown-try-running-a-validation": "Status desconhecido. Tente executar uma validação.",
|
||||
"validate": "Validar",
|
||||
"email-configuration-status": "Status da configuração do e-mail",
|
||||
"email-configured": "Email Configured",
|
||||
"email-test-results": "Email Test Results",
|
||||
"email-configured": "E-mail configurado",
|
||||
"email-test-results": "Resultados do teste de e-mail",
|
||||
"ready": "Pronto",
|
||||
"not-ready": "Não está Pronto - Verificar variáveis ambientais",
|
||||
"succeeded": "Sucesso",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "Senha modificada",
|
||||
"password": "Senha",
|
||||
"password-strength": "Senha é {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Por favor, digite sua nova senha.",
|
||||
"register": "Registre-se",
|
||||
"reset-password": "Alterar senha",
|
||||
"sign-in": "Iniciar sessão",
|
||||
@ -852,7 +853,7 @@
|
||||
"username": "Nome de usuário",
|
||||
"users-header": "USUÁRIOS",
|
||||
"users": "Usuários",
|
||||
"user-not-found": "User not found",
|
||||
"user-not-found": "Usuário não encontrado",
|
||||
"webhook-time": "Hora do Webhook",
|
||||
"webhooks-enabled": "Webhooks ativados",
|
||||
"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",
|
||||
"reset-locked-users": "Redefinir Usuários Bloqueados",
|
||||
"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-name": "Nome do usuário",
|
||||
"authentication-method": "Método de autenticação",
|
||||
@ -886,11 +887,11 @@
|
||||
"user-can-manage-group": "Usuário pode gerenciar o grupo",
|
||||
"user-can-organize-group-data": "Usuário pode organizar dados do grupo",
|
||||
"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.",
|
||||
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!",
|
||||
"forgot-password": "Forgot Password",
|
||||
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.",
|
||||
"changes-reflected-immediately": "Changes to this user will be reflected immediately."
|
||||
"it-looks-like-this-is-your-first-time-logging-in": "Parece que este é seu primeiro login.",
|
||||
"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": "Esqueci minha senha",
|
||||
"forgot-password-text": "Digite seu endereço de e-mail e enviaremos um link para redefinir sua senha.",
|
||||
"changes-reflected-immediately": "As alterações deste usuário serão refletidas imediatamente."
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "traduzido",
|
||||
@ -912,8 +913,8 @@
|
||||
"food-label": "Rótulo da Comida",
|
||||
"edit-food": "Editar Comida",
|
||||
"food-data": "Dados da Comida",
|
||||
"example-food-singular": "ex: Onion",
|
||||
"example-food-plural": "ex: Onions"
|
||||
"example-food-singular": "ex: Cebola",
|
||||
"example-food-plural": "ex: Cebolas"
|
||||
},
|
||||
"units": {
|
||||
"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}",
|
||||
"create-unit": "Criar unidade",
|
||||
"abbreviation": "Abreviação",
|
||||
"plural-abbreviation": "Plural Abbreviation",
|
||||
"plural-abbreviation": "Abreviação Plural",
|
||||
"description": "Descrição",
|
||||
"display-as-fraction": "Exibir como fração",
|
||||
"use-abbreviation": "Usar abreviação",
|
||||
@ -932,10 +933,10 @@
|
||||
"unit-data": "Dados da Unidade",
|
||||
"use-abbv": "Usar abreviação",
|
||||
"fraction": "Fração",
|
||||
"example-unit-singular": "ex: Tablespoon",
|
||||
"example-unit-plural": "ex: Tablespoons",
|
||||
"example-unit-abbreviation-singular": "ex: Tbsp",
|
||||
"example-unit-abbreviation-plural": "ex: Tbsps"
|
||||
"example-unit-singular": "ex: Colher de Sopa",
|
||||
"example-unit-plural": "ex: Colheres de Sopa",
|
||||
"example-unit-abbreviation-singular": "ex: Clsp",
|
||||
"example-unit-abbreviation-plural": "ex: Clssp"
|
||||
},
|
||||
"labels": {
|
||||
"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",
|
||||
"source-unit-will-be-deleted": "Unidade de origem será excluída"
|
||||
},
|
||||
"create-alias": "Create Alias",
|
||||
"manage-aliases": "Manage Aliases",
|
||||
"create-alias": "Criar Apelido",
|
||||
"manage-aliases": "Gerenciar apelidos",
|
||||
"seed-data": "Semear dados",
|
||||
"seed": "Semear",
|
||||
"data-management": "Gerenciamento de dados",
|
||||
@ -975,24 +976,24 @@
|
||||
"columns": "Colunas",
|
||||
"combine": "Combinar",
|
||||
"categories": {
|
||||
"edit-category": "Edit Category",
|
||||
"new-category": "New Category",
|
||||
"category-data": "Category Data"
|
||||
"edit-category": "Editar Categoria",
|
||||
"new-category": "Nova Categoria",
|
||||
"category-data": "Dados da Categoria"
|
||||
},
|
||||
"tags": {
|
||||
"new-tag": "New Tag",
|
||||
"edit-tag": "Edit Tag",
|
||||
"tag-data": "Tag Data"
|
||||
"new-tag": "Nova Tag",
|
||||
"edit-tag": "Editar Tag",
|
||||
"tag-data": "Dados da Tag"
|
||||
},
|
||||
"tools": {
|
||||
"new-tool": "New Tool",
|
||||
"edit-tool": "Edit Tool",
|
||||
"tool-data": "Tool Data"
|
||||
"new-tool": "Nova Ferramenta",
|
||||
"edit-tool": "Editar Ferramenta",
|
||||
"tool-data": "Dados da Ferramenta"
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "Cadastro de usuário",
|
||||
"registration-success": "Registration Success",
|
||||
"registration-success": "Registrado com Sucesso",
|
||||
"join-a-group": "Junte-se a um 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.",
|
||||
@ -1039,7 +1040,7 @@
|
||||
},
|
||||
"ocr-editor": {
|
||||
"ocr-editor": "Editor OCR",
|
||||
"toolbar": "Toolbar",
|
||||
"toolbar": "Barra de Ferramentas",
|
||||
"selection-mode": "Modo de seleção",
|
||||
"pan-and-zoom-picture": "Imagem pan e zoom",
|
||||
"split-text": "Dividir texto",
|
||||
@ -1047,8 +1048,8 @@
|
||||
"split-by-block": "Dividir por bloco de texto",
|
||||
"flatten": "Achatar independentemente da formatação original",
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"mouse-modes": "Mouse modes",
|
||||
"help": "Ajuda",
|
||||
"mouse-modes": "Modos do mouse",
|
||||
"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-steps": {
|
||||
|
@ -3,7 +3,7 @@
|
||||
"about": "Sobre",
|
||||
"about-mealie": "Sobre Mealie",
|
||||
"api-docs": "Documentação de API",
|
||||
"api-port": "Porta de API",
|
||||
"api-port": "Porta da API",
|
||||
"application-mode": "Modo de aplicação",
|
||||
"database-type": "Tipo de Base de Dados",
|
||||
"database-url": "Endereço da Base de Dados",
|
||||
@ -77,7 +77,7 @@
|
||||
"tag-events": "Eventos de Etiquetagem",
|
||||
"category-events": "Eventos de Categoria",
|
||||
"when-a-new-user-joins-your-group": "Quando um novo utilizador entra no seu grupo",
|
||||
"recipe-events": "Recipe Events"
|
||||
"recipe-events": "Eventos de receita"
|
||||
},
|
||||
"general": {
|
||||
"cancel": "Cancelar",
|
||||
@ -114,9 +114,10 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Palavra-chave",
|
||||
"link-copied": "Ligação copiada",
|
||||
"loading": "Loading",
|
||||
"loading-events": "A carregar Eventos",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
"loading-recipe": "A carregar receita...",
|
||||
"loading-ocr-data": "A carregar dados OCR...",
|
||||
"loading-recipes": "A carregar receitas",
|
||||
"message": "Mensagem",
|
||||
"monday": "Segunda-feira",
|
||||
@ -127,7 +128,7 @@
|
||||
"no-recipe-found": "Nenhuma Receita Encontrada",
|
||||
"ok": "OK",
|
||||
"options": "Opções:",
|
||||
"plural-name": "Plural Name",
|
||||
"plural-name": "Nome no Plural",
|
||||
"print": "Imprimir",
|
||||
"print-preferences": "Preferências de impressão",
|
||||
"random": "Aleatório",
|
||||
@ -197,7 +198,7 @@
|
||||
"refresh": "Atualizar",
|
||||
"upload-file": "Carregar ficheiro",
|
||||
"created-on-date": "Criado em: {0}",
|
||||
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes."
|
||||
"unsaved-changes": "Tem alterações por gravar. Quer gravar antes de sair? OK para gravar, Cancelar para descartar alterações."
|
||||
},
|
||||
"group": {
|
||||
"are-you-sure-you-want-to-delete-the-group": "Tem a certeza que quer eliminar <b>{groupName}</b>?",
|
||||
@ -212,7 +213,7 @@
|
||||
"group-id-with-value": "ID do Grupo: {groupID}",
|
||||
"group-name": "Nome do grupo",
|
||||
"group-not-found": "Grupo não encontrado",
|
||||
"group-token": "Group Token",
|
||||
"group-token": "Token do Grupo",
|
||||
"group-with-value": "Grupo: {groupID}",
|
||||
"groups": "Grupos",
|
||||
"manage-groups": "Gerir Grupos",
|
||||
@ -248,7 +249,7 @@
|
||||
"general-preferences": "Preferências Gerais",
|
||||
"group-recipe-preferences": "Agrupar preferências de receita",
|
||||
"report": "Relatório",
|
||||
"report-with-id": "Report ID: {id}",
|
||||
"report-with-id": "ID do relatório: {id}",
|
||||
"group-management": "Gestão de Grupos",
|
||||
"admin-group-management": "Gestão do Grupo Admin",
|
||||
"admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.",
|
||||
@ -469,9 +470,9 @@
|
||||
"add-to-plan": "Adicionar ao plano",
|
||||
"add-to-timeline": "Adicionar à Linha Temporal",
|
||||
"recipe-added-to-list": "Receita adicionada à lista",
|
||||
"recipes-added-to-list": "Recipes added to list",
|
||||
"recipes-added-to-list": "Receitas adicionadas à lista",
|
||||
"recipe-added-to-mealplan": "Receita adicionada ao plano de refeições",
|
||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||
"failed-to-add-recipes-to-list": "Erro ao adicionar a receita à lista",
|
||||
"failed-to-add-recipe-to-mealplan": "Erro ao adicionar receita ao plano de refeições",
|
||||
"yield": "Rendimento",
|
||||
"quantity": "Quantidade",
|
||||
@ -513,7 +514,7 @@
|
||||
"message-key": "Chave de Mensagem",
|
||||
"parse": "Interpretar",
|
||||
"attach-images-hint": "Anexe imagens arrastando e soltando-as no editor",
|
||||
"drop-image": "Drop image",
|
||||
"drop-image": "Remover imagem",
|
||||
"enable-ingredient-amounts-to-use-this-feature": "Ativar para usar esta funcionalidade nas quantidades de ingredientes",
|
||||
"recipes-with-units-or-foods-defined-cannot-be-parsed": "Receitas com unidades ou alimentos definidos não podem ser interpretadas.",
|
||||
"parse-ingredients": "Interpretar ingredientes",
|
||||
@ -572,16 +573,16 @@
|
||||
"search-hint": "Prima '/'",
|
||||
"advanced": "Avançado",
|
||||
"auto-search": "Pesquisa Automática",
|
||||
"no-results": "No results found"
|
||||
"no-results": "Nenhum resultado encontrado"
|
||||
},
|
||||
"settings": {
|
||||
"add-a-new-theme": "Adicionar novo tema",
|
||||
"admin-settings": "Definições de Administrador",
|
||||
"backup": {
|
||||
"backup-created": "Backup created successfully",
|
||||
"backup-created": "Backup realizado com sucesso",
|
||||
"backup-created-at-response-export_path": "Backup criado em {path}",
|
||||
"backup-deleted": "Backup eliminado",
|
||||
"restore-success": "Restore successful",
|
||||
"restore-success": "Restauro bem-sucedido",
|
||||
"backup-tag": "Cópia de segurança de Etiqueta",
|
||||
"create-heading": "Criar um Backup",
|
||||
"delete-backup": "Eliminar Backup",
|
||||
@ -690,13 +691,13 @@
|
||||
"configuration": "Configuração",
|
||||
"docker-volume": "Volume do Docker",
|
||||
"docker-volume-help": "O Mealie requer que o contentor do frontend e do backend partilhem o mesmo volume ou armazenamento do docker. Isso garante que o contentor do frontend pode aceder corretamente às imagens e recursos armazenados no disco.",
|
||||
"volumes-are-misconfigured": "Volumes are misconfigured.",
|
||||
"volumes-are-misconfigured": "Os volumes estão mal configurados.",
|
||||
"volumes-are-configured-correctly": "Os volumes estão configurados corretamente.",
|
||||
"status-unknown-try-running-a-validation": "Estado desconhecido. Tente executar uma validação.",
|
||||
"validate": "Validar",
|
||||
"email-configuration-status": "Estado de configuração do correio eletrónico",
|
||||
"email-configured": "Email Configured",
|
||||
"email-test-results": "Email Test Results",
|
||||
"email-configured": "Email configurado",
|
||||
"email-test-results": "Resultados do Teste de Email",
|
||||
"ready": "Pronto",
|
||||
"not-ready": "Não Pronto — Verificar Variáveis de Ambiente",
|
||||
"succeeded": "Sucesso",
|
||||
@ -831,7 +832,7 @@
|
||||
"password-updated": "Palavra-passe atualizada",
|
||||
"password": "Palavra-passe",
|
||||
"password-strength": "A palavra-passe é {strength}",
|
||||
"please-enter-password": "Please enter your new password.",
|
||||
"please-enter-password": "Por favor, introduza a sua nova palavra-passe.",
|
||||
"register": "Registar",
|
||||
"reset-password": "Repor Palavra-passe",
|
||||
"sign-in": "Inscreva-se",
|
||||
@ -852,7 +853,7 @@
|
||||
"username": "Nome de utilizador",
|
||||
"users-header": "UTILIZADORES",
|
||||
"users": "Utilizadores",
|
||||
"user-not-found": "User not found",
|
||||
"user-not-found": "Utilizador não encontrado",
|
||||
"webhook-time": "Hora do Webhook",
|
||||
"webhooks-enabled": "Webhooks ativados",
|
||||
"you-are-not-allowed-to-create-a-user": "Não tem permissão para criar um utilizador",
|
||||
@ -875,7 +876,7 @@
|
||||
"user-management": "Gestão de utilizadores",
|
||||
"reset-locked-users": "Reiniciar utilizadores bloqueados",
|
||||
"admin-user-creation": "Criação do Utilizador Administrador",
|
||||
"admin-user-management": "Admin User Management",
|
||||
"admin-user-management": "Gestão do Grupo Admin",
|
||||
"user-details": "Detalhes do Utilizador",
|
||||
"user-name": "Nome do Utilizador",
|
||||
"authentication-method": "Método de Autenticação",
|
||||
@ -886,11 +887,11 @@
|
||||
"user-can-manage-group": "O utilizador pode gerir o grupo",
|
||||
"user-can-organize-group-data": "O utilizador pode organizar dados do grupo",
|
||||
"enable-advanced-features": "Habilitar recursos avançados",
|
||||
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.",
|
||||
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!",
|
||||
"forgot-password": "Forgot Password",
|
||||
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.",
|
||||
"changes-reflected-immediately": "Changes to this user will be reflected immediately."
|
||||
"it-looks-like-this-is-your-first-time-logging-in": "Parece que este é o seu primeiro login.",
|
||||
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Não quer voltar a ver isto? Não se esqueça de alterar o seu email nas suas definições de utilizador!",
|
||||
"forgot-password": "Esqueceu-se da palavra-passe",
|
||||
"forgot-password-text": "Por favor, digite o seu endereço de email para enviarmos um link para redefinir a sua palavra-passe.",
|
||||
"changes-reflected-immediately": "As alterações a este utilizador serão aplicadas imediatamente."
|
||||
},
|
||||
"language-dialog": {
|
||||
"translated": "traduzido",
|
||||
@ -912,8 +913,8 @@
|
||||
"food-label": "Rótulo de Alimento",
|
||||
"edit-food": "Editar Alimento",
|
||||
"food-data": "Dados do Alimento",
|
||||
"example-food-singular": "ex: Onion",
|
||||
"example-food-plural": "ex: Onions"
|
||||
"example-food-singular": "ex: Cebola",
|
||||
"example-food-plural": "ex: Cebolas"
|
||||
},
|
||||
"units": {
|
||||
"seed-dialog-text": "Popule a base de dados com unidades comuns no seu idioma.",
|
||||
@ -924,7 +925,7 @@
|
||||
"merging-unit-into-unit": "A juntar {0} com {1}",
|
||||
"create-unit": "Criar Unidade",
|
||||
"abbreviation": "Abreviatura",
|
||||
"plural-abbreviation": "Plural Abbreviation",
|
||||
"plural-abbreviation": "Abreviatura no Plural",
|
||||
"description": "Descrição",
|
||||
"display-as-fraction": "Mostrar como fração",
|
||||
"use-abbreviation": "Usar abreviatura",
|
||||
@ -932,10 +933,10 @@
|
||||
"unit-data": "Dados da Unidade",
|
||||
"use-abbv": "Usar Abrev.",
|
||||
"fraction": "Fração",
|
||||
"example-unit-singular": "ex: Tablespoon",
|
||||
"example-unit-plural": "ex: Tablespoons",
|
||||
"example-unit-abbreviation-singular": "ex: Tbsp",
|
||||
"example-unit-abbreviation-plural": "ex: Tbsps"
|
||||
"example-unit-singular": "ex: Colher de Sopa",
|
||||
"example-unit-plural": "ex: Colheres de Sopa",
|
||||
"example-unit-abbreviation-singular": "ex: Cdsp",
|
||||
"example-unit-abbreviation-plural": "ex: Cdsps"
|
||||
},
|
||||
"labels": {
|
||||
"seed-dialog-text": "Adicionar à base de dados rótulos comuns no seu idioma local.",
|
||||
@ -964,8 +965,8 @@
|
||||
"delete-recipes": "Eliminar Receitas",
|
||||
"source-unit-will-be-deleted": "Unidade de origem será eliminada"
|
||||
},
|
||||
"create-alias": "Create Alias",
|
||||
"manage-aliases": "Manage Aliases",
|
||||
"create-alias": "Criar Pseudónimo",
|
||||
"manage-aliases": "Gerir Pseudónimos",
|
||||
"seed-data": "Gerar dados",
|
||||
"seed": "Gerar",
|
||||
"data-management": "Gestão de dados",
|
||||
@ -975,24 +976,24 @@
|
||||
"columns": "Colunas",
|
||||
"combine": "Combinar",
|
||||
"categories": {
|
||||
"edit-category": "Edit Category",
|
||||
"new-category": "New Category",
|
||||
"category-data": "Category Data"
|
||||
"edit-category": "Editar Categoria",
|
||||
"new-category": "Nova Categoria",
|
||||
"category-data": "Dados de Categoria"
|
||||
},
|
||||
"tags": {
|
||||
"new-tag": "New Tag",
|
||||
"edit-tag": "Edit Tag",
|
||||
"tag-data": "Tag Data"
|
||||
"new-tag": "Nova etiqueta",
|
||||
"edit-tag": "Editar Etiqueta",
|
||||
"tag-data": "Dados de Etiqueta"
|
||||
},
|
||||
"tools": {
|
||||
"new-tool": "New Tool",
|
||||
"edit-tool": "Edit Tool",
|
||||
"tool-data": "Tool Data"
|
||||
"new-tool": "Novo Utensílio",
|
||||
"edit-tool": "Editar Utensílio",
|
||||
"tool-data": "Dados do Utensílio"
|
||||
}
|
||||
},
|
||||
"user-registration": {
|
||||
"user-registration": "Registo de Utilizador",
|
||||
"registration-success": "Registration Success",
|
||||
"registration-success": "Registo Bem-sucedido",
|
||||
"join-a-group": "Juntar-se a um grupo",
|
||||
"create-a-new-group": "Criar um Novo Grupo",
|
||||
"provide-registration-token-description": "Por favor, forneça o token de registo associado ao grupo a que gostaria de aderir. Terá de o obter de um membro atual do grupo.",
|
||||
@ -1039,7 +1040,7 @@
|
||||
},
|
||||
"ocr-editor": {
|
||||
"ocr-editor": "Editor OCR",
|
||||
"toolbar": "Toolbar",
|
||||
"toolbar": "Barra de ferramentas",
|
||||
"selection-mode": "Modo de seleção",
|
||||
"pan-and-zoom-picture": "Deslocar e ampliar imagem",
|
||||
"split-text": "Dividir texto",
|
||||
@ -1047,8 +1048,8 @@
|
||||
"split-by-block": "Dividir por bloco de texto",
|
||||
"flatten": "Nivelar independentemente da formatação original",
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"mouse-modes": "Mouse modes",
|
||||
"help": "Ajuda",
|
||||
"mouse-modes": "Modos do rato",
|
||||
"selection-mode": "Modo de Seleção (padrão)",
|
||||
"selection-mode-desc": "O modo de seleção é o modo principal disponível para inserir dados:",
|
||||
"selection-mode-steps": {
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Cuvânt cheie",
|
||||
"link-copied": "Link copiat",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Se încarcă evenimentele",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Ключевое слово",
|
||||
"link-copied": "Ссылка скопирована",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Загрузка событий",
|
||||
"loading-recipe": "Загрузка рецепта...",
|
||||
"loading-ocr-data": "Загрузка данных OCR...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Kľučové slovo",
|
||||
"link-copied": "Odkaz bol skopírovaný",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Načítanie udalostí",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Ključna beseda",
|
||||
"link-copied": "Povezava kopirana",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Ključna reč",
|
||||
"link-copied": "Линк је копиран",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Учитавање догађаја",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Nyckelord",
|
||||
"link-copied": "Länk kopierad",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Laddar händelser",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Anahtar Kelime",
|
||||
"link-copied": "Bağlantı Kopyalandı",
|
||||
"loading": "Yükleniyor",
|
||||
"loading-events": "Etkinlikler yükleniyor",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "OCR verileri yükleniyor...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Ключове слово",
|
||||
"link-copied": "Посилання скопійовано",
|
||||
"loading": "Завантаження",
|
||||
"loading-events": "Завантаження подій",
|
||||
"loading-recipe": "Завантаження рецепта...",
|
||||
"loading-ocr-data": "Завантаження даних OCR...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "Keyword",
|
||||
"link-copied": "Link Copied",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "关键字",
|
||||
"link-copied": "链接已复制",
|
||||
"loading": "Loading",
|
||||
"loading-events": "正在加载事件",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -114,6 +114,7 @@
|
||||
"json": "JSON",
|
||||
"keyword": "關鍵字",
|
||||
"link-copied": "已複製連結",
|
||||
"loading": "Loading",
|
||||
"loading-events": "Loading Events",
|
||||
"loading-recipe": "Loading recipe...",
|
||||
"loading-ocr-data": "Loading OCR data...",
|
||||
|
@ -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>
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<v-container fluid class="narrow-container">
|
||||
<!-- Image -->
|
||||
<BasePageTitle divider>
|
||||
<template #header>
|
||||
<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>
|
||||
</BasePageTitle>
|
||||
|
||||
<!-- Bug Report -->
|
||||
<BaseDialog v-model="bugReportDialog" :title="$t('settings.bug-report')" :width="800" :icon="$globals.icons.github">
|
||||
<v-card-text>
|
||||
<div class="pb-4">
|
||||
@ -27,7 +29,6 @@
|
||||
<BaseButton
|
||||
color="info"
|
||||
@click="
|
||||
dockerValidate();
|
||||
bugReportDialog = true;
|
||||
"
|
||||
>
|
||||
@ -36,6 +37,7 @@
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<!-- Configuration -->
|
||||
<section>
|
||||
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$tc('settings.configuration')"> </BaseCardSectionTitle>
|
||||
<v-card class="mb-4">
|
||||
@ -60,40 +62,7 @@
|
||||
</v-card>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<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>
|
||||
|
||||
<!-- Email -->
|
||||
<section>
|
||||
<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">
|
||||
@ -130,40 +99,47 @@
|
||||
<section class="mt-4">
|
||||
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$tc('settings.general-about')"> </BaseCardSectionTitle>
|
||||
<v-card class="mb-4">
|
||||
<template v-for="(property, idx) in appInfo">
|
||||
<v-list-item :key="property.name">
|
||||
<v-list-item-icon>
|
||||
<v-icon> {{ property.icon || $globals.icons.user }} </v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
<div>{{ property.name }}</div>
|
||||
</v-list-item-title>
|
||||
<template v-if="property.slot === 'recipe-scraper'">
|
||||
<v-list-item-subtitle>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="`https://github.com/hhursev/recipe-scrapers/releases/tag/${property.value}`"
|
||||
>
|
||||
<template v-if="appInfo && appInfo.length">
|
||||
<template v-for="(property, idx) in appInfo">
|
||||
<v-list-item :key="property.name">
|
||||
<v-list-item-icon>
|
||||
<v-icon> {{ property.icon || $globals.icons.user }} </v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
<div>{{ property.name }}</div>
|
||||
</v-list-item-title>
|
||||
<template v-if="property.slot === 'recipe-scraper'">
|
||||
<v-list-item-subtitle>
|
||||
<a
|
||||
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 }}
|
||||
</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 }}
|
||||
</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>
|
||||
</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>
|
||||
<template v-else>
|
||||
<div class="mb-3 text-center">
|
||||
<AppLoader :waiting-text="$tc('general.loading')" />
|
||||
</div>
|
||||
</template>
|
||||
</v-card>
|
||||
</section>
|
||||
@ -186,6 +162,7 @@ import { useAdminApi, useUserApi } from "~/composables/api";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
import { CheckAppConfig } from "~/lib/api/types/admin";
|
||||
import AppLoader from "~/components/global/AppLoader.vue";
|
||||
|
||||
enum DockerVolumeState {
|
||||
Unknown = "unknown",
|
||||
@ -208,294 +185,230 @@ interface CheckApp extends CheckAppConfig {
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
layout: "admin",
|
||||
setup() {
|
||||
// ==========================================================
|
||||
// Docker Volume Validation
|
||||
const docker = reactive({
|
||||
loading: false,
|
||||
state: DockerVolumeState.Unknown,
|
||||
});
|
||||
|
||||
async function dockerValidate() {
|
||||
docker.loading = true;
|
||||
|
||||
// Do API Check
|
||||
const { data } = await adminApi.about.checkDocker();
|
||||
if (data == null) {
|
||||
docker.state = DockerVolumeState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
components: { AppLoader },
|
||||
layout: "admin",
|
||||
setup() {
|
||||
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:";
|
||||
}
|
||||
}
|
||||
state.loading = false;
|
||||
state.tested = true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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>
|
||||
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
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 { useAsyncKey } from "~/composables/use-utils";
|
||||
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
|
||||
import { usePublicExploreApi } from "~/composables/api/api-client";
|
||||
import { useRecipe } from "~/composables/recipes";
|
||||
@ -15,14 +17,13 @@ import { Recipe } from "~/lib/api/types/recipe";
|
||||
export default defineComponent({
|
||||
components: { RecipePage },
|
||||
setup() {
|
||||
const { $auth } = useContext();
|
||||
const { $auth } = useContext();
|
||||
const { isOwnGroup } = useLoggedInState();
|
||||
const { title } = useMeta();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const slug = route.value.params.slug;
|
||||
|
||||
const { title } = useMeta();
|
||||
|
||||
let recipe = ref<Recipe | null>(null);
|
||||
if (isOwnGroup.value) {
|
||||
const { recipe: data } = useRecipe(slug);
|
||||
@ -32,28 +33,28 @@ export default defineComponent({
|
||||
const api = usePublicExploreApi(groupSlug.value);
|
||||
recipe = useAsync(async () => {
|
||||
const { data, error } = await api.explore.recipes.getOne(slug);
|
||||
|
||||
if (error) {
|
||||
console.error("error loading recipe -> ", error);
|
||||
router.push(`/g/${groupSlug.value}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
}, useAsyncKey())
|
||||
}
|
||||
|
||||
title.value = recipe.value?.name || "";
|
||||
whenever(
|
||||
() => recipe.value,
|
||||
() => {
|
||||
if (recipe.value) {
|
||||
title.value = recipe.value.name;
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
recipe,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
if (this.recipe) {
|
||||
return {
|
||||
title: this.recipe.name
|
||||
}
|
||||
}
|
||||
}
|
||||
head: {},
|
||||
});
|
||||
</script>
|
||||
|
@ -103,7 +103,11 @@ export default defineComponent({
|
||||
if (refreshTags) {
|
||||
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({
|
||||
|
@ -6,7 +6,6 @@
|
||||
</template>
|
||||
<template #title> {{ $t('data-pages.data-management') }} </template>
|
||||
{{ $t('data-pages.data-management-description') }}
|
||||
<BannerExperimental class="mt-5"></BannerExperimental>
|
||||
<template #content>
|
||||
<div>
|
||||
<BaseOverflowButton
|
||||
|
@ -121,7 +121,7 @@
|
||||
<template #icon>
|
||||
{{ $globals.icons.database }}
|
||||
</template>
|
||||
{{ $t('general.import') }}}
|
||||
{{ $t('general.import') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
color="info"
|
||||
|
@ -24,8 +24,8 @@
|
||||
</v-date-picker>
|
||||
</v-menu>
|
||||
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<v-tabs>
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-2">
|
||||
<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/edit`">{{ $t('general.edit') }}</v-tab>
|
||||
</v-tabs>
|
||||
|
@ -21,7 +21,7 @@
|
||||
{{ $d(Date.parse(item.timestamp), "short") }}
|
||||
</template>
|
||||
<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>
|
||||
</v-data-table>
|
||||
</v-container>
|
||||
|
@ -55,7 +55,7 @@
|
||||
<p>{{ $t('profile.account-summary-description') }}</p>
|
||||
</div>
|
||||
<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-title class="headline pb-0"> {{ $t('profile.group-statistics') }} </v-card-title>
|
||||
<v-card-text class="py-0">
|
||||
@ -75,22 +75,6 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</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>
|
||||
</section>
|
||||
<v-divider class="my-7"></v-divider>
|
||||
@ -344,33 +328,8 @@ export default defineComponent({
|
||||
return statsTo.value[key] ?? "unknown";
|
||||
}
|
||||
|
||||
const storage = useAsync(async () => {
|
||||
const { data } = await api.groups.storage();
|
||||
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
}, useAsyncKey());
|
||||
|
||||
const storageUsedPercentage = computed(() => {
|
||||
if (!storage.value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (storage.value?.usedStorageBytes / storage.value?.totalStorageBytes) * 100 ?? 0;
|
||||
});
|
||||
|
||||
const storageText = computed(() => {
|
||||
if (!storage.value) {
|
||||
return "Loading...";
|
||||
}
|
||||
return `${storage.value.usedStorageStr} / ${storage.value.totalStorageStr}`;
|
||||
});
|
||||
|
||||
return {
|
||||
groupSlug,
|
||||
storageText,
|
||||
storageUsedPercentage,
|
||||
getStatsTitle,
|
||||
getStatsIcon,
|
||||
getStatsTo,
|
||||
|
4
makefile
4
makefile
@ -122,8 +122,8 @@ frontend-lint: ## 🧺 Run yarn lint
|
||||
# -----------------------------------------------------------------------------
|
||||
# Docker makefile
|
||||
|
||||
docker/prod: ## 🐳 Build and Start Docker Production Stack
|
||||
cd docker && docker-compose -f docker-compose.yml -p mealie up --build
|
||||
prod: ## 🐳 Build and Start Docker Production Stack
|
||||
cd docker && docker compose -f docker-compose.yml -p mealie up --build
|
||||
|
||||
generate:
|
||||
poetry run python dev/code-generation/main.py
|
||||
|
50
mealie/db/fixes/fix_group_with_no_name.py
Normal file
50
mealie/db/fixes/fix_group_with_no_name.py
Normal 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
|
@ -13,7 +13,7 @@ def fix_slug_food_names(db: AllRepositories):
|
||||
logger = root_logger.get_logger("init_db")
|
||||
|
||||
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
|
||||
|
||||
all_foods = db.ingredient_foods.get_all()
|
||||
|
@ -10,6 +10,7 @@ from alembic.runtime import migration
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import get_app_settings
|
||||
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.repos.all_repositories import get_repositories
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
@ -104,6 +105,7 @@ def main():
|
||||
init_db(db)
|
||||
|
||||
safe_try(lambda: fix_slug_food_names(db))
|
||||
safe_try(lambda: fix_group_with_no_name(session))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -6,7 +6,7 @@
|
||||
"unique-name-error": "Recipe names must be unique"
|
||||
},
|
||||
"mealplan": {
|
||||
"no-recipes-match-your-rules": "No recipes match your rules"
|
||||
"no-recipes-match-your-rules": "ルールに一致するレシピはありません"
|
||||
},
|
||||
"user": {
|
||||
"user-updated": "ユーザを更新しました。",
|
||||
|
@ -2,37 +2,37 @@
|
||||
"acorn-squash": "acorn squash",
|
||||
"alfalfa-sprouts": "alfalfa sprouts",
|
||||
"anchovies": "anchovies",
|
||||
"apples": "apples",
|
||||
"artichoke": "artichoke",
|
||||
"arugula": "arugula",
|
||||
"apples": "تفاح",
|
||||
"artichoke": "خرشوف",
|
||||
"arugula": "جرجير",
|
||||
"asparagus": "asparagus",
|
||||
"aubergine": "aubergine",
|
||||
"avocado": "avocado",
|
||||
"avocado": "اﻷفوكادو",
|
||||
"bacon": "bacon",
|
||||
"baking-powder": "baking powder",
|
||||
"baking-powder": "مسحوق الخبز",
|
||||
"baking-soda": "baking soda",
|
||||
"baking-sugar": "baking sugar",
|
||||
"baking-sugar": "سكر الخبز",
|
||||
"bar-sugar": "bar sugar",
|
||||
"basil": "basil",
|
||||
"basil": "ريحان",
|
||||
"bell-peppers": "bell peppers",
|
||||
"blackberries": "blackberries",
|
||||
"blackberries": "توت الأسود",
|
||||
"brassicas": "brassicas",
|
||||
"bok-choy": "bok choy",
|
||||
"broccoflower": "broccoflower",
|
||||
"broccoli": "broccoli",
|
||||
"broccoli": "بروكلي",
|
||||
"broccolini": "broccolini",
|
||||
"broccoli-rabe": "broccoli rabe",
|
||||
"brussels-sprouts": "brussels sprouts",
|
||||
"cabbage": "cabbage",
|
||||
"cauliflower": "cauliflower",
|
||||
"cabbage": "كرنب",
|
||||
"cauliflower": "قرنبيط",
|
||||
"chinese-leaves": "chinese leaves",
|
||||
"collard-greens": "collard greens",
|
||||
"kohlrabi": "kohlrabi",
|
||||
"bread": "bread",
|
||||
"bread": "خبز",
|
||||
"breadfruit": "breadfruit",
|
||||
"broad-beans": "broad beans",
|
||||
"brown-sugar": "brown sugar",
|
||||
"butter": "butter",
|
||||
"brown-sugar": "سكر بني",
|
||||
"butter": "زبدة",
|
||||
"butternut-pumpkin": "butternut pumpkin",
|
||||
"butternut-squash": "butternut squash",
|
||||
"cactus-edible": "cactus, edible",
|
||||
@ -40,70 +40,70 @@
|
||||
"cannabis": "cannabis",
|
||||
"capsicum": "capsicum",
|
||||
"caraway": "caraway",
|
||||
"carrot": "carrot",
|
||||
"carrot": "جزر",
|
||||
"castor-sugar": "castor sugar",
|
||||
"cayenne-pepper": "cayenne pepper",
|
||||
"cayenne-pepper": "فلفل الكايين",
|
||||
"celeriac": "celeriac",
|
||||
"celery": "celery",
|
||||
"celery": "كرفس",
|
||||
"cereal-grains": "cereal grains",
|
||||
"rice": "rice",
|
||||
"rice": "أرز",
|
||||
"chard": "chard",
|
||||
"cheese": "cheese",
|
||||
"cheese": "جبن",
|
||||
"chicory": "chicory",
|
||||
"chilli-peppers": "chilli peppers",
|
||||
"chives": "chives",
|
||||
"chocolate": "chocolate",
|
||||
"cilantro": "cilantro",
|
||||
"cinnamon": "cinnamon",
|
||||
"chocolate": "بالشوكولاتة",
|
||||
"cilantro": "كزبرة",
|
||||
"cinnamon": "قرفة",
|
||||
"clarified-butter": "clarified butter",
|
||||
"coconut": "coconut",
|
||||
"coconut-milk": "coconut milk",
|
||||
"coffee": "coffee",
|
||||
"coconut": "جوز الهند",
|
||||
"coconut-milk": "حليب جوز الهند",
|
||||
"coffee": "قهوة",
|
||||
"confectioners-sugar": "confectioners' sugar",
|
||||
"coriander": "coriander",
|
||||
"corn": "corn",
|
||||
"coriander": "كزبرة",
|
||||
"corn": "ذرة",
|
||||
"corn-syrup": "corn syrup",
|
||||
"cottonseed-oil": "cottonseed oil",
|
||||
"courgette": "courgette",
|
||||
"cream-of-tartar": "cream of tartar",
|
||||
"cucumber": "cucumber",
|
||||
"cumin": "cumin",
|
||||
"cucumber": "خيار",
|
||||
"cumin": "كمون",
|
||||
"daikon": "daikon",
|
||||
"dairy-products-and-dairy-substitutes": "dairy products and dairy substitutes",
|
||||
"eggs": "eggs",
|
||||
"eggs": "بيض",
|
||||
"ghee": "ghee",
|
||||
"milk": "milk",
|
||||
"milk": "حليب",
|
||||
"dandelion": "dandelion",
|
||||
"demerara-sugar": "demerara sugar",
|
||||
"dough": "dough",
|
||||
"edible-cactus": "edible cactus",
|
||||
"eggplant": "eggplant",
|
||||
"eggplant": "باذنجان",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
"fish": "سَمَكٌ",
|
||||
"catfish": "catfish ",
|
||||
"cod": "cod",
|
||||
"salt-cod": "salt cod",
|
||||
"salmon": "salmon",
|
||||
"salmon": "سمك السالمون",
|
||||
"skate": "skate",
|
||||
"stockfish": "stockfish",
|
||||
"trout": "trout",
|
||||
"tuna": "tuna",
|
||||
"five-spice-powder": "five spice powder",
|
||||
"flour": "flour",
|
||||
"trout": "سمك السلمون المرقط",
|
||||
"tuna": "تونة",
|
||||
"five-spice-powder": "مسحوق التوابل 5",
|
||||
"flour": "دقيق",
|
||||
"frisee": "frisee",
|
||||
"fructose": "fructose",
|
||||
"fruit": "fruit",
|
||||
"apple": "apple",
|
||||
"oranges": "oranges",
|
||||
"pear": "pear",
|
||||
"tomato": "tomato ",
|
||||
"fruit": "فاكهة",
|
||||
"apple": "تفاح",
|
||||
"oranges": "برتقال",
|
||||
"pear": "كمثرى",
|
||||
"tomato": "طماطم ",
|
||||
"fruit-sugar": "fruit sugar",
|
||||
"garam-masala": "garam masala",
|
||||
"garlic": "garlic",
|
||||
"garlic": "ثوم",
|
||||
"gem-squash": "gem squash",
|
||||
"ginger": "ginger",
|
||||
"giblets": "giblets",
|
||||
@ -115,8 +115,8 @@
|
||||
"green-onion": "green onion",
|
||||
"heart-of-palm": "heart of palm",
|
||||
"hemp": "hemp",
|
||||
"herbs": "herbs",
|
||||
"oregano": "oregano",
|
||||
"herbs": "أعشاب",
|
||||
"oregano": "توابل اوريجانو",
|
||||
"parsley": "parsley",
|
||||
"honey": "honey",
|
||||
"icing-sugar": "icing sugar",
|
||||
@ -136,55 +136,55 @@
|
||||
"beans": "beans",
|
||||
"lentils": "lentils",
|
||||
"lemongrass": "lemongrass",
|
||||
"lettuce": "lettuce",
|
||||
"liver": "liver",
|
||||
"lettuce": "خس",
|
||||
"liver": "كبد",
|
||||
"maple-syrup": "maple syrup",
|
||||
"meat": "meat",
|
||||
"mortadella": "mortadella",
|
||||
"mushroom": "mushroom",
|
||||
"white-mushroom": "white mushroom",
|
||||
"mussels": "mussels",
|
||||
"meat": "لحم",
|
||||
"mortadella": "الموتادلا",
|
||||
"mushroom": "فطر",
|
||||
"white-mushroom": "الفطر الأبيض",
|
||||
"mussels": "بلح البحر",
|
||||
"nori": "nori",
|
||||
"nutmeg": "nutmeg",
|
||||
"nutritional-yeast-flakes": "nutritional yeast flakes",
|
||||
"nuts": "nuts",
|
||||
"nanaimo-bar-mix": "nanaimo bar mix",
|
||||
"octopuses": "octopuses",
|
||||
"oils": "oils",
|
||||
"olive-oil": "olive oil",
|
||||
"oils": "زيوت",
|
||||
"olive-oil": "زيت الزيتون",
|
||||
"okra": "okra",
|
||||
"olive": "olive",
|
||||
"onion-family": "onion family",
|
||||
"onion": "onion",
|
||||
"olive": "زيتون",
|
||||
"onion-family": "عائلة البصل",
|
||||
"onion": "بصل",
|
||||
"scallion": "scallion",
|
||||
"shallot": "shallot",
|
||||
"spring-onion": "spring onion",
|
||||
"spring-onion": "البصل الأخضر",
|
||||
"orange-blossom-water": "orange blossom water",
|
||||
"oysters": "oysters",
|
||||
"oysters": "محار",
|
||||
"panch-puran": "panch puran",
|
||||
"paprika": "paprika",
|
||||
"parsnip": "parsnip",
|
||||
"pepper": "pepper",
|
||||
"peppers": "peppers",
|
||||
"pepper": "فلفل",
|
||||
"peppers": "الفلفل",
|
||||
"plantain": "plantain",
|
||||
"pineapple": "pineapple",
|
||||
"poppy-seeds": "poppy seeds",
|
||||
"potatoes": "potatoes",
|
||||
"poultry": "poultry",
|
||||
"powdered-sugar": "powdered sugar",
|
||||
"pineapple": "أناناس",
|
||||
"poppy-seeds": "بذور الخشخاش",
|
||||
"potatoes": "بطاطس",
|
||||
"poultry": "دواجن",
|
||||
"powdered-sugar": "سكر مسحوق",
|
||||
"pumpkin": "pumpkin",
|
||||
"pumpkin-seeds": "pumpkin seeds",
|
||||
"radish": "radish",
|
||||
"raw-sugar": "raw sugar",
|
||||
"raw-sugar": "السكر الخام",
|
||||
"refined-sugar": "refined sugar",
|
||||
"rice-flour": "rice flour",
|
||||
"rice-flour": "دقيق الأرز",
|
||||
"rock-sugar": "rock sugar",
|
||||
"rum": "rum",
|
||||
"salt": "salt",
|
||||
"seafood": "seafood",
|
||||
"seeds": "seeds",
|
||||
"sesame-seeds": "sesame seeds",
|
||||
"sunflower-seeds": "sunflower seeds",
|
||||
"salt": "ملح",
|
||||
"seafood": "المأكولات البحرية",
|
||||
"seeds": "بذور",
|
||||
"sesame-seeds": "بذور السمسم",
|
||||
"sunflower-seeds": "بذور عباد الشمس",
|
||||
"soda": "soda",
|
||||
"soda-baking": "soda, baking",
|
||||
"soybean": "soybean",
|
||||
|
@ -1,20 +1,20 @@
|
||||
{
|
||||
"acorn-squash": "acorn squash",
|
||||
"alfalfa-sprouts": "alfalfa sprouts",
|
||||
"anchovies": "anchovies",
|
||||
"anchovies": "アンチョビ",
|
||||
"apples": "りんご",
|
||||
"artichoke": "artichoke",
|
||||
"arugula": "arugula",
|
||||
"asparagus": "asparagus",
|
||||
"artichoke": "アーティチョーク",
|
||||
"arugula": "ルッコラ",
|
||||
"asparagus": "アスパラガス",
|
||||
"aubergine": "茄子",
|
||||
"avocado": "アボカド",
|
||||
"bacon": "ベーコン",
|
||||
"baking-powder": "baking powder",
|
||||
"baking-soda": "baking soda",
|
||||
"baking-powder": "ベーキングパウダー",
|
||||
"baking-soda": "重曹",
|
||||
"baking-sugar": "baking sugar",
|
||||
"bar-sugar": "bar sugar",
|
||||
"basil": "バジル",
|
||||
"bell-peppers": "bell peppers",
|
||||
"bell-peppers": "ピーマン",
|
||||
"blackberries": "ブラックベリー",
|
||||
"brassicas": "brassicas",
|
||||
"bok-choy": "bok choy",
|
||||
@ -64,7 +64,7 @@
|
||||
"corn": "トウモロコシ",
|
||||
"corn-syrup": "corn syrup",
|
||||
"cottonseed-oil": "cottonseed oil",
|
||||
"courgette": "courgette",
|
||||
"courgette": "ズッキーニ",
|
||||
"cream-of-tartar": "cream of tartar",
|
||||
"cucumber": "きゅうり",
|
||||
"cumin": "cumin",
|
||||
@ -157,12 +157,12 @@
|
||||
"onion-family": "onion family",
|
||||
"onion": "玉ねぎ",
|
||||
"scallion": "scallion",
|
||||
"shallot": "shallot",
|
||||
"spring-onion": "spring onion",
|
||||
"orange-blossom-water": "orange blossom water",
|
||||
"shallot": "エシャロット",
|
||||
"spring-onion": "ネギ",
|
||||
"orange-blossom-water": "オレンジの花の水",
|
||||
"oysters": "oysters",
|
||||
"panch-puran": "panch puran",
|
||||
"paprika": "paprika",
|
||||
"paprika": "パプリカ",
|
||||
"parsnip": "parsnip",
|
||||
"pepper": "pepper",
|
||||
"peppers": "peppers",
|
||||
@ -179,9 +179,9 @@
|
||||
"refined-sugar": "refined sugar",
|
||||
"rice-flour": "rice flour",
|
||||
"rock-sugar": "rock sugar",
|
||||
"rum": "rum",
|
||||
"rum": "ラム酒",
|
||||
"salt": "塩",
|
||||
"seafood": "seafood",
|
||||
"seafood": "シーフード",
|
||||
"seeds": "seeds",
|
||||
"sesame-seeds": "sesame seeds",
|
||||
"sunflower-seeds": "sunflower seeds",
|
||||
|
@ -104,7 +104,7 @@
|
||||
"fruit-sugar": "cukier z owoców",
|
||||
"garam-masala": "garam masala",
|
||||
"garlic": "czosnek",
|
||||
"gem-squash": "gem squash",
|
||||
"gem-squash": "dynia zielona",
|
||||
"ginger": "imbir",
|
||||
"giblets": "podroby",
|
||||
"grains": "zboże",
|
||||
|
@ -39,7 +39,7 @@
|
||||
"calabrese": "calabresa",
|
||||
"cannabis": "cannabis",
|
||||
"capsicum": "páprica",
|
||||
"caraway": "caraway",
|
||||
"caraway": "cominho",
|
||||
"carrot": "cenoura",
|
||||
"castor-sugar": "açúcar de confeiteiro",
|
||||
"cayenne-pepper": "pimenta caiena",
|
||||
@ -51,7 +51,7 @@
|
||||
"cheese": "queijo",
|
||||
"chicory": "chicória",
|
||||
"chilli-peppers": "pimenta picante",
|
||||
"chives": "chives",
|
||||
"chives": "cebolinha",
|
||||
"chocolate": "chocolate",
|
||||
"cilantro": "coentro",
|
||||
"cinnamon": "canela",
|
||||
@ -68,7 +68,7 @@
|
||||
"cream-of-tartar": "creme de tartar",
|
||||
"cucumber": "pepino",
|
||||
"cumin": "cominho",
|
||||
"daikon": "daikon",
|
||||
"daikon": "rabanete",
|
||||
"dairy-products-and-dairy-substitutes": "produtos lácteos e substitutos de leite",
|
||||
"eggs": "ovos",
|
||||
"ghee": "ghee",
|
||||
|
@ -3,28 +3,28 @@
|
||||
"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"
|
||||
@ -33,10 +33,10 @@
|
||||
"name": "Confectionary"
|
||||
},
|
||||
{
|
||||
"name": "Dairy Products"
|
||||
"name": "منتجات الألبان"
|
||||
},
|
||||
{
|
||||
"name": "Frozen Foods"
|
||||
"name": "الأطعمة المجمدة"
|
||||
},
|
||||
{
|
||||
"name": "Health Foods"
|
||||
@ -45,21 +45,21 @@
|
||||
"name": "Household"
|
||||
},
|
||||
{
|
||||
"name": "Meat Products"
|
||||
"name": "منتجات اللحوم"
|
||||
},
|
||||
{
|
||||
"name": "Snacks"
|
||||
"name": "الوجبات الخفيفة"
|
||||
},
|
||||
{
|
||||
"name": "Spices"
|
||||
"name": "التوابل"
|
||||
},
|
||||
{
|
||||
"name": "Sweets"
|
||||
"name": "الحلويات"
|
||||
},
|
||||
{
|
||||
"name": "Alcohol"
|
||||
"name": "الكحول"
|
||||
},
|
||||
{
|
||||
"name": "Other"
|
||||
"name": "أخرى"
|
||||
}
|
||||
]
|
||||
|
@ -1,15 +1,10 @@
|
||||
import asyncio
|
||||
import random
|
||||
import shutil
|
||||
import string
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks
|
||||
from fastapi import APIRouter
|
||||
from recipe_scrapers import __version__ as recipe_scraper_version
|
||||
|
||||
from mealie.core.release_checker import get_latest_version
|
||||
from mealie.core.settings.static import APP_VERSION
|
||||
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")
|
||||
|
||||
@ -57,25 +52,3 @@ class AdminAboutController(BaseAdminController):
|
||||
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,
|
||||
)
|
||||
|
||||
@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)
|
||||
|
@ -5,6 +5,7 @@ from pathlib import Path
|
||||
from fastapi import APIRouter, File, HTTPException, UploadFile, status
|
||||
|
||||
from mealie.core.config import get_app_dirs
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.core.security import create_file_token
|
||||
from mealie.pkgs.stats.fs_stats import pretty_size
|
||||
from mealie.routes._base import BaseAdminController, controller
|
||||
@ -12,6 +13,7 @@ from mealie.schema.admin.backup import AllBackups, BackupFile
|
||||
from mealie.schema.response.responses import ErrorResponse, FileTokenResponse, SuccessResponse
|
||||
from mealie.services.backups_v2.backup_v2 import BackupSchemaMismatch, BackupV2
|
||||
|
||||
logger = get_logger()
|
||||
router = APIRouter(prefix="/backups")
|
||||
|
||||
|
||||
@ -42,6 +44,7 @@ class AdminBackupController(BaseAdminController):
|
||||
try:
|
||||
backup.backup()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e
|
||||
|
||||
return SuccessResponse.respond("Backup created successfully")
|
||||
@ -106,6 +109,7 @@ class AdminBackupController(BaseAdminController):
|
||||
ErrorResponse.respond("database backup schema version does not match current database"),
|
||||
) from e
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e
|
||||
|
||||
return SuccessResponse.respond("Restore successful")
|
||||
|
@ -1,6 +1,8 @@
|
||||
import json
|
||||
import pathlib
|
||||
from dataclasses import dataclass
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from fastapi import Depends, FastAPI, Response
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@ -16,6 +18,13 @@ from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
||||
|
||||
@dataclass
|
||||
class MetaTag:
|
||||
hid: str
|
||||
property_name: str
|
||||
content: str
|
||||
|
||||
|
||||
class SPAStaticFiles(StaticFiles):
|
||||
async def get_response(self, path: str, scope):
|
||||
try:
|
||||
@ -33,10 +42,51 @@ __app_settings = get_app_settings()
|
||||
__contents = ""
|
||||
|
||||
|
||||
def inject_meta(contents: str, tags: list[MetaTag]) -> str:
|
||||
soup = BeautifulSoup(contents, "lxml")
|
||||
scraped_meta_tags = soup.find_all("meta")
|
||||
|
||||
tags_by_hid = {tag.hid: tag for tag in tags}
|
||||
for scraped_meta_tag in scraped_meta_tags:
|
||||
try:
|
||||
scraped_hid = scraped_meta_tag["data-hid"]
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
if not (matched_tag := tags_by_hid.pop(scraped_hid, None)):
|
||||
continue
|
||||
|
||||
scraped_meta_tag["property"] = matched_tag.property_name
|
||||
scraped_meta_tag["content"] = matched_tag.content
|
||||
|
||||
# add any tags we didn't find
|
||||
if soup.html and soup.html.head:
|
||||
for tag in tags_by_hid.values():
|
||||
html_tag = soup.new_tag(
|
||||
"meta",
|
||||
**{"data-n-head": "1", "data-hid": tag.hid, "property": tag.property_name, "content": tag.content},
|
||||
)
|
||||
soup.html.head.append(html_tag)
|
||||
|
||||
return str(soup)
|
||||
|
||||
|
||||
def inject_recipe_json(contents: str, schema: dict) -> str:
|
||||
schema_as_html_tag = f"""<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:
|
||||
# Inject meta tags
|
||||
recipe_url = f"{__app_settings.BASE_URL}/g/{group_slug}/r/{recipe.slug}"
|
||||
image_url = f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}"
|
||||
if recipe.image:
|
||||
image_url = (
|
||||
f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}"
|
||||
)
|
||||
else:
|
||||
image_url = (
|
||||
"https://raw.githubusercontent.com/hay-kot/mealie/dev/frontend/public/img/icons/android-chrome-512x512.png"
|
||||
)
|
||||
|
||||
ingredients: list[str] = []
|
||||
if recipe.settings.disable_amount: # type: ignore
|
||||
@ -84,20 +134,22 @@ def content_with_meta(group_slug: str, recipe: Recipe) -> str:
|
||||
"nutrition": nutrition,
|
||||
}
|
||||
|
||||
tags = [
|
||||
f'<meta property="og:title" content="{recipe.name}" />',
|
||||
f'<meta property="og:description" content="{recipe.description}" />',
|
||||
f'<meta property="og:image" content="{image_url}" />',
|
||||
f'<meta property="og:url" content="{recipe_url}" />',
|
||||
'<meta name="twitter:card" content="summary_large_image" />',
|
||||
f'<meta name="twitter:title" content="{recipe.name}" />',
|
||||
f'<meta name="twitter:description" content="{recipe.description}" />',
|
||||
f'<meta name="twitter:image" content="{image_url}" />',
|
||||
f'<meta name="twitter:url" content="{recipe_url}" />',
|
||||
f"""<script type="application/ld+json">{json.dumps(jsonable_encoder(as_schema_org))}</script>""",
|
||||
meta_tags = [
|
||||
MetaTag(hid="og:title", property_name="og:title", content=recipe.name or ""),
|
||||
MetaTag(hid="og:description", property_name="og:description", content=recipe.description or ""),
|
||||
MetaTag(hid="og:image", property_name="og:image", content=image_url),
|
||||
MetaTag(hid="og:url", property_name="og:url", content=recipe_url),
|
||||
MetaTag(hid="twitter:card", property_name="twitter:card", content="summary_large_image"),
|
||||
MetaTag(hid="twitter:title", property_name="twitter:title", content=recipe.name or ""),
|
||||
MetaTag(hid="twitter:description", property_name="twitter:description", content=recipe.description or ""),
|
||||
MetaTag(hid="twitter:image", property_name="twitter:image", content=image_url),
|
||||
MetaTag(hid="twitter:url", property_name="twitter:url", content=recipe_url),
|
||||
]
|
||||
|
||||
return __contents.replace("</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():
|
||||
@ -133,7 +185,7 @@ async def serve_recipe_with_meta(
|
||||
user: PrivateUser | None = Depends(try_get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
if not user:
|
||||
if not user or user.group_slug != group_slug:
|
||||
return serve_recipe_with_meta_public(group_slug, recipe_slug, session)
|
||||
|
||||
try:
|
||||
@ -149,6 +201,19 @@ async def serve_recipe_with_meta(
|
||||
return response_404()
|
||||
|
||||
|
||||
async def serve_shared_recipe_with_meta(group_slug: str, token_id: str, session: Session = Depends(generate_session)):
|
||||
try:
|
||||
repos = AllRepositories(session)
|
||||
token_summary = repos.recipe_share_tokens.get_one(token_id)
|
||||
if token_summary is None:
|
||||
raise Exception("Token Not Found")
|
||||
|
||||
return Response(content_with_meta(group_slug, token_summary.recipe), media_type="text/html")
|
||||
|
||||
except Exception:
|
||||
return response_404()
|
||||
|
||||
|
||||
def mount_spa(app: FastAPI):
|
||||
if not os.path.exists(__app_settings.STATIC_FILES):
|
||||
return
|
||||
@ -157,4 +222,5 @@ def mount_spa(app: FastAPI):
|
||||
__contents = pathlib.Path(__app_settings.STATIC_FILES).joinpath("index.html").read_text()
|
||||
|
||||
app.get("/g/{group_slug}/r/{recipe_slug}")(serve_recipe_with_meta)
|
||||
app.get("/g/{group_slug}/shared/r/{token_id}")(serve_shared_recipe_with_meta)
|
||||
app.mount("/", SPAStaticFiles(directory=__app_settings.STATIC_FILES, html=True), name="spa")
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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 .email import EmailReady, EmailSuccess, EmailTest
|
||||
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
|
||||
@ -31,7 +31,6 @@ __all__ = [
|
||||
"AppStatistics",
|
||||
"AppTheme",
|
||||
"CheckAppConfig",
|
||||
"DockerVolumeText",
|
||||
"EmailReady",
|
||||
"EmailSuccess",
|
||||
"EmailTest",
|
||||
|
@ -59,7 +59,3 @@ class CheckAppConfig(MealieModel):
|
||||
ldap_ready: bool
|
||||
base_url_set: bool
|
||||
is_up_to_date: bool
|
||||
|
||||
|
||||
class DockerVolumeText(MealieModel):
|
||||
text: str
|
||||
|
@ -61,7 +61,7 @@ class ChangePassword(MealieModel):
|
||||
|
||||
|
||||
class GroupBase(MealieModel):
|
||||
name: str
|
||||
name: constr(strip_whitespace=True, min_length=1) # type: ignore
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
@ -12,14 +12,17 @@ from mealie.services._base_service import BaseService
|
||||
_FIREFOX_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
|
||||
|
||||
|
||||
async def gather_with_concurrency(n, *coros):
|
||||
async def gather_with_concurrency(n, *coros, ignore_exceptions=False):
|
||||
semaphore = asyncio.Semaphore(n)
|
||||
|
||||
async def sem_coro(coro):
|
||||
async with semaphore:
|
||||
return await coro
|
||||
|
||||
return await asyncio.gather(*(sem_coro(c) for c in coros))
|
||||
results = await asyncio.gather(*(sem_coro(c) for c in coros), return_exceptions=ignore_exceptions)
|
||||
if ignore_exceptions:
|
||||
results = [r for r in results if not isinstance(r, Exception)]
|
||||
return results
|
||||
|
||||
|
||||
async def largest_content_len(urls: list[str]) -> tuple[str, int]:
|
||||
@ -31,7 +34,7 @@ async def largest_content_len(urls: list[str]) -> tuple[str, int]:
|
||||
|
||||
async with AsyncClient() as client:
|
||||
tasks = [do(client, url) for url in urls]
|
||||
responses: list[Response] = await gather_with_concurrency(10, *tasks)
|
||||
responses: list[Response] = await gather_with_concurrency(10, *tasks, ignore_exceptions=True)
|
||||
for response in responses:
|
||||
len_int = int(response.headers.get("Content-Length", 0))
|
||||
if len_int > largest_len:
|
||||
|
@ -9,6 +9,11 @@ from datetime import datetime, timedelta
|
||||
|
||||
from slugify import slugify
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
|
||||
logger = get_logger("recipe-scraper")
|
||||
|
||||
|
||||
MATCH_DIGITS = re.compile(r"\d+([.,]\d+)?")
|
||||
""" Allow for commas as decimals (common in Europe) """
|
||||
|
||||
@ -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
|
||||
|
||||
Supported Structures:
|
||||
- `https://exmaple.com` - A string
|
||||
- `{ "url": "https://exmaple.com" }` - A dictionary with a `url` key
|
||||
- `["https://exmaple.com"]` - A list of strings
|
||||
- `[{ "url": "https://exmaple.com" }]` - A list of dictionaries with a `url` key
|
||||
- `https://example.com` - A string
|
||||
- `{ "url": "https://example.com" }` - A dictionary with a `url` key
|
||||
- `["https://example.com"]` - A list of strings
|
||||
- `[{ "url": "https://example.com" }]` - A list of dictionaries with a `url` key
|
||||
|
||||
Raises:
|
||||
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]
|
||||
case {"url": str(image)}:
|
||||
return [image]
|
||||
case [{"@id": str(_)}, *_]:
|
||||
return [x["@id"] for x in image]
|
||||
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]:
|
||||
@ -335,6 +343,7 @@ def clean_time(time_entry: str | timedelta | None) -> None | str:
|
||||
- `"PT1H"` - returns "1 hour"
|
||||
- `"PT1H30M"` - returns "1 hour 30 minutes"
|
||||
- `timedelta(hours=1, minutes=30)` - returns "1 hour 30 minutes"
|
||||
- `{"minValue": "PT1H30M"}` - returns "1 hour 30 minutes"
|
||||
|
||||
Raises:
|
||||
TypeError: if the type is not supported a TypeError is raised
|
||||
@ -357,11 +366,16 @@ def clean_time(time_entry: str | timedelta | None) -> None | str:
|
||||
return str(time_entry)
|
||||
case timedelta():
|
||||
return pretty_print_timedelta(time_entry)
|
||||
case {"minValue": str(value)}:
|
||||
return clean_time(value)
|
||||
case [str(), *_]:
|
||||
return clean_time(time_entry[0])
|
||||
case datetime():
|
||||
# TODO: Not sure what to do here
|
||||
return str(time_entry)
|
||||
case _:
|
||||
raise TypeError(f"Unexpected type for time: {type(time_entry)}, {time_entry}")
|
||||
logger.warning("[SCRAPER] Unexpected type or structure for time_entrys")
|
||||
return None
|
||||
|
||||
|
||||
def parse_duration(iso_duration: str) -> timedelta:
|
||||
|
@ -1,10 +1,16 @@
|
||||
from asyncio import gather
|
||||
import asyncio
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.recipe.recipe import CreateRecipeByUrlBulk, Recipe
|
||||
from mealie.schema.reports.reports import ReportCategory, ReportCreate, ReportEntryCreate, ReportSummaryStatus
|
||||
from mealie.schema.reports.reports import (
|
||||
ReportCategory,
|
||||
ReportCreate,
|
||||
ReportEntryCreate,
|
||||
ReportEntryOut,
|
||||
ReportSummaryStatus,
|
||||
)
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
from mealie.services._base_service import BaseService
|
||||
from mealie.services.recipe.recipe_service import RecipeService
|
||||
@ -47,6 +53,7 @@ class RecipeBulkScraperService(BaseService):
|
||||
is_success = True
|
||||
is_failure = True
|
||||
|
||||
new_entries: list[ReportEntryOut] = []
|
||||
for entry in self.report_entries:
|
||||
if is_failure and entry.success:
|
||||
is_failure = False
|
||||
@ -54,7 +61,7 @@ class RecipeBulkScraperService(BaseService):
|
||||
if is_success and not entry.success:
|
||||
is_success = False
|
||||
|
||||
self.repos.group_report_entries.create(entry)
|
||||
new_entries.append(self.repos.group_report_entries.create(entry))
|
||||
|
||||
if is_success:
|
||||
self.report.status = ReportSummaryStatus.success
|
||||
@ -65,25 +72,29 @@ class RecipeBulkScraperService(BaseService):
|
||||
if not is_success and not is_failure:
|
||||
self.report.status = ReportSummaryStatus.partial
|
||||
|
||||
self.report.entries = new_entries
|
||||
self.repos.group_reports.update(self.report.id, self.report)
|
||||
|
||||
async def scrape(self, urls: CreateRecipeByUrlBulk) -> None:
|
||||
sem = asyncio.Semaphore(3)
|
||||
|
||||
async def _do(url: str) -> Recipe | None:
|
||||
try:
|
||||
recipe, _ = await create_from_url(url)
|
||||
return recipe
|
||||
except Exception as e:
|
||||
self.service.logger.error(f"failed to scrape url during bulk url import {b.url}")
|
||||
self.service.logger.exception(e)
|
||||
self._add_error_entry(f"failed to scrape url {url}", str(e))
|
||||
return None
|
||||
async with sem:
|
||||
try:
|
||||
recipe, _ = await create_from_url(url)
|
||||
return recipe
|
||||
except Exception as e:
|
||||
self.service.logger.error(f"failed to scrape url during bulk url import {url}")
|
||||
self.service.logger.exception(e)
|
||||
self._add_error_entry(f"failed to scrape url {url}", str(e))
|
||||
return None
|
||||
|
||||
if self.report is None:
|
||||
self.get_report_id()
|
||||
tasks = [_do(b.url) for b in urls.imports]
|
||||
results = await gather(*tasks)
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
for b, recipe in zip(urls.imports, results, strict=True):
|
||||
if not recipe:
|
||||
if not recipe or isinstance(recipe, Exception):
|
||||
continue
|
||||
|
||||
if b.tags:
|
||||
|
@ -172,7 +172,7 @@ class RecipeScraperPackage(ABCScraperStrategy):
|
||||
try:
|
||||
scraped_schema = scrape_html(recipe_html, org_url=self.url)
|
||||
except (NoSchemaFoundInWildMode, AttributeError):
|
||||
self.logger.error("Recipe Scraper was unable to extract a recipe.")
|
||||
self.logger.error(f"Recipe Scraper was unable to extract a recipe from {self.url}")
|
||||
return None
|
||||
|
||||
except ConnectionError as e:
|
||||
@ -208,10 +208,6 @@ class RecipeScraperPackage(ABCScraperStrategy):
|
||||
|
||||
|
||||
class RecipeScraperOpenGraph(ABCScraperStrategy):
|
||||
"""
|
||||
Abstract class for all recipe parsers.
|
||||
"""
|
||||
|
||||
async def get_html(self, url: str) -> str:
|
||||
return await safe_scrape_html(url)
|
||||
|
||||
@ -241,7 +237,7 @@ class RecipeScraperOpenGraph(ABCScraperStrategy):
|
||||
"recipeIngredient": ["Could not detect ingredients"],
|
||||
"recipeInstructions": [{"text": "Could not detect instructions"}],
|
||||
"slug": slugify(og_field(properties, "og:title")),
|
||||
"orgURL": og_field(properties, "og:url"),
|
||||
"orgURL": self.url,
|
||||
"categories": [],
|
||||
"tags": og_fields(properties, "og:article:tag"),
|
||||
"dateAdded": None,
|
||||
|
@ -22,6 +22,8 @@ images_test_image_1 = CWD / "images/test-image-1.jpg"
|
||||
|
||||
images_test_image_2 = CWD / "images/test-image-2.png"
|
||||
|
||||
html_mealie_recipe = CWD / "html/mealie-recipe.html"
|
||||
|
||||
html_sous_vide_smoked_beef_ribs = CWD / "html/sous-vide-smoked-beef-ribs.html"
|
||||
|
||||
html_sous_vide_shrimp = CWD / "html/sous-vide-shrimp.html"
|
||||
|
41
tests/data/html/mealie-recipe.html
Normal file
41
tests/data/html/mealie-recipe.html
Normal 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
Loading…
x
Reference in New Issue
Block a user