Merge branch 'mealie-next' into feat/mealplan-nutrition-filters

This commit is contained in:
Michael Genson 2025-01-19 12:50:47 -06:00 committed by GitHub
commit 323e41a01f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
167 changed files with 5922 additions and 2766 deletions

View file

@ -20,20 +20,6 @@
--> -->
## What type of PR is this?
_(REQUIRED)_
<!--
Delete any of the following that do not apply:
-->
- feature
- bug
- documentation
- cleanup
- dev (Internal development)
## What this PR does / why we need it: ## What this PR does / why we need it:
_(REQUIRED)_ _(REQUIRED)_

View file

@ -12,7 +12,7 @@ repos:
exclude: ^tests/data/ exclude: ^tests/data/
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.8.4 rev: v0.9.0
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format

View file

@ -1,164 +1,268 @@
# Frequently Asked Questions # Frequently Asked Questions
## How do I enable "smart" ingredient handling? ## Features and Functionality
You might have noticed that scaling up a recipe or making a shopping list doesn't by default handle the ingredients in a way you might expect. Depending on your settings, scaling up might yield things like `2 1 cup broth` instead of `2 cup broth`. And, making shopping lists from recipes that have shared ingredients can yield multiple lines of the same ingredient. **But**, Mealie has a mechanism to intelligently handle ingredients and make your day better. How? ??? question "How do I enable 'smart' ingredient handling?"
### Set up your Foods and Units
Do the following just **once**. Doing this applies to your whole group, so be careful.
1. Click on your name in the upper left corner to get to your settings ### How do I enable "smart" ingredient handling?
2. In the bottom right, select `Manage Data`
3. In the Management page, make sure that a little orange button says `Foods`
4. If your Foods database is empty, click `Seed` and choose your language. You should end up with a list of foods. (Wait a bit for seeding to happen, and try not to seed more than once or you will have duplicates)
5. Click the little orange `Foods` button and now choose `Units`.
6. Click `Seed` and choose your language. You should end up with a list of units (e.g. `tablespoon`)
Initial seeding of Units is pretty complete, but there are many Foods in the world. You'll probably find that you need to add Foods to the database during parsing for the first several recipes. Once you have a well-populated Food database, there are API routes to parse ingredients automatically in bulk. But this is not a good idea without a very complete set of Foods. You might have noticed that scaling up a recipe or making a shopping list doesn't by default handle the ingredients in a way you might expect. Depending on your settings, scaling up might yield things like `2 1 cup broth` instead of `2 cup broth`. And, making shopping lists from recipes that have shared ingredients can yield multiple lines of the same ingredient. **But**, Mealie has a mechanism to intelligently handle ingredients and make your day better. How?
### Set up Recipes to use Foods and Units <p style="font-size: 0.75rem; font-weight: 500;">Set up your Foods and Units</p>
Do the following for each recipe you want to intelligently handle ingredients. Do the following just **once**. Doing this applies to your whole group, so be careful.
1. Go to a recipe 1. Click on your name in the upper left corner to get to your settings
2. Click the Edit button/icon 2. In the bottom right, select `Manage Data`
3. Click the Recipe Settings gear and deselect `Disable Ingredient Amounts` 3. In the Management page, make sure that a little orange button says `Foods`
4. Save 4. If your Foods database is empty, click `Seed` and choose your language. You should end up with a list of foods. (Wait a bit for seeding to happen, and try not to seed more than once or you will have duplicates)
5. The ingredients should now look a little weird (`1 1 cup broth` and so on) 5. Click the little orange `Foods` button and now choose `Units`.
6. Click the Edit button/icon again 6. Click `Seed` and choose your language. You should end up with a list of units (e.g. `tablespoon`)
7. Scroll to the ingredients and you should see new fields for Amount, Unit, Food, and Note. The Note in particular will contain the original text of the Recipe.
8. Click `Parse` and you will be taken to the ingredient parsing page.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`, or the `OpenAI Parser` if you've [enabled OpenAI support](./installation/backend-config.md#openai).
10. Click `Parse All`, and your ingredients should be separated out into Units and Foods based on your seeding in Step 1 above.
11. For ingredients where the Unit or Food was not found, you can click a button to accept an automatically suggested Food to add to the database. Or, manually enter the Unit/Food and hit `Enter` (or click `Create`) to add it to the database
12. When done, click `Save All` and you will be taken back to the recipe. Now the Unit and Food fields of the recipe should be filled out.
Scaling up this recipe or adding it to a Shopping List will now smartly take care of ingredient amounts and duplicate combinations. Initial seeding of Units is pretty complete, but there are many Foods in the world. You'll probably find that you need to add Foods to the database during parsing for the first several recipes. Once you have a well-populated Food database, there are API routes to parse ingredients automatically in bulk. But this is not a good idea without a very complete set of Foods.
## Is it safe to upgrade Mealie? <p style="font-size: 0.75rem; font-weight: 500;">Set up Recipes to use Foods and Units</p>
Yes. If you are using the v1 branches (including beta), you can upgrade to the latest version of Mealie without performing a site Export/Restore. This process was required in previous versions of Mealie, however we've automated the database migration process to make it easier to upgrade. Note that if you were using the v0.5.x version, you CANNOT upgrade to the latest version automatically. You must follow the migration instructions in the documentation. Do the following for each recipe you want to intelligently handle ingredients.
- [Migration From v0.5.x](./migrating-to-mealie-v1.md) 1. Go to a recipe
2. Click the Edit button/icon
3. Click the Recipe Settings gear and deselect `Disable Ingredient Amounts`
4. Save
5. The ingredients should now look a little weird (`1 1 cup broth` and so on)
6. Click the Edit button/icon again
7. Scroll to the ingredients and you should see new fields for Amount, Unit, Food, and Note. The Note in particular will contain the original text of the Recipe.
8. Click `Parse` and you will be taken to the ingredient parsing page.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`, or the `OpenAI Parser` if you've [enabled OpenAI support](./installation/backend-config.md#openai).
10. Click `Parse All`, and your ingredients should be separated out into Units and Foods based on your seeding in Step 1 above.
11. For ingredients where the Unit or Food was not found, you can click a button to accept an automatically suggested Food to add to the database. Or, manually enter the Unit/Food and hit `Enter` (or click `Create`) to add it to the database
12. When done, click `Save All` and you will be taken back to the recipe. Now the Unit and Food fields of the recipe should be filled out.
## How can I change the theme? Scaling up this recipe or adding it to a Shopping List will now smartly take care of ingredient amounts and duplicate combinations.
You can change the theme by settings the environment variables.
- [Backend Config - Themeing](./installation/backend-config.md#themeing)
## How can I change the login session timeout?
Login session can be configured by setting the `TOKEN_TIME` variable on the backend container.
- [Backend Config](./installation/backend-config.md)
## Can I serve Mealie on a subpath?
No. Due to limitations from the JavaScript Framework, Mealie doesn't support serving Mealie on a subpath.
## Can I install Mealie without docker?
Yes, you can install Mealie on your local machine. HOWEVER, it is recommended that you don't. Managing non-system versions of python, node, and npm is a pain. Moreover, updating and upgrading your system with this configuration is unsupported and will likely require manual interventions.
## What is fuzzy search and how do I use it?
Mealie can use fuzzy search, which is robust to minor typos. For example, searching for "brocolli" will still find your recipe for "broccoli soup". But fuzzy search is only functional on a Postgres database backend. To enable fuzzy search you will need to migrate to Postgres:
1. Backup your database and download the .zip file (same as when [migrating](./migrating-to-mealie-v1.md))
2. Set up a [Postgres](./installation/postgres.md) instance of Mealie
3. Upload the backup .zip and click to apply it (as as migration)
## How can I attach an image or video to a Recipe?
Mealie's Recipe Steps and other fields support markdown syntax and therefore support images and videos. To attach an image to the recipe, you can upload it as an asset and use the provided copy button to generate the html image tag required to render the image. For videos, Mealie provides no way to host videos. You'll need to host your videos with another provider and embed them in your recipe. Generally, the video provider will provide a link to the video and the html tag required to render the video. For example, YouTube provides the following link that works inside a step. You can adjust the width and height attributes as necessary to ensure a fit.
```html
<iframe width="560" height="315" src="https://www.youtube.com/embed/nAUwKeO93bY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
```
## How can I unlock my account?
If your account has been locked by bad password attempts, you can use an administrator account to unlock another account. Alternatively, you can unlock all accounts via a script within the container.
```shell
docker exec -it mealie bash
python /app/mealie/scripts/reset_locked_users.py
```
## How can I change my password?
You can change your password by going to the user profile page and clicking the "Change Password" button. Alternatively you can use the following script to change your password via the CLI if you are locked out of your account.
```shell
docker exec -it mealie bash
python /app/mealie/scripts/change_password.py
```
## I can't log in with external auth. How can I change my authentication method?
Follow the [steps above](#how-can-i-change-my-password) for changing your password. You will be prompted if you would like to switch your authentication method back to local auth so you can log in again.
## How do private groups, households, and recipes work?
Managing private groups and recipes can be confusing. The following diagram and notes should help explain how they work to determine if a recipe can be shared publicly.
- Private links that are generated from the recipe page using the `Share` button bypass all group and recipe permissions
- Private groups block all access to recipes, including those that are public, except as noted above.
- Private households, similar to private groups, block all access to recipes, except as noted above.
- Households with "Allow users outside of your group to see your recipes" disabled block all access to recipes, except as noted above.
- Private recipes block all access to the recipe from public links. This does not affect Private Links.
```mermaid
stateDiagram-v2
r1: Request Access
p1: Using Private Link?
p2: Is Group Private?
p3: Is Household Private?
p4: Is Recipe Private?
s1: Deny Access
n1: Allow Access
r1 --> p1 ??? question "How do I enable Nutritional Values?"
p1 --> p2: No
p1 --> n1: Yes
p2 --> s1: Yes ### How do I enable Nutritional Values?
p2 --> p3: No
p3 --> s1: Yes Mealie can store Nutritional Information for Recipes. Please note that the values you enter are static for the recipe and no scaling is being done when changing Servings / Yield.
p3 --> p4: No
p4 --> s1: Yes Do the following to enable Nutritional Values on individual Recipes, or to modify your Household Recipe Preferences
p4 --> n1: No
```
For more information on public access, check out the [Permissions and Public Access guide](./usage/permissions-and-public-access.md). For more information on groups vs. households, check out the [Groups and Households](./features.md#groups-and-households) section in the Features guide. **Show Nutritional Values on a Single Recipe**
## Can I use fail2ban with Mealie? 1. Go to a recipe
Yes, Mealie is configured to properly forward external IP addresses into the `mealie.log` logfile. Note that due to restrictions in docker, IP address forwarding only works on Linux. 2. Click the Edit button/icon
3. Click the Recipe Settings gear and select `Show Nutritional Values`
4. Scroll down to manually fill out the Nutritional Values
5. Save
Your fail2ban usage should look like the following: **Show Nutritional Values by default**
```
Use datepattern : %d-%b-%y %H:%M:%S : Day-MON-Year2 24hour:Minute:Second
Use failregex line : ^ERROR:\s+Incorrect username or password from <HOST>
```
## Why an API? 1. Click your username in the top left
An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based on Meal Plan data to remind you to defrost the chicken, marinate the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation. 2. Click the 'Household Settings' button
3. Under 'Household Recipe Preferences', click to select 'Show nutrition information'
4. Click 'Update'
## Why a database?
Some users of static-site generator applications like ChowDown have expressed concerns about their data being stuck in a database. Considering this is a new project, it is a valid concern to be worried about your data. Mealie specifically addresses this concern by providing automatic daily backups that export your data in json, plain-text markdown files, and/or custom Jinja2 templates. **This puts you in control of how your data is represented** when exported from Mealie, which means you can easily migrate to any other service provided Mealie doesn't work for you.
As to why we need a database? ??? question "What is fuzzy search and how do I use it?"
- **Developer Experience:** Without a database, a lot of the work to maintain your data is taken on by the developer instead of a battle-tested platform for storing data. ### What is fuzzy search and how do I use it?
- **Multi User Support:** With a solid database as backend storage for your data, Mealie can better support multi-user sites and avoid read/write access errors when multiple actions are taken at the same time.
## Why is there no "Keep Screen Alive" button when I access a recipe? Mealie can use fuzzy search, which is robust to minor typos. For example, searching for "brocolli" will still find your recipe for "broccoli soup". But fuzzy search is only functional on a Postgres database backend. To enable fuzzy search you will need to migrate to Postgres:
You've perhaps visited the Mealie Demo and noticed that it had a "Keep Screen Alive" button, but it doesn't show up in your own Mealie instance.
There are typically two possible reasons for this:
1. You're accessing your Mealie instance without using HTTPS. The Wake Lock API is only available if HTTPS is used. Read more here: https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
2. You're accessing your Mealie instance on a browser which doesn't support the API. You can test this here: https://vueuse.org/core/useWakeLock/#demo
Solving the above points will most likely resolve your issues. However, if you're still having problems, you are welcome to create an issue. Just remember to add that you've tried the above two options first in your description. 1. Backup your database and download the .zip file (same as when [migrating](./migrating-to-mealie-v1.md))
2. Set up a [Postgres](./installation/postgres.md) instance of Mealie
3. Upload the backup .zip and click to apply it (as as migration)
??? question "How can I attach an image or video to a Recipe?"
### How can I attach an image or video to a Recipe?
Mealie's Recipe Steps and other fields support markdown syntax and therefore support images and videos. To attach an image to the recipe, you can upload it as an asset and use the provided copy button to generate the html image tag required to render the image. For videos, Mealie provides no way to host videos. You'll need to host your videos with another provider and embed them in your recipe. Generally, the video provider will provide a link to the video and the html tag required to render the video. For example, YouTube provides the following link that works inside a step. You can adjust the width and height attributes as necessary to ensure a fit.
```html
<iframe width="560" height="315" src="https://www.youtube.com/embed/nAUwKeO93bY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
```
## Customization and Configuration
??? question "How can I change the theme?"
### How can I change the theme?
You can change the theme by settings the environment variables.
- [Backend Config - Themeing](./installation/backend-config.md#themeing)
??? question "How can I change the login session timeout?"
### How can I change the login session timeout?
Login session can be configured by setting the `TOKEN_TIME` variable on the backend container.
- [Backend Config](./installation/backend-config.md)
??? question "Can I serve Mealie on a subpath?"
### Can I serve Mealie on a subpath?
No. Due to limitations from the JavaScript Framework, Mealie doesn't support serving Mealie on a subpath.
??? question "Can I install Mealie without docker?"
### Can I install Mealie without docker?
Yes, you can install Mealie on your local machine. HOWEVER, it is recommended that you don't. Managing non-system versions of python, node, and npm is a pain. Moreover, updating and upgrading your system with this configuration is unsupported and will likely require manual interventions.
## Account Management
??? question "How can I unlock my account?"
### How can I unlock my account?
If your account has been locked by bad password attempts, you can use an administrator account to unlock another account. Alternatively, you can unlock all accounts via a script within the container.
```shell
docker exec -it mealie bash
python /app/mealie/scripts/reset_locked_users.py
```
??? question "How can I reset admin privileges for my account?"
### How can I reset admin privileges for my account?
If you've lost admin privileges and no other admin can restore them, you can use the Command Line Interface (CLI) to grant admin access.
```shell
docker exec -it mealie bash
python /app/mealie/scripts/make_admin.py
```
??? question "How can I change my password?"
### How can I change my password?
You can change your password by going to the user profile page and clicking the "Change Password" button. Alternatively you can use the following script to change your password via the CLI if you are locked out of your account.
```shell
docker exec -it mealie bash
python /app/mealie/scripts/change_password.py
```
??? question "I can't log in with external auth. How can I change my authentication method?"
### I can't log in with external auth. How can I change my authentication method?
Follow the [steps above](#how-can-i-change-my-password) for changing your password. You will be prompted if you would like to switch your authentication method back to local auth so you can log in again.
## Collaboration and Privacy
??? question "How do private groups, households, and recipes work?"
### How do private groups, households, and recipes work?
Managing private groups and recipes can be confusing. The following diagram and notes should help explain how they work to determine if a recipe can be shared publicly.
- Private links that are generated from the recipe page using the `Share` button bypass all group and recipe permissions
- Private groups block all access to recipes, including those that are public, except as noted above.
- Private households, similar to private groups, block all access to recipes, except as noted above.
- Households with "Allow users outside of your group to see your recipes" disabled block all access to recipes, except as noted above.
- Private recipes block all access to the recipe from public links. This does not affect Private Links.
```mermaid
stateDiagram-v2
r1: Request Access
p1: Using Private Link?
p2: Is Group Private?
p3: Is Household Private?
p4: Is Recipe Private?
s1: Deny Access
n1: Allow Access
r1 --> p1
p1 --> p2: No
p1 --> n1: Yes
p2 --> s1: Yes
p2 --> p3: No
p3 --> s1: Yes
p3 --> p4: No
p4 --> s1: Yes
p4 --> n1: No
```
For more information on public access, check out the [Permissions and Public Access guide](./usage/permissions-and-public-access.md). For more information on groups vs. households, check out the [Groups and Households](./features.md#groups-and-households) section in the Features guide.
## Security and Maintenance
??? question "Can I use fail2ban with Mealie?"
### Can I use fail2ban with Mealie?
Yes, Mealie is configured to properly forward external IP addresses into the `mealie.log` logfile. Note that due to restrictions in docker, IP address forwarding only works on Linux.
Your fail2ban usage should look like the following:
```
Use datepattern : %d-%b-%y %H:%M:%S : Day-MON-Year2 24hour:Minute:Second
Use failregex line : ^ERROR:\s+Incorrect username or password from <HOST>
```
??? question "Is it safe to upgrade Mealie?"
### Is it safe to upgrade Mealie?
Yes. If you are using the v1 branches (including beta), you can upgrade to the latest version of Mealie without performing a site Export/Restore. This process was required in previous versions of Mealie, however we've automated the database migration process to make it easier to upgrade. Note that if you were using the v0.5.x version, you CANNOT upgrade to the latest version automatically. You must follow the migration instructions in the documentation.
- [Migration From v0.5.x](./migrating-to-mealie-v1.md)
## Technical Considerations
??? question "Why an API?"
### Why an API?
An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based on Meal Plan data to remind you to defrost the chicken, marinate the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation.
??? question "Why a database?"
### Why a database?
Some users of static-site generator applications like ChowDown have expressed concerns about their data being stuck in a database. Considering this is a new project, it is a valid concern to be worried about your data. Mealie specifically addresses this concern by providing automatic daily backups that export your data in json, plain-text markdown files, and/or custom Jinja2 templates. **This puts you in control of how your data is represented** when exported from Mealie, which means you can easily migrate to any other service provided Mealie doesn't work for you.
As to why we need a database?
- **Developer Experience:** Without a database, a lot of the work to maintain your data is taken on by the developer instead of a battle-tested platform for storing data.
- **Multi User Support:** With a solid database as backend storage for your data, Mealie can better support multi-user sites and avoid read/write access errors when multiple actions are taken at the same time.
## Usability
??? question "Why is there no 'Keep Screen Alive' button when I access a recipe?"
### Why is there no "Keep Screen Alive" button when I access a recipe?
You've perhaps visited the Mealie Demo and noticed that it had a "Keep Screen Alive" button, but it doesn't show up in your own Mealie instance.
There are typically two possible reasons for this:
1. You're accessing your Mealie instance without using HTTPS. The Wake Lock API is only available if HTTPS is used. Read more here: https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
2. You're accessing your Mealie instance on a browser which doesn't support the API. You can test this here: https://vueuse.org/core/useWakeLock/#demo
Solving the above points will most likely resolve your issues. However, if you're still having problems, you are welcome to create an issue. Just remember to add that you've tried the above two options first in your description.

View file

@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do: We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
1. Take a backup just in case! 1. Take a backup just in case!
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.4.1` 2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.4.2`
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access. 3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
4. Restart the container 4. Restart the container

View file

@ -7,7 +7,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
```yaml ```yaml
services: services:
mealie: mealie:
image: ghcr.io/mealie-recipes/mealie:v2.4.1 # (3) image: ghcr.io/mealie-recipes/mealie:v2.4.2 # (3)
container_name: mealie container_name: mealie
restart: always restart: always
ports: ports:

View file

@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
```yaml ```yaml
services: services:
mealie: mealie:
image: ghcr.io/mealie-recipes/mealie:v2.4.1 # (3) image: ghcr.io/mealie-recipes/mealie:v2.4.2 # (3)
container_name: mealie container_name: mealie
restart: always restart: always
ports: ports:

File diff suppressed because one or more lines are too long

View file

@ -48,6 +48,7 @@ markdown_extensions:
- name: mermaid - name: mermaid
class: mermaid class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.details
extra_css: extra_css:
- assets/stylesheets/custom.css - assets/stylesheets/custom.css
extra_javascript: extra_javascript:

View file

@ -34,12 +34,6 @@
</v-tooltip> </v-tooltip>
</div> </div>
<RecipeTimerMenu
fab
color="info"
class="ml-1"
/>
<RecipeContextMenu <RecipeContextMenu
show-print show-print
:menu-top="false" :menu-top="false"
@ -53,7 +47,6 @@
:recipe-id="recipe.id" :recipe-id="recipe.id"
:recipe-scale="recipeScale" :recipe-scale="recipeScale"
:use-items="{ :use-items="{
delete: false,
edit: false, edit: false,
download: loggedIn, download: loggedIn,
duplicate: loggedIn, duplicate: loggedIn,
@ -63,6 +56,7 @@
printPreferences: true, printPreferences: true,
share: loggedIn, share: loggedIn,
recipeActions: true, recipeActions: true,
delete: loggedIn,
}" }"
class="ml-1" class="ml-1"
@print="$emit('print')" @print="$emit('print')"
@ -88,7 +82,6 @@
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"; import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
import RecipeContextMenu from "./RecipeContextMenu.vue"; import RecipeContextMenu from "./RecipeContextMenu.vue";
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue"; import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
import RecipeTimerMenu from "./RecipeTimerMenu.vue";
import RecipeTimelineBadge from "./RecipeTimelineBadge.vue"; import RecipeTimelineBadge from "./RecipeTimelineBadge.vue";
import { Recipe } from "~/lib/api/types/recipe"; import { Recipe } from "~/lib/api/types/recipe";
@ -98,7 +91,7 @@ const CLOSE_EVENT = "close";
const JSON_EVENT = "json"; const JSON_EVENT = "json";
export default defineComponent({ export default defineComponent({
components: { RecipeContextMenu, RecipeFavoriteBadge, RecipeTimerMenu, RecipeTimelineBadge }, components: { RecipeContextMenu, RecipeFavoriteBadge, RecipeTimelineBadge },
props: { props: {
recipe: { recipe: {
required: true, required: true,

View file

@ -7,7 +7,7 @@
:elevation="hover ? 12 : 2" :elevation="hover ? 12 : 2"
:to="recipeRoute" :to="recipeRoute"
:min-height="imageHeight + 75" :min-height="imageHeight + 75"
@click="$emit('click')" @click.self="$emit('click')"
> >
<RecipeCardImage <RecipeCardImage
:icon-size="imageHeight" :icon-size="imageHeight"
@ -39,7 +39,7 @@
<RecipeRating class="pb-1" :value="rating" :recipe-id="recipeId" :slug="slug" :small="true" /> <RecipeRating class="pb-1" :value="rating" :recipe-id="recipeId" :slug="slug" :small="true" />
<v-spacer></v-spacer> <v-spacer></v-spacer>
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" /> <RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" v-on="$listeners" />
<!-- If we're not logged-in, no items display, so we hide this menu --> <!-- If we're not logged-in, no items display, so we hide this menu -->
<RecipeContextMenu <RecipeContextMenu

View file

@ -38,7 +38,7 @@
<SafeMarkdown :source="description" /> <SafeMarkdown :source="description" />
</v-list-item-subtitle> </v-list-item-subtitle>
<div class="d-flex flex-wrap justify-start ma-0"> <div class="d-flex flex-wrap justify-start ma-0">
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" /> <RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" v-on="$listeners" />
</div> </div>
<div class="d-flex flex-wrap justify-end align-center"> <div class="d-flex flex-wrap justify-end align-center">
<slot name="actions"> <slot name="actions">

View file

@ -82,6 +82,8 @@
:image="recipe.image" :image="recipe.image"
:tags="recipe.tags" :tags="recipe.tags"
:recipe-id="recipe.id" :recipe-id="recipe.id"
v-on="$listeners"
/> />
</v-lazy> </v-lazy>
</v-col> </v-col>
@ -105,6 +107,8 @@
:image="recipe.image" :image="recipe.image"
:tags="recipe.tags" :tags="recipe.tags"
:recipe-id="recipe.id" :recipe-id="recipe.id"
v-on="$listeners"
/> />
</v-lazy> </v-lazy>
</v-col> </v-col>
@ -296,6 +300,7 @@ export default defineComponent({
}, useAsyncKey()); }, useAsyncKey());
}, 500); }, 500);
function sortRecipes(sortType: string) { function sortRecipes(sortType: string) {
if (state.sortLoading || loading.value) { if (state.sortLoading || loading.value) {
return; return;

View file

@ -9,7 +9,8 @@
color="accent" color="accent"
:small="small" :small="small"
dark dark
:to="`${baseRecipeRoute}?${urlPrefix}=${category.id}`"
@click.prevent="() => $emit('item-selected', category, urlPrefix)"
> >
{{ truncateText(category.name) }} {{ truncateText(category.name) }}
</v-chip> </v-chip>

View file

@ -276,7 +276,7 @@ export default defineComponent({
delete: { delete: {
title: i18n.tc("general.delete"), title: i18n.tc("general.delete"),
icon: $globals.icons.delete, icon: $globals.icons.delete,
color: "error", color: undefined,
event: "delete", event: "delete",
isPublic: false, isPublic: false,
}, },
@ -383,7 +383,10 @@ export default defineComponent({
} }
async function deleteRecipe() { async function deleteRecipe() {
await api.recipes.deleteOne(props.slug); const { data } = await api.recipes.deleteOne(props.slug);
if (data?.slug) {
router.push(`/g/${groupSlug.value}`);
}
context.emit("delete", props.slug); context.emit("delete", props.slug);
} }

View file

@ -23,13 +23,13 @@
<a :href="`/g/${groupSlug}/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a> <a :href="`/g/${groupSlug}/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a>
</template> </template>
<template #item.tags="{ item }"> <template #item.tags="{ item }">
<RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" /> <RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" @item-selected="filterItems" />
</template> </template>
<template #item.recipeCategory="{ item }"> <template #item.recipeCategory="{ item }">
<RecipeChip small :items="item.recipeCategory" /> <RecipeChip small :items="item.recipeCategory" @item-selected="filterItems" />
</template> </template>
<template #item.tools="{ item }"> <template #item.tools="{ item }">
<RecipeChip small :items="item.tools" url-prefix="tools" /> <RecipeChip small :items="item.tools" url-prefix="tools" @item-selected="filterItems" />
</template> </template>
<template #item.userId="{ item }"> <template #item.userId="{ item }">
<v-list-item class="justify-start"> <v-list-item class="justify-start">
@ -48,12 +48,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api"; import { computed, defineComponent, onMounted, ref, useContext, useRouter } from "@nuxtjs/composition-api";
import UserAvatar from "../User/UserAvatar.vue"; import UserAvatar from "../User/UserAvatar.vue";
import RecipeChip from "./RecipeChips.vue"; import RecipeChip from "./RecipeChips.vue";
import { Recipe } from "~/lib/api/types/recipe"; import { Recipe, RecipeCategory, RecipeTool } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
import { UserSummary } from "~/lib/api/types/user"; import { UserSummary } from "~/lib/api/types/user";
import { RecipeTag } from "~/lib/api/types/household";
const INPUT_EVENT = "input"; const INPUT_EVENT = "input";
@ -106,7 +107,7 @@ export default defineComponent({
setup(props, context) { setup(props, context) {
const { $auth, i18n } = useContext(); const { $auth, i18n } = useContext();
const groupSlug = $auth.user?.groupSlug; const groupSlug = $auth.user?.groupSlug;
const router = useRouter();
function setValue(value: Recipe[]) { function setValue(value: Recipe[]) {
context.emit(INPUT_EVENT, value); context.emit(INPUT_EVENT, value);
} }
@ -167,6 +168,13 @@ export default defineComponent({
} }
} }
function filterItems(item: RecipeTag | RecipeCategory | RecipeTool, itemType: string) {
if (!groupSlug || !item.id) {
return;
}
router.push(`/g/${groupSlug}?${itemType}=${item.id}`);
}
onMounted(() => { onMounted(() => {
refreshMembers(); refreshMembers();
}); });
@ -186,6 +194,7 @@ export default defineComponent({
formatDate, formatDate,
members, members,
getMember, getMember,
filterItems,
}; };
}, },

View file

@ -204,6 +204,10 @@ export default defineComponent({
shoppingListShowAllToggled: false, shoppingListShowAllToggled: false,
}); });
const userHousehold = computed(() => {
return $auth.user?.householdSlug || "";
});
const shoppingListChoices = computed(() => { const shoppingListChoices = computed(() => {
return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id); return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id);
}); });
@ -248,8 +252,9 @@ export default defineComponent({
} }
const shoppingListIngredients: ShoppingListIngredient[] = recipe.recipeIngredient.map((ing) => { const shoppingListIngredients: ShoppingListIngredient[] = recipe.recipeIngredient.map((ing) => {
const householdsWithFood = (ing.food?.householdsWithIngredientFood || []);
return { return {
checked: !ing.food?.onHand, checked: !householdsWithFood.includes(userHousehold.value),
ingredient: ing, ingredient: ing,
disableAmount: recipe.settings?.disableAmount || false, disableAmount: recipe.settings?.disableAmount || false,
} }
@ -276,7 +281,8 @@ export default defineComponent({
} }
// Store the on-hand ingredients for later // Store the on-hand ingredients for later
if (ing.ingredient.food?.onHand) { const householdsWithFood = (ing.ingredient.food?.householdsWithIngredientFood || []);
if (householdsWithFood.includes(userHousehold.value)) {
onHandIngs.push(ing); onHandIngs.push(ing);
return sections; return sections;
} }

View file

@ -138,6 +138,7 @@
:title="$tc('general.recipes')" :title="$tc('general.recipes')"
:recipes="recipes" :recipes="recipes"
:query="passedQueryWithSeed" :query="passedQueryWithSeed"
@item-selected="filterItems"
@replaceRecipes="replaceRecipes" @replaceRecipes="replaceRecipes"
@appendRecipes="appendRecipes" @appendRecipes="appendRecipes"
/> />
@ -387,6 +388,19 @@ export default defineComponent({
} }
) )
function filterItems(item: RecipeCategory | RecipeTag | RecipeTool, urlPrefix: string) {
if (urlPrefix === "categories") {
const result = categories.store.value.filter((category) => (category.id as string).includes(item.id as string));
selectedCategories.value = result as NoUndefinedField<RecipeTag>[];
} else if (urlPrefix === "tags") {
const result = tags.store.value.filter((tag) => (tag.id as string).includes(item.id as string));
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
} else if (urlPrefix === "tools") {
const result = tools.store.value.filter((tool) => (tool.id ).includes(item.id || "" ));
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
}
}
async function hydrateSearch() { async function hydrateSearch() {
const query = router.currentRoute.query; const query = router.currentRoute.query;
if (query.auto?.length) { if (query.auto?.length) {
@ -592,6 +606,8 @@ export default defineComponent({
removeRecipe, removeRecipe,
replaceRecipes, replaceRecipes,
passedQueryWithSeed, passedQueryWithSeed,
filterItems,
}; };
}, },
head: {}, head: {},

View file

@ -96,7 +96,12 @@
<v-icon left> <v-icon left>
{{ $globals.icons.calendar }} {{ $globals.icons.calendar }}
</v-icon> </v-icon>
{{ $t('recipe.last-made-date', { date: value ? new Date(value).toLocaleDateString($i18n.locale) : $t("general.never") } ) }} <div v-if="lastMadeReady">
{{ $t('recipe.last-made-date', { date: lastMade ? new Date(lastMade).toLocaleDateString($i18n.locale) : $t("general.never") } ) }}
</div>
<div v-else>
<AppLoader tiny />
</div>
</v-chip> </v-chip>
</div> </div>
<div class="d-flex justify-center flex-wrap mt-1"> <div class="d-flex justify-center flex-wrap mt-1">
@ -110,7 +115,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api"; import { computed, defineComponent, onMounted, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { whenever } from "@vueuse/core"; import { whenever } from "@vueuse/core";
import { VForm } from "~/types/vuetify"; import { VForm } from "~/types/vuetify";
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
@ -119,10 +124,6 @@ import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe";
export default defineComponent({ export default defineComponent({
props: { props: {
value: {
type: String,
default: null,
},
recipe: { recipe: {
type: Object as () => Recipe, type: Object as () => Recipe,
required: true, required: true,
@ -146,6 +147,20 @@ export default defineComponent({
const newTimelineEventImagePreviewUrl = ref<string>(); const newTimelineEventImagePreviewUrl = ref<string>();
const newTimelineEventTimestamp = ref<string>(); const newTimelineEventTimestamp = ref<string>();
const lastMade = ref(props.recipe.lastMade);
const lastMadeReady = ref(false);
onMounted(async () => {
if (!$auth.user?.householdSlug) {
lastMade.value = props.recipe.lastMade;
} else {
const { data } = await userApi.households.getCurrentUserHouseholdRecipe(props.recipe.slug || "");
lastMade.value = data?.lastMade;
}
lastMadeReady.value = true;
});
whenever( whenever(
() => madeThisDialog.value, () => madeThisDialog.value,
() => { () => {
@ -195,11 +210,9 @@ export default defineComponent({
const newEvent = eventResponse.data; const newEvent = eventResponse.data;
// we also update the recipe's last made value // we also update the recipe's last made value
if (!props.value || newTimelineEvent.value.timestamp > props.value) { if (!lastMade.value || newTimelineEvent.value.timestamp > lastMade.value) {
lastMade.value = newTimelineEvent.value.timestamp;
await userApi.recipes.updateLastMade(props.recipe.slug, newTimelineEvent.value.timestamp); await userApi.recipes.updateLastMade(props.recipe.slug, newTimelineEvent.value.timestamp);
// update recipe in parent so the user can see it
context.emit("input", newTimelineEvent.value.timestamp);
} }
// update the image, if provided // update the image, if provided
@ -234,6 +247,8 @@ export default defineComponent({
newTimelineEventImage, newTimelineEventImage,
newTimelineEventImagePreviewUrl, newTimelineEventImagePreviewUrl,
newTimelineEventTimestamp, newTimelineEventTimestamp,
lastMade,
lastMadeReady,
createTimelineEvent, createTimelineEvent,
clearImage, clearImage,
uploadImage, uploadImage,

View file

@ -35,7 +35,7 @@
--> -->
<v-col v-if="!isCookMode || isEditForm" cols="12" sm="12" md="4" lg="4"> <v-col v-if="!isCookMode || isEditForm" cols="12" sm="12" md="4" lg="4">
<RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" /> <RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" />
<RecipePageOrganizers v-if="$vuetify.breakpoint.mdAndUp" :recipe="recipe" /> <RecipePageOrganizers v-if="$vuetify.breakpoint.mdAndUp" :recipe="recipe" @item-selected="chipClicked" />
</v-col> </v-col>
<v-divider v-if="$vuetify.breakpoint.mdAndUp && !isCookMode" class="my-divider" :vertical="true" /> <v-divider v-if="$vuetify.breakpoint.mdAndUp && !isCookMode" class="my-divider" :vertical="true" />
@ -166,7 +166,7 @@ import {
usePageUser, usePageUser,
} from "~/composables/recipe-page/shared-state"; } from "~/composables/recipe-page/shared-state";
import { NoUndefinedField } from "~/lib/api/types/non-generated"; import { NoUndefinedField } from "~/lib/api/types/non-generated";
import { Recipe } from "~/lib/api/types/recipe"; import { Recipe, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
import { useRouteQuery } from "~/composables/use-router"; import { useRouteQuery } from "~/composables/use-router";
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
import { uuid4, deepCopy } from "~/composables/use-utils"; import { uuid4, deepCopy } from "~/composables/use-utils";
@ -329,6 +329,17 @@ export default defineComponent({
*/ */
const { user } = usePageUser(); const { user } = usePageUser();
/** =============================================================
* RecipeChip Clicked
*/
function chipClicked(item: RecipeTag | RecipeCategory | RecipeTool, itemType: string) {
if (!item.id) {
return;
}
router.push(`/g/${groupSlug.value}?${itemType}=${item.id}`);
}
return { return {
user, user,
isOwnGroup, isOwnGroup,
@ -350,7 +361,8 @@ export default defineComponent({
deleteRecipe, deleteRecipe,
addStep, addStep,
hasLinkedIngredients, hasLinkedIngredients,
notLinkedIngredients notLinkedIngredients,
chipClicked,
}; };
}, },
head: {}, head: {},

View file

@ -34,7 +34,7 @@
<UserAvatar :tooltip="false" size="40" :user-id="comment.userId" /> <UserAvatar :tooltip="false" size="40" :user-id="comment.userId" />
<v-card outlined class="flex-grow-1"> <v-card outlined class="flex-grow-1">
<v-card-text class="pa-3 pb-0"> <v-card-text class="pa-3 pb-0">
<p class="">{{ comment.user.username }} {{ $d(Date.parse(comment.createdAt), "medium") }}</p> <p class="">{{ comment.user.fullName }} {{ $d(Date.parse(comment.createdAt), "medium") }}</p>
<SafeMarkdown :source="comment.text" /> <SafeMarkdown :source="comment.text" />
</v-card-text> </v-card-text>
<v-card-actions class="justify-end mt-0 pt-0"> <v-card-actions class="justify-end mt-0 pt-0">

View file

@ -30,7 +30,6 @@
<v-col cols="12" class="d-flex flex-wrap justify-center"> <v-col cols="12" class="d-flex flex-wrap justify-center">
<RecipeLastMade <RecipeLastMade
v-if="isOwnGroup" v-if="isOwnGroup"
:value="recipe.lastMade"
:recipe="recipe" :recipe="recipe"
:class="true ? undefined : 'force-bottom'" :class="true ? undefined : 'force-bottom'"
/> />

View file

@ -10,7 +10,7 @@
<h2 class="mb-2 mt-4">{{ $t('tool.required-tools') }}</h2> <h2 class="mb-2 mt-4">{{ $t('tool.required-tools') }}</h2>
<v-list-item v-for="(tool, index) in recipe.tools" :key="index" dense> <v-list-item v-for="(tool, index) in recipe.tools" :key="index" dense>
<v-checkbox <v-checkbox
v-model="recipe.tools[index].onHand" v-model="recipeTools[index].onHand"
hide-details hide-details
class="pt-0 my-auto py-auto" class="pt-0 my-auto py-auto"
color="secondary" color="secondary"
@ -26,14 +26,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"; import { computed, defineComponent } from "@nuxtjs/composition-api";
import { useLoggedInState } from "~/composables/use-logged-in-state"; import { useLoggedInState } from "~/composables/use-logged-in-state";
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state"; import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
import { useToolStore } from "~/composables/store"; import { useToolStore } from "~/composables/store";
import { NoUndefinedField } from "~/lib/api/types/non-generated"; import { NoUndefinedField } from "~/lib/api/types/non-generated";
import { Recipe } from "~/lib/api/types/recipe"; import { Recipe, RecipeTool } from "~/lib/api/types/recipe";
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue"; import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
interface RecipeToolWithOnHand extends RecipeTool {
onHand: boolean;
}
export default defineComponent({ export default defineComponent({
components: { components: {
RecipeIngredients, RecipeIngredients,
@ -59,9 +63,31 @@ export default defineComponent({
const { user } = usePageUser(); const { user } = usePageUser();
const { isEditMode } = usePageState(props.recipe.slug); const { isEditMode } = usePageState(props.recipe.slug);
const recipeTools = computed(() => {
if (!(user.householdSlug && toolStore)) {
return props.recipe.tools.map((tool) => ({ ...tool, onHand: false }) as RecipeToolWithOnHand);
} else {
return props.recipe.tools.map((tool) => {
const onHand = tool.householdsWithTool?.includes(user.householdSlug) || false;
return { ...tool, onHand } as RecipeToolWithOnHand;
});
}
})
function updateTool(index: number) { function updateTool(index: number) {
if (user.id && toolStore) { if (user.id && user.householdSlug && toolStore) {
toolStore.actions.updateOne(props.recipe.tools[index]); const tool = recipeTools.value[index];
if (tool.onHand && !tool.householdsWithTool?.includes(user.householdSlug)) {
if (!tool.householdsWithTool) {
tool.householdsWithTool = [user.householdSlug];
} else {
tool.householdsWithTool.push(user.householdSlug);
}
} else if (!tool.onHand && tool.householdsWithTool?.includes(user.householdSlug)) {
tool.householdsWithTool = tool.householdsWithTool.filter((household) => household !== user.householdSlug);
}
toolStore.actions.updateOne(tool);
} else { } else {
console.log("no user, skipping server update"); console.log("no user, skipping server update");
} }
@ -69,6 +95,7 @@ export default defineComponent({
return { return {
toolStore, toolStore,
recipeTools,
isEditMode, isEditMode,
updateTool, updateTool,
}; };

View file

@ -14,7 +14,7 @@
:show-add="true" :show-add="true"
selector-type="categories" selector-type="categories"
/> />
<RecipeChips v-else :items="recipe.recipeCategory" /> <RecipeChips v-else :items="recipe.recipeCategory" v-on="$listeners" />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -32,7 +32,7 @@
:show-add="true" :show-add="true"
selector-type="tags" selector-type="tags"
/> />
<RecipeChips v-else :items="recipe.tags" url-prefix="tags" /> <RecipeChips v-else :items="recipe.tags" url-prefix="tags" v-on="$listeners" />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -41,7 +41,7 @@
<v-card-title class="py-2"> {{ $t('tool.required-tools') }} </v-card-title> <v-card-title class="py-2"> {{ $t('tool.required-tools') }} </v-card-title>
<v-divider class="mx-2" /> <v-divider class="mx-2" />
<v-card-text class="pt-0"> <v-card-text class="pt-0">
<RecipeOrganizerSelector v-model="recipe.tools" selector-type="tools" /> <RecipeOrganizerSelector v-model="recipe.tools" selector-type="tools" v-on="$listeners" />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -82,6 +82,8 @@ export default defineComponent({
const { user } = usePageUser(); const { user } = usePageUser();
const { isEditForm } = usePageState(props.recipe.slug); const { isEditForm } = usePageState(props.recipe.slug);
return { return {
isEditForm, isEditForm,
user, user,

View file

@ -4,11 +4,18 @@
<div> <div>
<v-menu v-model="menu" :disabled="!canEditScale" offset-y top nudge-top="6" :close-on-content-click="false"> <v-menu v-model="menu" :disabled="!canEditScale" offset-y top nudge-top="6" :close-on-content-click="false">
<template #activator="{ on, attrs }"> <template #activator="{ on, attrs }">
<v-card class="pa-1 px-2" dark color="secondary darken-1" small v-bind="attrs" v-on="on"> <v-card
<v-icon small class="mr-2">{{ $globals.icons.edit }}</v-icon> class="pa-1 px-2"
dark
color="secondary darken-1"
small
v-bind="attrs"
:style="{ cursor: canEditScale ? '' : 'default' }"
v-on="on"
>
<v-icon v-if="canEditScale" small class="mr-2">{{ $globals.icons.edit }}</v-icon>
<!-- eslint-disable-next-line vue/no-v-html --> <!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="yieldDisplay"></span> <span v-html="yieldDisplay"></span>
</v-card> </v-card>
</template> </template>
<v-card min-width="300px"> <v-card min-width="300px">

View file

@ -1,317 +0,0 @@
<template>
<div class="text-center">
<v-menu
v-model="showMenu"
offset-x
offset-overflow
left
allow-overflow
close-delay="125"
:close-on-content-click="false"
content-class="d-print-none"
:z-index="2"
>
<template #activator="{ on, attrs }">
<v-badge :value="timerEnded" overlap color="red" content="!">
<v-btn :fab="fab" :small="fab" :color="timerEnded ? 'secondary' : color" :icon="!fab" dark v-bind="attrs" v-on="on" @click.prevent>
<v-progress-circular
v-if="timerInitialized && !timerEnded"
:value="timerProgress"
:rotate="270"
:color="timerRunning ? undefined : 'primary'"
>
<v-icon small>{{ timerRunning ? $globals.icons.timer : $globals.icons.timerPause }}</v-icon>
</v-progress-circular>
<v-icon v-else>{{ $globals.icons.timer }}</v-icon>
</v-btn>
</v-badge>
</template>
<v-card>
<v-card-title>
<v-icon class="pr-2">{{ $globals.icons.timer }}</v-icon>
{{ $i18n.tc("recipe.timer.kitchen-timer") }}
</v-card-title>
<div class="mx-auto" style="width: fit-content;">
<v-progress-circular
:value="timerProgress"
:rotate="270"
color="primary"
class="mb-2"
:size="128"
:width="24"
>
<v-icon
v-if="timerInitialized && !timerRunning"
x-large
:color="timerEnded ? 'red' : 'primary'"
@click="() => timerEnded ? resetTimer() : resumeTimer()"
>
{{ timerEnded ? $globals.icons.stop : $globals.icons.pause }}
</v-icon>
</v-progress-circular>
</div>
<v-container width="100%" fluid class="ma-0 px-auto py-2">
<v-row no-gutters justify="center">
<v-col cols="3" align-self="center">
<v-text-field
:value="timerHours"
:min="0"
outlined
single-line
solo
hide-details
type="number"
:disabled="timerInitialized"
class="centered-input my-0 py-0"
style="font-size: large; width: 100px;"
@input="(v) => timerHours = v.toString().padStart(2, '0')"
/>
</v-col>
<v-col cols="1" align-self="center" style="text-align: center;">
<h1>:</h1>
</v-col>
<v-col cols="3" align-self="center">
<v-text-field
:value="timerMinutes"
:min="0"
outlined
single-line
solo
hide-details
type="number"
:disabled="timerInitialized"
class="centered-input my-0 py-0"
style="font-size: large; width: 100px;"
@input="(v) => timerMinutes = v.toString().padStart(2, '0')"
/>
</v-col>
<v-col cols="1" align-self="center" style="text-align: center;" >
<h1>:</h1>
</v-col>
<v-col cols="3" align-self="center">
<v-text-field
:value="timerSeconds"
:min="0"
outlined
single-line
solo
hide-details
type="number"
:disabled="timerInitialized"
class="centered-input my-0 py-0"
style="font-size: large; width: 100px;"
@input="(v) => timerSeconds = v.toString().padStart(2, '0')"
/>
</v-col>
</v-row>
</v-container>
<div class="mx-auto" style="width: 100%;">
<BaseButtonGroup
stretch
:buttons="timerButtons"
@initialize-timer="initializeTimer"
@pause-timer="pauseTimer"
@resume-timer="resumeTimer"
@stop-timer="resetTimer"
/>
</div>
</v-card>
</v-menu>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, toRefs, useContext, watch } from "@nuxtjs/composition-api";
import { ButtonOption } from "~/components/global/BaseButtonGroup.vue";
// @ts-ignore typescript can't find our audio file, but it's there!
import timerAlarmAudio from "~/assets/audio/kitchen_alarm.mp3";
export default defineComponent({
props: {
fab: {
type: Boolean,
default: false,
},
color: {
type: String,
default: "primary",
},
},
setup() {
const { $globals, i18n } = useContext();
const state = reactive({
showMenu: false,
timerInitialized: false,
timerRunning: false,
timerEnded: false,
timerInitialValue: 0,
timerValue: 0,
});
watch(
() => state.showMenu,
() => {
if (state.showMenu && state.timerEnded) {
resetTimer();
}
}
);
// ts doesn't recognize timerAlarmAudio because it's a weird import
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const timerAlarm = new Audio(timerAlarmAudio);
timerAlarm.loop = true;
const timerHours = ref<string | number>("00");
const timerMinutes = ref<string | number>("00");
const timerSeconds = ref<string | number>("00");
const initializeButton: ButtonOption = {
icon: $globals.icons.timerPlus,
text: i18n.tc("recipe.timer.start-timer"),
event: "initialize-timer",
}
const pauseButton: ButtonOption = {
icon: $globals.icons.pause,
text: i18n.tc("recipe.timer.pause-timer"),
event: "pause-timer",
};
const resumeButton: ButtonOption = {
icon: $globals.icons.play,
text: i18n.tc("recipe.timer.resume-timer"),
event: "resume-timer",
};
const stopButton: ButtonOption = {
icon: $globals.icons.stop,
text: i18n.tc("recipe.timer.stop-timer"),
event: "stop-timer",
color: "red",
};
const timerButtons = computed<ButtonOption[]>(() => {
const buttons: ButtonOption[] = [];
if (state.timerInitialized) {
if (state.timerEnded) {
buttons.push(stopButton);
} else if (state.timerRunning) {
buttons.push(pauseButton, stopButton);
} else {
buttons.push(resumeButton, stopButton);
}
} else {
buttons.push(initializeButton);
}
// I don't know why this is failing the frontend lint test ¯\_()_/¯
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return buttons;
});
const timerProgress = computed(() => {
if(state.timerInitialValue) {
return (state.timerValue / state.timerInitialValue) * 100;
} else {
return 0;
}
});
let timerInterval: number | null = null;
function decrementTimer() {
if (state.timerValue > 0) {
state.timerValue -= 1;
timerHours.value = Math.floor(state.timerValue / 3600).toString().padStart(2, "0");
timerMinutes.value = Math.floor(state.timerValue % 3600 / 60).toString().padStart(2, "0");
timerSeconds.value = Math.floor(state.timerValue % 3600 % 60).toString().padStart(2, "0");
}
else {
state.timerRunning = false;
state.timerEnded = true;
timerAlarm.currentTime = 0;
timerAlarm.play();
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
}
}
function initializeTimer() {
state.timerInitialized = true;
state.timerRunning = true;
state.timerEnded = false;
console.log(timerSeconds.value);
const hours = parseFloat(timerHours.value.toString()) > 0 ? parseFloat(timerHours.value.toString()) : 0;
const minutes = parseFloat(timerMinutes.value.toString()) > 0 ? parseFloat(timerMinutes.value.toString()) : 0;
const seconds = parseFloat(timerSeconds.value.toString()) > 0 ? parseFloat(timerSeconds.value.toString()) : 0;
state.timerInitialValue = (hours * 3600) + (minutes * 60) + seconds;
state.timerValue = state.timerInitialValue;
timerInterval = setInterval(decrementTimer, 1000) as unknown as number;
timerHours.value = Math.floor(state.timerValue / 3600).toString().padStart(2, "0");
timerMinutes.value = Math.floor(state.timerValue % 3600 / 60).toString().padStart(2, "0");
timerSeconds.value = Math.floor(state.timerValue % 3600 % 60).toString().padStart(2, "0");
};
function pauseTimer() {
state.timerRunning = false;
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
};
function resumeTimer() {
state.timerRunning = true;
timerInterval = setInterval(decrementTimer, 1000) as unknown as number;
};
function resetTimer() {
state.timerInitialized = false;
state.timerRunning = false;
state.timerEnded = false;
timerAlarm.pause();
timerAlarm.currentTime = 0;
timerHours.value = "00";
timerMinutes.value = "00";
timerSeconds.value = "00";
state.timerValue = 0;
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
};
return {
...toRefs(state),
timerHours,
timerMinutes,
timerSeconds,
timerButtons,
timerProgress,
initializeTimer,
pauseTimer,
resumeTimer,
resetTimer,
};
},
});
</script>
<style scoped>
.centered-input >>> input {
text-align: center;
}
</style>

View file

@ -8,14 +8,14 @@
</v-icon> </v-icon>
<div v-if="large" class="text-small"> <div v-if="large" class="text-small">
<slot> <slot>
{{ small ? "" : waitingText }} {{ (small || tiny) ? "" : waitingText }}
</slot> </slot>
</div> </div>
</div> </div>
</v-progress-circular> </v-progress-circular>
<div v-if="!large" class="text-small"> <div v-if="!large" class="text-small">
<slot> <slot>
{{ small ? "" : waitingTextCalculated }} {{ (small || tiny) ? "" : waitingTextCalculated }}
</slot> </slot>
</div> </div>
</div> </div>
@ -31,6 +31,10 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
tiny: {
type: Boolean,
default: false,
},
small: { small: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -50,6 +54,13 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const size = computed(() => { const size = computed(() => {
if (props.tiny) {
return {
width: 2,
icon: 0,
size: 25,
};
}
if (props.small) { if (props.small) {
return { return {
width: 2, width: 2,

View file

@ -9,7 +9,6 @@ export const useTools = function (eager = true) {
id: "", id: "",
name: "", name: "",
slug: "", slug: "",
onHand: false,
}); });
const api = useUserApi(); const api = useUserApi();

View file

@ -13,7 +13,6 @@ export const useFoodData = function () {
name: "", name: "",
description: "", description: "",
labelId: undefined, labelId: undefined,
onHand: false,
}); });
} }

View file

@ -3,16 +3,21 @@ import { useData, useReadOnlyStore, useStore } from "../partials/use-store-facto
import { RecipeTool } from "~/lib/api/types/recipe"; import { RecipeTool } from "~/lib/api/types/recipe";
import { usePublicExploreApi, useUserApi } from "~/composables/api"; import { usePublicExploreApi, useUserApi } from "~/composables/api";
interface RecipeToolWithOnHand extends RecipeTool {
onHand: boolean;
}
const store: Ref<RecipeTool[]> = ref([]); const store: Ref<RecipeTool[]> = ref([]);
const loading = ref(false); const loading = ref(false);
const publicLoading = ref(false); const publicLoading = ref(false);
export const useToolData = function () { export const useToolData = function () {
return useData<RecipeTool>({ return useData<RecipeToolWithOnHand>({
id: "", id: "",
name: "", name: "",
slug: "", slug: "",
onHand: false, onHand: false,
householdsWithTool: [],
}); });
} }

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Verhoog skaal met 1", "increase-scale-label": "Verhoog skaal met 1",
"locked": "Gesluit", "locked": "Gesluit",
"public-link": "Openbare skakel", "public-link": "Openbare skakel",
"timer": {
"kitchen-timer": "Kombuis timer",
"start-timer": "Begin die kombuis timer",
"pause-timer": "Onderbreek die kombuis timer",
"resume-timer": "Hervat kombuis timer",
"stop-timer": "Stop die kombuis timer"
},
"edit-timeline-event": "Wysig tydlyn gebeurtenis", "edit-timeline-event": "Wysig tydlyn gebeurtenis",
"timeline": "Tydlyn", "timeline": "Tydlyn",
"timeline-is-empty": "Nog niks op die tydlyn nie. Probeer hierdie resep maak!", "timeline-is-empty": "Nog niks op die tydlyn nie. Probeer hierdie resep maak!",

View file

@ -250,60 +250,60 @@
"invite": "دعوة", "invite": "دعوة",
"looking-to-update-your-profile": "هل ترغب في تحديث ملفك الشخصي؟", "looking-to-update-your-profile": "هل ترغب في تحديث ملفك الشخصي؟",
"default-recipe-preferences-description": "هذه هي الإعدادات الافتراضية عند إنشاء وصفة جديدة في مجموعتك. يمكن تغيير هذه الوصفات الفردية في قائمة إعدادات الوصفات.", "default-recipe-preferences-description": "هذه هي الإعدادات الافتراضية عند إنشاء وصفة جديدة في مجموعتك. يمكن تغيير هذه الوصفات الفردية في قائمة إعدادات الوصفات.",
"default-recipe-preferences": "Default Recipe Preferences", "default-recipe-preferences": "تفضيلات الوصفة الافتراضية",
"group-preferences": "إعدادات المجموعة", "group-preferences": "إعدادات المجموعة",
"private-group": "مجموعة خاصة", "private-group": "مجموعة خاصة",
"private-group-description": "Setting your group to private will disable all public view options. This overrides any individual public view settings", "private-group-description": "سيؤدي تعيين مجموعتك إلى الخاص إلى تعطيل جميع خيارات العرض العام. وهذا يلغي أي إعدادات عرض عام فردية",
"enable-public-access": "Enable Public Access", "enable-public-access": "تمكين الوصول للعموم",
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in", "enable-public-access-description": "جعل وصفات المجموعة عامة بشكل افتراضي، والسماح للزوار بعرض الوصفات دون تسجيل الدخول",
"allow-users-outside-of-your-group-to-see-your-recipes": "السماح للمستخدمين خارج مجموعتك لمشاهدة وصفاتك", "allow-users-outside-of-your-group-to-see-your-recipes": "السماح للمستخدمين خارج مجموعتك لمشاهدة وصفاتك",
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link", "allow-users-outside-of-your-group-to-see-your-recipes-description": "عند التمكين يمكنك استخدام رابط المشاركة العامة لمشاركة وصفات محددة دون تفويض المستخدم. عند التعطيل، يمكنك مشاركة الوصفات فقط مع المستخدمين الموجودين في مجموعتك أو مع رابط خاص تم إنشاؤه مسبقاً",
"show-nutrition-information": "عرض معلومات التغذية", "show-nutrition-information": "عرض معلومات التغذية",
"show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown", "show-nutrition-information-description": "عندما يتم تمكين المعلومات الغذائية ستظهر على الوصفة إذا كانت متاحة. وفي حالة عدم توافر معلومات عن التغذية، لن تظهر المعلومات المتعلقة بالتغذية",
"show-recipe-assets": "Show recipe assets", "show-recipe-assets": "إظهار أصول الوصفة",
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available", "show-recipe-assets-description": "عند تمكين الوصفة، سيتم عرض أصول الوصفة على الوصفة إذا كانت متوفرة",
"default-to-landscape-view": "Default to landscape view", "default-to-landscape-view": "الافتراضي للعرض الأفقي",
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view", "default-to-landscape-view-description": "عند تمكين قسم رأس الوصفة سوف يظهر في العرض الأفقي",
"disable-users-from-commenting-on-recipes": "إيقاف المستخدمين من التعليق على الوصفات", "disable-users-from-commenting-on-recipes": "إيقاف المستخدمين من التعليق على الوصفات",
"disable-users-from-commenting-on-recipes-description": "Hides the comment section on the recipe page and disables commenting", "disable-users-from-commenting-on-recipes-description": "يخفي قسم التعليق على صفحة الوصفة ويعطل التعليق",
"disable-organizing-recipe-ingredients-by-units-and-food": "Disable organizing recipe ingredients by units and food", "disable-organizing-recipe-ingredients-by-units-and-food": "تعطيل تنظيم عناصر الوصفة حسب الوحدات والطعام",
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields", "disable-organizing-recipe-ingredients-by-units-and-food-description": "يخفي حقول الطعام والوحدة والكمية للمكونات ويعامل المكونات كحقول نصية عادية",
"general-preferences": "General Preferences", "general-preferences": "الإعدادات العامة",
"group-recipe-preferences": "Group Recipe Preferences", "group-recipe-preferences": "تفضيلات الوصفة للمجموعة",
"report": "تقرير", "report": "تقرير",
"report-with-id": "Report ID: {id}", "report-with-id": "معرف التقرير: {id}",
"group-management": "Group Management", "group-management": "إدارة المجموعة",
"admin-group-management": "Admin Group Management", "admin-group-management": "إدارة مجموعة المشرف",
"admin-group-management-text": "Changes to this group will be reflected immediately.", "admin-group-management-text": "التغييرات التي ستطرأ على هذه المجموعة ستنعكس على الفور.",
"group-id-value": "Group Id: {0}", "group-id-value": "معرف المجموعة: {0}",
"total-households": "Total Households", "total-households": "مجموع المنزل",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household" "you-must-select-a-group-before-selecting-a-household": "يجب عليك تحديد مجموعة قبل تحديد المنزل"
}, },
"household": { "household": {
"household": "Household", "household": "المنزل",
"households": "Households", "households": "المنازل",
"user-household": "User Household", "user-household": "منزل المستخدم",
"create-household": "Create Household", "create-household": "إنشاء منزل",
"household-name": "Household Name", "household-name": "اسم المنزل",
"household-group": "Household Group", "household-group": "مجموعة المنزل",
"household-management": "Household Management", "household-management": "إدارة المنزل",
"manage-households": "Manage Households", "manage-households": "إدارة المنازل",
"admin-household-management": "Admin Household Management", "admin-household-management": "إدارة مشرف المنزل",
"admin-household-management-text": "Changes to this household will be reflected immediately.", "admin-household-management-text": "التغييرات التي ستطرأ على هذا المنزل ستنعكس على الفور.",
"household-id-value": "Household Id: {0}", "household-id-value": "معرف المنزل: {0}",
"private-household": "Private Household", "private-household": "منزل خاص",
"private-household-description": "Setting your household to private will disable all public view options. This overrides any individual public view settings", "private-household-description": "سيؤدي تعيين المنزل إلى خاص إلى تعطيل جميع خيارات العرض العام. وهذا يلغي أي إعدادات عرض عام فردية",
"lock-recipe-edits-from-other-households": "Lock recipe edits from other households", "lock-recipe-edits-from-other-households": "إقفال تحرير الوصفة من المنازل الأخرى",
"lock-recipe-edits-from-other-households-description": "When enabled only users in your household can edit recipes created by your household", "lock-recipe-edits-from-other-households-description": "عند التمكين, المستخدمين فقط في أسرتك المعيشية يمكنهم تعديل الوصفات التي أنشأتها أسرتك",
"household-recipe-preferences": "Household Recipe Preferences", "household-recipe-preferences": "تفضيلات الوصفة المنزلية",
"default-recipe-preferences-description": "These are the default settings when a new recipe is created in your household. These can be changed for individual recipes in the recipe settings menu.", "default-recipe-preferences-description": "هذه هي الإعدادات الافتراضية عند إنشاء وصفة جديدة في منزلك. يمكن تغيير الوصفات الفردية في قائمة إعدادات الوصفة.",
"allow-users-outside-of-your-household-to-see-your-recipes": "Allow users outside of your household to see your recipes", "allow-users-outside-of-your-household-to-see-your-recipes": "السماح للمستخدمين خارج منزلك بمشاهدة وصفاتك",
"allow-users-outside-of-your-household-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 household or with a pre-generated private link", "allow-users-outside-of-your-household-to-see-your-recipes-description": "عند التمكين يمكنك استخدام رابط المشاركة العامة لمشاركة وصفات محددة دون تفويض المستخدم. عند التعطيل، يمكنك مشاركة الوصفات فقط مع المستخدمين الموجودين في منزلك أو مع رابط خاص تم إنشاؤه مسبقاً",
"household-preferences": "Household Preferences" "household-preferences": "تفضيلات المنزل"
}, },
"meal-plan": { "meal-plan": {
"create-a-new-meal-plan": "إنشاء خطة وجبة جديدة", "create-a-new-meal-plan": "إنشاء خطة وجبة جديدة",
"update-this-meal-plan": "Update this Meal Plan", "update-this-meal-plan": "تحديث خِطَّة الوجبة الغذائية هذه",
"dinner-this-week": "العشاء لهذا الأسبوع", "dinner-this-week": "العشاء لهذا الأسبوع",
"dinner-today": "العشاء اليوم", "dinner-today": "العشاء اليوم",
"dinner-tonight": "العشاء الليلة", "dinner-tonight": "العشاء الليلة",
@ -321,95 +321,95 @@
"mealplan-settings": "اعدادات خطة الوجبات", "mealplan-settings": "اعدادات خطة الوجبات",
"mealplan-update-failed": "فشل تحديث خطة الوجبات", "mealplan-update-failed": "فشل تحديث خطة الوجبات",
"mealplan-updated": "تم تحديث خطة الوجبات", "mealplan-updated": "تم تحديث خطة الوجبات",
"mealplan-households-description": "If no household is selected, recipes can be added from any household", "mealplan-households-description": "إذا لم يتم اختيار منزل، يمكن إضافة وصفات من أي منزل",
"any-category": "Any Category", "any-category": "أي فئة",
"any-tag": "Any Tag", "any-tag": "أي وسم",
"any-household": "Any Household", "any-household": "أي منزل",
"no-meal-plan-defined-yet": "لم يتم تحديد خطة بعد", "no-meal-plan-defined-yet": "لم يتم تحديد خطة بعد",
"no-meal-planned-for-today": "لم يتم تخطيط وجبة لهذا اليوم", "no-meal-planned-for-today": "لم يتم تخطيط وجبة لهذا اليوم",
"numberOfDays-hint": "Number of days on page load", "numberOfDays-hint": "عدد الأيام عند تحميل الصفحة",
"numberOfDays-label": "Default Days", "numberOfDays-label": "الأيام الافتراضية",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "فقط الوجبات التي تحتوي على التصنيفات التالية سوف تستخدم لإنشاء خطتك", "only-recipes-with-these-categories-will-be-used-in-meal-plans": "فقط الوجبات التي تحتوي على التصنيفات التالية سوف تستخدم لإنشاء خطتك",
"planner": "المخطط", "planner": "المخطط",
"quick-week": "Quick Week", "quick-week": "أسبوع سريع",
"side": "وجبة جانبية", "side": "وجبة جانبية",
"sides": "الوجبات الجانبية", "sides": "الوجبات الجانبية",
"start-date": "تاريخ البدء", "start-date": "تاريخ البدء",
"rule-day": "Rule Day", "rule-day": "يوم القاعدة",
"meal-type": "نوع الوجبة", "meal-type": "نوع الوجبة",
"breakfast": "الإفطار", "breakfast": "الإفطار",
"lunch": "الغداء", "lunch": "الغداء",
"dinner": "العشاء", "dinner": "العشاء",
"type-any": "أي", "type-any": "أي",
"day-any": "أي", "day-any": "أي",
"editor": "Editor", "editor": "المحرر",
"meal-recipe": "وصفة الوجبة", "meal-recipe": "وصفة الوجبة",
"meal-title": "عنوان الوجبة", "meal-title": "عنوان الوجبة",
"meal-note": "ملاحظة الوجبة", "meal-note": "ملاحظة الوجبة",
"note-only": "ملاحظة فقط", "note-only": "ملاحظة فقط",
"random-meal": "وجبة عشوائية", "random-meal": "وجبة عشوائية",
"random-dinner": "عشاء عشوائي", "random-dinner": "عشاء عشوائي",
"random-side": "Random Side", "random-side": "جانب عشوائي",
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.", "this-rule-will-apply": "هذه القاعدة سوف تطبق على {dayCriteria} {mealTypeCriteria}.",
"to-all-days": "إلى جميع الأيام", "to-all-days": "إلى جميع الأيام",
"on-days": "on {0}s", "on-days": "على أيام {0}",
"for-all-meal-types": "لجميع أنواع الوجبات", "for-all-meal-types": "لجميع أنواع الوجبات",
"for-type-meal-types": "for {0} meal types", "for-type-meal-types": "لأنواع الوجبات {0}",
"meal-plan-rules": "Meal Plan Rules", "meal-plan-rules": "قواعد خِطَّة وجبة الطعام",
"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 rule filters will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.", "meal-plan-rules-description": "يمكنك إنشاء قواعد لاختيار الوصفات التلقائية لخطط وجبتك الغذائية. وتستخدم هذه القواعد من قبل الخادم لتحديد مجموعة عشوائية من الوصفات التي يتم اختيارها من خلال إنشاء خطط الوجبات. لاحظ أنه إذا كانت القواعد تحتوي على نفس قيود اليوم/النوع فسيتم دمج عوامل تصفية القاعدة. من الناحية العملية، ليس من الضروري إنشاء قواعد مكررة، ولكن من الممكن فعل ذلك.",
"new-rule-description": "When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to \"Any\" which will apply it to all the possible values for the day and/or meal type.", "new-rule-description": "عند إنشاء قاعدة جديدة لخطة وجبة غذائية، يمكنك تقييد القاعدة لتكون قابلة للتطبيق ليوم محدد من الأسبوع و/أو نوع محدد من الوجبات. لتطبيق قاعدة على جميع الأيام أو جميع أنواع الوجبات الغذائية يمكنك تعيين القاعدة إلى \"أي كان\" التي ستطبقها على جميع القيم الممكنة لليوم و/أو نوع الوجبة.",
"recipe-rules": "قواعد الوصفات", "recipe-rules": "قواعد الوصفات",
"applies-to-all-days": "ينطبق على جميع الأيام", "applies-to-all-days": "ينطبق على جميع الأيام",
"applies-on-days": "Applies on {0}s", "applies-on-days": "يطبق على أيام {0}",
"meal-plan-settings": "Meal Plan Settings" "meal-plan-settings": "إعدادات خِطَّة الوجبات الغذائية"
}, },
"migration": { "migration": {
"migration-data-removed": "Migration data removed", "migration-data-removed": "حذف بيانات الهجرة",
"new-migration": "New Migration", "new-migration": "هجرة جديدة",
"no-file-selected": "لم يتمّ اختيار أيّ ملفّ", "no-file-selected": "لم يتمّ اختيار أيّ ملفّ",
"no-migration-data-available": "No Migration Data Available", "no-migration-data-available": "لا توجد بيانات هجرة متوفرة",
"previous-migrations": "Previous Migrations", "previous-migrations": "الهجرة السابقة",
"recipe-migration": "نقل الوصفة", "recipe-migration": "نقل الوصفة",
"chowdown": { "chowdown": {
"description": "Migrate data from Chowdown", "description": "نقل البيانات من \"Chowdown\"",
"description-long": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.", "description-long": "ميلي يدعم بشكل محلي تنسيق مستودع طعام. يجب تنزيل مستودع التعليمات البرمجية CODE REPOSITORY كملف مضغوط ZIP وتحميله أدناه.",
"title": "Chowdown" "title": "\"Chowdown\""
}, },
"nextcloud": { "nextcloud": {
"description": "Migrate data from a Nextcloud Cookbook instance", "description": "نقل البيانات من نموذج كتاب طبخ NEXTCLOUD",
"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": "يمكن استيراد الوصفات السحابية من مِلَفّ مضغوط ZIP يحتوي على البيانات المخزنة في Nextcloud. راجع بنية مجلد المثال أدناه للتأكد من أن وصفاتك قابلة للاستيراد.",
"title": "Nextcloud Cookbook" "title": "كتاب طبخ <Nextcloud>"
}, },
"copymethat": { "copymethat": {
"description-long": "Mealie can import recipes from Copy Me That. Export your recipes in HTML format, then upload the .zip below.", "description-long": "يمكن لميلي استيراد الوصفات من نسخ لي. يجب تصدير وصفاتك بتنسيق HTML، ثم تحميل ZIP أدناه.",
"title": "Copy Me That Recipe Manager" "title": "انسخ لي مدير الوصفة"
}, },
"paprika": { "paprika": {
"description-long": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.", "description-long": "يمكن لميلي استيراد الوصفات من تطبيق PAPRIKA. يجب تصدير وصفاتك من PAPRIKA، وإعادة تسمية امتداد التصدير إلى .ZIP وتحميله أدناه.",
"title": "Paprika Recipe Manager" "title": "مدير وصفة بابريكا"
}, },
"mealie-pre-v1": { "mealie-pre-v1": {
"description-long": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.", "description-long": "يمكن لميلي استيراد الوصفات من تطبيق ميلي من إصدار قبل 1.0. يجب تصدير وصفاتك من نموذجك القديم، وتحميل المِلَفّ المضغوط أدناه. لاحظ أنه يمكن استيراد الوصفات فقط من التصدير.",
"title": "Mealie Pre v1.0" "title": "ميلي إصدار قبل 1.0"
}, },
"tandoor": { "tandoor": {
"description-long": "Mealie can import recipes from Tandoor. Export your data in the \"Default\" format, then upload the .zip below.", "description-long": "يمكن لميلي استيراد الوصفات من تندور. يجب تصدير بياناتك بالتنسيق \"الافتراضي\"، ثم يجب تحميل المِلَفّ المضغوط أدناه.",
"title": "Tandoor Recipes" "title": "وصفات تاندور"
}, },
"recipe-data-migrations": "Recipe Data Migrations", "recipe-data-migrations": "وصفة 2",
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.", "recipe-data-migrations-explanation": "يمكن نقل الوصفات من تطبيق آخر مدعوم إلى ميلي. هذه طريقة رائعة للبدء مع ميلي.",
"coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.", "coming-from-another-application-or-an-even-older-version-of-mealie": "هل تأتي من تطبيق آخر أو حتى إصدار قديم من ميلي؟ يجب التحقق من عمليات الترحيل لمعرفة ما إذا كان يمكن استيراد بياناتك.",
"choose-migration-type": "Choose Migration Type", "choose-migration-type": "اختر نوع الترحيل",
"tag-all-recipes": "Tag all recipes with {tag-name} tag", "tag-all-recipes": "وسم جميع الوصفات باستخدام علامة {tag-name}",
"nextcloud-text": "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.", "nextcloud-text": "يمكن استيراد الوصفات السحابية من مِلَفّ مضغوط Zip يحتوي على البيانات المخزنة في Nextcloud. راجع بنية مجلد المثال أدناه للتأكد من أن وصفاتك قابلة للاستيراد.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.", "chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
"recipe-1": "Recipe 1", "recipe-1": "وصفة 1",
"recipe-2": "Recipe 2", "recipe-2": "وصفة 2",
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.", "paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
"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 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.",
"plantoeat": { "plantoeat": {
"title": "Plan to Eat", "title": "خِطَّة تناول الطعام",
"description-long": "Mealie can import recipies from Plan to Eat." "description-long": "Mealie can import recipies from Plan to Eat."
}, },
"myrecipebox": { "myrecipebox": {
@ -417,44 +417,44 @@
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below." "description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
}, },
"recipekeeper": { "recipekeeper": {
"title": "Recipe Keeper", "title": "مدير الوصفة",
"description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below."
} }
}, },
"new-recipe": { "new-recipe": {
"bulk-add": "Bulk Add", "bulk-add": "إضافة مجموعة",
"error-details": "Only websites containing ld+json or microdata can be imported by Mealie. Most major recipe websites support this data structure. If your site cannot be imported but there is json data in the log, please submit a github issue with the URL and data.", "error-details": "Only websites containing ld+json or microdata can be imported by Mealie. Most major recipe websites support this data structure. If your site cannot be imported but there is json data in the log, please submit a github issue with the URL and data.",
"error-title": "Looks Like We Couldn't Find Anything", "error-title": "Looks Like We Couldn't Find Anything",
"from-url": "Import a Recipe", "from-url": "استيراد وصفة",
"github-issues": "مشاكل GitHub", "github-issues": "مشاكل GitHub",
"google-ld-json-info": "معرف Google + معلومات json", "google-ld-json-info": "معرف Google + معلومات json",
"must-be-a-valid-url": "يجب أن يكون عنوان URL صالحًا", "must-be-a-valid-url": "يجب أن يكون عنوان URL صالحًا",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list", "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list",
"recipe-markup-specification": "Recipe Markup Specification", "recipe-markup-specification": "Recipe Markup Specification",
"recipe-url": "Recipe URL", "recipe-url": "رابط الوصفة",
"recipe-html-or-json": "Recipe HTML or JSON", "recipe-html-or-json": "وصفة HTML أو JSON",
"upload-a-recipe": "Upload a Recipe", "upload-a-recipe": "تحميل وصفة",
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.", "upload-individual-zip-file": "تحميل مِلَفّ zip فردي تم تصديره من مثيل Malie آخر.",
"url-form-hint": "Copy and paste a link from your favorite recipe website", "url-form-hint": "نسخ ولصق رابط من موقعك المفضل للوصفة",
"view-scraped-data": "View Scraped Data", "view-scraped-data": "عرض البيانات المكشوفة",
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines", "trim-whitespace-description": "قص المسافات البيضاء البادئة واللاحقة وكذلك الأسطر الفارغة",
"trim-prefix-description": "Trim first character from each line", "trim-prefix-description": "قص الحرف الأول من كل سطر",
"split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns", "split-by-numbered-line-description": "Attempts to split a paragraph by matching '1)' or '1.' patterns",
"import-by-url": "Import a recipe by URL", "import-by-url": "استيراد وصفة عن طريق عنوان URL",
"create-manually": "Create a recipe manually", "create-manually": "إنشاء وصفة يدوياً",
"make-recipe-image": "Make this the recipe image" "make-recipe-image": "اجعل هذه صورة الوصفة"
}, },
"page": { "page": {
"404-page-not-found": "404 Page not found", "404-page-not-found": "404: لم يتم العثور على الصفحة",
"all-recipes": "All Recipes", "all-recipes": "جميع الوصفات",
"new-page-created": "New page created", "new-page-created": "تم إنشاء الصفحة الجديدة",
"page": "الصفحة", "page": "الصفحة",
"page-creation-failed": "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": "تم تحديث صفحة",
"pages-update-failed": "Pages update failed", "pages-update-failed": "فشل تحديث الصفحات",
"pages-updated": "Pages updated", "pages-updated": "Pages updated",
"404-not-found": "لم يتم العثور على الصفحة. خطأ 404", "404-not-found": "لم يتم العثور على الصفحة. خطأ 404",
"an-error-occurred": "حصل خطأ ما" "an-error-occurred": "حصل خطأ ما"
@ -500,42 +500,42 @@
"object-value": "Object Value", "object-value": "Object Value",
"original-url": "Original URL", "original-url": "Original URL",
"perform-time": "Cook Time", "perform-time": "Cook Time",
"prep-time": "Prep Time", "prep-time": "وقت التحضير",
"protein-content": "Protein", "protein-content": "البروتين",
"public-recipe": "Public Recipe", "public-recipe": "وصفة عامة",
"recipe-created": "Recipe created", "recipe-created": "تم إنشاء الوصفة",
"recipe-creation-failed": "Recipe creation failed", "recipe-creation-failed": "فشل إنشاء الوصفة",
"recipe-deleted": "Recipe deleted", "recipe-deleted": "تم حذف الوصفة",
"recipe-image": "Recipe Image", "recipe-image": "صورة الوصفة",
"recipe-image-updated": "Recipe image updated", "recipe-image-updated": "تم تحديث صورة الوصفة",
"recipe-name": "Recipe Name", "recipe-name": "اسم الوصفة",
"recipe-settings": "Recipe Settings", "recipe-settings": "إعدادات الوصفة",
"recipe-update-failed": "Recipe update failed", "recipe-update-failed": "فشل تحديث الوصفة",
"recipe-updated": "Recipe updated", "recipe-updated": "تم تحديث الوصفة",
"remove-from-favorites": "Remove from Favorites", "remove-from-favorites": "إزالة من المفضلات",
"remove-section": "Remove Section", "remove-section": "إزالة القسم",
"saturated-fat-content": "Saturated fat", "saturated-fat-content": "الدهون المشبعة",
"save-recipe-before-use": "Save recipe before use", "save-recipe-before-use": "حفظ الوصفة قبل الاستخدام",
"section-title": "Section Title", "section-title": "عنوان القسم",
"servings": "Servings", "servings": "حصص الطعام",
"serves-amount": "Serves {amount}", "serves-amount": "{amount} حصص",
"share-recipe-message": "I wanted to share my {0} recipe with you.", "share-recipe-message": "أردت أن أشارككم وصفة {0} الخاصة بي.",
"show-nutrition-values": "Show Nutrition Values", "show-nutrition-values": "Show Nutrition Values",
"sodium-content": "Sodium", "sodium-content": "صوديوم",
"step-index": "Step: {step}", "step-index": "الخطوة: {step}",
"sugar-content": "Sugar", "sugar-content": "سكر",
"title": "Title", "title": "العنوان",
"total-time": "Total Time", "total-time": "الوقت الإجمالي",
"trans-fat-content": "Trans-fat", "trans-fat-content": "الدهون المتحولة",
"unable-to-delete-recipe": "Unable to Delete Recipe", "unable-to-delete-recipe": "تعذر حذف الوصفة",
"unsaturated-fat-content": "Unsaturated fat", "unsaturated-fat-content": "دهون غير مشبعة",
"no-recipe": "No Recipe", "no-recipe": "لا يوجد وصفة",
"locked-by-owner": "Locked by Owner", "locked-by-owner": "مقفلة من قبل المالك",
"join-the-conversation": "Join the Conversation", "join-the-conversation": "انضم للمحادثة",
"add-recipe-to-mealplan": "Add Recipe to Mealplan", "add-recipe-to-mealplan": "إضافة الوصفة إلى خِطَّة الوجبة",
"entry-type": "Entry Type", "entry-type": "نوع الإدخال",
"date-format-hint": "MM/DD/YYYY format", "date-format-hint": "صيغة MM/DD/YYYYY",
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD format", "date-format-hint-yyyy-mm-dd": "صيغة YYY-MM-DD",
"add-to-list": "Add to List", "add-to-list": "Add to List",
"add-to-plan": "Add to Plan", "add-to-plan": "Add to Plan",
"add-to-timeline": "Add to Timeline", "add-to-timeline": "Add to Timeline",
@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Увеличи мащаба с 1", "increase-scale-label": "Увеличи мащаба с 1",
"locked": "Заключено", "locked": "Заключено",
"public-link": "Публична връзка", "public-link": "Публична връзка",
"timer": {
"kitchen-timer": "Кухненски таймер",
"start-timer": "Стартирай таймера",
"pause-timer": "Поставяне таймера на пауза",
"resume-timer": "Възобновяване на таймера",
"stop-timer": "Спри таймера"
},
"edit-timeline-event": "Редактирай събитие", "edit-timeline-event": "Редактирай събитие",
"timeline": "Хронология на събитията", "timeline": "Хронология на събитията",
"timeline-is-empty": "Няма история на събитията. Опитайте да приготвите рецептата!", "timeline-is-empty": "Няма история на събитията. Опитайте да приготвите рецептата!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Multiplica", "increase-scale-label": "Multiplica",
"locked": "Bloquejat", "locked": "Bloquejat",
"public-link": "Enllaç públic", "public-link": "Enllaç públic",
"timer": {
"kitchen-timer": "Temporitzador de cuina",
"start-timer": "Iniciar temporitzador",
"pause-timer": "Pausa el temporitzador",
"resume-timer": "Reprèn el temporitzador",
"stop-timer": "Atura el temporitzador"
},
"edit-timeline-event": "Edita l'esdeveniment de la cronologia", "edit-timeline-event": "Edita l'esdeveniment de la cronologia",
"timeline": "Cronologia", "timeline": "Cronologia",
"timeline-is-empty": "Encara no hi ha res a la cronologia. Prova de fer aquesta recepta!", "timeline-is-empty": "Encara no hi ha res a la cronologia. Prova de fer aquesta recepta!",

View file

@ -259,7 +259,7 @@
"allow-users-outside-of-your-group-to-see-your-recipes": "Povolit uživatelům mimo vaši skupinu vidět vaše recepty", "allow-users-outside-of-your-group-to-see-your-recipes": "Povolit uživatelům mimo vaši skupinu vidět vaše recepty",
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Pokud je tato možnost povolena, můžete použít veřejný odkaz pro sdílení konkrétních receptů bez autorizace uživatele. Pokud je tato možnost vypnutá, můžete sdílet recepty pouze s uživateli, kteří jsou ve vaší skupině, nebo s předem vygenerovaným soukromým odkazem", "allow-users-outside-of-your-group-to-see-your-recipes-description": "Pokud je tato možnost povolena, můžete použít veřejný odkaz pro sdílení konkrétních receptů bez autorizace uživatele. Pokud je tato možnost vypnutá, můžete sdílet recepty pouze s uživateli, kteří jsou ve vaší skupině, nebo s předem vygenerovaným soukromým odkazem",
"show-nutrition-information": "Zobrazit nutriční informace", "show-nutrition-information": "Zobrazit nutriční informace",
"show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown", "show-nutrition-information-description": "Pokud je povoleno, informace o výživě se zobrazí na receptu, pokud je k dispozici. Nejsou-li k dispozici údaje o výživové hodnotě, nebudou zobrazeny údaje o výživové hodnotě",
"show-recipe-assets": "Zobrazit položky receptu", "show-recipe-assets": "Zobrazit položky receptu",
"show-recipe-assets-description": "Pokud je tato možnost povolena, zobrazí se u receptu zdroje, pokud jsou k dispozici", "show-recipe-assets-description": "Pokud je tato možnost povolena, zobrazí se u receptu zdroje, pokud jsou k dispozici",
"default-to-landscape-view": "Výchozí zobrazení na šířku", "default-to-landscape-view": "Výchozí zobrazení na šířku",
@ -270,19 +270,19 @@
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Skryje pole Potravina, Jednotka a Množství pro ingredience a považuje ingredience za textová pole", "disable-organizing-recipe-ingredients-by-units-and-food-description": "Skryje pole Potravina, Jednotka a Množství pro ingredience a považuje ingredience za textová pole",
"general-preferences": "Všeobecné předvolby", "general-preferences": "Všeobecné předvolby",
"group-recipe-preferences": "Preference receptů pro skupinu", "group-recipe-preferences": "Preference receptů pro skupinu",
"report": "Report", "report": "Nahlásit",
"report-with-id": "Report ID: {id}", "report-with-id": "ID hlášení: {id}",
"group-management": "Správa skupin", "group-management": "Správa skupin",
"admin-group-management": "Administrátorská správa skupiny", "admin-group-management": "Administrátorská správa skupiny",
"admin-group-management-text": "Změny v této skupině budou okamžitě zohledněny.", "admin-group-management-text": "Změny v této skupině budou okamžitě zohledněny.",
"group-id-value": "ID skupiny: {0}", "group-id-value": "ID skupiny: {0}",
"total-households": "Celkem domácností", "total-households": "Celkem domácností",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household" "you-must-select-a-group-before-selecting-a-household": "Před výběrem domácnosti musíte vybrat skupinu"
}, },
"household": { "household": {
"household": "Domácnost", "household": "Domácnost",
"households": "Domácnosti", "households": "Domácnosti",
"user-household": "User Household", "user-household": "Uživatelova domácnost",
"create-household": "Vytvořit domácnost", "create-household": "Vytvořit domácnost",
"household-name": "Název domácnosti", "household-name": "Název domácnosti",
"household-group": "Skupina domácnosti", "household-group": "Skupina domácnosti",
@ -357,8 +357,8 @@
"for-type-meal-types": "pro {0} druhy jídel", "for-type-meal-types": "pro {0} druhy jídel",
"meal-plan-rules": "Pravidla tvůrce jídelníčků", "meal-plan-rules": "Pravidla tvůrce jídelníčků",
"new-rule": "Nové pravidlo", "new-rule": "Nové pravidlo",
"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 rule filters will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.", "meal-plan-rules-description": "Můžete vytvořit pravidla pro automatický výběr receptů pro vaše stravovací plány. Tato pravidla používají server k určení náhodného souboru receptů, ze kterých se při vytváření plánů jídla vybírat. Všimněte si, že pokud mají pravidla stejná omezení den/typ, budou filtry pravidel sloučeny. V praxi je zbytečné vytvářet duplicitní pravidla, ale je to možné.",
"new-rule-description": "When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to \"Any\" which will apply it to all the possible values for the day and/or meal type.", "new-rule-description": "Při vytváření nového pravidla pro plán jídla můžete omezit použití pravidla pro konkrétní den v týdnu a/nebo konkrétní druh jídla. Chcete-li použít pravidlo pro všechny dny nebo všechny typy jídla, můžete nastavit pravidlo na \"Jakékoliv\", které se použije na všechny možné hodnoty pro den a/nebo druh jídla.",
"recipe-rules": "Pravidla receptu", "recipe-rules": "Pravidla receptu",
"applies-to-all-days": "Použije se na všechny dny", "applies-to-all-days": "Použije se na všechny dny",
"applies-on-days": "Platí pro {0}", "applies-on-days": "Platí pro {0}",
@ -373,7 +373,7 @@
"recipe-migration": "Přenést recept", "recipe-migration": "Přenést recept",
"chowdown": { "chowdown": {
"description": "Migrovat data z aplikace Chowdown", "description": "Migrovat data z aplikace 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 nativně podporuje formát chowdown. Stáhněte si z repozitáře kód jako .zip soubor a nahrajte ho níže.",
"title": "Chowdown" "title": "Chowdown"
}, },
"nextcloud": { "nextcloud": {
@ -403,7 +403,7 @@
"choose-migration-type": "Zvolte si typ migrace", "choose-migration-type": "Zvolte si typ migrace",
"tag-all-recipes": "Označit všechny recepty pomocí štítku {tag-name}", "tag-all-recipes": "Označit všechny recepty pomocí štítku {tag-name}",
"nextcloud-text": "Nextcloud recepty lze importovat ze souboru zip, který obsahuje data uložená v Nextcloudu. Podívejte se na příklad struktury složek níže, abyste se ujistili, že vaše recepty lze importovat.", "nextcloud-text": "Nextcloud recepty lze importovat ze souboru zip, který obsahuje data uložená v Nextcloudu. Podívejte se na příklad struktury složek níže, abyste se ujistili, že vaše recepty lze importovat.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.", "chowdown-text": "Mealie nativně podporuje formát chowdown. Stáhněte si z repozitáře kód jako .zip soubor a nahrajte ho níže.",
"recipe-1": "Recept 1", "recipe-1": "Recept 1",
"recipe-2": "Recept 2", "recipe-2": "Recept 2",
"paprika-text": "Mealie může importovat recepty z aplikace Paprika. Exportujte své recepty z papriky, přejmenujte příponu exportovaného souboru na .zip a nahrajte jej níže.", "paprika-text": "Mealie může importovat recepty z aplikace Paprika. Exportujte své recepty z papriky, přejmenujte příponu exportovaného souboru na .zip a nahrajte jej níže.",
@ -414,7 +414,7 @@
}, },
"myrecipebox": { "myrecipebox": {
"title": "My Recipe Box", "title": "My Recipe Box",
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below." "description-long": "Mealie může importovat recepty z My Recipe Box. Exportujte recepty ve formátu CSV, poté nahrajte soubor .zip níže."
}, },
"recipekeeper": { "recipekeeper": {
"title": "Recipe Keeper", "title": "Recipe Keeper",
@ -570,13 +570,6 @@
"increase-scale-label": "Zvýšit násobení o 1", "increase-scale-label": "Zvýšit násobení o 1",
"locked": "Uzamčeno", "locked": "Uzamčeno",
"public-link": "Veřejný odkaz", "public-link": "Veřejný odkaz",
"timer": {
"kitchen-timer": "Kuchyňský časovač",
"start-timer": "Spustit časovač",
"pause-timer": "Pozastavit časovač",
"resume-timer": "Obnovit časovač",
"stop-timer": "Zastavit časovač"
},
"edit-timeline-event": "Upravit událost časové osy", "edit-timeline-event": "Upravit událost časové osy",
"timeline": "Časová osa", "timeline": "Časová osa",
"timeline-is-empty": "Zatím nic na časové ose není. Zkuste vytvořit tento recept!", "timeline-is-empty": "Zatím nic na časové ose není. Zkuste vytvořit tento recept!",
@ -587,9 +580,9 @@
"how-did-it-turn-out": "Jak to dopadlo?", "how-did-it-turn-out": "Jak to dopadlo?",
"user-made-this": "{user} udělal toto", "user-made-this": "{user} udělal toto",
"last-made-date": "Naposledy uvařeno {date}", "last-made-date": "Naposledy uvařeno {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 party applications. You can use these keys to provide information, for example to trigger automations or custom messages to relay to your desired device.", "api-extras-description": "Recepty jsou klíčovým rysem rozhraní pro API Mealie. Umožňují vytvářet vlastní klíče/hodnoty JSON v rámci receptu pro odkazy na aplikace třetích stran. Tyto klíče můžete použít pro poskytnutí informací, například pro aktivaci automatizace nebo vlastních zpráv pro přenos do požadovaného zařízení.",
"message-key": "Message Key", "message-key": "Klíč zprávy",
"parse": "Parse", "parse": "Analyzovat",
"attach-images-hint": "Přiložit obrázky přetažením jich do editoru", "attach-images-hint": "Přiložit obrázky přetažením jich do editoru",
"drop-image": "Vložit obrázek", "drop-image": "Vložit obrázek",
"enable-ingredient-amounts-to-use-this-feature": "Chcete-li tuto funkci používat, povolte množství ingrediencí", "enable-ingredient-amounts-to-use-this-feature": "Chcete-li tuto funkci používat, povolte množství ingrediencí",
@ -614,9 +607,9 @@
"debug-scraper": "Ladící Scraper", "debug-scraper": "Ladící Scraper",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Vytvořte recept zadáním názvu. Všechny recepty musí mít jedinečná jména.", "create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Vytvořte recept zadáním názvu. Všechny recepty musí mít jedinečná jména.",
"new-recipe-names-must-be-unique": "Názvy receptů musí být jedinečné", "new-recipe-names-must-be-unique": "Názvy receptů musí být jedinečné",
"scrape-recipe": "Scrape Recipe", "scrape-recipe": "Zpracovat recept",
"scrape-recipe-description": "Scrape a recipe by url. Provide the url for the site you want to scrape, and Mealie will attempt to scrape the recipe from that site and add it to your collection.", "scrape-recipe-description": "Zpracovat recept na url. Uveďte adresu url pro str8nku, kterou chcete zpracovat a Mealie se pokusí zpracovat recept z tohoto webu a přidat jej do vaší sbírky.",
"scrape-recipe-have-a-lot-of-recipes": "Have a lot of recipes you want to scrape at once?", "scrape-recipe-have-a-lot-of-recipes": "Máte spoustu receptů, které chcete zpracovat najednou?",
"scrape-recipe-suggest-bulk-importer": "Vyzkoušejte hromadný import", "scrape-recipe-suggest-bulk-importer": "Vyzkoušejte hromadný import",
"scrape-recipe-have-raw-html-or-json-data": "Máte surová data HTML nebo JSON?", "scrape-recipe-have-raw-html-or-json-data": "Máte surová data HTML nebo JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Můžete importovat přímo ze surových dat", "scrape-recipe-you-can-import-from-raw-data-directly": "Můžete importovat přímo ze surových dat",
@ -632,19 +625,19 @@
"create-a-recipe-by-uploading-a-scan": "Vytvořte recept nahráním skenu.", "create-a-recipe-by-uploading-a-scan": "Vytvořte recept nahráním skenu.",
"upload-a-png-image-from-a-recipe-book": "Nahrát png obrázek z knihy receptů", "upload-a-png-image-from-a-recipe-book": "Nahrát png obrázek z knihy receptů",
"recipe-bulk-importer": "Hromadný import receptů", "recipe-bulk-importer": "Hromadný import receptů",
"recipe-bulk-importer-description": "The Bulk recipe importer allows you to import multiple recipes at once by queueing the sites on the backend and running the task in the background. This can be useful when initially migrating to Mealie, or when you want to import a large number of recipes.", "recipe-bulk-importer-description": "Hromadný import receptů vám umožní importovat více receptů najednou tím, že ve frontě stránek na podpůrné vrstvě bude spuštěn úkol na pozadí. To může být užitečné při počáteční migraci na Mealie, nebo když chcete importovat velké množství receptů.",
"set-categories-and-tags": "Nastavte kategorie a štítky", "set-categories-and-tags": "Nastavte kategorie a štítky",
"bulk-imports": "Hromadný import", "bulk-imports": "Hromadný import",
"bulk-import-process-has-started": "Proces hromadného importu byl zahájen", "bulk-import-process-has-started": "Proces hromadného importu byl zahájen",
"bulk-import-process-has-failed": "Proces hromadného importu se nezdařil", "bulk-import-process-has-failed": "Proces hromadného importu se nezdařil",
"report-deletion-failed": "Odstranění reportu se nezdařilo", "report-deletion-failed": "Odstranění reportu se nezdařilo",
"recipe-debugger": "Recipe Debugger", "recipe-debugger": "Ladění receptů",
"recipe-debugger-description": "Grab the URL of the recipe you want to debug and paste it here. The URL will be scraped by the recipe scraper and the results will be displayed. If you don't see any data returned, the site you are trying to scrape is not supported by Mealie or its scraper library.", "recipe-debugger-description": "Získejte URL receptu, který chcete ladit a vložte jej zde. URL bude zpracováno procesorem receptů a budou zobrazeny výsledky. Pokud nevidíte žádná data, stránka, kterou se pokoušíte zpracovat, není podporována ani v Mealie ani jeho knihovně pro zpracování.",
"use-openai": "Použít OpenAI", "use-openai": "Použít OpenAI",
"recipe-debugger-use-openai-description": "Use OpenAI to parse the results instead of relying on the scraper library. When creating a recipe via URL, this is done automatically if the scraper library fails, but you may test it manually here.", "recipe-debugger-use-openai-description": "Použijte OpenAI k analýze výsledků namísto spoléhání se na knihovnu pro zpracování. Při vytváření receptu prostřednictvím adresy URL se to provádí automaticky, pokud knihovna pro zpracování selže, ale můžete to zde otestovat ručně.",
"debug": "Ladit", "debug": "Ladit",
"tree-view": "Stromové zobrazení", "tree-view": "Stromové zobrazení",
"recipe-servings": "Recipe Servings", "recipe-servings": "Počet porcí",
"recipe-yield": "Recipe Yield", "recipe-yield": "Recipe Yield",
"recipe-yield-text": "Recipe Yield Text", "recipe-yield-text": "Recipe Yield Text",
"unit": "Jednotka", "unit": "Jednotka",
@ -654,14 +647,14 @@
"nextStep": "Další krok", "nextStep": "Další krok",
"recipe-actions": "Akce receptu", "recipe-actions": "Akce receptu",
"parser": { "parser": {
"experimental-alert-text": "Mealie uses natural language processing to parse and create units and food items for your recipe ingredients. This feature is experimental and may not always work as expected. If you prefer not to use the parsed results, you can select 'Cancel' and your changes will not be saved.", "experimental-alert-text": "Mealie používá přirozené zpracování jazyka k analýze a vytváření jednotek a položek jídla pro vaše ingredience. Tato funkce je experimentální a nemusí vždy fungovat podle očekávání. Pokud raději nepoužíváte analyzované výsledky, můžete zvolit 'Zrušit' a vaše změny nebudou uloženy.",
"ingredient-parser": "Ingredient Parser", "ingredient-parser": "Analyzátor ingrediencí",
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.", "explanation": "Chcete-li použít analyzátor ingrediencí, klikněte na tlačítko \"Analyzovat vše\" pro zahájení procesu. Jakmile budou zpracované suroviny k dispozici, můžete zkontrolovat položky a ověřit, že byly správně analyzovány. Skóre důvěry modelu se zobrazuje vpravo od názvu položky. Toto skóre je průměrem všech jednotlivých skóre a nemusí být vždy zcela přesné.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.", "alerts-explainer": "Upozornění se zobrazí v případě, že je nalezena odpovídající potravina nebo jednotka, ale v databázi neexistuje.",
"select-parser": "Select Parser", "select-parser": "Vyberte analyzátor",
"natural-language-processor": "Natural Language Processor", "natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser", "brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser", "openai-parser": "Analyzátor OpenAI",
"parse-all": "Parsovat vše", "parse-all": "Parsovat vše",
"no-unit": "Žádná jednotka", "no-unit": "Žádná jednotka",
"missing-unit": "Vytvořit chybějící jednotku: {unit}", "missing-unit": "Vytvořit chybějící jednotku: {unit}",
@ -669,22 +662,22 @@
"no-food": "Žádné jídlo" "no-food": "Žádné jídlo"
}, },
"reset-servings-count": "Resetovat počet porcí", "reset-servings-count": "Resetovat počet porcí",
"not-linked-ingredients": "Additional Ingredients" "not-linked-ingredients": "Další ingredience"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Vyhledávač receptů",
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", "recipe-finder-description": "Vyhledávání receptů na základě přísad, které máte na ruce. Můžete také filtrovat pomocí nástrojů, které máte k dispozici, a nastavit maximální počet chybějících ingrediencí nebo nástrojů.",
"selected-ingredients": "Selected Ingredients", "selected-ingredients": "Vybrané ingredience",
"no-ingredients-selected": "No ingredients selected", "no-ingredients-selected": "Nebyly vybrány žádné ingredience",
"missing": "Chybějící", "missing": "Chybějící",
"no-recipes-found": "Nebyly nalezeny žádné recepty", "no-recipes-found": "Nebyly nalezeny žádné recepty",
"no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters", "no-recipes-found-description": "Zkuste do hledání přidat další ingredience nebo upravit své filtry",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Zahrnout ingredience jež k dispozici",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Zahrnout nástroje, které máte po ruce",
"max-missing-ingredients": "Max Missing Ingredients", "max-missing-ingredients": "Maximální počet chybějících ingrediencí",
"max-missing-tools": "Max Missing Tools", "max-missing-tools": "Maximální počet chybějících nástrojů",
"selected-tools": "Selected Tools", "selected-tools": "Vybrané nástroje",
"other-filters": "Other Filters", "other-filters": "Jiné filtry",
"ready-to-make": "Ready to Make", "ready-to-make": "Ready to Make",
"almost-ready-to-make": "Almost Ready to Make" "almost-ready-to-make": "Almost Ready to Make"
}, },
@ -725,9 +718,9 @@
"import-summary": "Shrnutí importu", "import-summary": "Shrnutí importu",
"partial-backup": "Částečná záloha", "partial-backup": "Částečná záloha",
"unable-to-delete-backup": "Zálohu nelze odstranit.", "unable-to-delete-backup": "Zálohu nelze odstranit.",
"experimental-description": "Backups are 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 of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.", "experimental-description": "Zálohy jsou celkové snímky databáze a datového adresáře webu. Tato položka zahrnuje všechny údaje a nelze ji nastavit pro vyloučení podsouborů údajů. Můžete ji brát jako snímek Mealie v určitém čase. Tyto slouží jako způsob, jak nezávisle na databázi exportovat a importovat data nebo zálohovat stránky na externí umístění.",
"backup-restore": "Obnova zálohy", "backup-restore": "Obnova zálohy",
"back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.", "back-restore-description": "Obnovení této zálohy přepíše všechna aktuální data ve vaší databázi a v datovém adresáři a nahradí je obsahem této zálohy. {cannot-be-undone} Pokud je obnovení úspěšné, budete odhlášeni.",
"cannot-be-undone": "Tuto akci nelze vrátit zpět - používejte ji s opatrností.", "cannot-be-undone": "Tuto akci nelze vrátit zpět - používejte ji s opatrností.",
"postgresql-note": "Pokud používáte PostgreSQL, před obnovením si prosím přečtete {backup-restore-process}.", "postgresql-note": "Pokud používáte PostgreSQL, před obnovením si prosím přečtete {backup-restore-process}.",
"backup-restore-process-in-the-documentation": "proces zálohy/obnovení v dokumentaci", "backup-restore-process-in-the-documentation": "proces zálohy/obnovení v dokumentaci",
@ -820,11 +813,11 @@
"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": "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."
}, },
"bug-report": "Chybové hlášení", "bug-report": "Chybové hlášení",
"bug-report-information": "Use this information to report a bug. Providing details of your instance to developers is the best way to get your issues resolved quickly.", "bug-report-information": "Použijte tyto informace k nahlášení chyby. Poskytnutí podrobností vaší instance vývojářům je nejlepší způsob, jak rychle vyřešit vaše problémy.",
"tracker": "Tracker", "tracker": "Tracker",
"configuration": "Konfigurace", "configuration": "Konfigurace",
"docker-volume": "Volume dockeru", "docker-volume": "Volume dockeru",
"docker-volume-help": "Mealie requires that the frontend container and the backend share the same docker volume or storage. This ensures that the frontend container can properly access the images and assets stored on disk.", "docker-volume-help": "Mealie vyžaduje, aby kontejner prostředí a podpůrné vrstvy sdílely stejný úložný prostor dockeru. Tím se zajistí, že kontejner prostředí bude moci správně přistupovat k obrázkům a informacím uloženým na disku.",
"volumes-are-misconfigured": "Svazky jsou špatně nakonfigurovány.", "volumes-are-misconfigured": "Svazky jsou špatně nakonfigurovány.",
"volumes-are-configured-correctly": "Volumy jsou nastaveny správně.", "volumes-are-configured-correctly": "Volumy jsou nastaveny správně.",
"status-unknown-try-running-a-validation": "Neznámý stav. Zkuste provést validaci.", "status-unknown-try-running-a-validation": "Neznámý stav. Zkuste provést validaci.",
@ -891,7 +884,7 @@
"are-you-sure-you-want-to-check-all-items": "Opravdu chcete vybrat všechny položky?", "are-you-sure-you-want-to-check-all-items": "Opravdu chcete vybrat všechny položky?",
"are-you-sure-you-want-to-uncheck-all-items": "Opravdu chcete zrušit výběr všech položek?", "are-you-sure-you-want-to-uncheck-all-items": "Opravdu chcete zrušit výběr všech položek?",
"are-you-sure-you-want-to-delete-checked-items": "Opravdu chcete odstranit všechny vybrané položky?", "are-you-sure-you-want-to-delete-checked-items": "Opravdu chcete odstranit všechny vybrané položky?",
"no-shopping-lists-found": "No Shopping Lists Found" "no-shopping-lists-found": "Nebyly nalezeny žádné nákupní seznamy"
}, },
"sidebar": { "sidebar": {
"all-recipes": "Všechny recepty", "all-recipes": "Všechny recepty",
@ -1354,7 +1347,7 @@
"cookbooks": "Kuchařky", "cookbooks": "Kuchařky",
"description": "Kuchařky jsou dalším způsobem, jak uspořádat recepty vytvořením průřezů receptů, organizátorů a dalších filtrů. Vytvořením kuchařky se přidá položka na postranní panel a v kuchařce se zobrazí všechny recepty s vybranými filtry.", "description": "Kuchařky jsou dalším způsobem, jak uspořádat recepty vytvořením průřezů receptů, organizátorů a dalších filtrů. Vytvořením kuchařky se přidá položka na postranní panel a v kuchařce se zobrazí všechny recepty s vybranými filtry.",
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", "hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households",
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar", "hide-cookbooks-from-other-households-description": "Pokud je povoleno, objeví se na postranním panelu pouze kuchařské knihy z vaší domácnosti",
"public-cookbook": "Veřejná kuchařka", "public-cookbook": "Veřejná kuchařka",
"public-cookbook-description": "Veřejné kuchařky mohou být sdíleny s neregistrovanými uživateli a budou zobrazeny na stránce vaší skupiny.", "public-cookbook-description": "Veřejné kuchařky mohou být sdíleny s neregistrovanými uživateli a budou zobrazeny na stránce vaší skupiny.",
"filter-options": "Možnosti filtru", "filter-options": "Možnosti filtru",

View file

@ -277,7 +277,7 @@
"admin-group-management-text": "Ændringer i denne gruppe vil træde i kraft øjeblikkeligt.", "admin-group-management-text": "Ændringer i denne gruppe vil træde i kraft øjeblikkeligt.",
"group-id-value": "Gruppe-ID: {0}", "group-id-value": "Gruppe-ID: {0}",
"total-households": "Husholdninger i Alt", "total-households": "Husholdninger i Alt",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household" "you-must-select-a-group-before-selecting-a-household": "Du skal vælge en gruppe, før du vælger en husstand"
}, },
"household": { "household": {
"household": "Husholdning", "household": "Husholdning",
@ -570,13 +570,6 @@
"increase-scale-label": "Forøg skala med 1", "increase-scale-label": "Forøg skala med 1",
"locked": "Låst", "locked": "Låst",
"public-link": "Offentligt link", "public-link": "Offentligt link",
"timer": {
"kitchen-timer": "Køkken Ur",
"start-timer": "Start timer",
"pause-timer": "Sæt timer på pause",
"resume-timer": "Genoptag Timer",
"stop-timer": "Stop timer"
},
"edit-timeline-event": "Rediger tidslinjebegivenhed", "edit-timeline-event": "Rediger tidslinjebegivenhed",
"timeline": "Tidslinje", "timeline": "Tidslinje",
"timeline-is-empty": "Intet på tidslinjen endnu. Prøv at lave denne opskrift!", "timeline-is-empty": "Intet på tidslinjen endnu. Prøv at lave denne opskrift!",
@ -672,21 +665,21 @@
"not-linked-ingredients": "Additional Ingredients" "not-linked-ingredients": "Additional Ingredients"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Opskrift Finder",
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", "recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.",
"selected-ingredients": "Selected Ingredients", "selected-ingredients": "Valgte Ingredienser",
"no-ingredients-selected": "No ingredients selected", "no-ingredients-selected": "Ingen ingredienser valgt",
"missing": "Missing", "missing": "Mangler",
"no-recipes-found": "No recipes found", "no-recipes-found": "Ingen opskrifter fundet",
"no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters", "no-recipes-found-description": "Prøv at tilføje flere ingredienser til din søgning eller justere dine filtre",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Inkluder ingredienser du allerede har",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Inkluder værktøjer du allerede har",
"max-missing-ingredients": "Max Missing Ingredients", "max-missing-ingredients": "Maksimum Manglende Ingredienser",
"max-missing-tools": "Max Missing Tools", "max-missing-tools": "Max Missing Tools",
"selected-tools": "Selected Tools", "selected-tools": "Selected Tools",
"other-filters": "Other Filters", "other-filters": "Andre filtre",
"ready-to-make": "Ready to Make", "ready-to-make": "Klar til at lave",
"almost-ready-to-make": "Almost Ready to Make" "almost-ready-to-make": "Næsten klar til at lave"
}, },
"search": { "search": {
"advanced-search": "Avanceret søgning", "advanced-search": "Avanceret søgning",
@ -891,7 +884,7 @@
"are-you-sure-you-want-to-check-all-items": "Er du sikker på, at du vil markere alle elementer?", "are-you-sure-you-want-to-check-all-items": "Er du sikker på, at du vil markere alle elementer?",
"are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på, at du vil fjerne markeringen af alle elementer?", "are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på, at du vil fjerne markeringen af alle elementer?",
"are-you-sure-you-want-to-delete-checked-items": "Er du sikker på, at du vil sletter de valgte elementer?", "are-you-sure-you-want-to-delete-checked-items": "Er du sikker på, at du vil sletter de valgte elementer?",
"no-shopping-lists-found": "No Shopping Lists Found" "no-shopping-lists-found": "Ingen Indkøbslister fundet"
}, },
"sidebar": { "sidebar": {
"all-recipes": "Alle opskr.", "all-recipes": "Alle opskr.",
@ -1303,7 +1296,7 @@
"profile": { "profile": {
"welcome-user": "👋 Velkommen, {0}!", "welcome-user": "👋 Velkommen, {0}!",
"description": "Administrer din profil, opskrifter og gruppeindstillinger.", "description": "Administrer din profil, opskrifter og gruppeindstillinger.",
"invite-link": "Invite Link", "invite-link": "Invitationslink",
"get-invite-link": "Få Invitationslink", "get-invite-link": "Få Invitationslink",
"get-public-link": "Offentligt link", "get-public-link": "Offentligt link",
"account-summary": "Kontooversigt", "account-summary": "Kontooversigt",
@ -1353,8 +1346,8 @@
"cookbook": { "cookbook": {
"cookbooks": "Kogebøger", "cookbooks": "Kogebøger",
"description": "Kogebøger er en anden måde at organisere opskrifter ved at skabe tværsnit af opskrifter, arrangører, og andre filtre. Oprettelse af en kogebog vil tilføje et link i sidemenuen, og alle opskrifter med de valgte filtre vil blive vist i kogebogen.", "description": "Kogebøger er en anden måde at organisere opskrifter ved at skabe tværsnit af opskrifter, arrangører, og andre filtre. Oprettelse af en kogebog vil tilføje et link i sidemenuen, og alle opskrifter med de valgte filtre vil blive vist i kogebogen.",
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", "hide-cookbooks-from-other-households": "Skjul kogebøger fra andre husholdninger",
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar", "hide-cookbooks-from-other-households-description": "Når aktiveret, kun kogebøger fra din husstand vises på sidepanelet",
"public-cookbook": "Offentlig kogebog", "public-cookbook": "Offentlig kogebog",
"public-cookbook-description": "Offentlige kogebøger kan deles med personer, der ikke er oprettet som brugere i Mealie og vil blive vist på din gruppe side.", "public-cookbook-description": "Offentlige kogebøger kan deles med personer, der ikke er oprettet som brugere i Mealie og vil blive vist på din gruppe side.",
"filter-options": "Filtreringsindstillinger", "filter-options": "Filtreringsindstillinger",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Maßstab um 1 erhöhen", "increase-scale-label": "Maßstab um 1 erhöhen",
"locked": "Gesperrt", "locked": "Gesperrt",
"public-link": "Öffentlicher Link", "public-link": "Öffentlicher Link",
"timer": {
"kitchen-timer": "Küchenwecker",
"start-timer": "Wecker starten",
"pause-timer": "Wecker pausieren",
"resume-timer": "Wecker fortsetzen",
"stop-timer": "Wecker stoppen"
},
"edit-timeline-event": "Zeitstrahl-Ereignis bearbeiten", "edit-timeline-event": "Zeitstrahl-Ereignis bearbeiten",
"timeline": "Zeitstrahl", "timeline": "Zeitstrahl",
"timeline-is-empty": "Noch nichts auf dem Zeitstrahl. Probier dieses Rezept aus!", "timeline-is-empty": "Noch nichts auf dem Zeitstrahl. Probier dieses Rezept aus!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Αύξηση κλίμακας κατά 1", "increase-scale-label": "Αύξηση κλίμακας κατά 1",
"locked": "Κλειδωμένο", "locked": "Κλειδωμένο",
"public-link": "Δημόσιος σύνδεσμος", "public-link": "Δημόσιος σύνδεσμος",
"timer": {
"kitchen-timer": "Χρονόμετρο Κουζίνας",
"start-timer": "Εναρξη χρονομέτρου",
"pause-timer": "Παύση χρονόμετρου",
"resume-timer": "Συνέχιση χρονομέτρου",
"stop-timer": "Διακοπή χρονόμετρου"
},
"edit-timeline-event": "Επεξεργασία συμβάντος χρονοδιαγράμματος", "edit-timeline-event": "Επεξεργασία συμβάντος χρονοδιαγράμματος",
"timeline": "Χρονοδιάγραμμα", "timeline": "Χρονοδιάγραμμα",
"timeline-is-empty": "Δεν υπάρχει τίποτα ακόμα στο χρονοδιάγραμμα. Δοκιμάστε να κάνετε αυτή τη συνταγή!", "timeline-is-empty": "Δεν υπάρχει τίποτα ακόμα στο χρονοδιάγραμμα. Δοκιμάστε να κάνετε αυτή τη συνταγή!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Aumentar escala en 1", "increase-scale-label": "Aumentar escala en 1",
"locked": "Bloqueada", "locked": "Bloqueada",
"public-link": "Enlace público", "public-link": "Enlace público",
"timer": {
"kitchen-timer": "Temporizador de cocina",
"start-timer": "Iniciar Temporizador",
"pause-timer": "Pausar Temporizador",
"resume-timer": "Reanudar Temporizador",
"stop-timer": "Detener temporizador"
},
"edit-timeline-event": "Editar evento en la cronología", "edit-timeline-event": "Editar evento en la cronología",
"timeline": "Cronología", "timeline": "Cronología",
"timeline-is-empty": "Aún no hay nada en la línea de tiempo. ¡Intenta hacer esta receta!", "timeline-is-empty": "Aún no hay nada en la línea de tiempo. ¡Intenta hacer esta receta!",

File diff suppressed because it is too large Load diff

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Suurenna mittakaavaa yhdellä", "increase-scale-label": "Suurenna mittakaavaa yhdellä",
"locked": "Lukittu", "locked": "Lukittu",
"public-link": "Julkinen Linkki", "public-link": "Julkinen Linkki",
"timer": {
"kitchen-timer": "Munakello",
"start-timer": "Käynnistä ajastin",
"pause-timer": "Keskeytä ajastin",
"resume-timer": "Jatka ajastusta",
"stop-timer": "Pysäytä ajastin"
},
"edit-timeline-event": "Muokkaa Aikajanan Tapahtumaa", "edit-timeline-event": "Muokkaa Aikajanan Tapahtumaa",
"timeline": "Aikajana", "timeline": "Aikajana",
"timeline-is-empty": "Aikajana on tyhjä. Tee resepti.", "timeline-is-empty": "Aikajana on tyhjä. Tee resepti.",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Augmenter léchelle de 1", "increase-scale-label": "Augmenter léchelle de 1",
"locked": "Verrouillé", "locked": "Verrouillé",
"public-link": "Lien public", "public-link": "Lien public",
"timer": {
"kitchen-timer": "Minuteur",
"start-timer": "Démarrer le minuteur",
"pause-timer": "Mettre en pause le minuteur",
"resume-timer": "Reprendre le minuteur",
"stop-timer": "Arrêter le minuteur"
},
"edit-timeline-event": "Modifier lévénement dans lhistorique", "edit-timeline-event": "Modifier lévénement dans lhistorique",
"timeline": "Historique", "timeline": "Historique",
"timeline-is-empty": "Pas encore dhistorique. Essayez de cuisiner cette recette!", "timeline-is-empty": "Pas encore dhistorique. Essayez de cuisiner cette recette!",
@ -1352,7 +1345,7 @@
}, },
"cookbook": { "cookbook": {
"cookbooks": "Livres de recettes", "cookbooks": "Livres de recettes",
"description": "Les livres de recettes sont un autre moyen dorganiser des recettes en sélectionnant un ensemble précis de recettes, de classification et de filtres. La création d'un livre de recettes ajoute une entrée à la barre latérale et toutes les recettes avec les filtres choisies seront affichées dans le livre de recettes.", "description": "Les livres de recettes sont un autre moyen d'organiser des recettes en sélectionnant un ensemble précis de recettes, de classification et de filtres. La création d'un livre de recettes ajoute une entrée à la barre latérale et toutes les recettes avec les filtres choisies seront affichées dans le livre de recettes.",
"hide-cookbooks-from-other-households": "Masquer les livres de cuisine des autres foyers", "hide-cookbooks-from-other-households": "Masquer les livres de cuisine des autres foyers",
"hide-cookbooks-from-other-households-description": "Lorsque cette option est activée, seuls les livres de cuisine de votre foyer apparaîtront dans la barre latérale", "hide-cookbooks-from-other-households-description": "Lorsque cette option est activée, seuls les livres de cuisine de votre foyer apparaîtront dans la barre latérale",
"public-cookbook": "Livre de recettes public", "public-cookbook": "Livre de recettes public",
@ -1384,8 +1377,8 @@
"relational-keywords": { "relational-keywords": {
"is": "est", "is": "est",
"is-not": "nest pas", "is-not": "nest pas",
"is-one-of": "est un de", "is-one-of": "fait partie de",
"is-not-one-of": "n'est pas un de", "is-not-one-of": "ne fait pas partie de",
"contains-all-of": "contient tout", "contains-all-of": "contient tout",
"is-like": "est comme", "is-like": "est comme",
"is-not-like": "n'est pas similaire à" "is-not-like": "n'est pas similaire à"

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Augmenter l'échelle de 1", "increase-scale-label": "Augmenter l'échelle de 1",
"locked": "Verrouillé", "locked": "Verrouillé",
"public-link": "Lien public", "public-link": "Lien public",
"timer": {
"kitchen-timer": "Minuteur",
"start-timer": "Démarrer le minuteur",
"pause-timer": "Mettre en pause le minuteur",
"resume-timer": "Reprendre le minuteur",
"stop-timer": "Arrêter le minuteur"
},
"edit-timeline-event": "Modifier lévénement dans lhistorique", "edit-timeline-event": "Modifier lévénement dans lhistorique",
"timeline": "Historique", "timeline": "Historique",
"timeline-is-empty": "Pas encore dhistorique. Essayez de cuisiner cette recette!", "timeline-is-empty": "Pas encore dhistorique. Essayez de cuisiner cette recette!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Augmenter léchelle de 1", "increase-scale-label": "Augmenter léchelle de 1",
"locked": "Verrouillé", "locked": "Verrouillé",
"public-link": "Lien public", "public-link": "Lien public",
"timer": {
"kitchen-timer": "Minuteur",
"start-timer": "Démarrer le minuteur",
"pause-timer": "Mettre en pause le minuteur",
"resume-timer": "Reprendre le minuteur",
"stop-timer": "Arrêter le minuteur"
},
"edit-timeline-event": "Modifier lévénement dans lhistorique", "edit-timeline-event": "Modifier lévénement dans lhistorique",
"timeline": "Historique", "timeline": "Historique",
"timeline-is-empty": "Pas encore dhistorique. Essayez de cuisiner cette recette!", "timeline-is-empty": "Pas encore dhistorique. Essayez de cuisiner cette recette!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "העלה קנה מידה ב-1", "increase-scale-label": "העלה קנה מידה ב-1",
"locked": "נעול", "locked": "נעול",
"public-link": "כתובת פומבית", "public-link": "כתובת פומבית",
"timer": {
"kitchen-timer": "טיימר למטבח",
"start-timer": "התחל את הטיימר",
"pause-timer": "השהה את הטיימר",
"resume-timer": "המשך את הטיימר",
"stop-timer": "עצור את הטיימר"
},
"edit-timeline-event": "עריכת אירוע ציר זמן", "edit-timeline-event": "עריכת אירוע ציר זמן",
"timeline": "ציר זמן", "timeline": "ציר זמן",
"timeline-is-empty": "אין כלום בציר הזמן. נסה לעשות את המתכון הזה!", "timeline-is-empty": "אין כלום בציר הזמן. נסה לעשות את המתכון הזה!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Povećaj skaliranje za 1", "increase-scale-label": "Povećaj skaliranje za 1",
"locked": "Zaključano", "locked": "Zaključano",
"public-link": "Javni Link", "public-link": "Javni Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Uredi Događaj Vremenske Crte", "edit-timeline-event": "Uredi Događaj Vremenske Crte",
"timeline": "Vremenska Crta", "timeline": "Vremenska Crta",
"timeline-is-empty": "Još nema ništa na vremenskoj crti. Pokušajte napraviti ovaj recept!", "timeline-is-empty": "Još nema ništa na vremenskoj crti. Pokušajte napraviti ovaj recept!",

View file

@ -277,7 +277,7 @@
"admin-group-management-text": "A csoporthoz tartozó változtatások azonnal megjelennek.", "admin-group-management-text": "A csoporthoz tartozó változtatások azonnal megjelennek.",
"group-id-value": "Csoport azonosító: {0}", "group-id-value": "Csoport azonosító: {0}",
"total-households": "Háztartások száma", "total-households": "Háztartások száma",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household" "you-must-select-a-group-before-selecting-a-household": "A háztartás kiválasztása előtt ki kell választania egy csoportot"
}, },
"household": { "household": {
"household": "Háztartás", "household": "Háztartás",
@ -518,7 +518,7 @@
"save-recipe-before-use": "Recept mentése használat előtt", "save-recipe-before-use": "Recept mentése használat előtt",
"section-title": "Szakasz címe", "section-title": "Szakasz címe",
"servings": "Adag", "servings": "Adag",
"serves-amount": "Serves {amount}", "serves-amount": "Adag {amount}",
"share-recipe-message": "Szeretném megossztani veled a {0} receptemet.", "share-recipe-message": "Szeretném megossztani veled a {0} receptemet.",
"show-nutrition-values": "Tápértékek megjelenítése", "show-nutrition-values": "Tápértékek megjelenítése",
"sodium-content": "Nátrium", "sodium-content": "Nátrium",
@ -570,13 +570,6 @@
"increase-scale-label": "Skála növelése 1-gyel", "increase-scale-label": "Skála növelése 1-gyel",
"locked": "Zárolt", "locked": "Zárolt",
"public-link": "Nyilvános link", "public-link": "Nyilvános link",
"timer": {
"kitchen-timer": "Konyhai időzítő",
"start-timer": "Időzítő elindítása",
"pause-timer": "Időzítő megállítása",
"resume-timer": "Időzítő folytatása",
"stop-timer": "Időzítő leállítása"
},
"edit-timeline-event": "Idővonal-esemény szerkesztése", "edit-timeline-event": "Idővonal-esemény szerkesztése",
"timeline": "Idővonal", "timeline": "Idővonal",
"timeline-is-empty": "Az idővonalon még semmi sincs. Próbálja meg elkészíteni ezt a receptet!", "timeline-is-empty": "Az idővonalon még semmi sincs. Próbálja meg elkészíteni ezt a receptet!",
@ -644,7 +637,7 @@
"recipe-debugger-use-openai-description": "Használja az OpenAI-t az eredmények elemzésére, ahelyett, hogy a scraper könyvtárra hagyatkozna. Ha URL-címen keresztül hoz létre receptet, ez automatikusan megtörténik, ha a scraper könyvtár nem működik, ám itt manuálisan is tesztelheti.", "recipe-debugger-use-openai-description": "Használja az OpenAI-t az eredmények elemzésére, ahelyett, hogy a scraper könyvtárra hagyatkozna. Ha URL-címen keresztül hoz létre receptet, ez automatikusan megtörténik, ha a scraper könyvtár nem működik, ám itt manuálisan is tesztelheti.",
"debug": "Hibakeresés", "debug": "Hibakeresés",
"tree-view": "Fa nézet", "tree-view": "Fa nézet",
"recipe-servings": "Recipe Servings", "recipe-servings": "Recept tálalások",
"recipe-yield": "Adagonkénti információk", "recipe-yield": "Adagonkénti információk",
"recipe-yield-text": "Recipe Yield Text", "recipe-yield-text": "Recipe Yield Text",
"unit": "Mennyiségi egység", "unit": "Mennyiségi egység",
@ -669,24 +662,24 @@
"no-food": "Élelmiszer nélküli" "no-food": "Élelmiszer nélküli"
}, },
"reset-servings-count": "Adagok számának visszaállítása", "reset-servings-count": "Adagok számának visszaállítása",
"not-linked-ingredients": "Additional Ingredients" "not-linked-ingredients": "Kiegészítő hozzávalók"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Receptkereső",
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", "recipe-finder-description": "Keressen recepteket a kéznél lévő összetevők alapján. A rendelkezésre álló eszközök alapján is szűrhet, és beállíthatja a hiányzó összetevők vagy eszközök maximális számát.",
"selected-ingredients": "Selected Ingredients", "selected-ingredients": "Kiválasztott összetevők",
"no-ingredients-selected": "No ingredients selected", "no-ingredients-selected": "Nincsenek kiválasztott összetevők",
"missing": "Missing", "missing": "Hiányzó",
"no-recipes-found": "No recipes found", "no-recipes-found": "Nem található recept",
"no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters", "no-recipes-found-description": "Próbáljon meg több összetevőt hozzáadni a kereséshez, vagy állítsa be a szűrőket",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Beleértve a kéznél lévő összetevőket",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Beleértve a kéznél lévő eszközöket",
"max-missing-ingredients": "Max Missing Ingredients", "max-missing-ingredients": "Maximálisan hiányzó összetevők száma",
"max-missing-tools": "Max Missing Tools", "max-missing-tools": "Maximálisan hiányzó eszközök száma",
"selected-tools": "Selected Tools", "selected-tools": "Kiválasztott eszközök",
"other-filters": "Other Filters", "other-filters": "További szűrők",
"ready-to-make": "Ready to Make", "ready-to-make": "Előkészítve",
"almost-ready-to-make": "Almost Ready to Make" "almost-ready-to-make": "Majdnem készen áll"
}, },
"search": { "search": {
"advanced-search": "Részletes keresés", "advanced-search": "Részletes keresés",
@ -891,7 +884,7 @@
"are-you-sure-you-want-to-check-all-items": "Biztos, hogy minden elemet be akar jelölni?", "are-you-sure-you-want-to-check-all-items": "Biztos, hogy minden elemet be akar jelölni?",
"are-you-sure-you-want-to-uncheck-all-items": "Biztos, hogy minden elem kijelölését visszavonja?", "are-you-sure-you-want-to-uncheck-all-items": "Biztos, hogy minden elem kijelölését visszavonja?",
"are-you-sure-you-want-to-delete-checked-items": "Biztosan törölni akarja az összes bejelölt elemet?", "are-you-sure-you-want-to-delete-checked-items": "Biztosan törölni akarja az összes bejelölt elemet?",
"no-shopping-lists-found": "No Shopping Lists Found" "no-shopping-lists-found": "Nem találhatók bevásárlólisták"
}, },
"sidebar": { "sidebar": {
"all-recipes": "Minden recept", "all-recipes": "Minden recept",
@ -1303,7 +1296,7 @@
"profile": { "profile": {
"welcome-user": "👋 Üdvözöljük, {0}!", "welcome-user": "👋 Üdvözöljük, {0}!",
"description": "Profiljának, receptjeinek és csoportbeállításainak kezelése.", "description": "Profiljának, receptjeinek és csoportbeállításainak kezelése.",
"invite-link": "Invite Link", "invite-link": "Meghívó link",
"get-invite-link": "Meghívó link beszerzése", "get-invite-link": "Meghívó link beszerzése",
"get-public-link": "Nyilvánon link beszerzése", "get-public-link": "Nyilvánon link beszerzése",
"account-summary": "Fiók áttekintése", "account-summary": "Fiók áttekintése",
@ -1333,7 +1326,7 @@
"notifiers-description": "Állítson be olyan e-mail és push-értesítéseket, amelyek meghatározott események esetén lépnek működésbe.", "notifiers-description": "Állítson be olyan e-mail és push-értesítéseket, amelyek meghatározott események esetén lépnek működésbe.",
"manage-data": "Adatok kezelése", "manage-data": "Adatok kezelése",
"manage-data-description": "Az Ön Mealie adatainak kezelése: alapanyagok, mértékegységek, kategóriák, címkék, stb.", "manage-data-description": "Az Ön Mealie adatainak kezelése: alapanyagok, mértékegységek, kategóriák, címkék, stb.",
"data-migrations": "Adat migráció", "data-migrations": "Adatmigráció",
"data-migrations-description": "Migrálja meglévő adatait más alkalmazásokból, például Nextcloud Recipes vagy Chowdown.", "data-migrations-description": "Migrálja meglévő adatait más alkalmazásokból, például Nextcloud Recipes vagy Chowdown.",
"email-sent": "E-mail elküldve", "email-sent": "E-mail elküldve",
"error-sending-email": "Hiba történt az e-Mail küldésénél", "error-sending-email": "Hiba történt az e-Mail küldésénél",
@ -1348,13 +1341,13 @@
"manage-members": "Tagok Kezelése", "manage-members": "Tagok Kezelése",
"manage-webhooks": "Webhookok kezelése", "manage-webhooks": "Webhookok kezelése",
"manage-notifiers": "Értesítések kezelése", "manage-notifiers": "Értesítések kezelése",
"manage-data-migrations": "Adatok migrációjának kezelése" "manage-data-migrations": "Adatmigráció kezelése"
}, },
"cookbook": { "cookbook": {
"cookbooks": "Szakácskönyvek", "cookbooks": "Szakácskönyvek",
"description": "A szakácskönyvek egy másik módja a receptek rendszerezésének a receptek, szervezők és egyéb szűrők keresztmetszeteinek létrehozásával. Egy szakácskönyv létrehozása egy bejegyzést ad az oldalsávhoz, és a kiválasztott szűrőkkel rendelkező összes recept megjelenik a szakácskönyvben.", "description": "A szakácskönyvek egy másik módja a receptek rendszerezésének a receptek, szervezők és egyéb szűrők keresztmetszeteinek létrehozásával. Egy szakácskönyv létrehozása egy bejegyzést ad az oldalsávhoz, és a kiválasztott szűrőkkel rendelkező összes recept megjelenik a szakácskönyvben.",
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", "hide-cookbooks-from-other-households": "Szakácskönyvek elrejtése más háztartásoktól",
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar", "hide-cookbooks-from-other-households-description": "Ha engedélyezve van, csak az Ön háztartásának szakácskönyvei jelennek meg az oldalsávban",
"public-cookbook": "Nyilvános szakácskönyv", "public-cookbook": "Nyilvános szakácskönyv",
"public-cookbook-description": "A nyilvános szakácskönyvek megoszthatók a nem mealie felhasználókkal, és megjelennek a csoportod oldalán.", "public-cookbook-description": "A nyilvános szakácskönyvek megoszthatók a nem mealie felhasználókkal, és megjelennek a csoportod oldalán.",
"filter-options": "Szűrési beállítások", "filter-options": "Szűrési beállítások",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -526,7 +526,7 @@
"sugar-content": "Zuccheri", "sugar-content": "Zuccheri",
"title": "Titolo", "title": "Titolo",
"total-time": "Tempo Totale", "total-time": "Tempo Totale",
"trans-fat-content": "Trans-fat", "trans-fat-content": "Grassi trans",
"unable-to-delete-recipe": "Impossibile eliminare ricetta", "unable-to-delete-recipe": "Impossibile eliminare ricetta",
"unsaturated-fat-content": "Grassi insaturi", "unsaturated-fat-content": "Grassi insaturi",
"no-recipe": "Nessuna Ricetta", "no-recipe": "Nessuna Ricetta",
@ -547,8 +547,8 @@
"failed-to-add-recipe-to-mealplan": "Impossibile aggiungere la ricetta al piano alimentare", "failed-to-add-recipe-to-mealplan": "Impossibile aggiungere la ricetta al piano alimentare",
"failed-to-add-to-list": "Errore durante l'aggiunta alla lista", "failed-to-add-to-list": "Errore durante l'aggiunta alla lista",
"yield": "Porzioni", "yield": "Porzioni",
"yields-amount-with-text": "Yields {amount} {text}", "yields-amount-with-text": "Rendimenti {amount} {text}",
"yield-text": "Yield Text", "yield-text": "Testo di rendimento",
"quantity": "Quantità", "quantity": "Quantità",
"choose-unit": "Scegli Unità", "choose-unit": "Scegli Unità",
"press-enter-to-create": "Premi invio per creare", "press-enter-to-create": "Premi invio per creare",
@ -570,13 +570,6 @@
"increase-scale-label": "Aumenta la scala di 1", "increase-scale-label": "Aumenta la scala di 1",
"locked": "Bloccato", "locked": "Bloccato",
"public-link": "Link Pubblico", "public-link": "Link Pubblico",
"timer": {
"kitchen-timer": "Contaminuti da cucina",
"start-timer": "Avvia timer",
"pause-timer": "Metti in pausa il contaminuti",
"resume-timer": "Riprendi il contaminuti",
"stop-timer": "Arresta il Timer"
},
"edit-timeline-event": "Modifica evento sulla linea temporale", "edit-timeline-event": "Modifica evento sulla linea temporale",
"timeline": "Linea temporale", "timeline": "Linea temporale",
"timeline-is-empty": "Niente sulla linea temporale. Prova a fare questa ricetta!", "timeline-is-empty": "Niente sulla linea temporale. Prova a fare questa ricetta!",
@ -624,9 +617,9 @@
"stay-in-edit-mode": "Rimani in modalità Modifica", "stay-in-edit-mode": "Rimani in modalità Modifica",
"import-from-zip": "Importa da Zip", "import-from-zip": "Importa da Zip",
"import-from-zip-description": "Importa una singola ricetta esportata da un'altra istanza di Mealie.", "import-from-zip-description": "Importa una singola ricetta esportata da un'altra istanza di Mealie.",
"import-from-html-or-json": "Import from HTML or JSON", "import-from-html-or-json": "Importa da HTML o JSON",
"import-from-html-or-json-description": "Import a single recipe from raw HTML or JSON. This is useful if you have a recipe from a site that Mealie can't scrape normally, or from some other external source.", "import-from-html-or-json-description": "Importa una singola ricetta da HTML o JSON grezzi. Utile se si ha una ricetta proveniente da siti da cui solitamente Mealie non riesce a importare, o da qualche altra fonte esterna.",
"json-import-format-description-colon": "To import via JSON, it must be in valid format:", "json-import-format-description-colon": "Per importare tramite JSON, deve essere in un formato valido:",
"json-editor": "Editor JSON", "json-editor": "Editor JSON",
"zip-files-must-have-been-exported-from-mealie": "I file .zip devono essere stati esportati da Mealie", "zip-files-must-have-been-exported-from-mealie": "I file .zip devono essere stati esportati da Mealie",
"create-a-recipe-by-uploading-a-scan": "Crea una ricetta caricando una scansione.", "create-a-recipe-by-uploading-a-scan": "Crea una ricetta caricando una scansione.",
@ -644,9 +637,9 @@
"recipe-debugger-use-openai-description": "Usa OpenAI per analizzare i risultati invece di affidarsi alla libreria scraper. Quando si crea una ricetta tramite URL, questo viene fatto automaticamente se la libreria scraper fallisce, ma è possibile testarlo manualmente qui.", "recipe-debugger-use-openai-description": "Usa OpenAI per analizzare i risultati invece di affidarsi alla libreria scraper. Quando si crea una ricetta tramite URL, questo viene fatto automaticamente se la libreria scraper fallisce, ma è possibile testarlo manualmente qui.",
"debug": "Debug", "debug": "Debug",
"tree-view": "Visualizzazione ad Albero", "tree-view": "Visualizzazione ad Albero",
"recipe-servings": "Recipe Servings", "recipe-servings": "Porzioni ricetta",
"recipe-yield": "Resa Ricetta", "recipe-yield": "Resa Ricetta",
"recipe-yield-text": "Recipe Yield Text", "recipe-yield-text": "Testo del rendimento ricetta",
"unit": "Unità", "unit": "Unità",
"upload-image": "Carica immagine", "upload-image": "Carica immagine",
"screen-awake": "Mantieni lo schermo acceso", "screen-awake": "Mantieni lo schermo acceso",
@ -668,19 +661,19 @@
"missing-food": "Crea cibo mancante: {food}", "missing-food": "Crea cibo mancante: {food}",
"no-food": "Nessun Alimento" "no-food": "Nessun Alimento"
}, },
"reset-servings-count": "Reset Servings Count", "reset-servings-count": "Reimposta conteggio porzioni",
"not-linked-ingredients": "Ingredienti Aggiuntivi" "not-linked-ingredients": "Ingredienti Aggiuntivi"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Trova ricette", "recipe-finder": "Trova ricette",
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", "recipe-finder-description": "Cerca ricette in base agli ingredienti a portata di mano. Si può anche filtrare in base agli utensili che si ha a disposizione e impostare un numero massimo d'ingredienti o utensili mancanti.",
"selected-ingredients": "Seleziona ingredienti", "selected-ingredients": "Seleziona ingredienti",
"no-ingredients-selected": "Nessun ingrediente selezionato", "no-ingredients-selected": "Nessun ingrediente selezionato",
"missing": "Mancante", "missing": "Mancante",
"no-recipes-found": "Nessuna ricetta trovata", "no-recipes-found": "Nessuna ricetta trovata",
"no-recipes-found-description": "Prova ad aggiungere altri ingredienti alla tua ricerca o a regolare i tuoi filtri", "no-recipes-found-description": "Prova ad aggiungere altri ingredienti alla tua ricerca o a regolare i tuoi filtri",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Includi ingredienti a mano",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Includi strumenti a mano",
"max-missing-ingredients": "Max Ingredienti Mancanti", "max-missing-ingredients": "Max Ingredienti Mancanti",
"max-missing-tools": "Massimo Strumenti Mancanti", "max-missing-tools": "Massimo Strumenti Mancanti",
"selected-tools": "Strumenti Selezionati", "selected-tools": "Strumenti Selezionati",
@ -1298,7 +1291,7 @@
"run-test": "Esegui test", "run-test": "Esegui test",
"test-results": "Risultati dei test", "test-results": "Risultati dei test",
"group-delete-note": "I gruppi con utenti o famiglie non possono essere eliminati", "group-delete-note": "I gruppi con utenti o famiglie non possono essere eliminati",
"household-delete-note": "Households with users cannot be deleted" "household-delete-note": "Le famiglie con utenti non possono essere eliminate"
}, },
"profile": { "profile": {
"welcome-user": "👋 Benvenutǝ, {0}!", "welcome-user": "👋 Benvenutǝ, {0}!",
@ -1352,9 +1345,9 @@
}, },
"cookbook": { "cookbook": {
"cookbooks": "Ricettari", "cookbooks": "Ricettari",
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.", "description": "I libri di cucina sono un altro modo per organizzare le ricette creando insiemi di ricette, ordinamenti e altri filtri. La creazione di un libro di cucina aggiungerà una voce alla barra laterale e tutte le ricette con i filtri scelti verranno visualizzate nel libro di cucina.",
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", "hide-cookbooks-from-other-households": "Nascondi i libri di cucina delle altre famiglie",
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar", "hide-cookbooks-from-other-households-description": "Se abilitata, nella barra laterale appariranno solo libri di cucina della propria famiglia",
"public-cookbook": "Ricettario Pubblico", "public-cookbook": "Ricettario Pubblico",
"public-cookbook-description": "I ricettari pubblici possono essere condivisi con gli utenti non-mealie e saranno visualizzati nella pagina dei gruppi.", "public-cookbook-description": "I ricettari pubblici possono essere condivisi con gli utenti non-mealie e saranno visualizzati nella pagina dei gruppi.",
"filter-options": "Opzioni Filtro", "filter-options": "Opzioni Filtro",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "スケールを 1 ずつ増やす", "increase-scale-label": "スケールを 1 ずつ増やす",
"locked": "ロック済み", "locked": "ロック済み",
"public-link": "公開リンク", "public-link": "公開リンク",
"timer": {
"kitchen-timer": "キッチンタイマー",
"start-timer": "タイマー開始",
"pause-timer": "タイマーを一時停止",
"resume-timer": "タイマーを再開",
"stop-timer": "タイマーを停止"
},
"edit-timeline-event": "タイムラインイベントの編集", "edit-timeline-event": "タイムラインイベントの編集",
"timeline": "タイムライン", "timeline": "タイムライン",
"timeline-is-empty": "タイムラインにはまだ何もありません。レシピを作ってみてください!", "timeline-is-empty": "タイムラインにはまだ何もありません。レシピを作ってみてください!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "잠김", "locked": "잠김",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "타이머 시작",
"pause-timer": "타이머 일시 정지",
"resume-timer": "타이머 재개",
"stop-timer": "타이머 정지"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "타임라인", "timeline": "타임라인",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Padidinti mastelį 1 k.", "increase-scale-label": "Padidinti mastelį 1 k.",
"locked": "Užrakinta", "locked": "Užrakinta",
"public-link": "Vieša nuoroda", "public-link": "Vieša nuoroda",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Redaguoti laiko juostos įvykį", "edit-timeline-event": "Redaguoti laiko juostos įvykį",
"timeline": "Laiko juosta", "timeline": "Laiko juosta",
"timeline-is-empty": "Laiko juosta tuščia. Pabandykit pagaminti šį receptą!", "timeline-is-empty": "Laiko juosta tuščia. Pabandykit pagaminti šį receptą!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Palieliniet skalu par 1", "increase-scale-label": "Palieliniet skalu par 1",
"locked": "Bloķēts", "locked": "Bloķēts",
"public-link": "Publiskā saite", "public-link": "Publiskā saite",
"timer": {
"kitchen-timer": "Virtuves taimeris",
"start-timer": "Sākuma taimeris",
"pause-timer": "Pauzes taimeris",
"resume-timer": "Atjaunot taimeri",
"stop-timer": "Apturēšanas taimeris"
},
"edit-timeline-event": "Laika skalas notikuma rediģēšana", "edit-timeline-event": "Laika skalas notikuma rediģēšana",
"timeline": "Laika skala", "timeline": "Laika skala",
"timeline-is-empty": "Pagaidām nekas laika skalā. Mēģiniet pagatavot šo recepti!", "timeline-is-empty": "Pagaidām nekas laika skalā. Mēģiniet pagatavot šo recepti!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Verhoog de schaal met 1", "increase-scale-label": "Verhoog de schaal met 1",
"locked": "Vergrendeld", "locked": "Vergrendeld",
"public-link": "Openbare link", "public-link": "Openbare link",
"timer": {
"kitchen-timer": "Kookwekker",
"start-timer": "Kookwekker starten",
"pause-timer": "Kookwekker pauzeren",
"resume-timer": "Kookwekker hervatten",
"stop-timer": "Kookwekker stoppen"
},
"edit-timeline-event": "Bewerk tijdlijngebeurtenis", "edit-timeline-event": "Bewerk tijdlijngebeurtenis",
"timeline": "Tijdlijn", "timeline": "Tijdlijn",
"timeline-is-empty": "Nog niets op de tijdlijn. Probeer dit recept te maken!", "timeline-is-empty": "Nog niets op de tijdlijn. Probeer dit recept te maken!",

View file

@ -8,7 +8,7 @@
"database-type": "Databasetype", "database-type": "Databasetype",
"database-url": "URL til database", "database-url": "URL til database",
"default-group": "Standardgruppe", "default-group": "Standardgruppe",
"default-household": "Standard hushold", "default-household": "Standard husholdning",
"demo": "Demo", "demo": "Demo",
"demo-status": "Demostatus", "demo-status": "Demostatus",
"development": "Utvikling", "development": "Utvikling",
@ -51,7 +51,7 @@
"category": "Kategori" "category": "Kategori"
}, },
"events": { "events": {
"apprise-url": "Apprise URL", "apprise-url": "Apprise-URL",
"database": "Database", "database": "Database",
"delete-event": "Slett hendelse", "delete-event": "Slett hendelse",
"event-delete-confirmation": "Er du sikker på at du ønsker å slette denne hendelsen?", "event-delete-confirmation": "Er du sikker på at du ønsker å slette denne hendelsen?",
@ -66,9 +66,9 @@
"subscribed-events": "Abonnerte hendelser", "subscribed-events": "Abonnerte hendelser",
"test-message-sent": "Testmelding sendt", "test-message-sent": "Testmelding sendt",
"message-sent": "Melding sendt", "message-sent": "Melding sendt",
"new-notification": "Ny varsel", "new-notification": "Nytt varsel",
"event-notifiers": "Hendelsesvarsler", "event-notifiers": "Hendelsesvarsler",
"apprise-url-skipped-if-blank": "Apprise URL (hoppes over hvis tom)", "apprise-url-skipped-if-blank": "Apprise-URL (hoppes over hvis tom)",
"enable-notifier": "Aktiver varslingsagenten", "enable-notifier": "Aktiver varslingsagenten",
"what-events": "Hvilke hendelser skal denne varslingsagenten abonnere på?", "what-events": "Hvilke hendelser skal denne varslingsagenten abonnere på?",
"user-events": "Brukerhendelser", "user-events": "Brukerhendelser",
@ -87,7 +87,7 @@
"clear": "Tøm", "clear": "Tøm",
"close": "Lukk", "close": "Lukk",
"confirm": "Bekreft", "confirm": "Bekreft",
"confirm-how-does-everything-look": "Hvordan ser alt ut?", "confirm-how-does-everything-look": "Hvordan ser ting ut?",
"confirm-delete-generic": "Er du sikker på at du vil slette denne?", "confirm-delete-generic": "Er du sikker på at du vil slette denne?",
"copied_message": "Kopiert!", "copied_message": "Kopiert!",
"create": "Opprett", "create": "Opprett",
@ -148,7 +148,7 @@
"share": "Del", "share": "Del",
"show-all": "Vis alle", "show-all": "Vis alle",
"shuffle": "Tilfeldig rekkefølge", "shuffle": "Tilfeldig rekkefølge",
"sort": "Sortér", "sort": "Sorter",
"sort-ascending": "Sorter stigende", "sort-ascending": "Sorter stigende",
"sort-descending": "Sortere synkende", "sort-descending": "Sortere synkende",
"sort-alphabetically": "Alfabetisk", "sort-alphabetically": "Alfabetisk",
@ -193,7 +193,7 @@
"confirm-delete-own-admin-account": "Vær oppmerksom på at du holder på å slette din egen administrator-konto! Dette kan ikke angres og vil slette kontoen din permanent!", "confirm-delete-own-admin-account": "Vær oppmerksom på at du holder på å slette din egen administrator-konto! Dette kan ikke angres og vil slette kontoen din permanent!",
"organizer": "Organisator", "organizer": "Organisator",
"transfer": "Overfør", "transfer": "Overfør",
"copy": "Kopiér", "copy": "Kopier",
"color": "Farge", "color": "Farge",
"timestamp": "Tidsstempel", "timestamp": "Tidsstempel",
"last-made": "Sist laget", "last-made": "Sist laget",
@ -281,25 +281,25 @@
}, },
"household": { "household": {
"household": "Husholdning", "household": "Husholdning",
"households": "Husholdning", "households": "Husholdninger",
"user-household": "Brukers husholdning", "user-household": "Brukers husholdning",
"create-household": "Opprett husholdning", "create-household": "Opprett husholdning",
"household-name": "Husholdningenes navn", "household-name": "Husholdningens navn",
"household-group": "Husholdningenes gruppe", "household-group": "Husholdningens gruppe",
"household-management": "Administrer husholdninger", "household-management": "Administrering av husholdninger",
"manage-households": "Administrer husholdninger", "manage-households": "Administrer husholdninger",
"admin-household-management": "Admin husholdningsadministrasjon", "admin-household-management": "Admin husholdningsadministrasjon",
"admin-household-management-text": "Endringer i denne husholdningen vil umiddelbart gjelde.", "admin-household-management-text": "Endringer i denne husholdningen vil umiddelbart gjelde.",
"household-id-value": "Husholdningenes id: {0}", "household-id-value": "Husholdningens id: {0}",
"private-household": "Privat husholdning", "private-household": "Privat husholdning",
"private-household-description": "Setting your household to private will disable all public view options. This overrides any individual public view settings", "private-household-description": "Setting your household to private will disable all public view options. This overrides any individual public view settings",
"lock-recipe-edits-from-other-households": "Lock recipe edits from other households", "lock-recipe-edits-from-other-households": "Lås redigering av oppskrifter fra andre husholdninger",
"lock-recipe-edits-from-other-households-description": "When enabled only users in your household can edit recipes created by your household", "lock-recipe-edits-from-other-households-description": "When enabled only users in your household can edit recipes created by your household",
"household-recipe-preferences": "Husholdningenes oppskriftsinnstillinger", "household-recipe-preferences": "Husholdningenes oppskriftsinnstillinger",
"default-recipe-preferences-description": "Dette er standardinnstillingene når en ny oppskrift blir opprettet i din husholdning. Disse kan endres for individuelle oppskrifter i oppskrifters innstillinger.", "default-recipe-preferences-description": "Dette er standardinnstillingene når en ny oppskrift blir opprettet i din husholdning. Disse kan endres for individuelle oppskrifter i oppskrifters innstillinger.",
"allow-users-outside-of-your-household-to-see-your-recipes": "Tillat brukere utenfor din husholdning å se oppskriftene dine", "allow-users-outside-of-your-household-to-see-your-recipes": "Tillat brukere utenfor din husholdning å se oppskriftene dine",
"allow-users-outside-of-your-household-to-see-your-recipes-description": "Når aktivert, kan du bruke en offentlig lenke for å dele spesifikke oppskrifter uten å autorisere brukeren. Når deaktivert, kan du kun dele oppskrifter med brukere som er i husholdningen din eller med en forhåndsgenerert privat lenke", "allow-users-outside-of-your-household-to-see-your-recipes-description": "Når aktivert, kan du bruke en offentlig lenke for å dele spesifikke oppskrifter uten å autorisere brukeren. Når deaktivert, kan du kun dele oppskrifter med brukere som er i husholdningen din eller med en forhåndsgenerert privat lenke",
"household-preferences": "Husholdnings innstillinger" "household-preferences": "Innstillinger for husholdning"
}, },
"meal-plan": { "meal-plan": {
"create-a-new-meal-plan": "Opprett en ny måltidsplan", "create-a-new-meal-plan": "Opprett en ny måltidsplan",
@ -313,12 +313,12 @@
"main": "Hovedrett", "main": "Hovedrett",
"meal-planner": "Måltidsplanlegger", "meal-planner": "Måltidsplanlegger",
"meal-plans": "Måltidsplaner", "meal-plans": "Måltidsplaner",
"mealplan-categories": "MÅLTIDSPLANKATEGORIER", "mealplan-categories": "MÅLTIDSPLAN-KATEGORIER",
"mealplan-created": "Måltidsplan opprettet", "mealplan-created": "Måltidsplan opprettet",
"mealplan-creation-failed": "Opprettelse av måltidsplan mislyktes", "mealplan-creation-failed": "Opprettelse av måltidsplan mislyktes",
"mealplan-deleted": "Måltidsplan slettet", "mealplan-deleted": "Måltidsplan slettet",
"mealplan-deletion-failed": "Sletting av måltidsplan mislyktes", "mealplan-deletion-failed": "Sletting av måltidsplan mislyktes",
"mealplan-settings": "Måltidsplaninnstillinger", "mealplan-settings": "Innstillinger for måltidsplan",
"mealplan-update-failed": "Oppdatering av måltidsplan mislyktes", "mealplan-update-failed": "Oppdatering av måltidsplan mislyktes",
"mealplan-updated": "Måltidsplan oppdatert", "mealplan-updated": "Måltidsplan oppdatert",
"mealplan-households-description": "If no household is selected, recipes can be added from any household", "mealplan-households-description": "If no household is selected, recipes can be added from any household",
@ -426,7 +426,7 @@
"error-details": "Bare nettsteder som inneholder ld+json eller mikrodata kan importeres av Mealie. De største oppskriftsnettstedene støtter denne datastrukturen. Dersom nettstedet ditt ikke kan importeres, men det er json-data i loggen, må du rapportere et GitHub-problem med nettadressen og data.", "error-details": "Bare nettsteder som inneholder ld+json eller mikrodata kan importeres av Mealie. De største oppskriftsnettstedene støtter denne datastrukturen. Dersom nettstedet ditt ikke kan importeres, men det er json-data i loggen, må du rapportere et GitHub-problem med nettadressen og data.",
"error-title": "Ser ut til at ingenting ble funnet", "error-title": "Ser ut til at ingenting ble funnet",
"from-url": "Importer en oppskrift", "from-url": "Importer en oppskrift",
"github-issues": "GitHub-problemer", "github-issues": "GitHub Issues",
"google-ld-json-info": "Google ld+json-informasjon", "google-ld-json-info": "Google ld+json-informasjon",
"must-be-a-valid-url": "Må være en gyldig nettadresse", "must-be-a-valid-url": "Må være en gyldig nettadresse",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Lim inn oppskriftsdataene. Hver linje blir behandlet som et element i en liste", "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Lim inn oppskriftsdataene. Hver linje blir behandlet som et element i en liste",
@ -467,7 +467,7 @@
"calories-suffix": "kalorier", "calories-suffix": "kalorier",
"carbohydrate-content": "Karbohydrater", "carbohydrate-content": "Karbohydrater",
"categories": "Kategorier", "categories": "Kategorier",
"cholesterol-content": "Cholesterol", "cholesterol-content": "Kolesterol",
"comment-action": "Kommenter", "comment-action": "Kommenter",
"comment": "Kommentar", "comment": "Kommentar",
"comments": "Kommentarer", "comments": "Kommentarer",
@ -514,7 +514,7 @@
"recipe-updated": "Oppskrift oppdatert", "recipe-updated": "Oppskrift oppdatert",
"remove-from-favorites": "Fjern fra favoritter", "remove-from-favorites": "Fjern fra favoritter",
"remove-section": "Fjern seksjon", "remove-section": "Fjern seksjon",
"saturated-fat-content": "Saturated fat", "saturated-fat-content": "Mettet fett",
"save-recipe-before-use": "Lagre oppskrift før bruk", "save-recipe-before-use": "Lagre oppskrift før bruk",
"section-title": "Seksjonstittel", "section-title": "Seksjonstittel",
"servings": "Porsjoner", "servings": "Porsjoner",
@ -526,9 +526,9 @@
"sugar-content": "Sukker", "sugar-content": "Sukker",
"title": "Tittel", "title": "Tittel",
"total-time": "Total tid", "total-time": "Total tid",
"trans-fat-content": "Trans-fat", "trans-fat-content": "Transfett",
"unable-to-delete-recipe": "Kan ikke slette oppskrift", "unable-to-delete-recipe": "Kan ikke slette oppskrift",
"unsaturated-fat-content": "Unsaturated fat", "unsaturated-fat-content": "Umettet fett",
"no-recipe": "Ingen oppskrift", "no-recipe": "Ingen oppskrift",
"locked-by-owner": "Låst av eier", "locked-by-owner": "Låst av eier",
"join-the-conversation": "Delta i samtalen", "join-the-conversation": "Delta i samtalen",
@ -547,7 +547,7 @@
"failed-to-add-recipe-to-mealplan": "Klarte ikke å legge til oppskrift i måltidsplan", "failed-to-add-recipe-to-mealplan": "Klarte ikke å legge til oppskrift i måltidsplan",
"failed-to-add-to-list": "Klarte ikke å legge til oppskrift i listen", "failed-to-add-to-list": "Klarte ikke å legge til oppskrift i listen",
"yield": "Gir", "yield": "Gir",
"yields-amount-with-text": "Yields {amount} {text}", "yields-amount-with-text": "Gir {amount} {text}",
"yield-text": "Yield Text", "yield-text": "Yield Text",
"quantity": "Antall", "quantity": "Antall",
"choose-unit": "Velg enhet", "choose-unit": "Velg enhet",
@ -570,23 +570,16 @@
"increase-scale-label": "Øk skala med 1", "increase-scale-label": "Øk skala med 1",
"locked": "Låst", "locked": "Låst",
"public-link": "Offentlig lenke", "public-link": "Offentlig lenke",
"timer": {
"kitchen-timer": "Kjøkkentimer",
"start-timer": "Start nedtelling",
"pause-timer": "Pause nedtelling",
"resume-timer": "Fortsett nedtelling",
"stop-timer": "Stopp nedtelling"
},
"edit-timeline-event": "Endre tidslinjehendelser", "edit-timeline-event": "Endre tidslinjehendelser",
"timeline": "Tidslinje", "timeline": "Tidslinje",
"timeline-is-empty": "Ingenting på tidslinjen ennå. Prøv å lage denne oppskriften!", "timeline-is-empty": "Ingenting på tidslinjen ennå. Prøv å lage denne oppskriften!",
"timeline-no-events-found-try-adjusting-filters": "Ingen hendelser funnet. Prøv å endre søkefiltrene.", "timeline-no-events-found-try-adjusting-filters": "Ingen hendelser funnet. Prøv å endre søkefiltrene.",
"group-global-timeline": "{groupName} Global tidslinje", "group-global-timeline": "{groupName} Global tidslinje",
"open-timeline": "Åpne tidslinje", "open-timeline": "Åpne tidslinje",
"made-this": "Jeg har laget denne", "made-this": "Jeg har laget dette",
"how-did-it-turn-out": "Hvordan ble den?", "how-did-it-turn-out": "Hvordan ble det?",
"user-made-this": "{user} har laget denne", "user-made-this": "{user} har laget dette",
"last-made-date": "Sist laget: {date}", "last-made-date": "Sist laget {date}",
"api-extras-description": "Ekstramaterialer til oppskrifter er en viktig funksjon i Mealie API-en. De lar deg opprette egendefinerte JSON-nøkkel/verdi-par innenfor en oppskrift for å referere fra tredjepartsapplikasjoner. Du kan bruke disse nøklene til å gi informasjon for eksempel for å utløse automatiseringer eller egendefinerte meldinger som skal videreformidles til ønsket enhet.", "api-extras-description": "Ekstramaterialer til oppskrifter er en viktig funksjon i Mealie API-en. De lar deg opprette egendefinerte JSON-nøkkel/verdi-par innenfor en oppskrift for å referere fra tredjepartsapplikasjoner. Du kan bruke disse nøklene til å gi informasjon for eksempel for å utløse automatiseringer eller egendefinerte meldinger som skal videreformidles til ønsket enhet.",
"message-key": "Meldingsnøkkel", "message-key": "Meldingsnøkkel",
"parse": "Analyser", "parse": "Analyser",
@ -617,7 +610,7 @@
"scrape-recipe": "Skrap oppskrift", "scrape-recipe": "Skrap oppskrift",
"scrape-recipe-description": "Skrap en oppskrift ved bruk av nettadresse. Oppgi nettadressen til nettstedet du vil skrape, så vil Mealie forsøke å skrape oppskriften fra den siden og legge den til i samlingen din.", "scrape-recipe-description": "Skrap en oppskrift ved bruk av nettadresse. Oppgi nettadressen til nettstedet du vil skrape, så vil Mealie forsøke å skrape oppskriften fra den siden og legge den til i samlingen din.",
"scrape-recipe-have-a-lot-of-recipes": "Har du mange oppskrifter du ønsker å skrape samtidig?", "scrape-recipe-have-a-lot-of-recipes": "Har du mange oppskrifter du ønsker å skrape samtidig?",
"scrape-recipe-suggest-bulk-importer": "Prøv masseimporten", "scrape-recipe-suggest-bulk-importer": "Prøv masseimportering",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?", "scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly", "scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"import-original-keywords-as-tags": "Importer originale søkeord som emneord", "import-original-keywords-as-tags": "Importer originale søkeord som emneord",
@ -644,7 +637,7 @@
"recipe-debugger-use-openai-description": "Bruke OpenAI til å analysere resultatene i stedet for å basere seg på scraper-biblioteket. Når du oppretter en oppskrift via URL, blir dette gjort automatisk hvis scraper-biblioteket mislykkes, men du kan teste det manuelt her.", "recipe-debugger-use-openai-description": "Bruke OpenAI til å analysere resultatene i stedet for å basere seg på scraper-biblioteket. Når du oppretter en oppskrift via URL, blir dette gjort automatisk hvis scraper-biblioteket mislykkes, men du kan teste det manuelt her.",
"debug": "Feilsøk", "debug": "Feilsøk",
"tree-view": "Trevisning", "tree-view": "Trevisning",
"recipe-servings": "Recipe Servings", "recipe-servings": "Oppskriftsporsjoner",
"recipe-yield": "Utbytte av oppskrift", "recipe-yield": "Utbytte av oppskrift",
"recipe-yield-text": "Recipe Yield Text", "recipe-yield-text": "Recipe Yield Text",
"unit": "Enhet", "unit": "Enhet",
@ -655,7 +648,7 @@
"recipe-actions": "Oppskriftshandlinger", "recipe-actions": "Oppskriftshandlinger",
"parser": { "parser": {
"experimental-alert-text": "Mealie bruker naturlig språkbehandling til å analysere og lage enheter og matvarer til oppskriftsingrediensene dine. Denne funksjonen er eksperimentell og fungerer kanskje ikke som forventet. Hvis du foretrekker ikke å bruke de foreslåtte resultatene, kan du velge 'Avbryt', og endringene dine vil ikke bli lagret.", "experimental-alert-text": "Mealie bruker naturlig språkbehandling til å analysere og lage enheter og matvarer til oppskriftsingrediensene dine. Denne funksjonen er eksperimentell og fungerer kanskje ikke som forventet. Hvis du foretrekker ikke å bruke de foreslåtte resultatene, kan du velge 'Avbryt', og endringene dine vil ikke bli lagret.",
"ingredient-parser": "Ingrediens-parser", "ingredient-parser": "Ingrediens-analyserer",
"explanation": "For å bruke ingrediens-forslag, klikk på 'Analyser alle' for å starte prosessen. Når de prosesserte ingrediensene er tilgjengelige, kan du se gjennom elementene og kontrollere at de er sjekket korrekt. Modellens tillitsverdi vises på høyre side av elementet. Denne scoren er et gjennomsnitt av alle de individuelle poengene og alltid er ikke helt nøyaktige.", "explanation": "For å bruke ingrediens-forslag, klikk på 'Analyser alle' for å starte prosessen. Når de prosesserte ingrediensene er tilgjengelige, kan du se gjennom elementene og kontrollere at de er sjekket korrekt. Modellens tillitsverdi vises på høyre side av elementet. Denne scoren er et gjennomsnitt av alle de individuelle poengene og alltid er ikke helt nøyaktige.",
"alerts-explainer": "Varsler vil bli vist dersom en matchende matvare eller enhet blir funnet, men ikke finnes i databasen.", "alerts-explainer": "Varsler vil bli vist dersom en matchende matvare eller enhet blir funnet, men ikke finnes i databasen.",
"select-parser": "Velg analyserer", "select-parser": "Velg analyserer",
@ -668,25 +661,25 @@
"missing-food": "Opprett manglende mat: {food}", "missing-food": "Opprett manglende mat: {food}",
"no-food": "Ingen matvarer" "no-food": "Ingen matvarer"
}, },
"reset-servings-count": "Reset Servings Count", "reset-servings-count": "Nullstill antall porsjoner",
"not-linked-ingredients": "Additional Ingredients" "not-linked-ingredients": "Tilleggsingredienser"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Oppskriftsfinner",
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", "recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.",
"selected-ingredients": "Selected Ingredients", "selected-ingredients": "Velg ingredienser",
"no-ingredients-selected": "No ingredients selected", "no-ingredients-selected": "Ingen ingredienser valgt",
"missing": "Missing", "missing": "Mangler",
"no-recipes-found": "No recipes found", "no-recipes-found": "Ingen oppskrifter funnet",
"no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters", "no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Include Ingredients On Hand",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Include Tools On Hand",
"max-missing-ingredients": "Max Missing Ingredients", "max-missing-ingredients": "Maks antall manglende ingredienser",
"max-missing-tools": "Max Missing Tools", "max-missing-tools": "Maks antall manglende redskaper",
"selected-tools": "Selected Tools", "selected-tools": "Velg redskaper",
"other-filters": "Other Filters", "other-filters": "Andre filtre",
"ready-to-make": "Ready to Make", "ready-to-make": "Klar til å lages",
"almost-ready-to-make": "Almost Ready to Make" "almost-ready-to-make": "Nesten klar til å lages"
}, },
"search": { "search": {
"advanced-search": "Avansert søk", "advanced-search": "Avansert søk",
@ -891,7 +884,7 @@
"are-you-sure-you-want-to-check-all-items": "Er du sikker på at du vil velge alle elementer?", "are-you-sure-you-want-to-check-all-items": "Er du sikker på at du vil velge alle elementer?",
"are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på at du vil fjerne valg av alle elementer?", "are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på at du vil fjerne valg av alle elementer?",
"are-you-sure-you-want-to-delete-checked-items": "Er du sikker på at du vil slette alle valgte elementer?", "are-you-sure-you-want-to-delete-checked-items": "Er du sikker på at du vil slette alle valgte elementer?",
"no-shopping-lists-found": "No Shopping Lists Found" "no-shopping-lists-found": "Ingen handlelister funnet"
}, },
"sidebar": { "sidebar": {
"all-recipes": "Alle oppskrifter", "all-recipes": "Alle oppskrifter",
@ -1293,17 +1286,17 @@
"restore-from-v1-backup": "Har du en sikkerhetskopi fra en tidligere forekomst av Mealie v1? Du kan gjenopprette den her.", "restore-from-v1-backup": "Har du en sikkerhetskopi fra en tidligere forekomst av Mealie v1? Du kan gjenopprette den her.",
"manage-profile-or-get-invite-link": "Administrer din egen profil, eller hent en invitasjonslenke for å dele med andre." "manage-profile-or-get-invite-link": "Administrer din egen profil, eller hent en invitasjonslenke for å dele med andre."
}, },
"debug-openai-services": "Debug OpenAI Services", "debug-openai-services": "Feilsøk OpenAI-tjenester",
"debug-openai-services-description": "Use this page to debug OpenAI services. You can test your OpenAI connection and see the results here. If you have image services enabled, you can also provide an image.", "debug-openai-services-description": "Use this page to debug OpenAI services. You can test your OpenAI connection and see the results here. If you have image services enabled, you can also provide an image.",
"run-test": "Run Test", "run-test": "Kjør test",
"test-results": "Test Results", "test-results": "Testresultater",
"group-delete-note": "Groups with users or households cannot be deleted", "group-delete-note": "Groups with users or households cannot be deleted",
"household-delete-note": "Households with users cannot be deleted" "household-delete-note": "Households with users cannot be deleted"
}, },
"profile": { "profile": {
"welcome-user": "Velkommen, {0}!", "welcome-user": "Velkommen, {0}!",
"description": "Administrer din profil, oppskrifter og gruppeinnstillinger.", "description": "Administrer din profil, oppskrifter og gruppeinnstillinger.",
"invite-link": "Invite Link", "invite-link": "Invitasjonslenke",
"get-invite-link": "Få invitasjonslenke", "get-invite-link": "Få invitasjonslenke",
"get-public-link": "Få offentlig lenke", "get-public-link": "Få offentlig lenke",
"account-summary": "Kontosammendrag", "account-summary": "Kontosammendrag",
@ -1370,25 +1363,25 @@
}, },
"query-filter": { "query-filter": {
"logical-operators": { "logical-operators": {
"and": "AND", "and": "OG",
"or": "OR" "or": "ELLER"
}, },
"relational-operators": { "relational-operators": {
"equals": "equals", "equals": "er lik",
"does-not-equal": "does not equal", "does-not-equal": "er ikke lik",
"is-greater-than": "is greater than", "is-greater-than": "er større enn",
"is-greater-than-or-equal-to": "is greater than or equal to", "is-greater-than-or-equal-to": "er større enn eller lik",
"is-less-than": "is less than", "is-less-than": "er mindre enn",
"is-less-than-or-equal-to": "is less than or equal to" "is-less-than-or-equal-to": "er mindre enn eller lik"
}, },
"relational-keywords": { "relational-keywords": {
"is": "is", "is": "er",
"is-not": "is not", "is-not": "er ikke",
"is-one-of": "is one of", "is-one-of": "er en av",
"is-not-one-of": "is not one of", "is-not-one-of": "er ikke en av",
"contains-all-of": "contains all of", "contains-all-of": "inneholder alle",
"is-like": "is like", "is-like": "er som",
"is-not-like": "is not like" "is-not-like": "er ikke som"
} }
} }
} }

View file

@ -518,7 +518,7 @@
"save-recipe-before-use": "Zapisz przepis przed użyciem", "save-recipe-before-use": "Zapisz przepis przed użyciem",
"section-title": "Tytuł rozdziału", "section-title": "Tytuł rozdziału",
"servings": "Porcje", "servings": "Porcje",
"serves-amount": "{amount} porcji", "serves-amount": "Porcje {amount}",
"share-recipe-message": "Chcę podzielić się z Tobą moim przepisem na {0}.", "share-recipe-message": "Chcę podzielić się z Tobą moim przepisem na {0}.",
"show-nutrition-values": "Pokaż wartości odżywcze", "show-nutrition-values": "Pokaż wartości odżywcze",
"sodium-content": "Sód", "sodium-content": "Sód",
@ -570,13 +570,6 @@
"increase-scale-label": "Zwiększ Skalę o 1", "increase-scale-label": "Zwiększ Skalę o 1",
"locked": "Zablokowany", "locked": "Zablokowany",
"public-link": "Link publiczny", "public-link": "Link publiczny",
"timer": {
"kitchen-timer": "Minutnik",
"start-timer": "Włącz minutnik",
"pause-timer": "Zatrzymaj minutnik",
"resume-timer": "Wznów minutnik",
"stop-timer": "Zatrzymaj minutnik"
},
"edit-timeline-event": "Edytuj zdarzenie osi czasu", "edit-timeline-event": "Edytuj zdarzenie osi czasu",
"timeline": "Oś czasu", "timeline": "Oś czasu",
"timeline-is-empty": "Nie ma jeszcze nic na osi czasu. Spróbuj przygotować ten przepis!", "timeline-is-empty": "Nie ma jeszcze nic na osi czasu. Spróbuj przygotować ten przepis!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Aumentar Escala por 1", "increase-scale-label": "Aumentar Escala por 1",
"locked": "Bloqueado", "locked": "Bloqueado",
"public-link": "Link público", "public-link": "Link público",
"timer": {
"kitchen-timer": "Temporizador da Cozinha",
"start-timer": "Iniciar Temporizador",
"pause-timer": "Pausar Temporizador",
"resume-timer": "Continuar Temporizador",
"stop-timer": "Parar Temporizador"
},
"edit-timeline-event": "Editar Linha do Tempo do Evento", "edit-timeline-event": "Editar Linha do Tempo do Evento",
"timeline": "Linha do Tempo", "timeline": "Linha do Tempo",
"timeline-is-empty": "Nada na linha do tempo ainda. Tente fazer esta receita!", "timeline-is-empty": "Nada na linha do tempo ainda. Tente fazer esta receita!",

View file

@ -277,7 +277,7 @@
"admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.", "admin-group-management-text": "As alterações a este grupo serão aplicadas imediatamente.",
"group-id-value": "ID do Grupo: {0}", "group-id-value": "ID do Grupo: {0}",
"total-households": "Total de Lares", "total-households": "Total de Lares",
"you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household" "you-must-select-a-group-before-selecting-a-household": "Tens de selecionar um grupo antes de selecionar uma casa"
}, },
"household": { "household": {
"household": "Casa", "household": "Casa",
@ -518,7 +518,7 @@
"save-recipe-before-use": "Guardar receita antes de usar", "save-recipe-before-use": "Guardar receita antes de usar",
"section-title": "Título da secção", "section-title": "Título da secção",
"servings": "Porções", "servings": "Porções",
"serves-amount": "Serves {amount}", "serves-amount": "Serve {amount}",
"share-recipe-message": "Eu queria partilhar a minha {0} receita consigo.", "share-recipe-message": "Eu queria partilhar a minha {0} receita consigo.",
"show-nutrition-values": "Mostrar valores nutricionais", "show-nutrition-values": "Mostrar valores nutricionais",
"sodium-content": "Sódio", "sodium-content": "Sódio",
@ -547,8 +547,8 @@
"failed-to-add-recipe-to-mealplan": "Erro ao adicionar receita ao plano de refeições", "failed-to-add-recipe-to-mealplan": "Erro ao adicionar receita ao plano de refeições",
"failed-to-add-to-list": "Erro ao adicionar à lista", "failed-to-add-to-list": "Erro ao adicionar à lista",
"yield": "Rendimento", "yield": "Rendimento",
"yields-amount-with-text": "Yields {amount} {text}", "yields-amount-with-text": "Rendimentos {amount} {text}",
"yield-text": "Yield Text", "yield-text": "Rendimento Texto",
"quantity": "Quantidade", "quantity": "Quantidade",
"choose-unit": "Escolha uma unidade", "choose-unit": "Escolha uma unidade",
"press-enter-to-create": "Prima 'Enter' para criar", "press-enter-to-create": "Prima 'Enter' para criar",
@ -570,13 +570,6 @@
"increase-scale-label": "Aumentar Escala em 1", "increase-scale-label": "Aumentar Escala em 1",
"locked": "Bloqueado", "locked": "Bloqueado",
"public-link": "Link público", "public-link": "Link público",
"timer": {
"kitchen-timer": "Temporizador de cozinha",
"start-timer": "Iniciar Temporizador",
"pause-timer": "Pausar Temporizador",
"resume-timer": "Retomar Temporizador",
"stop-timer": "Parar Temporizador"
},
"edit-timeline-event": "Editar evento da Cronologia", "edit-timeline-event": "Editar evento da Cronologia",
"timeline": "Cronologia", "timeline": "Cronologia",
"timeline-is-empty": "Nada na Cronologia, ainda. Tente fazer esta receita!", "timeline-is-empty": "Nada na Cronologia, ainda. Tente fazer esta receita!",
@ -644,9 +637,9 @@
"recipe-debugger-use-openai-description": "Utilize o OpenAI para analisar os resultados em vez de depender da biblioteca de scrapers. Ao criar uma receita através de um URL, isto é feito automaticamente se a biblioteca de scrapers falhar, mas pode testá-la manualmente aqui.", "recipe-debugger-use-openai-description": "Utilize o OpenAI para analisar os resultados em vez de depender da biblioteca de scrapers. Ao criar uma receita através de um URL, isto é feito automaticamente se a biblioteca de scrapers falhar, mas pode testá-la manualmente aqui.",
"debug": "Depurar", "debug": "Depurar",
"tree-view": "Vista em árvore", "tree-view": "Vista em árvore",
"recipe-servings": "Recipe Servings", "recipe-servings": "Porções por receita",
"recipe-yield": "Rendimento da receita", "recipe-yield": "Rendimento da receita",
"recipe-yield-text": "Recipe Yield Text", "recipe-yield-text": "Rendimento da Receita Texto",
"unit": "Unidade", "unit": "Unidade",
"upload-image": "Carregar imagem", "upload-image": "Carregar imagem",
"screen-awake": "Manter ecrã ligado", "screen-awake": "Manter ecrã ligado",
@ -669,24 +662,24 @@
"no-food": "Nenhum Ingrediente" "no-food": "Nenhum Ingrediente"
}, },
"reset-servings-count": "Reiniciar Contador de Doses", "reset-servings-count": "Reiniciar Contador de Doses",
"not-linked-ingredients": "Additional Ingredients" "not-linked-ingredients": "Ingredientes Adicionais"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Localizador de Receitas",
"recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", "recipe-finder-description": "Procure receitas com base nos ingredientes que tem em mãos. Pode também filtrar pelas ferramentas disponíveis e definir um número máximo de ingredientes ou ferramentas ausentes.",
"selected-ingredients": "Selected Ingredients", "selected-ingredients": "Ingredientes Selecionados",
"no-ingredients-selected": "No ingredients selected", "no-ingredients-selected": "Nenhum ingrediente selecionado",
"missing": "Missing", "missing": "Em falta",
"no-recipes-found": "No recipes found", "no-recipes-found": "Nenhuma receita encontrada",
"no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters", "no-recipes-found-description": "Experimente adicionar mais ingredientes à sua pesquisa ou ajustar os seus filtros",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Incluir ingredientes disponíveis",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Incluir ferramentas disponíveis",
"max-missing-ingredients": "Max Missing Ingredients", "max-missing-ingredients": "Máximo de ingredientes em falta",
"max-missing-tools": "Max Missing Tools", "max-missing-tools": "Máximo de ferramentas em falta",
"selected-tools": "Selected Tools", "selected-tools": "Ferramentas selecionadas",
"other-filters": "Other Filters", "other-filters": "Outros filtros",
"ready-to-make": "Ready to Make", "ready-to-make": "Pronto para fazer",
"almost-ready-to-make": "Almost Ready to Make" "almost-ready-to-make": "Quase Pronto para fazer"
}, },
"search": { "search": {
"advanced-search": "Pesquisa Avançada", "advanced-search": "Pesquisa Avançada",
@ -891,7 +884,7 @@
"are-you-sure-you-want-to-check-all-items": "Tem a certeza de que pretende selecionar todos os itens?", "are-you-sure-you-want-to-check-all-items": "Tem a certeza de que pretende selecionar todos os itens?",
"are-you-sure-you-want-to-uncheck-all-items": "Tem a certeza de que pretende desmarcar todos os itens?", "are-you-sure-you-want-to-uncheck-all-items": "Tem a certeza de que pretende desmarcar todos os itens?",
"are-you-sure-you-want-to-delete-checked-items": "Tem a certeza de que pretende eliminar todos os itens selecionados?", "are-you-sure-you-want-to-delete-checked-items": "Tem a certeza de que pretende eliminar todos os itens selecionados?",
"no-shopping-lists-found": "No Shopping Lists Found" "no-shopping-lists-found": "Nenhuma lista de compras encontrada"
}, },
"sidebar": { "sidebar": {
"all-recipes": "Todas as Receitas", "all-recipes": "Todas as Receitas",
@ -1303,7 +1296,7 @@
"profile": { "profile": {
"welcome-user": "👋 Bem-vindo, {0}!", "welcome-user": "👋 Bem-vindo, {0}!",
"description": "Gira o seu perfil, receitas e definições de grupo.", "description": "Gira o seu perfil, receitas e definições de grupo.",
"invite-link": "Invite Link", "invite-link": "Link de convite",
"get-invite-link": "Obter ligação de convite", "get-invite-link": "Obter ligação de convite",
"get-public-link": "Obter ligação pública", "get-public-link": "Obter ligação pública",
"account-summary": "Resumo da conta", "account-summary": "Resumo da conta",
@ -1353,8 +1346,8 @@
"cookbook": { "cookbook": {
"cookbooks": "Livros de Receitas", "cookbooks": "Livros de Receitas",
"description": "Os livros de receitas são outra forma de organizar as receitas, criando secções cruzadas de receitas, organizadores e outros filtros. A criação de um livro de receitas adicionará uma entrada à barra lateral e todas as receitas com os filtros selecionados serão apresentadas no livro de receitas.", "description": "Os livros de receitas são outra forma de organizar as receitas, criando secções cruzadas de receitas, organizadores e outros filtros. A criação de um livro de receitas adicionará uma entrada à barra lateral e todas as receitas com os filtros selecionados serão apresentadas no livro de receitas.",
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", "hide-cookbooks-from-other-households": "Ocultar livros de receitas de outras famílias",
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar", "hide-cookbooks-from-other-households-description": "Quando ativado, apenas os livros de receitas da sua família aparecerão na barra lateral",
"public-cookbook": "Livro de Receitas público", "public-cookbook": "Livro de Receitas público",
"public-cookbook-description": "Os Livros de Receitas públicos podem ser partilhados com utilizadores não registados e serão exibidos na sua página de grupos.", "public-cookbook-description": "Os Livros de Receitas públicos podem ser partilhados com utilizadores não registados e serão exibidos na sua página de grupos.",
"filter-options": "Opções de Filtro", "filter-options": "Opções de Filtro",

File diff suppressed because it is too large Load diff

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Добавить порцию", "increase-scale-label": "Добавить порцию",
"locked": "Заблокировано", "locked": "Заблокировано",
"public-link": "Публичная ссылка", "public-link": "Публичная ссылка",
"timer": {
"kitchen-timer": "Кухонный таймер",
"start-timer": "Запустить таймер",
"pause-timer": "Приостановить таймер",
"resume-timer": "Возобновить таймер",
"stop-timer": "Остановить таймер"
},
"edit-timeline-event": "Редактировать событие в истории", "edit-timeline-event": "Редактировать событие в истории",
"timeline": "История", "timeline": "История",
"timeline-is-empty": "В истории пока ничего нет. Попробуйте сделать этот рецепт!", "timeline-is-empty": "В истории пока ничего нет. Попробуйте сделать этот рецепт!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Zvýšiť škálovanie o 1", "increase-scale-label": "Zvýšiť škálovanie o 1",
"locked": "Zamknuté", "locked": "Zamknuté",
"public-link": "Verejný odkaz", "public-link": "Verejný odkaz",
"timer": {
"kitchen-timer": "Kuchynský časovač",
"start-timer": "Spustiť časovač",
"pause-timer": "Pozastaviť časovač",
"resume-timer": "Znova spustiť časovač",
"stop-timer": "Zastaviť časovač"
},
"edit-timeline-event": "Upraviť udalosť na časovej osi", "edit-timeline-event": "Upraviť udalosť na časovej osi",
"timeline": "Časová os", "timeline": "Časová os",
"timeline-is-empty": "Na časovej osi zatiaľ nič nie je. Skúste pripraviť tento recept!", "timeline-is-empty": "Na časovej osi zatiaľ nič nie je. Skúste pripraviť tento recept!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Zvišaj merilo za 1", "increase-scale-label": "Zvišaj merilo za 1",
"locked": "Zaklenjeno", "locked": "Zaklenjeno",
"public-link": "Javna povezava", "public-link": "Javna povezava",
"timer": {
"kitchen-timer": "Kuhinjski časovnik",
"start-timer": "Zaženi časovnik",
"pause-timer": "Ustavi časovnik",
"resume-timer": "Nadaljuj časovnik",
"stop-timer": "Ustavi časovnik"
},
"edit-timeline-event": "Uredi dogodek na časovnici", "edit-timeline-event": "Uredi dogodek na časovnici",
"timeline": "Časovnica", "timeline": "Časovnica",
"timeline-is-empty": "Zaenkrat je časovnica prazna. Poskusi tale recept!", "timeline-is-empty": "Zaenkrat je časovnica prazna. Poskusi tale recept!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Уреди догађај на временској линији", "edit-timeline-event": "Уреди догађај на временској линији",
"timeline": "Временска линија", "timeline": "Временска линија",
"timeline-is-empty": "Још увек нема ништа на временској линији. Покушајте направити овај рецепт!", "timeline-is-empty": "Још увек нема ништа на временској линији. Покушајте направити овај рецепт!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Skala upp med 1", "increase-scale-label": "Skala upp med 1",
"locked": "Låst", "locked": "Låst",
"public-link": "Publik länk", "public-link": "Publik länk",
"timer": {
"kitchen-timer": "Kökstimer",
"start-timer": "Starta Timer",
"pause-timer": "Pausa Timer",
"resume-timer": "Återuppta Timer",
"stop-timer": "Stoppa Timer"
},
"edit-timeline-event": "Redigera tidslinjehändelse", "edit-timeline-event": "Redigera tidslinjehändelse",
"timeline": "Tidslinje", "timeline": "Tidslinje",
"timeline-is-empty": "Inget på tidslinjen än. Försök att göra detta recept!", "timeline-is-empty": "Inget på tidslinjen än. Försök att göra detta recept!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Ölçeği 1 artır", "increase-scale-label": "Ölçeği 1 artır",
"locked": "Kilitli", "locked": "Kilitli",
"public-link": "Genel bağlantı", "public-link": "Genel bağlantı",
"timer": {
"kitchen-timer": "Mutfak Saati",
"start-timer": "Zamanlayıcıyı Başlat",
"pause-timer": "Zamanlayıcıyı Duraklat",
"resume-timer": "Zamanlayıcıyı Sürdür",
"stop-timer": "Zamanlayıcıyı Durdur"
},
"edit-timeline-event": "Zaman Çizelgesi Etkinliğini Düzenle", "edit-timeline-event": "Zaman Çizelgesi Etkinliğini Düzenle",
"timeline": "Zaman çizelgesi", "timeline": "Zaman çizelgesi",
"timeline-is-empty": "Zaman çizelgesinde henüz bir şey yok. Bu tarifi yapmayı deneyin!", "timeline-is-empty": "Zaman çizelgesinde henüz bir şey yok. Bu tarifi yapmayı deneyin!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Збільшити масштабування на 1", "increase-scale-label": "Збільшити масштабування на 1",
"locked": "Заблоковано", "locked": "Заблоковано",
"public-link": "Публічне посилання", "public-link": "Публічне посилання",
"timer": {
"kitchen-timer": "Кухонний таймер",
"start-timer": "Запустити таймер",
"pause-timer": "Призупинити таймер",
"resume-timer": "Відновити таймер",
"stop-timer": "Зупинити таймер"
},
"edit-timeline-event": "Редагувати подію хронології", "edit-timeline-event": "Редагувати подію хронології",
"timeline": "Хронологія", "timeline": "Хронологія",
"timeline-is-empty": "Хронологія порожня. Спробуйте зробити цей рецепт!", "timeline-is-empty": "Хронологія порожня. Спробуйте зробити цей рецепт!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "加1倍", "increase-scale-label": "加1倍",
"locked": "已锁定", "locked": "已锁定",
"public-link": "公开链接", "public-link": "公开链接",
"timer": {
"kitchen-timer": "厨房计时器",
"start-timer": "开始计时",
"pause-timer": "暂停计时",
"resume-timer": "继续计时",
"stop-timer": "终止计时"
},
"edit-timeline-event": "编辑时间轴事件", "edit-timeline-event": "编辑时间轴事件",
"timeline": "时间轴", "timeline": "时间轴",
"timeline-is-empty": "时间轴还空空如也,试着先去制作一个食谱吧!", "timeline-is-empty": "时间轴还空空如也,试着先去制作一个食谱吧!",

View file

@ -570,13 +570,6 @@
"increase-scale-label": "Increase Scale by 1", "increase-scale-label": "Increase Scale by 1",
"locked": "Locked", "locked": "Locked",
"public-link": "Public Link", "public-link": "Public Link",
"timer": {
"kitchen-timer": "Kitchen Timer",
"start-timer": "Start Timer",
"pause-timer": "Pause Timer",
"resume-timer": "Resume Timer",
"stop-timer": "Stop Timer"
},
"edit-timeline-event": "Edit Timeline Event", "edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline", "timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",

View file

@ -161,7 +161,7 @@ export interface RecipeTool {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
onHand?: boolean; householdsWithTool?: string[];
[k: string]: unknown; [k: string]: unknown;
} }
export interface CustomPageImport { export interface CustomPageImport {

View file

@ -97,7 +97,7 @@ export interface RecipeTool {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
onHand?: boolean; householdsWithTool?: string[];
[k: string]: unknown; [k: string]: unknown;
} }
export interface SaveCookBook { export interface SaveCookBook {

View file

@ -208,6 +208,27 @@ export interface ReadWebhook {
householdId: string; householdId: string;
id: string; id: string;
} }
export interface HouseholdRecipeBase {
lastMade?: string | null;
}
export interface HouseholdRecipeCreate {
lastMade?: string | null;
householdId: string;
recipeId: string;
}
export interface HouseholdRecipeOut {
lastMade?: string | null;
householdId: string;
recipeId: string;
id: string;
}
export interface HouseholdRecipeSummary {
lastMade?: string | null;
recipeId: string;
}
export interface HouseholdRecipeUpdate {
lastMade?: string | null;
}
export interface HouseholdSave { export interface HouseholdSave {
groupId: string; groupId: string;
name: string; name: string;
@ -297,7 +318,6 @@ export interface IngredientUnit {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string | null; pluralAbbreviation?: string | null;
@ -318,7 +338,6 @@ export interface CreateIngredientUnit {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string | null; pluralAbbreviation?: string | null;
@ -338,9 +357,9 @@ export interface IngredientFood {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
labelId?: string | null; labelId?: string | null;
aliases?: IngredientFoodAlias[]; aliases?: IngredientFoodAlias[];
householdsWithIngredientFood?: string[];
label?: MultiPurposeLabelSummary | null; label?: MultiPurposeLabelSummary | null;
createdAt?: string | null; createdAt?: string | null;
updatedAt?: string | null; updatedAt?: string | null;
@ -363,9 +382,9 @@ export interface CreateIngredientFood {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
labelId?: string | null; labelId?: string | null;
aliases?: CreateIngredientFoodAlias[]; aliases?: CreateIngredientFoodAlias[];
householdsWithIngredientFood?: string[];
[k: string]: unknown; [k: string]: unknown;
} }
export interface CreateIngredientFoodAlias { export interface CreateIngredientFoodAlias {
@ -592,7 +611,7 @@ export interface RecipeTool {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
onHand?: boolean; householdsWithTool?: string[];
[k: string]: unknown; [k: string]: unknown;
} }
export interface ShoppingListRemoveRecipeParams { export interface ShoppingListRemoveRecipeParams {

View file

@ -117,7 +117,7 @@ export interface RecipeTool {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
onHand?: boolean; householdsWithTool?: string[];
[k: string]: unknown; [k: string]: unknown;
} }
export interface SavePlanEntry { export interface SavePlanEntry {

View file

@ -64,9 +64,9 @@ export interface CreateIngredientFood {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
labelId?: string | null; labelId?: string | null;
aliases?: CreateIngredientFoodAlias[]; aliases?: CreateIngredientFoodAlias[];
householdsWithIngredientFood?: string[];
} }
export interface CreateIngredientFoodAlias { export interface CreateIngredientFoodAlias {
name: string; name: string;
@ -79,7 +79,6 @@ export interface CreateIngredientUnit {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string | null; pluralAbbreviation?: string | null;
@ -136,9 +135,9 @@ export interface IngredientFood {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
labelId?: string | null; labelId?: string | null;
aliases?: IngredientFoodAlias[]; aliases?: IngredientFoodAlias[];
householdsWithIngredientFood?: string[];
label?: MultiPurposeLabelSummary | null; label?: MultiPurposeLabelSummary | null;
createdAt?: string | null; createdAt?: string | null;
updatedAt?: string | null; updatedAt?: string | null;
@ -167,7 +166,6 @@ export interface IngredientUnit {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string | null; pluralAbbreviation?: string | null;
@ -262,7 +260,7 @@ export interface RecipeTool {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
onHand?: boolean; householdsWithTool?: string[];
} }
export interface RecipeStep { export interface RecipeStep {
id?: string | null; id?: string | null;
@ -447,24 +445,24 @@ export interface RecipeTimelineEventUpdate {
} }
export interface RecipeToolCreate { export interface RecipeToolCreate {
name: string; name: string;
onHand?: boolean; householdsWithTool?: string[];
} }
export interface RecipeToolOut { export interface RecipeToolOut {
name: string; name: string;
onHand?: boolean; householdsWithTool?: string[];
id: string; id: string;
slug: string; slug: string;
} }
export interface RecipeToolResponse { export interface RecipeToolResponse {
name: string; name: string;
onHand?: boolean; householdsWithTool?: string[];
id: string; id: string;
slug: string; slug: string;
recipes?: RecipeSummary[]; recipes?: RecipeSummary[];
} }
export interface RecipeToolSave { export interface RecipeToolSave {
name: string; name: string;
onHand?: boolean; householdsWithTool?: string[];
groupId: string; groupId: string;
} }
export interface RecipeZipTokenResponse { export interface RecipeZipTokenResponse {
@ -478,9 +476,9 @@ export interface SaveIngredientFood {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
labelId?: string | null; labelId?: string | null;
aliases?: CreateIngredientFoodAlias[]; aliases?: CreateIngredientFoodAlias[];
householdsWithIngredientFood?: string[];
groupId: string; groupId: string;
} }
export interface SaveIngredientUnit { export interface SaveIngredientUnit {
@ -491,7 +489,6 @@ export interface SaveIngredientUnit {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
fraction?: boolean; fraction?: boolean;
abbreviation?: string; abbreviation?: string;
pluralAbbreviation?: string | null; pluralAbbreviation?: string | null;
@ -536,7 +533,6 @@ export interface UnitFoodBase {
extras?: { extras?: {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
onHand?: boolean;
} }
export interface UpdateImageResponse { export interface UpdateImageResponse {
image: string; image: string;

View file

@ -11,6 +11,7 @@ import {
CreateInviteToken, CreateInviteToken,
ReadInviteToken, ReadInviteToken,
HouseholdSummary, HouseholdSummary,
HouseholdRecipeSummary,
} from "~/lib/api/types/household"; } from "~/lib/api/types/household";
const prefix = "/api"; const prefix = "/api";
@ -26,6 +27,7 @@ const routes = {
invitation: `${prefix}/households/invitations`, invitation: `${prefix}/households/invitations`,
householdsId: (id: string | number) => `${prefix}/groups/households/${id}`, householdsId: (id: string | number) => `${prefix}/groups/households/${id}`,
householdsSelfRecipesSlug: (recipeSlug: string) => `${prefix}/households/self/recipes/${recipeSlug}`,
}; };
export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> { export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> {
@ -37,6 +39,10 @@ export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> {
return await this.requests.get<HouseholdInDB>(routes.householdsSelf); return await this.requests.get<HouseholdInDB>(routes.householdsSelf);
} }
async getCurrentUserHouseholdRecipe(recipeSlug: string) {
return await this.requests.get<HouseholdRecipeSummary>(routes.householdsSelfRecipesSlug(recipeSlug));
}
async getPreferences() { async getPreferences() {
return await this.requests.get<ReadHouseholdPreferences>(routes.preferences); return await this.requests.get<ReadHouseholdPreferences>(routes.preferences);
} }

View file

@ -138,12 +138,6 @@ import {
mdiDockTop, mdiDockTop,
mdiDockBottom, mdiDockBottom,
mdiCheckboxOutline, mdiCheckboxOutline,
mdiTimer,
mdiTimerPlus,
mdiPause,
mdiStop,
mdiPlay,
mdiTimerPause,
mdiFlipHorizontal, mdiFlipHorizontal,
mdiFlipVertical, mdiFlipVertical,
mdiRotateLeft, mdiRotateLeft,
@ -243,8 +237,6 @@ export const icons = {
openInNew: mdiOpenInNew, openInNew: mdiOpenInNew,
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending, orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
pageLayoutBody: mdiPageLayoutBody, pageLayoutBody: mdiPageLayoutBody,
pause: mdiPause,
play: mdiPlay,
printer: mdiPrinter, printer: mdiPrinter,
printerSettings: mdiPrinterPosCog, printerSettings: mdiPrinterPosCog,
refreshCircle: mdiRefreshCircle, refreshCircle: mdiRefreshCircle,
@ -265,12 +257,8 @@ export const icons = {
sortClockAscending: mdiSortClockAscending, sortClockAscending: mdiSortClockAscending,
sortClockDescending: mdiSortClockDescending, sortClockDescending: mdiSortClockDescending,
star: mdiStar, star: mdiStar,
stop: mdiStop,
testTube: mdiTestTube, testTube: mdiTestTube,
timelineText: mdiTimelineText, timelineText: mdiTimelineText,
timer: mdiTimer,
timerPause: mdiTimerPause,
timerPlus: mdiTimerPlus,
tools: mdiTools, tools: mdiTools,
potSteam: mdiPotSteamOutline, potSteam: mdiPotSteamOutline,
translate: mdiTranslate, translate: mdiTranslate,

View file

@ -370,6 +370,7 @@ export default {
dir: "auto", dir: "auto",
name: "Mealie", name: "Mealie",
short_name: "Mealie", short_name: "Mealie",
crossorigin: "use-credentials",
id: "mealie", id: "mealie",
description: "Mealie is a recipe management and meal planning app", description: "Mealie is a recipe management and meal planning app",
theme_color: process.env.THEME_LIGHT_PRIMARY || "#E58325", theme_color: process.env.THEME_LIGHT_PRIMARY || "#E58325",
@ -532,17 +533,6 @@ export default {
// ["@nuxtjs/composition-api/dist/babel-plugin"], // ["@nuxtjs/composition-api/dist/babel-plugin"],
], ],
}, },
// audio file support
// https://v2.nuxt.com/docs/features/configuration/#extend-webpack-to-load-audio-files
extend(config, ctx) {
config.module.rules.push({
test: /\.(ogg|mp3|wav|mpe?g)$/i,
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
})
},
transpile: process.env.NODE_ENV !== "production" ? [/@vue[\\/]composition-api/] : null, transpile: process.env.NODE_ENV !== "production" ? [/@vue[\\/]composition-api/] : null,
}, },
}; };

View file

@ -1,6 +1,6 @@
{ {
"name": "mealie", "name": "mealie",
"version": "2.4.1", "version": "2.4.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",

View file

@ -5,8 +5,8 @@
:icon="$globals.icons.potSteam" :icon="$globals.icons.potSteam"
:items="tools" :items="tools"
item-type="tools" item-type="tools"
@delete="actions.deleteOne" @delete="deleteOne"
@update="actions.updateOne" @update="updateOne"
> >
<template #title> {{ $t("tool.tools") }} </template> <template #title> {{ $t("tool.tools") }} </template>
</RecipeOrganizerPage> </RecipeOrganizerPage>
@ -14,9 +14,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api"; import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
import RecipeOrganizerPage from "~/components/Domain/Recipe/RecipeOrganizerPage.vue"; import RecipeOrganizerPage from "~/components/Domain/Recipe/RecipeOrganizerPage.vue";
import { useToolStore } from "~/composables/store"; import { useToolStore } from "~/composables/store";
import { RecipeTool } from "~/lib/api/types/recipe";
interface RecipeToolWithOnHand extends RecipeTool {
onHand: boolean;
}
export default defineComponent({ export default defineComponent({
components: { components: {
@ -24,13 +29,42 @@ export default defineComponent({
}, },
middleware: ["auth", "group-only"], middleware: ["auth", "group-only"],
setup() { setup() {
const { $auth } = useContext();
const toolStore = useToolStore(); const toolStore = useToolStore();
const dialog = ref(false); const dialog = ref(false);
const userHousehold = computed(() => $auth.user?.householdSlug || "");
const tools = computed(() => toolStore.store.value.map((tool) => (
{
...tool,
onHand: tool.householdsWithTool?.includes(userHousehold.value) || false
} as RecipeToolWithOnHand
)));
async function deleteOne(id: string | number) {
await toolStore.actions.deleteOne(id);
}
async function updateOne(tool: RecipeToolWithOnHand) {
if (userHousehold.value) {
if (tool.onHand && !tool.householdsWithTool?.includes(userHousehold.value)) {
if (!tool.householdsWithTool) {
tool.householdsWithTool = [userHousehold.value];
} else {
tool.householdsWithTool.push(userHousehold.value);
}
} else if (!tool.onHand && tool.householdsWithTool?.includes(userHousehold.value)) {
tool.householdsWithTool = tool.householdsWithTool.filter((household) => household !== userHousehold.value);
}
}
await toolStore.actions.updateOne(tool);
}
return { return {
dialog, dialog,
tools: toolStore.store, tools,
actions: toolStore.actions, deleteOne,
updateOne,
}; };
}, },
head() { head() {

View file

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<!-- Merge Dialog --> <!-- Merge Dialog -->
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" :title="$t('data-pages.foods.combine-food')" @confirm="mergeFoods"> <BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" :title="$tc('data-pages.foods.combine-food')" @confirm="mergeFoods">
<v-card-text> <v-card-text>
<div> <div>
{{ $t("data-pages.foods.merge-dialog-text") }} {{ $t("data-pages.foods.merge-dialog-text") }}
@ -58,7 +58,7 @@
<BaseDialog <BaseDialog
v-model="createDialog" v-model="createDialog"
:icon="$globals.icons.foods" :icon="$globals.icons.foods"
:title="$t('data-pages.foods.create-food')" :title="$tc('data-pages.foods.create-food')"
:submit-icon="$globals.icons.save" :submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')" :submit-text="$tc('general.save')"
@submit="createFood" @submit="createFood"
@ -111,7 +111,7 @@
<BaseDialog <BaseDialog
v-model="editDialog" v-model="editDialog"
:icon="$globals.icons.foods" :icon="$globals.icons.foods"
:title="$t('data-pages.foods.edit-food')" :title="$tc('data-pages.foods.edit-food')"
:submit-icon="$globals.icons.save" :submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')" :submit-text="$tc('general.save')"
@submit="editSaveFood" @submit="editSaveFood"
@ -196,7 +196,7 @@
</v-card-text> </v-card-text>
</BaseDialog> </BaseDialog>
<!-- Bulk Asign Labels Dialog --> <!-- Bulk Assign Labels Dialog -->
<BaseDialog <BaseDialog
v-model="bulkAssignLabelDialog" v-model="bulkAssignLabelDialog"
:title="$tc('data-pages.labels.assign-label')" :title="$tc('data-pages.labels.assign-label')"
@ -292,11 +292,20 @@ import { useFoodStore, useLabelStore } from "~/composables/store";
import { VForm } from "~/types/vuetify"; import { VForm } from "~/types/vuetify";
import { MultiPurposeLabelOut } from "~/lib/api/types/labels"; import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
interface CreateIngredientFoodWithOnHand extends CreateIngredientFood {
onHand: boolean;
householdsWithIngredientFood: string[];
}
interface IngredientFoodWithOnHand extends IngredientFood {
onHand: boolean;
}
export default defineComponent({ export default defineComponent({
components: { MultiPurposeLabel, RecipeDataAliasManagerDialog }, components: { MultiPurposeLabel, RecipeDataAliasManagerDialog },
setup() { setup() {
const userApi = useUserApi(); const userApi = useUserApi();
const { i18n } = useContext(); const { $auth, i18n } = useContext();
const tableConfig = { const tableConfig = {
hideColumns: true, hideColumns: true,
canExport: true, canExport: true,
@ -352,15 +361,22 @@ export default defineComponent({
} }
} }
const userHousehold = computed(() => $auth.user?.householdSlug || "");
const foodStore = useFoodStore(); const foodStore = useFoodStore();
const foods = computed(() => foodStore.store.value.map((food) => {
const onHand = food.householdsWithIngredientFood?.includes(userHousehold.value) || false;
return { ...food, onHand } as IngredientFoodWithOnHand;
}));
// =============================================================== // ===============================================================
// Food Creator // Food Creator
const domNewFoodForm = ref<VForm>(); const domNewFoodForm = ref<VForm>();
const createDialog = ref(false); const createDialog = ref(false);
const createTarget = ref<CreateIngredientFood>({ const createTarget = ref<CreateIngredientFoodWithOnHand>({
name: "", name: "",
onHand: false,
householdsWithIngredientFood: [],
}); });
function createEventHandler() { function createEventHandler() {
@ -372,6 +388,10 @@ export default defineComponent({
return; return;
} }
if (createTarget.value.onHand) {
createTarget.value.householdsWithIngredientFood = [userHousehold.value];
}
// @ts-expect-error the createOne function erroneously expects an id because it uses the IngredientFood type // @ts-expect-error the createOne function erroneously expects an id because it uses the IngredientFood type
await foodStore.actions.createOne(createTarget.value); await foodStore.actions.createOne(createTarget.value);
createDialog.value = false; createDialog.value = false;
@ -379,6 +399,8 @@ export default defineComponent({
domNewFoodForm.value?.reset(); domNewFoodForm.value?.reset();
createTarget.value = { createTarget.value = {
name: "", name: "",
onHand: false,
householdsWithIngredientFood: [],
}; };
} }
@ -386,10 +408,11 @@ export default defineComponent({
// Food Editor // Food Editor
const editDialog = ref(false); const editDialog = ref(false);
const editTarget = ref<IngredientFood | null>(null); const editTarget = ref<IngredientFoodWithOnHand | null>(null);
function editEventHandler(item: IngredientFood) { function editEventHandler(item: IngredientFoodWithOnHand) {
editTarget.value = item; editTarget.value = item;
editTarget.value.onHand = item.householdsWithIngredientFood?.includes(userHousehold.value) || false;
editDialog.value = true; editDialog.value = true;
} }
@ -397,6 +420,17 @@ export default defineComponent({
if (!editTarget.value) { if (!editTarget.value) {
return; return;
} }
if (editTarget.value.onHand && !editTarget.value.householdsWithIngredientFood?.includes(userHousehold.value)) {
if (!editTarget.value.householdsWithIngredientFood) {
editTarget.value.householdsWithIngredientFood = [userHousehold.value];
} else {
editTarget.value.householdsWithIngredientFood.push(userHousehold.value);
}
} else if (!editTarget.value.onHand && editTarget.value.householdsWithIngredientFood?.includes(userHousehold.value)) {
editTarget.value.householdsWithIngredientFood = editTarget.value.householdsWithIngredientFood.filter(
(household) => household !== userHousehold.value
);
}
await foodStore.actions.updateOne(editTarget.value); await foodStore.actions.updateOne(editTarget.value);
editDialog.value = false; editDialog.value = false;
@ -406,8 +440,8 @@ export default defineComponent({
// Food Delete // Food Delete
const deleteDialog = ref(false); const deleteDialog = ref(false);
const deleteTarget = ref<IngredientFood | null>(null); const deleteTarget = ref<IngredientFoodWithOnHand | null>(null);
function deleteEventHandler(item: IngredientFood) { function deleteEventHandler(item: IngredientFoodWithOnHand) {
deleteTarget.value = item; deleteTarget.value = item;
deleteDialog.value = true; deleteDialog.value = true;
} }
@ -421,9 +455,9 @@ export default defineComponent({
} }
const bulkDeleteDialog = ref(false); const bulkDeleteDialog = ref(false);
const bulkDeleteTarget = ref<IngredientFood[]>([]); const bulkDeleteTarget = ref<IngredientFoodWithOnHand[]>([]);
function bulkDeleteEventHandler(selection: IngredientFood[]) { function bulkDeleteEventHandler(selection: IngredientFoodWithOnHand[]) {
bulkDeleteTarget.value = selection; bulkDeleteTarget.value = selection;
bulkDeleteDialog.value = true; bulkDeleteDialog.value = true;
} }
@ -455,8 +489,8 @@ export default defineComponent({
// Merge Foods // Merge Foods
const mergeDialog = ref(false); const mergeDialog = ref(false);
const fromFood = ref<IngredientFood | null>(null); const fromFood = ref<IngredientFoodWithOnHand | null>(null);
const toFood = ref<IngredientFood | null>(null); const toFood = ref<IngredientFoodWithOnHand | null>(null);
const canMerge = computed(() => { const canMerge = computed(() => {
return fromFood.value && toFood.value && fromFood.value.id !== toFood.value.id; return fromFood.value && toFood.value && fromFood.value.id !== toFood.value.id;
@ -506,10 +540,10 @@ export default defineComponent({
// ============================================================ // ============================================================
// Bulk Assign Labels // Bulk Assign Labels
const bulkAssignLabelDialog = ref(false); const bulkAssignLabelDialog = ref(false);
const bulkAssignTarget = ref<IngredientFood[]>([]); const bulkAssignTarget = ref<IngredientFoodWithOnHand[]>([]);
const bulkAssignLabelId = ref<string | undefined>(); const bulkAssignLabelId = ref<string | undefined>();
function bulkAssignEventHandler(selection: IngredientFood[]) { function bulkAssignEventHandler(selection: IngredientFoodWithOnHand[]) {
bulkAssignTarget.value = selection; bulkAssignTarget.value = selection;
bulkAssignLabelDialog.value = true; bulkAssignLabelDialog.value = true;
} }
@ -530,7 +564,7 @@ export default defineComponent({
return { return {
tableConfig, tableConfig,
tableHeaders, tableHeaders,
foods: foodStore.store, foods,
allLabels, allLabels,
validators, validators,
formatDate, formatDate,

View file

@ -109,11 +109,6 @@
<template #button-row> <template #button-row>
<BaseButton create @click="state.createDialog = true">{{ $t("general.create") }}</BaseButton> <BaseButton create @click="state.createDialog = true">{{ $t("general.create") }}</BaseButton>
</template> </template>
<template #item.onHand="{ item }">
<v-icon :color="item.onHand ? 'success' : undefined">
{{ item.onHand ? $globals.icons.check : $globals.icons.close }}
</v-icon>
</template>
</CrudTable> </CrudTable>
</div> </div>
</template> </template>

View file

@ -101,14 +101,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api"; import { computed, defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
import { validators } from "~/composables/use-validators"; import { validators } from "~/composables/use-validators";
import { useToolStore, useToolData } from "~/composables/store"; import { useToolStore, useToolData } from "~/composables/store";
import { RecipeTool } from "~/lib/api/types/admin"; import { RecipeTool } from "~/lib/api/types/recipe";
interface RecipeToolWithOnHand extends RecipeTool {
onHand: boolean;
}
export default defineComponent({ export default defineComponent({
setup() { setup() {
const { i18n } = useContext(); const { $auth, i18n } = useContext();
const tableConfig = { const tableConfig = {
hideColumns: true, hideColumns: true,
canExport: true, canExport: true,
@ -138,27 +142,38 @@ export default defineComponent({
bulkDeleteDialog: false, bulkDeleteDialog: false,
}); });
const userHousehold = computed(() => $auth.user?.householdSlug || "");
const toolData = useToolData(); const toolData = useToolData();
const toolStore = useToolStore(); const toolStore = useToolStore();
const tools = computed(() => toolStore.store.value.map((tools) => {
const onHand = tools.householdsWithTool?.includes(userHousehold.value) || false;
return { ...tools, onHand } as RecipeToolWithOnHand;
}));
// ============================================================ // ============================================================
// Create Tag // Create Tool
async function createTool() { async function createTool() {
if (toolData.data.onHand) {
toolData.data.householdsWithTool = [userHousehold.value];
} else {
toolData.data.householdsWithTool = [];
}
// @ts-ignore - only property really required is the name and onHand (RecipeOrganizerPage) // @ts-ignore - only property really required is the name and onHand (RecipeOrganizerPage)
await toolStore.actions.createOne({ name: toolData.data.name, onHand: toolData.data.onHand }); await toolStore.actions.createOne({ name: toolData.data.name, householdsWithTool: toolData.data.householdsWithTool });
toolData.reset(); toolData.reset();
state.createDialog = false; state.createDialog = false;
} }
// ============================================================ // ============================================================
// Edit Tag // Edit Tool
const editTarget = ref<RecipeTool | null>(null); const editTarget = ref<RecipeToolWithOnHand | null>(null);
function editEventHandler(item: RecipeTool) { function editEventHandler(item: RecipeToolWithOnHand) {
state.editDialog = true; state.editDialog = true;
editTarget.value = item; editTarget.value = item;
} }
@ -167,17 +182,29 @@ export default defineComponent({
if (!editTarget.value) { if (!editTarget.value) {
return; return;
} }
if (editTarget.value.onHand && !editTarget.value.householdsWithTool?.includes(userHousehold.value)) {
if (!editTarget.value.householdsWithTool) {
editTarget.value.householdsWithTool = [userHousehold.value];
} else {
editTarget.value.householdsWithTool.push(userHousehold.value);
}
} else if (!editTarget.value.onHand && editTarget.value.householdsWithTool?.includes(userHousehold.value)) {
editTarget.value.householdsWithTool = editTarget.value.householdsWithTool.filter(
(household) => household !== userHousehold.value
);
}
await toolStore.actions.updateOne(editTarget.value); await toolStore.actions.updateOne(editTarget.value);
state.editDialog = false; state.editDialog = false;
} }
// ============================================================ // ============================================================
// Delete Tag // Delete Tool
const deleteTarget = ref<RecipeTool | null>(null); const deleteTarget = ref<RecipeToolWithOnHand | null>(null);
function deleteEventHandler(item: RecipeTool) { function deleteEventHandler(item: RecipeToolWithOnHand) {
state.deleteDialog = true; state.deleteDialog = true;
deleteTarget.value = item; deleteTarget.value = item;
} }
@ -191,10 +218,10 @@ export default defineComponent({
} }
// ============================================================ // ============================================================
// Bulk Delete Tag // Bulk Delete Tool
const bulkDeleteTarget = ref<RecipeTool[]>([]); const bulkDeleteTarget = ref<RecipeToolWithOnHand[]>([]);
function bulkDeleteEventHandler(selection: RecipeTool[]) { function bulkDeleteEventHandler(selection: RecipeToolWithOnHand[]) {
bulkDeleteTarget.value = selection; bulkDeleteTarget.value = selection;
state.bulkDeleteDialog = true; state.bulkDeleteDialog = true;
} }
@ -210,7 +237,7 @@ export default defineComponent({
state, state,
tableConfig, tableConfig,
tableHeaders, tableHeaders,
tools: toolStore.store, tools,
validators, validators,
// create // create

View file

@ -0,0 +1,263 @@
"""add household to recipe last made, household to foods, and household to tools
Revision ID: b9e516e2d3b3
Revises: b1020f328e98
Create Date: 2024-11-20 17:30:41.152332
"""
from datetime import datetime
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import DeclarativeBase
import mealie.db.migration_types
from alembic import op
from mealie.core.root_logger import get_logger
from mealie.db.models._model_utils.datetime import NaiveDateTime
from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "b9e516e2d3b3"
down_revision: str | None = "b1020f328e98"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
logger = get_logger()
class SqlAlchemyBase(DeclarativeBase):
pass
# Intermediate table definitions
class Group(SqlAlchemyBase):
__tablename__ = "groups"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
class Household(SqlAlchemyBase):
__tablename__ = "households"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
class RecipeModel(SqlAlchemyBase):
__tablename__ = "recipes"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
last_made: orm.Mapped[datetime | None] = orm.mapped_column(NaiveDateTime)
class HouseholdToRecipe(SqlAlchemyBase):
__tablename__ = "households_to_recipes"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
household_id = sa.Column(GUID, sa.ForeignKey("households.id"), index=True, primary_key=True)
recipe_id = sa.Column(GUID, sa.ForeignKey("recipes.id"), index=True, primary_key=True)
last_made: orm.Mapped[datetime | None] = orm.mapped_column(NaiveDateTime)
class IngredientFoodModel(SqlAlchemyBase):
__tablename__ = "ingredient_foods"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
on_hand: orm.Mapped[bool] = orm.mapped_column(sa.Boolean, default=False)
class Tool(SqlAlchemyBase):
__tablename__ = "tools"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
on_hand: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=False)
def migrate_recipe_last_made_to_household(session: orm.Session):
for group in session.query(Group).all():
households = session.query(Household).filter(Household.group_id == group.id).all()
recipes = (
session.query(RecipeModel)
.filter(
RecipeModel.group_id == group.id,
RecipeModel.last_made != None, # noqa E711
)
.all()
)
for recipe in recipes:
for household in households:
session.add(
HouseholdToRecipe(
household_id=household.id,
recipe_id=recipe.id,
last_made=recipe.last_made,
)
)
def migrate_foods_on_hand_to_household(session: orm.Session):
dialect = op.get_bind().dialect
for group in session.query(Group).all():
households = session.query(Household).filter(Household.group_id == group.id).all()
foods = (
session.query(IngredientFoodModel)
.filter(
IngredientFoodModel.group_id == group.id,
IngredientFoodModel.on_hand == True, # noqa E712
)
.all()
)
for food in foods:
for household in households:
session.execute(
sa.text(
"INSERT INTO households_to_ingredient_foods (household_id, food_id)"
"VALUES (:household_id, :food_id)"
),
{
"household_id": GUID.convert_value_to_guid(household.id, dialect),
"food_id": GUID.convert_value_to_guid(food.id, dialect),
},
)
def migrate_tools_on_hand_to_household(session: orm.Session):
dialect = op.get_bind().dialect
for group in session.query(Group).all():
households = session.query(Household).filter(Household.group_id == group.id).all()
tools = (
session.query(Tool)
.filter(
Tool.group_id == group.id,
Tool.on_hand == True, # noqa E712
)
.all()
)
for tool in tools:
for household in households:
session.execute(
sa.text("INSERT INTO households_to_tools (household_id, tool_id) VALUES (:household_id, :tool_id)"),
{
"household_id": GUID.convert_value_to_guid(household.id, dialect),
"tool_id": GUID.convert_value_to_guid(tool.id, dialect),
},
)
def migrate_to_new_models():
bind = op.get_bind()
session = orm.Session(bind=bind)
for migration_func in [
migrate_recipe_last_made_to_household,
migrate_foods_on_hand_to_household,
migrate_tools_on_hand_to_household,
]:
try:
logger.info(f"Running new model migration ({migration_func.__name__})")
migration_func(session)
session.commit()
except Exception:
session.rollback()
logger.error(f"Error during new model migration ({migration_func.__name__})")
raise
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"households_to_recipes",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("last_made", mealie.db.migration_types.NaiveDateTime(), nullable=True),
sa.Column("created_at", mealie.db.migration_types.NaiveDateTime(), nullable=True),
sa.Column("update_at", mealie.db.migration_types.NaiveDateTime(), nullable=True),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id", "household_id", "recipe_id"),
sa.UniqueConstraint("household_id", "recipe_id", name="household_id_recipe_id_key"),
)
with op.batch_alter_table("households_to_recipes", schema=None) as batch_op:
batch_op.create_index(batch_op.f("ix_households_to_recipes_created_at"), ["created_at"], unique=False)
batch_op.create_index(batch_op.f("ix_households_to_recipes_household_id"), ["household_id"], unique=False)
batch_op.create_index(batch_op.f("ix_households_to_recipes_recipe_id"), ["recipe_id"], unique=False)
op.create_table(
"households_to_tools",
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tool_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.ForeignKeyConstraint(
["tool_id"],
["tools.id"],
),
sa.UniqueConstraint("household_id", "tool_id", name="household_id_tool_id_key"),
)
with op.batch_alter_table("households_to_tools", schema=None) as batch_op:
batch_op.create_index(batch_op.f("ix_households_to_tools_household_id"), ["household_id"], unique=False)
batch_op.create_index(batch_op.f("ix_households_to_tools_tool_id"), ["tool_id"], unique=False)
op.create_table(
"households_to_ingredient_foods",
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.UniqueConstraint("household_id", "food_id", name="household_id_food_id_key"),
)
with op.batch_alter_table("households_to_ingredient_foods", schema=None) as batch_op:
batch_op.create_index(batch_op.f("ix_households_to_ingredient_foods_food_id"), ["food_id"], unique=False)
batch_op.create_index(
batch_op.f("ix_households_to_ingredient_foods_household_id"), ["household_id"], unique=False
)
# ### end Alembic commands ###
migrate_to_new_models()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("households_to_ingredient_foods", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_households_to_ingredient_foods_household_id"))
batch_op.drop_index(batch_op.f("ix_households_to_ingredient_foods_food_id"))
op.drop_table("households_to_ingredient_foods")
with op.batch_alter_table("households_to_tools", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_households_to_tools_tool_id"))
batch_op.drop_index(batch_op.f("ix_households_to_tools_household_id"))
op.drop_table("households_to_tools")
with op.batch_alter_table("households_to_recipes", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_households_to_recipes_recipe_id"))
batch_op.drop_index(batch_op.f("ix_households_to_recipes_household_id"))
batch_op.drop_index(batch_op.f("ix_households_to_recipes_created_at"))
op.drop_table("households_to_recipes")
# ### end Alembic commands ###

View file

@ -26,8 +26,7 @@ def fix_group_with_no_name(session: Session):
return return
logger.info( logger.info(
f'{len(groups)} {"group" if len(groups) == 1 else "groups"} found with a missing name; ' f"{len(groups)} {'group' if len(groups) == 1 else 'groups'} found with a missing name; applying default name"
f"applying default name"
) )
offset = 0 offset = 0

View file

@ -51,7 +51,7 @@ def fix_dangling_refs(session: Session):
if result.rowcount: if result.rowcount:
logger.info( logger.info(
f'Reassigned {result.rowcount} {"row" if result.rowcount == 1 else "rows"} ' f"Reassigned {result.rowcount} {'row' if result.rowcount == 1 else 'rows'} "
f'in "{table_name}" table to default user ({default_user.id})' f'in "{table_name}" table to default user ({default_user.id})'
) )
@ -63,7 +63,7 @@ def fix_dangling_refs(session: Session):
if result.rowcount: if result.rowcount:
logger.info( logger.info(
f'Deleted {result.rowcount} {"row" if result.rowcount == 1 else "rows"} ' f"Deleted {result.rowcount} {'row' if result.rowcount == 1 else 'rows'} "
f'in "{table_name}" table with invalid user ids' f'in "{table_name}" table with invalid user ids'
) )

View file

@ -1,6 +1,7 @@
from .cookbook import CookBook from .cookbook import CookBook
from .events import GroupEventNotifierModel, GroupEventNotifierOptionsModel from .events import GroupEventNotifierModel, GroupEventNotifierOptionsModel
from .household import Household from .household import Household
from .household_to_recipe import HouseholdToRecipe
from .invite_tokens import GroupInviteToken from .invite_tokens import GroupInviteToken
from .mealplan import GroupMealPlan, GroupMealPlanRules from .mealplan import GroupMealPlan, GroupMealPlanRules
from .preferences import HouseholdPreferencesModel from .preferences import HouseholdPreferencesModel
@ -24,6 +25,7 @@ __all__ = [
"GroupMealPlanRules", "GroupMealPlanRules",
"Household", "Household",
"HouseholdPreferencesModel", "HouseholdPreferencesModel",
"HouseholdToRecipe",
"GroupRecipeAction", "GroupRecipeAction",
"ShoppingList", "ShoppingList",
"ShoppingListExtras", "ShoppingListExtras",

View file

@ -8,9 +8,13 @@ from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils.auto_init import auto_init from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID from .._model_utils.guid import GUID
from ..recipe.ingredient import households_to_ingredient_foods
from ..recipe.tool import households_to_tools
from .household_to_recipe import HouseholdToRecipe
if TYPE_CHECKING: if TYPE_CHECKING:
from ..group import Group from ..group import Group
from ..recipe import IngredientFoodModel, RecipeModel, Tool
from ..users import User from ..users import User
from . import ( from . import (
CookBook, CookBook,
@ -62,6 +66,18 @@ class Household(SqlAlchemyBase, BaseMixins):
"GroupEventNotifierModel", **COMMON_ARGS "GroupEventNotifierModel", **COMMON_ARGS
) )
made_recipes: Mapped[list["RecipeModel"]] = orm.relationship(
"RecipeModel", secondary=HouseholdToRecipe.__tablename__, back_populates="made_by"
)
ingredient_foods_on_hand: Mapped[list["IngredientFoodModel"]] = orm.relationship(
"IngredientFoodModel",
secondary=households_to_ingredient_foods,
back_populates="households_with_ingredient_food",
)
tools_on_hand: Mapped[list["Tool"]] = orm.relationship(
"Tool", secondary=households_to_tools, back_populates="households_with_tool"
)
model_config = ConfigDict( model_config = ConfigDict(
exclude={ exclude={
"users", "users",
@ -72,6 +88,7 @@ class Household(SqlAlchemyBase, BaseMixins):
"invite_tokens", "invite_tokens",
"group_event_notifiers", "group_event_notifiers",
"group", "group",
"made_recipes",
} }
) )

View file

@ -0,0 +1,60 @@
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import Column, ForeignKey, UniqueConstraint, event
from sqlalchemy.engine.base import Connection
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.session import Session
from mealie.db.models._model_utils.datetime import NaiveDateTime
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from ..recipe import RecipeModel
from .household import Household
class HouseholdToRecipe(SqlAlchemyBase, BaseMixins):
__tablename__ = "households_to_recipes"
__table_args__ = (UniqueConstraint("household_id", "recipe_id", name="household_id_recipe_id_key"),)
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
household: Mapped["Household"] = relationship("Household", viewonly=True)
household_id = Column(GUID, ForeignKey("households.id"), index=True, primary_key=True)
recipe: Mapped["RecipeModel"] = relationship("RecipeModel", viewonly=True)
recipe_id = Column(GUID, ForeignKey("recipes.id"), index=True, primary_key=True)
group_id: AssociationProxy[GUID] = association_proxy("household", "group_id")
last_made: Mapped[datetime | None] = mapped_column(NaiveDateTime)
@auto_init()
def __init__(self, **_) -> None:
pass
def update_recipe_last_made(session: Session, target: HouseholdToRecipe):
if not target.last_made:
return
from mealie.db.models.recipe.recipe import RecipeModel
recipe = session.query(RecipeModel).filter(RecipeModel.id == target.recipe_id).first()
if not recipe:
return
recipe.last_made = recipe.last_made or target.last_made
recipe.last_made = max(recipe.last_made, target.last_made)
@event.listens_for(HouseholdToRecipe, "after_insert")
@event.listens_for(HouseholdToRecipe, "after_update")
@event.listens_for(HouseholdToRecipe, "after_delete")
def update_recipe_rating_on_insert_or_delete(_, connection: Connection, target: HouseholdToRecipe):
session = Session(bind=connection)
update_recipe_last_made(session, target)
session.commit()

View file

@ -1,6 +1,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import sqlalchemy as sa import sqlalchemy as sa
from pydantic import ConfigDict
from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, event, orm from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, event, orm
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -14,6 +15,16 @@ from .._model_utils.guid import GUID
if TYPE_CHECKING: if TYPE_CHECKING:
from ..group import Group from ..group import Group
from ..household import Household
households_to_ingredient_foods = sa.Table(
"households_to_ingredient_foods",
SqlAlchemyBase.metadata,
sa.Column("household_id", GUID, sa.ForeignKey("households.id"), index=True),
sa.Column("food_id", GUID, sa.ForeignKey("ingredient_foods.id"), index=True),
sa.UniqueConstraint("household_id", "food_id", name="household_id_food_id_key"),
)
class IngredientUnitModel(SqlAlchemyBase, BaseMixins): class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
@ -142,11 +153,13 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
# ID Relationships # ID Relationships
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True) group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id]) group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id])
households_with_ingredient_food: Mapped[list["Household"]] = orm.relationship(
"Household", secondary=households_to_ingredient_foods, back_populates="ingredient_foods_on_hand"
)
name: Mapped[str | None] = mapped_column(String) name: Mapped[str | None] = mapped_column(String)
plural_name: Mapped[str | None] = mapped_column(String) plural_name: Mapped[str | None] = mapped_column(String)
description: Mapped[str | None] = mapped_column(String) description: Mapped[str | None] = mapped_column(String)
on_hand: Mapped[bool] = mapped_column(Boolean)
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship( ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
"RecipeIngredientModel", back_populates="food" "RecipeIngredientModel", back_populates="food"
@ -165,20 +178,42 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True) name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True) plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
model_config = ConfigDict(
exclude={
"households_with_ingredient_food",
}
)
# Deprecated
on_hand: Mapped[bool] = mapped_column(Boolean, default=False)
@api_extras @api_extras
@auto_init() @auto_init()
def __init__( def __init__(
self, self,
session: Session, session: Session,
group_id: GUID,
name: str | None = None, name: str | None = None,
plural_name: str | None = None, plural_name: str | None = None,
households_with_ingredient_food: list[str] | None = None,
**_, **_,
) -> None: ) -> None:
from ..household import Household
if name is not None: if name is not None:
self.name_normalized = self.normalize(name) self.name_normalized = self.normalize(name)
if plural_name is not None: if plural_name is not None:
self.plural_name_normalized = self.normalize(plural_name) self.plural_name_normalized = self.normalize(plural_name)
if not households_with_ingredient_food:
self.households_with_ingredient_food = []
else:
self.households_with_ingredient_food = (
session.query(Household)
.filter(Household.group_id == group_id, Household.slug.in_(households_with_ingredient_food))
.all()
)
tableargs = [ tableargs = [
sa.UniqueConstraint("name", "group_id", name="ingredient_foods_name_group_id_key"), sa.UniqueConstraint("name", "group_id", name="ingredient_foods_name_group_id_key"),
sa.Index( sa.Index(

View file

@ -16,6 +16,7 @@ from mealie.db.models._model_utils.datetime import NaiveDateTime, get_utc_today
from mealie.db.models._model_utils.guid import GUID from mealie.db.models._model_utils.guid import GUID
from .._model_base import BaseMixins, SqlAlchemyBase from .._model_base import BaseMixins, SqlAlchemyBase
from ..household.household_to_recipe import HouseholdToRecipe
from ..users.user_to_recipe import UserToRecipe from ..users.user_to_recipe import UserToRecipe
from .api_extras import ApiExtras, api_extras from .api_extras import ApiExtras, api_extras
from .assets import RecipeAsset from .assets import RecipeAsset
@ -136,7 +137,11 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
# Time Stamp Properties # Time Stamp Properties
date_added: Mapped[date | None] = mapped_column(sa.Date, default=get_utc_today) date_added: Mapped[date | None] = mapped_column(sa.Date, default=get_utc_today)
date_updated: Mapped[datetime | None] = mapped_column(NaiveDateTime) date_updated: Mapped[datetime | None] = mapped_column(NaiveDateTime)
last_made: Mapped[datetime | None] = mapped_column(NaiveDateTime) last_made: Mapped[datetime | None] = mapped_column(NaiveDateTime)
made_by: Mapped[list["Household"]] = orm.relationship(
"Household", secondary=HouseholdToRecipe.__tablename__, back_populates="made_recipes"
)
# Shopping List Refs # Shopping List Refs
shopping_list_refs: Mapped[list["ShoppingListRecipeReference"]] = orm.relationship( shopping_list_refs: Mapped[list["ShoppingListRecipeReference"]] = orm.relationship(

View file

@ -1,5 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pydantic import ConfigDict
from slugify import slugify from slugify import slugify
from sqlalchemy import Boolean, Column, ForeignKey, String, Table, UniqueConstraint, orm from sqlalchemy import Boolean, Column, ForeignKey, String, Table, UniqueConstraint, orm
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
@ -10,8 +11,17 @@ from mealie.db.models._model_utils.guid import GUID
if TYPE_CHECKING: if TYPE_CHECKING:
from ..group import Group from ..group import Group
from ..household import Household
from . import RecipeModel from . import RecipeModel
households_to_tools = Table(
"households_to_tools",
SqlAlchemyBase.metadata,
Column("household_id", GUID, ForeignKey("households.id"), index=True),
Column("tool_id", GUID, ForeignKey("tools.id"), index=True),
UniqueConstraint("household_id", "tool_id", name="household_id_tool_id_key"),
)
recipes_to_tools = Table( recipes_to_tools = Table(
"recipes_to_tools", "recipes_to_tools",
SqlAlchemyBase.metadata, SqlAlchemyBase.metadata,
@ -40,11 +50,36 @@ class Tool(SqlAlchemyBase, BaseMixins):
name: Mapped[str] = mapped_column(String, index=True, nullable=False) name: Mapped[str] = mapped_column(String, index=True, nullable=False)
slug: Mapped[str] = mapped_column(String, index=True, nullable=False) slug: Mapped[str] = mapped_column(String, index=True, nullable=False)
on_hand: Mapped[bool | None] = mapped_column(Boolean, default=False)
households_with_tool: Mapped[list["Household"]] = orm.relationship(
"Household", secondary=households_to_tools, back_populates="tools_on_hand"
)
recipes: Mapped[list["RecipeModel"]] = orm.relationship( recipes: Mapped[list["RecipeModel"]] = orm.relationship(
"RecipeModel", secondary=recipes_to_tools, back_populates="tools" "RecipeModel", secondary=recipes_to_tools, back_populates="tools"
) )
model_config = ConfigDict(
exclude={
"households_with_tool",
}
)
# Deprecated
on_hand: Mapped[bool | None] = mapped_column(Boolean, default=False)
@auto_init() @auto_init()
def __init__(self, name, **_) -> None: def __init__(
self, session: orm.Session, group_id: GUID, name: str, households_with_tool: list[str] | None = None, **_
) -> None:
from ..household import Household
self.slug = slugify(name) self.slug = slugify(name)
if not households_with_tool:
self.households_with_tool = []
else:
self.households_with_tool = (
session.query(Household)
.filter(Household.group_id == group_id, Household.slug.in_(households_with_tool))
.all()
)

View file

@ -4,18 +4,18 @@
}, },
"recipe": { "recipe": {
"unique-name-error": "يجب أن تكون أسماء الوصفات فريدة", "unique-name-error": "يجب أن تكون أسماء الوصفات فريدة",
"recipe-created": "Recipe Created", "recipe-created": "تم إنشاء الوصفة",
"recipe-defaults": { "recipe-defaults": {
"ingredient-note": "1 Cup Flour", "ingredient-note": "كوب 1 طحين",
"step-text": "Recipe steps as well as other fields in the recipe page support markdown syntax.\n\n**Add a link**\n\n[My Link](https://demo.mealie.io)\n" "step-text": "تدعم خطوات الوصفة بالإضافة إلى الحقول الأخرى في صفحة الوصفة صياغة تخفيض السعر. **أضف رابطًا** [الرابط الخاص بي](https://demo.mealie.io)\n"
}, },
"servings-text": { "servings-text": {
"makes": "Makes", "makes": "تصنع",
"serves": "Serves", "serves": "يكفي ل",
"serving": "Serving", "serving": "حصة الطعام",
"servings": "Servings", "servings": "حصص الطعام",
"yield": "Yield", "yield": "العائد",
"yields": "Yields" "yields": "العائد"
} }
}, },
"mealplan": { "mealplan": {
@ -28,53 +28,53 @@
"ldap-update-password-unavailable": "غير قادر على تحديث كلمة المرور، المستخدم يتم التحكم به بواسطة LDAP" "ldap-update-password-unavailable": "غير قادر على تحديث كلمة المرور، المستخدم يتم التحكم به بواسطة LDAP"
}, },
"group": { "group": {
"report-deleted": "تم حذف التقرير" "report-deleted": "تم حذف التقرير."
}, },
"exceptions": { "exceptions": {
"permission_denied": "لا يوجد لديك صلاحيات كافية لتفيذ هذا الإجراء", "permission_denied": "لا يوجد لديك صلاحيات كافية لتنفيذ هذا الإجراء",
"no-entry-found": "لم يتم العثور على الصفحة المطلوبة", "no-entry-found": "لم يتم العثور على الصفحة المطلوبة",
"integrity-error": "خطأ في سلامة قاعدة البيانات", "integrity-error": "خطأ في سلامة قاعدة البيانات",
"username-conflict-error": "اسم المستخدم هذا مستخدم بالفعل", "username-conflict-error": "اسم المستخدم هذا مستخدم مسبقاً",
"email-conflict-error": "هذا البريد مستخدم مسبقاً" "email-conflict-error": "هذا البريد مستخدم مسبقاً"
}, },
"notifications": { "notifications": {
"generic-created": "تم إنشاء {name}", "generic-created": "تم إنشاء {name}",
"generic-updated": "تم تحديث {name}", "generic-updated": "تم تحديث {name}",
"generic-created-with-url": "تم إنشاء {name} ، {url}", "generic-created-with-url": "تم إنشاء {name}، {url}",
"generic-updated-with-url": "{name} تم تحديثه، {url}", "generic-updated-with-url": "{name} تم تحديثه، {url}",
"generic-duplicated": "تم تكرار {name}", "generic-duplicated": "تم تكرار {name}",
"generic-deleted": "تم حذف {name}" "generic-deleted": "تم حذف {name}"
}, },
"datetime": { "datetime": {
"year": "year|years", "year": "سنة|سنوات",
"day": "day|days", "day": "يوم|أيام",
"hour": "hour|hours", "hour": "ساعة|ساعات",
"minute": "minute|minutes", "minute": "دقيقة|دقائق",
"second": "second|seconds", "second": "ثانية|ثواني",
"millisecond": "millisecond|milliseconds", "millisecond": "مللي ثانية|مللي ثانية",
"microsecond": "microsecond|microseconds" "microsecond": "مكرو ثانية|مكرو ثانية"
}, },
"emails": { "emails": {
"password": { "password": {
"subject": "Mealie Forgot Password", "subject": "نسيت كلمة المرور",
"header_text": "Forgot Password", "header_text": "نسيت كلمة المرور",
"message_top": "You have requested to reset your password.", "message_top": "لقد طلبت إعادة تعيين كلمة المرور الخاصة بك.",
"message_bottom": "Please click the button above to reset your password.", "message_bottom": "الرجاء النقر على الزر أعلاه لإعادة تعيين كلمة المرور الخاصة بك.",
"button_text": "Reset Password" "button_text": "إعادة ضبط كلمة المرور"
}, },
"invitation": { "invitation": {
"subject": "Invitation to join Mealie", "subject": "دعوة للانضمام إلى ميلي",
"header_text": "You're Invited!", "header_text": "أنت مدعو!",
"message_top": "You have been invited to join Mealie.", "message_top": "لقد تمت دعوتك للانضمام إلى ميلي.",
"message_bottom": "Please click the button above to accept the invitation.", "message_bottom": "الرجاء النقر على الزر أعلاه لقبول الدعوة.",
"button_text": "Accept Invitation" "button_text": "قَبُول الدعوة"
}, },
"test": { "test": {
"subject": "Mealie Test Email", "subject": "بريد إلكتروني لاختبار ميلي",
"header_text": "Test Email", "header_text": "بريد إلكتروني تجريبي",
"message_top": "This is a test email.", "message_top": "هذا بريد إلكتروني اختباري.",
"message_bottom": "Please click the button above to test the email.", "message_bottom": "الرجاء النقر على الزر أعلاه لاختبار البريد الإلكتروني.",
"button_text": "Open Mealie" "button_text": "فتح ميلي"
} }
} }
} }

View file

@ -4,7 +4,7 @@
}, },
"recipe": { "recipe": {
"unique-name-error": "Názvy receptů musí být jedinečné", "unique-name-error": "Názvy receptů musí být jedinečné",
"recipe-created": "Recipe Created", "recipe-created": "Recept byl vytvořen",
"recipe-defaults": { "recipe-defaults": {
"ingredient-note": "1 hrnek mouky", "ingredient-note": "1 hrnek mouky",
"step-text": "Kroky receptu stejně jako další pole v receptu podporují markdown syntaxi.\n\n**Přidat odkaz**\n\n[Můj odkaz](https://demo.mealie.io)\n" "step-text": "Kroky receptu stejně jako další pole v receptu podporují markdown syntaxi.\n\n**Přidat odkaz**\n\n[Můj odkaz](https://demo.mealie.io)\n"
@ -12,8 +12,8 @@
"servings-text": { "servings-text": {
"makes": "Makes", "makes": "Makes",
"serves": "Porce", "serves": "Porce",
"serving": "Serving", "serving": "Porce",
"servings": "Servings", "servings": "Porcí",
"yield": "Yield", "yield": "Yield",
"yields": "Yields" "yields": "Yields"
} }

View file

@ -4,7 +4,7 @@
}, },
"recipe": { "recipe": {
"unique-name-error": "Opskriftsnavnet er allerede i brug", "unique-name-error": "Opskriftsnavnet er allerede i brug",
"recipe-created": "Recipe Created", "recipe-created": "Opskrift oprettet",
"recipe-defaults": { "recipe-defaults": {
"ingredient-note": "1 kop mel", "ingredient-note": "1 kop mel",
"step-text": "Du kan bruge markdown kode i beskrivelser og andre felter i opskrifter.\n\n**Tilføj et link**\n\n[Mit link](https://demo.mealie.io)\n" "step-text": "Du kan bruge markdown kode i beskrivelser og andre felter i opskrifter.\n\n**Tilføj et link**\n\n[Mit link](https://demo.mealie.io)\n"
@ -12,8 +12,8 @@
"servings-text": { "servings-text": {
"makes": "Makes", "makes": "Makes",
"serves": "Serves", "serves": "Serves",
"serving": "Serving", "serving": "Servering",
"servings": "Servings", "servings": "Portioner",
"yield": "Yield", "yield": "Yield",
"yields": "Yields" "yields": "Yields"
} }

View file

@ -0,0 +1,80 @@
{
"generic": {
"server-error": "Ilmnes ootamatu tõrge "
},
"recipe": {
"unique-name-error": "Retseptide nimed peavad olema unikaalsed",
"recipe-created": "Retsept loodud",
"recipe-defaults": {
"ingredient-note": "1 tass jahu",
"step-text": "Retsepti sammud nagu ka teised väljad retsepti lehed toetavad Markdown-i süntaksit\n\n** Lisa link **\n[Minu link](https://demo.mealie.io)"
},
"servings-text": {
"makes": "Teeb",
"serves": "Jätkub",
"serving": "Protsion",
"servings": "Portsioneid",
"yield": "Saagikus",
"yields": "Annab saagiks"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Ükski retsept ei vasta sinu reeglitele"
},
"user": {
"user-updated": "Kasutaja uuendatud",
"password-updated": "Salasõna uuendatud",
"invalid-current-password": "Mittesobiv praegune salasõna",
"ldap-update-password-unavailable": "Ei saanud uuendada salasõna, kasutajat kontrollib LDAP"
},
"group": {
"report-deleted": "Raport kustutatud."
},
"exceptions": {
"permission_denied": "Sul puuduvad selle toimingu tegemiseks õigused",
"no-entry-found": "Soovitud ressurssi ei leitud",
"integrity-error": "Tõrge andmebaasi terviklikkuses",
"username-conflict-error": "See kasutajanimi on juba kasutusel",
"email-conflict-error": "See e-maili aadress on juba kasutuses"
},
"notifications": {
"generic-created": "{name} loodud",
"generic-updated": "{name} uuendatud",
"generic-created-with-url": "{name} loodud, {url}",
"generic-updated-with-url": "{name} uuendatud, {url}",
"generic-duplicated": "{name} on duplitseeritud",
"generic-deleted": "{name} kustutatud"
},
"datetime": {
"year": "aasta|aastad",
"day": "päev|päevad",
"hour": "tund|tunnid",
"minute": "minut|minutid",
"second": "sekund|sekundid",
"millisecond": "millisekund|millisekundid",
"microsecond": "mikrosekund|mikrosekundid"
},
"emails": {
"password": {
"subject": "Mealie salasõna unustatud",
"header_text": "Unustasid salasõna",
"message_top": "Oled palunud salasõna lähtestamist",
"message_bottom": "Kliki alloleval nupul, et lähtestada oma salasõna",
"button_text": "Lähtesta salasõna"
},
"invitation": {
"subject": "Kutse Mealiega liitumiseks",
"header_text": "Said kutse!",
"message_top": "Sind on kutsutud liituma Mealiega.",
"message_bottom": "Palun vajuta allolevat nuppu, et võtta vastu kutse.",
"button_text": "Võta kutse vastu"
},
"test": {
"subject": "Mealie test-email",
"header_text": "Test-email",
"message_top": "See on test-email",
"message_bottom": "Palun vajuta allolevat nuppu, et testida emaili.",
"button_text": "Ava Mealie"
}
}
}

View file

@ -10,11 +10,11 @@
"step-text": "A recept lépései és a receptoldal egyéb mezői támogatják a markdown szintaxist.\n\n**Hivatkozás hozzáadása**\n\n[Saját link](https://demo.mealie.io)\n" "step-text": "A recept lépései és a receptoldal egyéb mezői támogatják a markdown szintaxist.\n\n**Hivatkozás hozzáadása**\n\n[Saját link](https://demo.mealie.io)\n"
}, },
"servings-text": { "servings-text": {
"makes": "Makes", "makes": "Elkészítések",
"serves": "Serves", "serves": "Adag",
"serving": "Adag", "serving": "Adag",
"servings": "Adag", "servings": "Adag",
"yield": "Yield", "yield": "Adag",
"yields": "Yields" "yields": "Yields"
} }
}, },

View file

@ -4,16 +4,16 @@
}, },
"recipe": { "recipe": {
"unique-name-error": "Oppskriftsnavn må være unike", "unique-name-error": "Oppskriftsnavn må være unike",
"recipe-created": "Recipe Created", "recipe-created": "Oppskrift opprettet",
"recipe-defaults": { "recipe-defaults": {
"ingredient-note": "1 kopp mel", "ingredient-note": "1 kopp mel",
"step-text": "Steg i oppskrifter og andre felter på siden støtter markdown syntax.\n\n**Legg til en link**\n\n[Min link](https://demo.mealie.io)\n" "step-text": "Steg i oppskrifter og andre felter på siden støtter markdown syntax.\n\n**Legg til en link**\n\n[Min link](https://demo.mealie.io)\n"
}, },
"servings-text": { "servings-text": {
"makes": "Makes", "makes": "Gir",
"serves": "Serves", "serves": "Nok til",
"serving": "Serving", "serving": "porsjon",
"servings": "Servings", "servings": "porsjoner",
"yield": "Yield", "yield": "Yield",
"yields": "Yields" "yields": "Yields"
} }

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