Compare commits

..

57 Commits

Author SHA1 Message Date
Michael Genson
418a8ec72b fix: Recipe Search Quirks and Session Storage (#3541)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-05-06 15:01:56 +00:00
renovate[bot]
770630bf73 fix(deps): update dependency sqlalchemy to v2.0.30 (#3568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-06 19:40:32 +10:00
renovate[bot]
89ee7475a6 fix(deps): update dependency jinja2 to v3.1.4 (#3570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-06 01:46:21 +00:00
Hayden
bca5dd8282 New Crowdin updates (#3569) 2024-05-05 20:35:46 -05:00
Michael Genson
dabd93c919 chore(deps): update dependency ruff to v0.4.3 (#3564) 2024-05-05 11:26:14 +00:00
Michael Genson
6991dff3e6 fix: Make Nextcloud Migrations Fault Tolerant (#3544) 2024-05-05 11:17:29 +00:00
Hayden
b0eece789d New Crowdin updates (#3565) 2024-05-04 21:44:22 -05:00
Arsène Reymond
9fad4a9dce fix: Shopping list labels reordering dialog (#3540)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-05-04 20:27:04 +00:00
renovate[bot]
22d8c4d5dc fix(deps): update dependency bcrypt to v4.1.3 (#3560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-04 16:19:05 +00:00
renovate[bot]
7be24d3479 chore(deps): update dependency coverage to v7.5.1 (#3563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-04 11:09:12 -05:00
renovate[bot]
fbceb61b9a chore(deps): update dependency mkdocs-material to v9.5.21 (#3555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-03 19:51:57 +00:00
renovate[bot]
1be5bfaef1 fix(deps): update dependency orjson to v3.10.3 (#3553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-03 11:05:52 -05:00
Carter
fac1df31d3 Make OIDC groups claim configurable and optional (#3552) 2024-05-02 22:55:47 -05:00
renovate[bot]
6957e2fa74 fix(deps): update dependency rapidfuzz to v3.9.0 (#3550)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-03 01:25:43 +00:00
renovate[bot]
4f02fae284 fix(deps): update dependency fastapi to ^0.111.0 (#3549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-02 20:15:56 -05:00
Hayden
f2615c97e9 New Crowdin updates (#3548) 2024-05-02 20:06:21 -05:00
Hayden
6b4c9a400d New Crowdin updates (#3542) 2024-05-02 08:33:54 +02:00
Michael Genson
cca11b5a12 chore(deps): update dependency ruff to v0.4.2 (#3533)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-05-01 16:07:10 +00:00
Michael Genson
f697a7ee34 docs: formatting (#3539) 2024-05-01 07:58:55 -08:00
Kuchenpirat
0d73338e12 cleanup: parser localization (#3538) 2024-05-01 09:06:43 -05:00
renovate[bot]
2f4c6bd500 fix(deps): update dependency orjson to v3.10.2 (#3535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-01 19:32:30 +10:00
Michael Genson
3807778e2f feat: Recipe Actions (#3448)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-05-01 09:20:52 +02:00
Hayden
ee87a14401 New Crowdin updates (#3534) 2024-05-01 00:58:59 +00:00
Michael Genson
ec458a0a08 fix: Security Issues (#3530)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-04-30 20:53:55 +00:00
renovate[bot]
2ff37c86d6 chore(deps): update dependency pytest to v8.2.0 (#3522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 14:58:32 +00:00
renovate[bot]
b7da3c0f73 fix(deps): update dependency tzdata to v2024 (#3527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-04-30 14:54:31 +00:00
renovate[bot]
d799136f0d fix(deps): update dependency fastapi to v0.110.3 (#3532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 14:46:52 +00:00
renovate[bot]
d1d5754c6d chore(deps): update dependency mkdocs-material to v9.5.20 (#3517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 14:34:55 +00:00
renovate[bot]
52662fdce2 chore(deps): update dependency mypy to v1.10.0 (#3516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 09:25:46 -05:00
Hayden
8df6033c19 New Crowdin updates (#3531) 2024-04-30 08:45:00 +02:00
Hayden
c23660007e chore: bump user agent (#3457) 2024-04-29 12:18:00 -05:00
Michael Genson
786aa2279c chore: Replace python-jose with PyJWT (#3521)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-04-29 09:49:13 +00:00
Hayden
ab8c3be367 New Crowdin updates (#3523) 2024-04-27 00:19:50 -05:00
Hayden
8bf8dfd3ed New Crowdin updates (#3520) 2024-04-26 09:51:33 +02:00
renovate[bot]
b3aa7aeb1a fix(deps): update dependency recipe-scrapers to v14.56.0 (#3518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-25 20:15:33 +02:00
Hayden
0f2b1d8d3a New Crowdin updates (#3515) 2024-04-24 16:18:23 +10:00
renovate[bot]
4de6391684 chore(deps): update dependency coverage to v7.5.0 (#3514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 18:21:41 +00:00
renovate[bot]
c3e68b7d8a fix(deps): update dependency pydantic to v2.7.1 (#3513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 13:11:19 -05:00
Hayden
7557d2e818 New Crowdin updates (#3510) 2024-04-23 09:04:28 +02:00
renovate[bot]
c22a2fc4a8 fix(deps): update dependency fastapi to v0.110.2 (#3497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-22 12:05:43 +00:00
Hayden
ad94a4f42f New Crowdin updates (#3507) 2024-04-22 08:22:55 +02:00
Hayden
e6bf3b3acd New Crowdin updates (#3501) 2024-04-19 19:31:21 +02:00
Michael Genson
711dd93851 fix: Ratings UI and Filter Issues (#3459)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-04-19 17:52:41 +02:00
Carter
2b6d7811ca OIDC - Specifically check for 401 status before resetting (#3500) 2024-04-19 14:51:04 +00:00
renovate[bot]
3373abf787 chore(deps): update dependency ruff to v0.4.1 (#3498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 09:20:26 -05:00
Michael Genson
741d37f59e feat: Group Shopping List Items By Food (#3471)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-04-19 11:00:40 +00:00
Michael Genson
b38c19ce71 fix: Missing Translations (#3494) 2024-04-19 10:42:50 +00:00
Carter
1a385e941c Add new OIDC TLS CA Certfile option (#3496) 2024-04-19 20:36:03 +10:00
Carter
c6f5b62ad0 Fix OIDC infinite loop if user is not in OIDC_USER_GROUP (#3487) 2024-04-19 00:17:45 +00:00
renovate[bot]
84dad84326 chore(deps): update dependency ruff to ^0.4.0 (#3495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-18 18:42:07 -05:00
Hayden
f369c8fd6e New Crowdin updates (#3493) 2024-04-18 12:19:22 -05:00
github-actions[bot]
467cf46c6d docs(auto): Update image tag, for release v1.5.1 (#3482)
Co-authored-by: hay-kot <64056131+hay-kot@users.noreply.github.com>
2024-04-18 06:28:13 +00:00
Kuchenpirat
360b8e21d9 fix: MultiPurposeLabel text color (#3485) 2024-04-17 12:24:54 -05:00
Hayden
0b851e79ec New Crowdin updates (#3484) 2024-04-17 16:44:40 +00:00
renovate[bot]
faf716cb7e fix(deps): update dependency gunicorn to v22 (#3479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-16 23:07:01 -05:00
Michael Genson
46f1ad7941 fix: Bad Recipe Rating Calc Preventing App Startup (#3475) 2024-04-16 20:47:15 +00:00
p0lycarpio
6e1112c73e fix: make groups private by default (#3474) 2024-04-16 15:12:00 -05:00
153 changed files with 3428 additions and 1013 deletions

View File

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

View File

@@ -0,0 +1,52 @@
"""add group recipe actions
Revision ID: 7788478a0338
Revises: d7c6efd2de42
Create Date: 2024-04-07 01:05:20.816270
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "7788478a0338"
down_revision = "d7c6efd2de42"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"recipe_actions",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("action_type", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("url", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_recipe_actions_action_type"), "recipe_actions", ["action_type"], unique=False)
op.create_index(op.f("ix_recipe_actions_created_at"), "recipe_actions", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_actions_group_id"), "recipe_actions", ["group_id"], unique=False)
op.create_index(op.f("ix_recipe_actions_title"), "recipe_actions", ["title"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_recipe_actions_title"), table_name="recipe_actions")
op.drop_index(op.f("ix_recipe_actions_group_id"), table_name="recipe_actions")
op.drop_index(op.f("ix_recipe_actions_created_at"), table_name="recipe_actions")
op.drop_index(op.f("ix_recipe_actions_action_type"), table_name="recipe_actions")
op.drop_table("recipe_actions")
# ### end Alembic commands ###

View File

@@ -35,18 +35,24 @@ LOCALE_DATA: dict[str, LocaleData] = {
"es-ES": LocaleData(name="Español (Spanish)"),
"fi-FI": LocaleData(name="Suomi (Finnish)"),
"fr-FR": LocaleData(name="Français (French)"),
"gl-ES": LocaleData(name="Galego (Galician)"),
"he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
"hr-HR": LocaleData(name="Hrvatski (Croatian)"),
"hu-HU": LocaleData(name="Magyar (Hungarian)"),
"is-IS": LocaleData(name="Íslenska (Icelandic)"),
"it-IT": LocaleData(name="Italiano (Italian)"),
"ja-JP": LocaleData(name="日本語 (Japanese)"),
"ko-KR": LocaleData(name="한국어 (Korean)"),
"no-NO": LocaleData(name="Norsk (Norwegian)"),
"lt-LT": LocaleData(name="Lietuvių (Lithuanian)"),
"lv-LV": LocaleData(name="Latviešu (Latvian)"),
"nl-NL": LocaleData(name="Nederlands (Dutch)"),
"no-NO": LocaleData(name="Norsk (Norwegian)"),
"pl-PL": LocaleData(name="Polski (Polish)"),
"pt-BR": LocaleData(name="Português do Brasil (Brazilian Portuguese)"),
"pt-PT": LocaleData(name="Português (Portuguese)"),
"ro-RO": LocaleData(name="Română (Romanian)"),
"ru-RU": LocaleData(name="Pусский (Russian)"),
"sl-SI": LocaleData(name="Slovenščina (Slovenian)"),
"sr-SP": LocaleData(name="српски (Serbian)"),
"sv-SE": LocaleData(name="Svenska (Swedish)"),
"tr-TR": LocaleData(name="Türkçe (Turkish)"),

View File

@@ -20,7 +20,7 @@ Before you can start using OIDC Authentication, you must first configure a new c
1. Create a new client application
- The Provider type should be OIDC or OAuth2
- The Grant type should be `Authorization Code`
- The Application type should be `Web`
- The Application type should be `Web` or `SPA`
- The Client type should be `public`
2. Configure redirect URI
@@ -42,7 +42,9 @@ Before you can start using OIDC Authentication, you must first configure a new c
4. Configure allowed scopes
The scopes required are `openid profile email groups`
The scopes required are `openid profile email`
If you plan to use the [groups](#groups) to configure access within Mealie, you will need to also add the scope defined by the `OIDC_GROUPS_CLAIM` environment variable. The default claim is `groups`
## Mealie Setup
@@ -50,7 +52,7 @@ Take the client id and your discovery URL and update your environment variables
### Groups
There are two (optional) [environment variables](../installation/backend-config.md#openid-connect-oidc) that can control which of the users in your IdP can log in to Mealie and what permissions they will have. The groups should be **defined in your IdP** and be returned in the `groups` claim.
There are two (optional) [environment variables](../installation/backend-config.md#openid-connect-oidc) that can control which of the users in your IdP can log in to Mealie and what permissions they will have. Keep in mind that these groups **do not necessarily correspond to groups in Mealie**. The groups claim is configurable via the `OIDC_GROUPS_CLAIM` environment variable. The groups should be **defined in your IdP** and be returned in the configured claim value.
`OIDC_USER_GROUP`: Users must be a part of this group (within your IdP) to be able to log in.

View File

@@ -81,12 +81,63 @@ The meal planner has the concept of plan rules. These offer a flexible way to us
The shopping lists feature is a great way to keep track of what you need to buy for your next meal. You can add items directly to the shopping list or link a recipe and all of it's ingredients to track meals during the week.
!!! warning
At this time there isn't a tight integration between meal-plans and shopping lists; however, it's something we have planned for the future.
[Shopping List Demo](https://demo.mealie.io/shopping-lists){ .md-button .md-button--primary }
## Integrations
Mealie is designed to integrate with many different external services. There are several ways you can integrate with Mealie to achieve custom IoT automations, data synchronization, and anything else you can think of. [You can work directly with Mealie through the API](./api-usage.md), or leverage other services to make seamless integrations.
### Notifiers
Notifiers are event-driven notifications sent when specific actions are performed within Mealie. Some actions include:
- creating a recipe
- adding items to a shopping list
- creating a new mealplan
Notifiers use the [Apprise library](https://github.com/caronc/apprise/wiki), which integrates with a large number of notification services. In addition, certain custom notifiers send basic event data to the consumer (e.g. the `id` of the resource). These include:
- `form` and `forms`
- `json` and `jsons`
- `xml` and `xmls`
[Notifiers Demo](https://demo.mealie.io/group/notifiers){ .md-button .md-button--primary }
### Webhooks
Unlike notifiers, which are event-driven notifications, Webhooks allow you to send scheduled notifications to your desired endpoint. Webhooks are sent on the day of a scheduled mealplan, at the specified time, and contain the mealplan data in the request.
[Webhooks Demo](https://demo.mealie.io/group/webhooks){ .md-button .md-button--primary }
### Recipe Actions
Recipe Actions are custom actions you can add to all recipes in Mealie. This is a great way to add custom integrations that are fired manually. There are two types of recipe actions:
1. link - these actions will take you directly to an external page
2. post - these actions will send a `POST` request to the specified URL, with the recipe JSON in the request body. These can be used, for instance, to manually trigger a webhook in Home Assistant
Recipe Action URLs can include merge fields to inject the current recipe's data. For instance, you can use the following URL to include a Google search with the recipe's slug:
```
https://www.google.com/search?q=${slug}
```
When the action is clicked on, the `${slug}` field is replaced with the recipe's slug value. So, for example, it might take you to this URL on one of your recipes:
```
https://www.google.com/search?q=pasta-fagioli
```
A common use case for "link" recipe actions is to integrate with the Bring! shopping list. Simply add a Recipe Action with the following URL:
```
https://api.getbring.com/rest/bringrecipes/deeplink?url=${url}&source=web
```
Below is a list of all valid merge fields:
- ${id}
- ${slug}
- ${url}
To add, modify, or delete Recipe Actions, visit the Data Management page (more on that below).
## Data Management

View File

@@ -98,7 +98,9 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
| OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with <OIDC_PROVIDER_NAME\>" |
| OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked |
| OIDC_SIGNING_ALGORITHM | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| OIDC_USER_CLAIM | email | Optional: 'email', 'preferred_username' |
| OIDC_USER_CLAIM | email | This is the claim which Mealie will use to look up an existing user by (e.g. "email", "preferred_username") |
| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim**|
| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
### Themeing

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -70,6 +70,7 @@
print: true,
printPreferences: true,
share: loggedIn,
recipeActions: true,
}"
@print="$emit('print')"
/>

View File

@@ -105,6 +105,26 @@
</v-list-item-icon>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
<div v-if="useItems.recipeActions && recipeActions && recipeActions.length">
<v-divider />
<v-list-group @click.stop>
<template #activator>
<v-list-item-title>{{ $tc("recipe.recipe-actions") }}</v-list-item-title>
</template>
<v-list dense class="ma-0 pa-0">
<v-list-item
v-for="(action, index) in recipeActions"
:key="index"
class="pl-6"
@click="executeRecipeAction(action)"
>
<v-list-item-title>
{{ action.title }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-list-group>
</div>
</v-list>
</v-menu>
</div>
@@ -117,11 +137,12 @@ import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
import RecipeDialogShare from "./RecipeDialogShare.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useUserApi } from "~/composables/api";
import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions";
import { useGroupSelf } from "~/composables/use-groups";
import { alert } from "~/composables/use-toast";
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
import { Recipe } from "~/lib/api/types/recipe";
import { ShoppingListSummary } from "~/lib/api/types/group";
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/group";
import { PlanEntryType } from "~/lib/api/types/meal-plan";
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
@@ -134,6 +155,7 @@ export interface ContextMenuIncludes {
print: boolean;
printPreferences: boolean;
share: boolean;
recipeActions: boolean;
}
export interface ContextMenuItem {
@@ -163,6 +185,7 @@ export default defineComponent({
print: true,
printPreferences: true,
share: true,
recipeActions: true,
}),
},
// Append items are added at the end of the useItems list
@@ -347,6 +370,19 @@ export default defineComponent({
}
const router = useRouter();
const groupRecipeActionsStore = useGroupRecipeActions();
async function executeRecipeAction(action: GroupRecipeActionOut) {
const response = await groupRecipeActionsStore.execute(action, props.recipe);
if (action.actionType === "post") {
if (!response || (response.status >= 200 && response.status < 300)) {
alert.success(i18n.tc("events.message-sent"));
} else {
alert.error(i18n.tc("events.something-went-wrong"));
}
}
}
async function deleteRecipe() {
await api.recipes.deleteOne(props.slug);
@@ -437,6 +473,8 @@ export default defineComponent({
...toRefs(state),
recipeRef,
recipeRefWithScale,
executeRecipeAction,
recipeActions: groupRecipeActionsStore.recipeActions,
shoppingLists,
duplicateRecipe,
contextMenuEventHandler,

View File

@@ -143,7 +143,7 @@ import { watchDebounced } from "@vueuse/shared";
import SearchFilter from "~/components/Domain/SearchFilter.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
import { useUserSortPreferences } from "~/composables/use-users/preferences";
import { useUserSearchQuerySession } from "~/composables/use-users/preferences";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
import { NoUndefinedField } from "~/lib/api/types/non-generated";
@@ -177,7 +177,7 @@ export default defineComponent({
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const preferences = useUserSortPreferences();
const searchQuerySession = useUserSearchQuerySession();
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
@@ -194,7 +194,9 @@ export default defineComponent({
function calcPassedQuery(): RecipeSearchQuery {
return {
search: state.value.search,
// the search clear button sets search to null, which still renders the query param for a moment,
// whereas an empty string is not rendered
search: state.value.search ? state.value.search : "",
categories: toIDArray(selectedCategories.value),
foods: toIDArray(selectedFoods.value),
tags: toIDArray(selectedTags.value),
@@ -217,14 +219,24 @@ export default defineComponent({
};
})
const queryDefaults = {
search: "",
orderBy: "created_at",
orderDirection: "desc" as "asc" | "desc",
requireAllCategories: false,
requireAllTags: false,
requireAllTools: false,
requireAllFoods: false,
}
function reset() {
state.value.search = "";
state.value.orderBy = "created_at";
state.value.orderDirection = "desc";
state.value.requireAllCategories = false;
state.value.requireAllTags = false;
state.value.requireAllTools = false;
state.value.requireAllFoods = false;
state.value.search = queryDefaults.search;
state.value.orderBy = queryDefaults.orderBy;
state.value.orderDirection = queryDefaults.orderDirection;
state.value.requireAllCategories = queryDefaults.requireAllCategories;
state.value.requireAllTags = queryDefaults.requireAllTags;
state.value.requireAllTools = queryDefaults.requireAllTools;
state.value.requireAllFoods = queryDefaults.requireAllFoods;
selectedCategories.value = [];
selectedFoods.value = [];
selectedTags.value = [];
@@ -262,12 +274,12 @@ export default defineComponent({
foods: passedQuery.value.foods,
tags: passedQuery.value.tags,
tools: passedQuery.value.tools,
// Only add the query param if it's or not default
// Only add the query param if it's not the default value
...{
auto: state.value.auto ? undefined : "false",
search: passedQuery.value.search === "" ? undefined : passedQuery.value.search,
orderBy: passedQuery.value.orderBy === "created_at" ? undefined : passedQuery.value.orderBy,
orderDirection: passedQuery.value.orderDirection === "desc" ? undefined : passedQuery.value.orderDirection,
search: passedQuery.value.search === queryDefaults.search ? undefined : passedQuery.value.search,
orderBy: passedQuery.value.orderBy === queryDefaults.orderBy ? undefined : passedQuery.value.orderBy,
orderDirection: passedQuery.value.orderDirection === queryDefaults.orderDirection ? undefined : passedQuery.value.orderDirection,
requireAllCategories: passedQuery.value.requireAllCategories ? "true" : undefined,
requireAllTags: passedQuery.value.requireAllTags ? "true" : undefined,
requireAllTools: passedQuery.value.requireAllTools ? "true" : undefined,
@@ -275,7 +287,7 @@ export default defineComponent({
},
}
await router.push({ query });
preferences.value.searchQuery = JSON.stringify(query);
searchQuerySession.value.recipe = JSON.stringify(query);
}
function waitUntilAndExecute(
@@ -360,25 +372,55 @@ export default defineComponent({
async function hydrateSearch() {
const query = router.currentRoute.query;
if (query.auto) {
if (query.auto?.length) {
state.value.auto = query.auto === "true";
}
if (query.search) {
if (query.search?.length) {
state.value.search = query.search as string;
} else {
state.value.search = queryDefaults.search;
}
if (query.orderBy) {
if (query.orderBy?.length) {
state.value.orderBy = query.orderBy as string;
} else {
state.value.orderBy = queryDefaults.orderBy;
}
if (query.orderDirection) {
if (query.orderDirection?.length) {
state.value.orderDirection = query.orderDirection as "asc" | "desc";
} else {
state.value.orderDirection = queryDefaults.orderDirection;
}
if (query.requireAllCategories?.length) {
state.value.requireAllCategories = query.requireAllCategories === "true";
} else {
state.value.requireAllCategories = queryDefaults.requireAllCategories;
}
if (query.requireAllTags?.length) {
state.value.requireAllTags = query.requireAllTags === "true";
} else {
state.value.requireAllTags = queryDefaults.requireAllTags;
}
if (query.requireAllTools?.length) {
state.value.requireAllTools = query.requireAllTools === "true";
} else {
state.value.requireAllTools = queryDefaults.requireAllTools;
}
if (query.requireAllFoods?.length) {
state.value.requireAllFoods = query.requireAllFoods === "true";
} else {
state.value.requireAllFoods = queryDefaults.requireAllFoods;
}
const promises: Promise<void>[] = [];
if (query.categories) {
if (query.categories?.length) {
promises.push(
waitUntilAndExecute(
() => categories.items.value.length > 0,
@@ -395,7 +437,35 @@ export default defineComponent({
selectedCategories.value = [];
}
if (query.foods) {
if (query.tags?.length) {
promises.push(
waitUntilAndExecute(
() => tags.items.value.length > 0,
() => {
const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string));
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
}
)
);
} else {
selectedTags.value = [];
}
if (query.tools?.length) {
promises.push(
waitUntilAndExecute(
() => tools.items.value.length > 0,
() => {
const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id));
selectedTools.value = result as NoUndefinedField<RecipeTool>[];
}
)
);
} else {
selectedTools.value = [];
}
if (query.foods?.length) {
promises.push(
waitUntilAndExecute(
() => {
@@ -414,45 +484,17 @@ export default defineComponent({
selectedFoods.value = [];
}
if (query.tags) {
promises.push(
waitUntilAndExecute(
() => tags.items.value.length > 0,
() => {
const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string));
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
}
)
);
} else {
selectedTags.value = [];
}
if (query.tools) {
promises.push(
waitUntilAndExecute(
() => tools.items.value.length > 0,
() => {
const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id));
selectedTools.value = result as NoUndefinedField<RecipeTool>[];
}
)
);
} else {
selectedTools.value = [];
}
await Promise.allSettled(promises);
};
onMounted(async () => {
// restore the user's last search query
if (preferences.value.searchQuery && !(Object.keys(route.value.query).length > 0)) {
if (searchQuerySession.value.recipe && !(Object.keys(route.value.query).length > 0)) {
try {
const query = JSON.parse(preferences.value.searchQuery);
const query = JSON.parse(searchQuerySession.value.recipe);
await router.replace({ query });
} catch (error) {
preferences.value.searchQuery = "";
searchQuerySession.value.recipe = "";
router.replace({ query: {} });
}
}

View File

@@ -1,12 +1,12 @@
<template>
<div @click.prevent>
<!-- User Rating -->
<v-hover v-slot="{ hover }">
<v-rating
:value="rating.ratingValue"
:half-increments="(!hover) || (!isOwnGroup)"
:readonly="!isOwnGroup"
:color="hover ? attrs.hoverColor : attrs.color"
:background-color="attrs.backgroundColor"
v-if="isOwnGroup && (userRating || hover || !ratingsLoaded)"
:value="userRating"
color="secondary"
background-color="secondary lighten-3"
length="5"
:dense="small ? true : undefined"
:size="small ? 15 : undefined"
@@ -15,12 +15,25 @@
@input="updateRating"
@click="updateRating"
/>
<!-- Group Rating -->
<v-rating
v-else
:value="groupRating"
:half-increments="true"
:readonly="true"
color="grey darken-1"
background-color="secondary lighten-3"
length="5"
:dense="small ? true : undefined"
:size="small ? 15 : undefined"
hover
/>
</v-hover>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, useContext, watch } from "@nuxtjs/composition-api";
import { computed, defineComponent, ref, watch } from "@nuxtjs/composition-api";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useUserSelfRatings } from "~/composables/use-users";
export default defineComponent({
@@ -45,61 +58,29 @@ export default defineComponent({
type: Boolean,
default: false,
},
preferGroupRating: {
type: Boolean,
default: false,
},
},
setup(props, context) {
const { $auth } = useContext();
const { isOwnGroup } = useLoggedInState();
const { userRatings, setRating, ready: ratingsLoaded } = useUserSelfRatings();
const hideGroupRating = ref(false);
type Rating = {
ratingValue: number | undefined;
hasUserRating: boolean | undefined
};
// prefer user rating over group rating
const rating = computed<Rating>(() => {
if (!ratingsLoaded.value) {
return { ratingValue: undefined, hasUserRating: undefined };
}
if (!($auth.user?.id) || props.preferGroupRating) {
return { ratingValue: props.value, hasUserRating: false };
}
const userRating = userRatings.value.find((r) => r.recipeId === props.recipeId);
return {
ratingValue: userRating?.rating || (hideGroupRating.value ? 0 : props.value),
hasUserRating: !!userRating?.rating
};
const userRating = computed(() => {
return userRatings.value.find((r) => r.recipeId === props.recipeId)?.rating;
});
// if a user unsets their rating, we don't want to fall back to the group rating since it's out of sync
const hideGroupRating = ref(!!userRating.value);
watch(
() => rating.value.hasUserRating,
() => userRating.value,
() => {
if (rating.value.hasUserRating && !props.preferGroupRating) {
if (userRating.value) {
hideGroupRating.value = true;
}
},
)
const attrs = computed(() => {
return isOwnGroup.value ? {
// Logged-in user
color: rating.value.hasUserRating ? "secondary" : "grey darken-1",
hoverColor: "secondary",
backgroundColor: "secondary lighten-3",
} : {
// Anonymous user
color: "secondary",
hoverColor: "secondary",
backgroundColor: "secondary lighten-3",
};
})
const groupRating = computed(() => {
return hideGroupRating.value ? 0 : props.value;
});
function updateRating(val: number | null) {
if (!isOwnGroup.value) {
@@ -113,9 +94,10 @@ export default defineComponent({
}
return {
attrs,
isOwnGroup,
rating,
ratingsLoaded,
groupRating,
userRating,
updateRating,
};
},

View File

@@ -8,8 +8,11 @@
<script lang="ts">
import { computed, defineComponent } from "@nuxtjs/composition-api";
// @ts-ignore missing color types
import Color from "@sphinxxxx/color-conversion";
import { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
export default defineComponent({
props: {
label: {
@@ -34,19 +37,27 @@ export default defineComponent({
const ACCESSIBILITY_THRESHOLD = 0.179;
function pickTextColorBasedOnBgColorAdvanced(bgColor: string, lightColor: string, darkColor: string) {
const color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
const r = parseInt(color.substring(0, 2), 16); // hexToR
const g = parseInt(color.substring(2, 4), 16); // hexToG
const b = parseInt(color.substring(4, 6), 16); // hexToB
const uicolors = [r / 255, g / 255, b / 255];
const c = uicolors.map((col) => {
if (col <= 0.03928) {
return col / 12.92;
try {
const color = new Color(bgColor);
// if opacity is less than 0.3 always return dark color
if (color._rgba[3] < 0.3) {
return darkColor;
}
return Math.pow((col + 0.055) / 1.055, 2.4);
});
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
return L > ACCESSIBILITY_THRESHOLD ? darkColor : lightColor;
const uicolors = [color._rgba[0] / 255, color._rgba[1] / 255, color._rgba[2] / 255];
const c = uicolors.map((col) => {
if (col <= 0.03928) {
return col / 12.92;
}
return Math.pow((col + 0.055) / 1.055, 2.4);
});
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
return L > ACCESSIBILITY_THRESHOLD ? darkColor : lightColor;
} catch (error) {
console.warn(error);
return "black";
}
}
return {

View File

@@ -0,0 +1,98 @@
import { computed, reactive, ref } from "@nuxtjs/composition-api";
import { useStoreActions } from "./partials/use-actions-factory";
import { useUserApi } from "~/composables/api";
import { GroupRecipeActionOut, RecipeActionType } from "~/lib/api/types/group";
import { Recipe } from "~/lib/api/types/recipe";
const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null);
const loading = ref(false);
export function useGroupRecipeActionData() {
const data = reactive({
id: "",
actionType: "link" as RecipeActionType,
title: "",
url: "",
});
function reset() {
data.id = "";
data.actionType = "link";
data.title = "";
data.url = "";
}
return {
data,
reset,
};
}
export const useGroupRecipeActions = function (
orderBy: string | null = "title",
orderDirection: string | null = "asc",
) {
const api = useUserApi();
async function refreshGroupRecipeActions() {
loading.value = true;
const { data } = await api.groupRecipeActions.getAll(1, -1, { orderBy, orderDirection });
groupRecipeActions.value = data?.items || null;
loading.value = false;
}
const recipeActions = computed<GroupRecipeActionOut[] | null>(() => {
return groupRecipeActions.value;
});
function parseRecipeActionUrl(url: string, recipe: Recipe): string {
/* eslint-disable no-template-curly-in-string */
return url
.replace("${url}", window.location.href)
.replace("${id}", recipe.id || "")
.replace("${slug}", recipe.slug || "")
/* eslint-enable no-template-curly-in-string */
};
async function execute(action: GroupRecipeActionOut, recipe: Recipe): Promise<void | Response> {
const url = parseRecipeActionUrl(action.url, recipe);
switch (action.actionType) {
case "link":
window.open(url, "_blank")?.focus();
break;
case "post":
return await fetch(url, {
method: "POST",
headers: {
// The "text/plain" content type header is used here to skip the CORS preflight request,
// since it may fail. This is fine, since we don't care about the response, we just want
// the request to get sent.
"Content-Type": "text/plain",
},
body: JSON.stringify(recipe),
}).catch((error) => {
console.error(error);
});
default:
break;
}
};
if (!groupRecipeActions.value && !loading.value) {
refreshGroupRecipeActions();
};
const actions = {
...useStoreActions<GroupRecipeActionOut>(api.groupRecipeActions, groupRecipeActions, loading),
flushStore() {
groupRecipeActions.value = [];
}
}
return {
actions,
execute,
recipeActions,
};
};

View File

@@ -1,5 +1,5 @@
import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage } from "@vueuse/core";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
import { TimelineEventType } from "~/lib/api/types/recipe";
export interface UserPrintPreferences {
@@ -8,6 +8,10 @@ export interface UserPrintPreferences {
showNotes: boolean;
}
export interface UserSearchQuery {
recipe: string;
}
export enum ImagePosition {
hidden = "hidden",
left = "left",
@@ -20,7 +24,6 @@ export interface UserRecipePreferences {
filterNull: boolean;
sortIcon: string;
useMobileCards: boolean;
searchQuery: string;
}
export interface UserShoppingListPreferences {
@@ -60,7 +63,6 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
filterNull: false,
sortIcon: $globals.icons.sortAlphabeticalAscending,
useMobileCards: false,
searchQuery: "",
},
{ mergeDefaults: true }
// we cast to a Ref because by default it will return an optional type ref
@@ -70,6 +72,20 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
return fromStorage;
}
export function useUserSearchQuerySession(): Ref<UserSearchQuery> {
const fromStorage = useSessionStorage(
"search-query",
{
recipe: "",
},
{ mergeDefaults: true }
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref<UserSearchQuery>;
return fromStorage;
}
export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
const fromStorage = useLocalStorage(

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Iets het verkeerd geloop!",
"subscribed-events": "Ingetekende Gebeure",
"test-message-sent": "Toets Boodskap Gestuur",
"message-sent": "Message Sent",
"new-notification": "Nuwe kennisgewing",
"event-notifiers": "Gebeurteniskennisgewers",
"apprise-url-skipped-if-blank": "Apprise URL (oorgeslaan indien leeg)",
@@ -160,6 +161,7 @@
"test": "Toets",
"themes": "Temas",
"thursday": "Donderdag",
"title": "Title",
"token": "Token",
"tuesday": "Dinsdag",
"type": "Tipe",
@@ -582,7 +584,22 @@
"upload-image": "Laai prent",
"screen-awake": "Hou die skerm aan",
"remove-image": "Verwyder prent",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Gevorderde soek",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Verwyder resepte",
"source-unit-will-be-deleted": "Bron-eenheid sal verwyder word"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Voorbeeld data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "حدث خطأ ما!",
"subscribed-events": "الأحداث التي تم الاشتراك فيها",
"test-message-sent": "تم إرسال رسالة تجريبية",
"message-sent": "Message Sent",
"new-notification": "إشعار جديد",
"event-notifiers": "إشعار الحدث",
"apprise-url-skipped-if-blank": "الرابط Apprise (يتم تجاهله إذا ما كان فارغً)",
@@ -160,6 +161,7 @@
"test": "تجربة",
"themes": "السمات",
"thursday": "الخميس",
"title": "Title",
"token": "الرمز التعريفي",
"tuesday": "الثلاثاء",
"type": "النوع",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Нещо се обърка!",
"subscribed-events": "Планирани събития",
"test-message-sent": "Тестово съобщение е изпратено",
"message-sent": "Message Sent",
"new-notification": "Ново известие",
"event-notifiers": "Известия за събитие",
"apprise-url-skipped-if-blank": "URL за известяване (пропуска се ако е празно)",
@@ -160,6 +161,7 @@
"test": "Тест",
"themes": "Теми",
"thursday": "четвъртък",
"title": "Title",
"token": "Токън",
"tuesday": "Вторник",
"type": "Тип",
@@ -208,7 +210,7 @@
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
"confirm-delete-generic-items": "Сигурни ли сте, че желаете да изтриете следните елементи?",
"organizers": "Organizers"
"organizers": "Органайзер"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
@@ -582,7 +584,22 @@
"upload-image": "Качване на изображение",
"screen-awake": "Запази екрана активен",
"remove-image": "Премахване на изображение",
"nextStep": "Следваща стъпка"
"nextStep": "Следваща стъпка",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Разширено търсене",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Изтрий рецепти",
"source-unit-will-be-deleted": "Изходната мерна единица ще бъде изтрита"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Създаване на псевдоним",
"manage-aliases": "Управление на псевдоними",
"seed-data": "Зареждане на данни",
@@ -1168,21 +1191,21 @@
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "👋 Добре дошъл(а), {0}!",
"description": "Настройки на профил, рецепти и настройки на групата.",
"get-invite-link": "Вземи линк за покана",
"get-public-link": "Вземи публичен линк",
"account-summary": "Обобщение на акаунта",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Обобщение на информацията за Вашата група.",
"group-statistics": "Статистики на групата",
"group-statistics-description": "Вашата статистика на групата дава известна представа как използвате Mealie.",
"storage-capacity": "Капацитет за съхранение",
"storage-capacity-description": "Вашият капацитет за съхранение е изчисление на изображенията и активите, които сте качили.",
"personal": "Лични",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Това са настройки, които са лични за Вас. Промените тук няма да засегнат други потребители.",
"user-settings": "Потребителски настройки",
"user-settings-description": "Manage your preferences, change your password, and update your email.",
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"user-settings-description": "Нстройки на предпочитанията, смяна на парола и актуализация на имей адрес.",
"api-tokens-description": "Управление на API токени за достъп от външни приложения.",
"group-description": "Тези елементи се споделят във вашата група. Редактирането на един от тях ще го промени за цялата група!",
"group-settings": "Настройки на групата",
"group-settings-description": "Общи групови настройки като седмично меню и настройки за поверителност.",
@@ -1193,9 +1216,9 @@
"notifiers": "Уведомители",
"notifiers-description": "Настройте имейл и push известия, които се задействат при конкретни събития.",
"manage-data": "Управление на данни",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Управлявай данните в Mealie: Храни, Единици, Категории, Тагове и други.",
"data-migrations": "Миграция на данни",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"data-migrations-description": "Мигрирайте вашите съществуващи данни от други приложения като Nextcloud Recipes и Chowdown.",
"email-sent": "Имейлът е изпратен",
"error-sending-email": "Грешка при изпращане на имейл",
"personal-information": "Лична информация",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Alguna cosa ha anat malament!",
"subscribed-events": "Esdeveniments subscrits",
"test-message-sent": "S'ha enviat el missatge",
"message-sent": "Message Sent",
"new-notification": "Nova notificació",
"event-notifiers": "Notificacions d'esdeveniments",
"apprise-url-skipped-if-blank": "Apprise URL (si es deixa buit, s'ignorarà)",
@@ -160,6 +161,7 @@
"test": "Prova",
"themes": "Temes",
"thursday": "Dijous",
"title": "Title",
"token": "Token",
"tuesday": "Dimarts",
"type": "Tipus",
@@ -582,7 +584,22 @@
"upload-image": "Puja una imatge",
"screen-awake": "Mantenir la pantalla encesa",
"remove-image": "Esborrar la imatge",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Cerca avançada",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Dades d'exemple",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Něco se nepovedlo!",
"subscribed-events": "Odebírané události",
"test-message-sent": "Testovací zpráva odeslána",
"message-sent": "Message Sent",
"new-notification": "Nové oznámení",
"event-notifiers": "Notifikace událostí",
"apprise-url-skipped-if-blank": "Apprise URL (přeskočeno pokud je prázdné)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Motivy",
"thursday": "Čtvrtek",
"title": "Title",
"token": "Token",
"tuesday": "Úterý",
"type": "Typ",
@@ -582,7 +584,22 @@
"upload-image": "Nahrát obrázek",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Další krok"
"nextStep": "Další krok",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Pokročilé vyhledávání",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Smazat recepty",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Noget gik galt!",
"subscribed-events": "Abonnerede begivenheder",
"test-message-sent": "Testbesked sendt",
"message-sent": "Message Sent",
"new-notification": "Ny notifikation",
"event-notifiers": "Notifikation om begivenheder",
"apprise-url-skipped-if-blank": "Informations link (sprunget over hvis ladet være tomt)",
@@ -160,6 +161,7 @@
"test": "Afprøv",
"themes": "Temaer",
"thursday": "Torsdag",
"title": "Title",
"token": "Token",
"tuesday": "Tirsdag",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "Upload billede",
"screen-awake": "Hold skærmen tændt",
"remove-image": "Fjern billede",
"nextStep": "Næste trin"
"nextStep": "Næste trin",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Avanceret søgning",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Slet Opskrifter",
"source-unit-will-be-deleted": "Kildeenhed vil blive slettet"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Opret alias",
"manage-aliases": "Administrer Aliaser",
"seed-data": "Opret standard data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Etwas ist schief gelaufen!",
"subscribed-events": "Abonnierte Ereignisse",
"test-message-sent": "Testnachricht gesendet",
"message-sent": "Daten gesendet",
"new-notification": "Neue Benachrichtigung",
"event-notifiers": "Ereignis-Benachrichtigungen",
"apprise-url-skipped-if-blank": "Apprise-URL (wird übersprungen, wenn leer)",
@@ -160,6 +161,7 @@
"test": "Testen",
"themes": "Themen",
"thursday": "Donnerstag",
"title": "Titel",
"token": "Token",
"tuesday": "Dienstag",
"type": "Typ",
@@ -185,7 +187,7 @@
"menu": "Menü",
"a-name-is-required": "Ein Name wird benötigt",
"delete-with-name": "{name} löschen",
"confirm-delete-generic-with-name": "Bist du dir sicher, dass du \"{name}\" löschen möchtest?",
"confirm-delete-generic-with-name": "Bist du dir sicher, dass du dies löschen möchtest?",
"confirm-delete-own-admin-account": "Bitte beachte, dass du versuchst, dein eigenes Administrator-Konto zu löschen! Diese Aktion kann nicht rückgängig gemacht werden und wird dein Konto dauerhaft löschen!",
"organizer": "Organisator",
"transfer": "Übertragen",
@@ -535,7 +537,7 @@
"last-made-date": "Zuletzt gemacht {date}",
"api-extras-description": "Rezepte-Extras sind ein Hauptmerkmal der Mealie API. Sie ermöglichen es dir, benutzerdefinierte JSON Key-Value-Paare zu einem Rezept zu erstellen, um Drittanbieter-Anwendungen zu steuern. Du kannst diese dazu verwenden, um Automatisierungen auszulösen oder benutzerdefinierte Nachrichten an bestimmte Geräte zu senden.",
"message-key": "Nachrichten-Schlüssel",
"parse": "Parse",
"parse": "Parsen",
"attach-images-hint": "Bilder durch Ziehen & Ablegen in den Editor hinzufügen",
"drop-image": "Bild hier ablegen",
"enable-ingredient-amounts-to-use-this-feature": "Aktiviere Zutatenmengen, um diese Funktion zu nutzen",
@@ -582,7 +584,22 @@
"upload-image": "Bild hochladen",
"screen-awake": "Bildschirm nicht abschalten",
"remove-image": "Bild entfernen",
"nextStep": "Nächster Schritt"
"nextStep": "Nächster Schritt",
"recipe-actions": "Rezept-Aktionen",
"parser": {
"experimental-alert-text": "Mealie verwendet natürliche Sprachverarbeitung (NLP), um Einheiten und Lebensmittel für deine Zutatenliste zu parsen. Diese Funktion ist experimentell und funktioniert möglicherweise nicht immer wie sie sollte. Wenn du die Parser-Ergebnisse nicht verwenden möchtest, wähle 'Abbrechen' und deine Änderungen werden nicht gespeichert.",
"ingredient-parser": "Zutaten-Parser",
"explanation": "Um den Zutaten-Parser zu verwenden, klicke auf den Button 'Alles parsen', um den Vorgang zu starten. Nachdem die Zutaten analysiert worden sind, kannst du überprüfen, ob die Einträge korrekt erkannt wurden. Der vom Modell errechnete Zuverlässigkeitswert wird rechts neben der Zutat angezeigt. Diese Angabe ist ein Durchschnitt der Einzelwerte und möglicherweise nicht immer ganz korrekt.",
"alerts-explainer": "Es werden Warnungen angezeigt, wenn ein passendes Lebensmittel oder eine Einheit gefunden wurde, aber in der Datenbank nicht vorhanden ist.",
"select-parser": "Parser auswählen",
"natural-language-processor": "Natürliche Sprachverarbeitung (NLP)",
"brute-parser": "Brute Parser",
"parse-all": "Alles parsen",
"no-unit": "Keine Einheit",
"missing-unit": "Fehlende Einheit erstellen: {unit}",
"missing-food": "Fehlendes Lebensmittel erstellen: {food}",
"no-food": "Kein Lebensmittel"
}
},
"search": {
"advanced-search": "Erweiterte Suche",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Rezepte löschen",
"source-unit-will-be-deleted": "Quell-Einheit wird gelöscht"
},
"recipe-actions": {
"recipe-actions-data": "Rezept-Aktionen Daten",
"new-recipe-action": "Neue Rezept-Aktion",
"edit-recipe-action": "Rezept-Aktion bearbeiten",
"action-type": "Aktionstyp"
},
"create-alias": "Alias erstellen",
"manage-aliases": "Aliasse verwalten",
"seed-data": "Musterdaten",
@@ -1119,10 +1142,10 @@
"info-description-cleanable-images": "Löschbare Bilder",
"storage": {
"title-temporary-directory": "Temporäres Verzeichnis (.temp)",
"title-backups-directory": "Sicherungsverzeichnis (backups)",
"title-backups-directory": "Sicherungen-Verzeichnis (backups)",
"title-groups-directory": "Gruppen-Verzeichnis (groups)",
"title-recipes-directory": "Rezept-Verzeichnis (recipes)",
"title-user-directory": "Benutzerverzeichnis (user)"
"title-user-directory": "Benutzer-Verzeichnis (user)"
},
"action-delete-log-files-name": "Logs löschen",
"action-delete-log-files-description": "Löscht alle Logdateien",
@@ -1147,9 +1170,9 @@
"ingredients-natural-language-processor-explanation-2": "Es ist nicht perfekt, aber es erzeugt meist sehr gute Ergebnisse und ist ein guter Anfang, um Zutaten manuell den einzelnen Feldern zuzuordnen. Alternativ kannst du auch den \"Brute\" Prozessor benutzen, der eine Musterabgleich-Technik verwendet, um Zutaten zu identifizieren.",
"nlp": "NLP",
"brute": "Brute",
"show-individual-confidence": "Zeige individuelle Überzeugungswerte an",
"show-individual-confidence": "Zeige individuelle Zuverlässigkeitswerte an",
"ingredient-text": "Zutaten-Angabe",
"average-confident": "{0} überzeugt",
"average-confident": "{0} zuverlässig",
"try-an-example": "Probier ein Beispiel aus",
"parser": "Parser",
"background-tasks": "Hintergrundaufgaben",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Κάτι δεν πήγε καλά!",
"subscribed-events": "Εγγεγραμμένα Γεγονότα",
"test-message-sent": "Το δοκιμαστικό μήνυμα εστάλη",
"message-sent": "Το μήνυμα εστάλη",
"new-notification": "Νέα ειδοποίηση",
"event-notifiers": "Ειδοποιητές Συμβάντος",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Δοκιμή",
"themes": "Θέματα",
"thursday": "Τρίτη",
"title": "Τίτλος",
"token": "Token",
"tuesday": "Τρίτη",
"type": "Τύπος",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Ενέργειες Συνταγής",
"parser": {
"experimental-alert-text": "Το Mealie χρησιμοποιεί επεξεργασία φυσικής γλώσσας για να αναλύσει και να δημιουργήσει μονάδες και είδη διατροφής για τα συστατικά της συνταγής σας. Αυτή η λειτουργία είναι πειραματική και μπορεί να μην λειτουργεί πάντα όπως πρέπει. Αν προτιμάτε να μην χρησιμοποιείτε τα αναλυμένα αποτελέσματα, μπορείτε να επιλέξετε 'Ακύρωση' και οι αλλαγές σας δεν θα αποθηκευτούν.",
"ingredient-parser": "Αναλυτής Συστατικών",
"explanation": "Για να χρησιμοποιήσετε τον αναλυτή συστατικών, κάντε κλικ στο πλήκτρο 'Ανάλυση Ολων' για να ξεκινήσετε τη διαδικασία. Μόλις τα αναλυμένα συστατικά είναι διαθέσιμα, μπορείτε να τα επανεξετάσετε και να βεβαιωθείτε ότι έχουν αναλυθεί σωστά. Η βαθμολογία εμπιστοσύνης του μοντέλου εμφανίζεται στα δεξιά του τίτλου αντικειμένου. Αυτό το σκορ είναι ένας μέσος όρος όλων των επιμέρους βαθμολογιών και μπορεί να μην είναι πάντα εντελώς ακριβής.",
"alerts-explainer": "Θα εμφανίζονται ειδοποιήσεις αν βρεθεί ένα αντίστοιχο φαγητό ή μονάδα αλλά δεν υπάρχει στη βάση δεδομένων.",
"select-parser": "Επιλέξτε Αναλυτή",
"natural-language-processor": "Επεξεργαστής Φυσικής Γλώσσας",
"brute-parser": "Αναλυτής Ωμής Βίας",
"parse-all": "Ανάλυση Ολων",
"no-unit": "Καμία μονάδα",
"missing-unit": "Δημιουργία μονάδας που λείπει: {unit}",
"missing-food": "Δημιουργία φαγητού που λείπει: {food}",
"no-food": "Χωρίς Φαγητό"
}
},
"search": {
"advanced-search": "Σύνθετη Αναζήτηση",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Δεδομένα Ενεργειών Συνταγής",
"new-recipe-action": "Νέα Ενέργεια Συνταγής",
"edit-recipe-action": "Επεξεργασία Ενέργειας Συνταγής",
"action-type": "Τύπος Ενέργειας"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Themes",
"thursday": "Thursday",
"title": "Title",
"token": "Token",
"tuesday": "Tuesday",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Themes",
"thursday": "Thursday",
"title": "Title",
"token": "Token",
"tuesday": "Tuesday",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "¡Algo ha salido mal!",
"subscribed-events": "Eventos suscritos",
"test-message-sent": "Mensaje Enviado",
"message-sent": "Message Sent",
"new-notification": "Nueva notificación",
"event-notifiers": "Notificaciones de eventos",
"apprise-url-skipped-if-blank": "URL de Apprise (omitida si está en blanco)",
@@ -160,6 +161,7 @@
"test": "Prueba",
"themes": "Temas",
"thursday": "Jueves",
"title": "Title",
"token": "Token",
"tuesday": "Martes",
"type": "Tipo",
@@ -582,7 +584,22 @@
"upload-image": "Subir imagen",
"screen-awake": "Mantener la pantalla encendida",
"remove-image": "Eliminar imagen",
"nextStep": "Siguiente paso"
"nextStep": "Siguiente paso",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Búsqueda avanzada",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Borrar Recetas",
"source-unit-will-be-deleted": "Se eliminará la unidad de origen"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Crear un Alias",
"manage-aliases": "Administrar Alias",
"seed-data": "Datos de ejemplo",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Jotain meni pieleen!",
"subscribed-events": "Tilatut tapahtumat",
"test-message-sent": "Viesti lähetetty",
"message-sent": "Message Sent",
"new-notification": "Uusi ilmoitus",
"event-notifiers": "Tapahtumien ilmoitukset",
"apprise-url-skipped-if-blank": "Ilmoitusverkko-osoite (voi jättää tyhjäksi)",
@@ -160,6 +161,7 @@
"test": "Testi",
"themes": "Teemat",
"thursday": "Torstai",
"title": "Title",
"token": "Tunniste",
"tuesday": "Tiistai",
"type": "Tyyppi",
@@ -582,7 +584,22 @@
"upload-image": "Lataa kuva",
"screen-awake": "Pidä näyttö aina päällä",
"remove-image": "Poista kuva",
"nextStep": "Seuraava askel"
"nextStep": "Seuraava askel",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Tarkennettu haku",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Poista Reseptit",
"source-unit-will-be-deleted": "Lähdeyksikkö poistetaan"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Tietokannan pohjadata",

View File

@@ -15,7 +15,7 @@
"download-log": "Télécharger les logs",
"download-recipe-json": "Dernier JSON récupéré",
"github": "GitHub",
"log-lines": "Lignes de log",
"log-lines": "Lignes du journal",
"not-demo": "Non démo",
"portfolio": "Portfolio",
"production": "Production",
@@ -50,7 +50,7 @@
"category": "Catégorie"
},
"events": {
"apprise-url": "URL apprise",
"apprise-url": "URL Apprise",
"database": "Base de données",
"delete-event": "Supprimer l'événement",
"event-delete-confirmation": "Voulez-vous vraiment supprimer cet évènement?",
@@ -64,15 +64,16 @@
"something-went-wrong": "Une erreur s'est produite!",
"subscribed-events": "Évènements suivis",
"test-message-sent": "Message de test envoyé",
"message-sent": "Message envoyé",
"new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)",
"enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Evénements utilisateur",
"mealplan-events": "Évènements du menu",
"user-events": "Événements de l'utilisateur",
"mealplan-events": "Événements du menu",
"when-a-user-in-your-group-creates-a-new-mealplan": "Lorsqu'un utilisateur de votre groupe crée un nouveau menu",
"shopping-list-events": "Événements de la liste de courses",
"shopping-list-events": "Événements de la liste d'épicerie",
"cookbook-events": "Événements du livre de recettes",
"tag-events": "Événements des mots-clés",
"category-events": "Événements de catégories",
@@ -147,8 +148,8 @@
"show-all": "Tout afficher",
"shuffle": "Mélanger",
"sort": "Trier",
"sort-ascending": "Tri croissant",
"sort-descending": "Tri décroissant",
"sort-ascending": "Trier par ordre croissant",
"sort-descending": "Trier par ordre décroissant",
"sort-alphabetically": "Alphabétique",
"status": "Statut",
"subject": "Sujet",
@@ -160,6 +161,7 @@
"test": "Tester",
"themes": "Thèmes",
"thursday": "Jeudi",
"title": "Titre",
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
@@ -203,12 +205,12 @@
"selected-count": "Sélectionné : {count}",
"export-all": "Exporter tout",
"refresh": "Actualiser",
"upload-file": "Transférer un fichier",
"upload-file": "Téléverser un fichier",
"created-on-date": "Créé le {0}",
"unsaved-changes": "Vous avez des modifications non enregistrées. Voulez-vous les enregistrer? Ok pour enregistrer, annuler pour ignorer les modifications.",
"clipboard-copy-failure": "Échec de la copie vers le presse-papiers.",
"confirm-delete-generic-items": "Êtes-vous sûr de vouloir supprimer les éléments suivants ?",
"organizers": "Organizers"
"organizers": "Classification"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Êtes-vous certain de vouloir supprimer <b>{groupName}<b/>?",
@@ -582,7 +584,22 @@
"upload-image": "Ajouter une image",
"screen-awake": "Garder lécran allumé",
"remove-image": "Supprimer limage",
"nextStep": "Étape suivante"
"nextStep": "Étape suivante",
"recipe-actions": "Actions de recette",
"parser": {
"experimental-alert-text": "Mealie utilise le traitement du langage naturel pour analyser et créer des unités et des aliments pour vos ingrédients de recettes. Cette fonctionnalité est expérimentale et peut ne pas toujours fonctionner comme prévu. Si vous préférez ne pas utiliser les résultats analysés, vous pouvez sélectionner « Annuler » et vos modifications ne seront pas enregistrées.",
"ingredient-parser": "Analyseur d'ingrédients",
"explanation": "Pour utiliser l'analyseur d'ingrédients, cliquez sur le bouton « Tout analyser » pour démarrer le processus. Une fois les ingrédients disponibles, vous pouvez vérifier qu'ils ont été analysés correctement. Le score de confiance du modèle est affiché à droite du titre de l'article. Ce score est une moyenne de tous les scores individuels et peut ne pas toujours être complètement exact.",
"alerts-explainer": "Les alertes seront affichées si un produit ou unité correspondant est trouvé mais n'existe pas dans la base de données.",
"select-parser": "Sélectionner l'analyseur",
"natural-language-processor": "Traitement du Langage Naturel",
"brute-parser": "Analyseur brut",
"parse-all": "Tout analyser",
"no-unit": "Pas d'unité",
"missing-unit": "Créer une unité manquante : {unit}",
"missing-food": "Créer un aliment manquant : {food}",
"no-food": "Aucun aliment"
}
},
"search": {
"advanced-search": "Recherche avancée",
@@ -723,8 +740,8 @@
"volumes-are-configured-correctly": "Les volumes sont configurés correctement.",
"status-unknown-try-running-a-validation": "Statut inconnu. Essayez de lancer une validation.",
"validate": "Valider",
"email-configuration-status": "État de la configuration e-mail",
"email-configured": "E-mail configuré",
"email-configuration-status": "État de la configuration courriel",
"email-configured": "Courriel configuré",
"email-test-results": "Résultats des tests e-mail",
"ready": "Prêt",
"not-ready": "Pas prêt - Vérifier les variables d'environnement",
@@ -897,9 +914,9 @@
"enable-advanced-content": "Activer le contenu avancé",
"enable-advanced-content-description": "Active les fonctionnalités avancées comme la mise à l'échelle des recettes, les clés API, les Webhooks, et la gestion des données. Ne vous inquiétez pas, vous pouvez toujours modifier cela plus tard",
"favorite-recipes": "Recettes préférées",
"email-or-username": "E-mail ou nom d'utilisateur",
"email-or-username": "Courriel ou nom d'utilisateur",
"remember-me": "Se souvenir de moi",
"please-enter-your-email-and-password": "Veuillez saisir votre e-mail et votre mot de passe",
"please-enter-your-email-and-password": "Veuillez entrer votre adresse courriel et votre mot de passe",
"invalid-credentials": "Identifiants invalides",
"account-locked-please-try-again-later": "Compte verrouillé. Veuillez réessayer plus tard",
"user-favorites": "Favoris utilisateur",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Supprimer les recettes",
"source-unit-will-be-deleted": "L'unité source sera supprimée"
},
"recipe-actions": {
"recipe-actions-data": "Données des actions de recette",
"new-recipe-action": "Nouvelle action de recette",
"edit-recipe-action": "Modifier l'action de recette",
"action-type": "Type d'action"
},
"create-alias": "Créer un alias",
"manage-aliases": "Gérer les alias",
"seed-data": "Initialiser les données",
@@ -1042,7 +1065,7 @@
"validation": {
"group-name-is-taken": "Le nom du groupe est déjà pris",
"username-is-taken": "Nom dutilisateur déjà utilisé",
"email-is-taken": "Cet e-mail est déjà pris",
"email-is-taken": "Cette adresse courriel est déjà prise",
"this-field-is-required": "Ce champ est obligatoire"
},
"export": {
@@ -1168,21 +1191,21 @@
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "👋 Bienvenue, {0}!",
"description": "Gérez votre profil, les recettes et les paramètres de groupe.",
"get-invite-link": "Obtenir un lien d'invitation",
"get-public-link": "Voir le lien public",
"account-summary": "Aperçu du compte",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Voici un résumé des informations de votre groupe.",
"group-statistics": "Statistiques du groupe",
"group-statistics-description": "Les statistiques de votre groupe vous donnent un aperçu de la façon dont vous utilisez Mealie.",
"storage-capacity": "Capacité de stockage",
"storage-capacity-description": "Votre capacité de stockage est un calcul des images et des ressources que vous avez téléchargées.",
"personal": "Personnel",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Il s'agit de paramètres qui vous sont personnels. Les modifications ici n'affecteront pas les autres utilisateurs.",
"user-settings": "Paramètres utilisateur",
"user-settings-description": "Manage your preferences, change your password, and update your email.",
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"user-settings-description": "Gérez vos préférences, changez votre mot de passe et mettez à jour votre adresse courriel.",
"api-tokens-description": "Gérez vos jetons d'API pour accéder à partir d'applications externes.",
"group-description": "Ces éléments sont partagés au sein de votre groupe. Un changement impactera l'ensemble du groupe !",
"group-settings": "Paramètres de groupe",
"group-settings-description": "Gérez vos paramètres de groupe habituels tels que le menu de la semaine et les paramètres de confidentialité.",
@@ -1191,13 +1214,13 @@
"members-description": "Voyez qui est dans votre groupe et gérez leurs autorisations.",
"webhooks-description": "Configurez les webhooks qui se déclenchent les jours où il y a un plan au menu.",
"notifiers": "Notifications",
"notifiers-description": "Configurer les e-mails et les notifications push qui se déclenchent sur des événements spécifiques.",
"notifiers-description": "Configurer les courriels et les notifications push qui se déclenchent sur des événements spécifiques.",
"manage-data": "Gérer les données",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Gérez vos données Mealie, Aliments, Unités, Catégories, Tags et plus.",
"data-migrations": "Migration des données",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"email-sent": "E-mail envoyé",
"error-sending-email": "Erreur lors de l'envoi de l'email",
"data-migrations-description": "Migrez vos données existantes à partir d'autres applications comme Nextcloud Cookbook et Chowdown.",
"email-sent": "Courriel envoyé",
"error-sending-email": "Erreur lors de l'envoi du courriel",
"personal-information": "Informations personnelles",
"preferences": "Préférences",
"show-advanced-description": "Afficher les fonctionnalités avancées (clés API, Webhooks, et gestion des données)",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Une erreur sest produite!",
"subscribed-events": "Évènements suivis",
"test-message-sent": "Message de test envoyé",
"message-sent": "Message envoyé",
"new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)",
@@ -160,6 +161,7 @@
"test": "Tester",
"themes": "Thèmes",
"thursday": "Jeudi",
"title": "Titre",
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
@@ -208,7 +210,7 @@
"unsaved-changes": "Vous avez des modifications non enregistrées. Voulez-vous enregistrer avant de partir? OK pour enregistrer, Annuler pour ignorer les modifications.",
"clipboard-copy-failure": "Échec de la copie dans le presse-papiers.",
"confirm-delete-generic-items": "Êtes-vous sûr de vouloir supprimer les éléments suivants ?",
"organizers": "Organizers"
"organizers": "Classification"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Voulez-vous vraiment supprimer <b>{groupName}<b/>?",
@@ -582,7 +584,22 @@
"upload-image": "Envoyer une image",
"screen-awake": "Garder lécran allumé",
"remove-image": "Supprimer limage",
"nextStep": "Étape suivante"
"nextStep": "Étape suivante",
"recipe-actions": "Actions de recette",
"parser": {
"experimental-alert-text": "Mealie utilise le traitement du langage naturel pour analyser et créer des unités et des aliments pour vos ingrédients de recettes. Cette fonctionnalité est expérimentale et peut ne pas toujours fonctionner comme prévu. Si vous préférez ne pas utiliser les résultats analysés, vous pouvez sélectionner « Annuler » et vos modifications ne seront pas enregistrées.",
"ingredient-parser": "Analyseur d'ingrédients",
"explanation": "Pour utiliser l'analyseur d'ingrédients, cliquez sur le bouton « Tout analyser » pour démarrer le processus. Une fois les ingrédients disponibles, vous pouvez vérifier qu'ils ont été analysés correctement. Le score de confiance du modèle est affiché à droite du titre de l'article. Ce score est une moyenne de tous les scores individuels et peut ne pas toujours être complètement exact.",
"alerts-explainer": "Les alertes seront affichées si un produit ou unité correspondant est trouvé mais n'existe pas dans la base de données.",
"select-parser": "Sélectionner l'analyseur",
"natural-language-processor": "Traitement du Langage Naturel",
"brute-parser": "Analyseur brut",
"parse-all": "Tout analyser",
"no-unit": "Pas d'unité",
"missing-unit": "Créer une unité manquante : {unit}",
"missing-food": "Créer un aliment manquant : {food}",
"no-food": "Aucun aliment"
}
},
"search": {
"advanced-search": "Recherche avancée",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Supprimer les recettes",
"source-unit-will-be-deleted": "L'unité source sera supprimée"
},
"recipe-actions": {
"recipe-actions-data": "Données des actions de recette",
"new-recipe-action": "Nouvelle action de recette",
"edit-recipe-action": "Modifier l'action de recette",
"action-type": "Type d'action"
},
"create-alias": "Créer un alias",
"manage-aliases": "Gérer les alias",
"seed-data": "Initialiser les données",
@@ -1168,21 +1191,21 @@
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "👋 Bienvenue, {0} !",
"description": "Gérez votre profil, les recettes et les paramètres de groupe.",
"get-invite-link": "Obtenir un lien d'invitation",
"get-public-link": "Voir le lien public",
"account-summary": "Aperçu du compte",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Voici un résumé des informations de votre groupe.",
"group-statistics": "Statistiques du groupe",
"group-statistics-description": "Les statistiques de votre groupe vous donnent un aperçu de la façon dont vous utilisez Mealie.",
"storage-capacity": "Capacité de stockage",
"storage-capacity-description": "Votre capacité de stockage est un calcul des images et des ressources que vous avez téléchargées.",
"personal": "Personnel",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Ici se trouvent des paramètres qui vous sont propres personnellement. Toute modification n'affectera pas les autres utilisateurs.",
"user-settings": "Paramètres utilisateur",
"user-settings-description": "Manage your preferences, change your password, and update your email.",
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"user-settings-description": "Gérez vos préférences, changez votre mot de passe et mettez à jour votre adresse e-mail.",
"api-tokens-description": "Gérez vos jetons API pour un accès à partir d'applications externes.",
"group-description": "Ces éléments sont partagés au sein de votre groupe. Un changement impactera l'ensemble du groupe !",
"group-settings": "Paramètres de groupe",
"group-settings-description": "Gérez vos paramètres de groupe habituels tels que le menu de la semaine et les paramètres de confidentialité.",
@@ -1193,9 +1216,9 @@
"notifiers": "Notifications",
"notifiers-description": "Configurer les e-mails et les notifications push qui se déclenchent sur des événements spécifiques.",
"manage-data": "Gérer les données",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Gérez vos données Mealie, Aliments, Unités, Catégories, Tags et plus.",
"data-migrations": "Migration des données",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"data-migrations-description": "Migrez vos données existantes à partir d'autres applications comme Nextcloud Cookbook et Chowdown.",
"email-sent": "E-mail envoyé",
"error-sending-email": "Erreur lors de l'envoi de l'email",
"personal-information": "Informations personnelles",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Probar",
"themes": "Temas",
"thursday": "Xoves",
"title": "Title",
"token": "Identificador",
"tuesday": "Martes",
"type": "Tipo",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "משהו השתבש!",
"subscribed-events": "אירועים שנרשמת אליהם",
"test-message-sent": "הודעת בדיקה נשלחה",
"message-sent": "Message Sent",
"new-notification": "התראה חדשה",
"event-notifiers": "אירועי נוטיפיקציות",
"apprise-url-skipped-if-blank": "כתובת Apprise (דלג אם ריק)",
@@ -160,6 +161,7 @@
"test": "ניסיון",
"themes": "ערכות נושא",
"thursday": "חמישי",
"title": "Title",
"token": "טוקן",
"tuesday": "שלישי",
"type": "סוג",
@@ -582,7 +584,22 @@
"upload-image": "העלה תמונה",
"screen-awake": "השאר את המסך פעיל",
"remove-image": "האם למחוק את התמונה?",
"nextStep": "השלב הבא"
"nextStep": "השלב הבא",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "חיפוש מתקדם",
@@ -1001,6 +1018,12 @@
"delete-recipes": "מחיקת מתכונים",
"source-unit-will-be-deleted": "יחידת המקור תמחק"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "יצירת שם נרדף",
"manage-aliases": "נהל שמות נרדפים",
"seed-data": "אכלס נתונים",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Nešto nije bilo u redu!",
"subscribed-events": "Pretplaćeni Događaji",
"test-message-sent": "Testna Poruka je Poslana",
"message-sent": "Message Sent",
"new-notification": "Nova Obavijest",
"event-notifiers": "Obavještavatelji Događaja",
"apprise-url-skipped-if-blank": "Apprise URL (preskočeno ako je prazno)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Teme",
"thursday": "Četvrtak",
"title": "Title",
"token": "Token",
"tuesday": "Utorak",
"type": "Tip",
@@ -582,7 +584,22 @@
"upload-image": "Učitavanje Slike",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Napredno Pretraživanje",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Obriši Recepte",
"source-unit-will-be-deleted": "Izvorna jedinica će biti izbrisana"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Unesi Podatke",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Hiba történt!",
"subscribed-events": "Feliratkozott események",
"test-message-sent": "Teszt üzenet elküldve",
"message-sent": "Üzenet elküldve",
"new-notification": "Új értesítés",
"event-notifiers": "Esemény értesítők",
"apprise-url-skipped-if-blank": "Értesítendő URL (kihagy, ha üres)",
@@ -85,7 +86,7 @@
"clear": "Törlés",
"close": "Bezár",
"confirm": "Megerősítés",
"confirm-how-does-everything-look": "How does everything look?",
"confirm-how-does-everything-look": "Hogy néz ki?",
"confirm-delete-generic": "Biztosan törölni szeretnéd ezt?",
"copied_message": "Másolva!",
"create": "Létrehozás",
@@ -144,11 +145,11 @@
"save": "Mentés",
"settings": "Beállítások",
"share": "Megosztás",
"show-all": "Show All",
"show-all": "Mutasd az összeset",
"shuffle": "Véletlenszerű",
"sort": "Rendezés",
"sort-ascending": "Sort Ascending",
"sort-descending": "Sort Descending",
"sort-ascending": "Rendezés növekvő sorrendben",
"sort-descending": "Rendezés csökkenő sorrendben",
"sort-alphabetically": "Betűrendben",
"status": "Állapot",
"subject": "Tárgy",
@@ -160,6 +161,7 @@
"test": "Teszt",
"themes": "Témák",
"thursday": "Csütörtök",
"title": "Cím",
"token": "Token",
"tuesday": "Kedd",
"type": "Típus",
@@ -245,7 +247,7 @@
"private-group": "Privát csoport",
"private-group-description": "Ha a csoportot privátra állítja, az összes nyilvános nézeti beállítás alapértelmezettre lesz állítva. Ez felülírja az egyes receptek nyilvános nézet beállításait.",
"enable-public-access": "Nyilvános hozzáférés engedélyezése",
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
"enable-public-access-description": "Legyenek a csoport receptek alapértelmezetten publikusak és engedélyezze a látogatóknak a megtekintést belépés nélkül",
"allow-users-outside-of-your-group-to-see-your-recipes": "Engedélyezze a csoporton kívüli felhasználók számára a receptek megtekintését",
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Ha engedélyezve van, nyilvános megosztási hivatkozással megoszthat adott recepteket a felhasználó felhatalmazása nélkül. Ha le van tiltva, csak olyan felhasználókkal oszthat meg recepteket, akik a csoportjába tartoznak, vagy egy előre generált privát linkkel",
"show-nutrition-information": "Táplálkozási információk megjelenítése",
@@ -359,11 +361,11 @@
},
"recipe-data-migrations": "Receptadatok migrációja",
"recipe-data-migrations-explanation": "A receptek átemelhetők más támogatott alkalmazásból Mealie-be. Ez egy remek módja a Mealie használatának elkezdésére.",
"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": "A Mealie egy másik verzióját használtad vagy valami teljesen más applikációt? Nézd meg az adat importálási lehetőségeinket.",
"choose-migration-type": "Válassza ki a migrációs típusát",
"tag-all-recipes": "Az összes recept címkézése a {tag-name} címkével",
"nextcloud-text": "A Nextcloud-receptek importálhatók a Nextcloudban tárolt adatokat tartalmazó zip-fájlból. Tekintse meg az alábbi példamappaszerkezetet, hogy receptjei biztosan importálhatók legyenek.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
"chowdown-text": "Mealie natívan támogatja a chowdown repository formátumot. Töltse le a kódtárat .zip fájlként, és töltse fel alább.",
"recipe-1": "Recept 1",
"recipe-2": "Recept 2",
"paprika-text": "Mealie képes recepteket importálni a Paprika alkalmazásból. Exportálja a receptjeit a Paprikából, nevezze át az export kiterjesztést .zip-re, és töltse fel alább.",
@@ -373,8 +375,8 @@
"description-long": "Mealie képes recepteket importálni a Plan to Eat alkalmazásból."
},
"myrecipebox": {
"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."
"title": "Az én receptes dobozom",
"description-long": "A Mealie képest recepteket importálni az Én Receptes Dobozomból. Exportáld a receptjeidet CSV formátúmba, aztán töltsd fel a .csv fájlt lentebb."
}
},
"new-recipe": {
@@ -509,8 +511,8 @@
"cook-mode": "Főzési mód",
"link-ingredients": "Hozzávalók összekapcsolása",
"merge-above": "Összevonás a fentivel",
"move-to-bottom": "Move To Bottom",
"move-to-top": "Move To Top",
"move-to-bottom": "Ugrás az aljára",
"move-to-top": "Ugrás a tetejére",
"reset-scale": "Skála alaphelyzetbe állítása",
"decrease-scale-label": "Skála csökkentése 1-gyel",
"increase-scale-label": "Skála növelése 1-gyel",
@@ -526,7 +528,7 @@
"edit-timeline-event": "Idővonal-esemény szerkesztése",
"timeline": "Idővonal",
"timeline-is-empty": "Az idővonalon még semmi sincs. Próbálja meg elkészíteni ezt a receptet!",
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
"timeline-no-events-found-try-adjusting-filters": "Nem találtunk eseményeket. Próbáld meg átállítani a keresési szűrőket.",
"group-global-timeline": "{groupName} Globális idővonal",
"open-timeline": "Idővonal megnyitása",
"made-this": "Elkészítettem ezt",
@@ -547,7 +549,7 @@
"looking-for-migrations": "Migrációt keres?",
"import-with-url": "Importálás URL-címről",
"create-recipe": "Recept létrehozása",
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipe-description": "Adj hozzá egy új receptet a nulláról kezdve.",
"create-recipes": "Receptek létrehozása",
"import-with-zip": "Importálás .zip formátummal",
"create-recipe-from-an-image": "Recept létrehozása fotóról",
@@ -582,7 +584,22 @@
"upload-image": "Kép feltöltése",
"screen-awake": "Képernyő ébren tartása",
"remove-image": "Kép etávolítása",
"nextStep": "Következő lépés"
"nextStep": "Következő lépés",
"recipe-actions": "Recipe Actions",
"parser": {
"experimental-alert-text": "A Mealie természetes nyelvi feldolgozást használ a recept összetevőinek elemzésére, az egységek és az élelmiszerelemek létrehozására. Ez a funkció kísérleti jellegű, és előfordulhat, hogy nem mindig működik az elvárt módon. Ha nem szeretné használni az elemzett eredményeket, válassza a 'Mégse' lehetőséget, és a módosítások nem kerülnek mentésre.",
"ingredient-parser": "Hozzávaló elemző",
"explanation": "A hozzávalók elemzőjének használatához kattintson a 'Parse All' gombra a folyamat elindításához. Amint a feldolgozott hozzávalók elérhetővé válnak, áttekintheti az elemeket, és ellenőrizheti, hogy azok helyesen lettek-e elemezve. A modell megbízhatósági pontszáma az elem címének jobb oldalán jelenik meg. Ez a pontszám az összes egyéni pontszám átlaga, és nem biztos, hogy mindig teljesen pontos.",
"alerts-explainer": "Figyelmeztetések jelennek meg, ha talál egy megfelelő élelmiszert vagy egységet, de az nem létezik az adatbázisban.",
"select-parser": "Válasszon elemzőt",
"natural-language-processor": "Természetes nyelvi feldolgozó",
"brute-parser": "Brute elemző",
"parse-all": "Összes elemzése",
"no-unit": "Mértékegység nélkül",
"missing-unit": "Hiányzó mértékegység létrehozása: {unit}",
"missing-food": "Hiányzó élelmiszer létrehozása: {food}",
"no-food": "Élelmiszer nélküli"
}
},
"search": {
"advanced-search": "Részletes keresés",
@@ -619,7 +636,7 @@
"import-summary": "Import összefoglaló",
"partial-backup": "Részleges biztonsági mentés",
"unable-to-delete-backup": "Nem lehetett létrehozni a biztonsági mentést.",
"experimental-description": "Backups 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": "A biztonsági mentések az oldal adatbázisának és adatkönyvtárának teljes pillanatfelvételei. Ez az összes adatot tartalmazza, és nem lehet beállítani, hogy az adatok részhalmazait kizárja. Ezt úgy is elképzelheti, mint a Mealie egy adott időpontban készült pillanatfelvételét. Ezek adatbázis-független módon szolgálnak az adatok exportálására és importálására, vagy a webhely külső helyre történő mentésére.",
"backup-restore": "Biztonsági Mentés/Visszaállítás",
"back-restore-description": "A biztonsági mentés visszaállítása felülírja az adatbázisban és az adatkönyvtárban lévő összes aktuális adatot, és a biztonsági mentés tartalmával helyettesíti azokat. {cannot-be-undone} Ha a visszaállítás sikeres, akkor a rendszer kilépteti Önt.",
"cannot-be-undone": "Ezt a műveletet visszavonható - óvatosan használja.",
@@ -745,9 +762,9 @@
"ldap-ready-success-text": "A szükséges LDAP-változók mind beállítva.",
"build": "Build",
"recipe-scraper-version": "Receptkinyerő verziója",
"oidc-ready": "OIDC Ready",
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
"oidc-ready-success-text": "Required OIDC variables are all set."
"oidc-ready": "OIDC készen áll",
"oidc-ready-error-text": "Nem minden OIDC érték van beállítva. Ezt figyelmen kívül lehet hagyni ha nem használ OIDC hitelesítést.",
"oidc-ready-success-text": "A szükséges OIDC változók mind beállítva."
},
"shopping-list": {
"all-lists": "Összes lista",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Receptek törlése",
"source-unit-will-be-deleted": "A forrás mennyiségi egység törlésre kerül"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Alias készítése",
"manage-aliases": "Alias kezelése",
"seed-data": "Kezdőérték adat",
@@ -1159,12 +1182,12 @@
"setup": {
"first-time-setup": "Első beállítás",
"welcome-to-mealie-get-started": "Üdvözöl a Mealie! Vágjunk bele",
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"already-set-up-bring-to-homepage": "Már kész is vagyok, vigyél az új kezdőoldalamra",
"common-settings-for-new-sites": "Itt van egy pár alap beállítás az új oldaladhoz",
"setup-complete": "Beállítás kész!",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"here-are-a-few-things-to-help-you-get-started": "Itt van egy pár dolog ami segíthet a kezdésben a Mealie-vel",
"restore-from-v1-backup": "Van egy korábbi Mealie v1 biztonsági másolatod? Itt visszaállíthatod.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."
"manage-profile-or-get-invite-link": "Alakítsa a profilját vagy szerezze meg a meghívó link-jét hogy megoszthassa másokkal."
}
},
"profile": {
@@ -1179,7 +1202,7 @@
"storage-capacity": "Tárhely mérete",
"storage-capacity-description": "A tárhely kapacitása az Ön által feltöltött képek és eszközök számításából adódik.",
"personal": "Személyes",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Ezek a beállítások az Ön személyes beállításai. Az itt végzett változtatások nincsenek hatással más felhasználókra.",
"user-settings": "Felhasználói beállítások",
"user-settings-description": "Itt kezelheti beállításait, módosíthatja jelszavát és frissítheti e-mail címét.",
"api-tokens-description": "API kulcsok kezelése külső alkalmazásokból.",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Themes",
"thursday": "Thursday",
"title": "Title",
"token": "Token",
"tuesday": "Tuesday",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Si è verificato un errore!",
"subscribed-events": "Eventi Sottoscritti",
"test-message-sent": "Messaggio di prova inviato",
"message-sent": "Message Sent",
"new-notification": "Nuova Notifica",
"event-notifiers": "Notifiche Evento",
"apprise-url-skipped-if-blank": "Url di Apprise (ignorato se vuoto)",
@@ -85,7 +86,7 @@
"clear": "Resetta",
"close": "Chiudi",
"confirm": "Conferma",
"confirm-how-does-everything-look": "How does everything look?",
"confirm-how-does-everything-look": "Come ti sembra tutto questo?",
"confirm-delete-generic": "Sei sicuro di volere eliminare?",
"copied_message": "Copiato!",
"create": "Crea",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Temi",
"thursday": "Giovedì",
"title": "Title",
"token": "Token",
"tuesday": "Martedì",
"type": "Tipo",
@@ -174,7 +176,7 @@
"units": "Unità",
"back": "Indietro",
"next": "Avanti",
"start": "Start",
"start": "Inizia",
"toggle-view": "Cambia Vista",
"date": "Data",
"id": "Id",
@@ -208,7 +210,7 @@
"unsaved-changes": "Sono state apportate modifiche non salvate. Salvare prima di uscire? Premi Ok per salvare, Annulla per scartare le modifiche.",
"clipboard-copy-failure": "Impossibile copiare negli appunti.",
"confirm-delete-generic-items": "Sei sicuro di voler eliminare i seguenti elementi?",
"organizers": "Organizers"
"organizers": "Organizzatori"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Sei sicuro di volerlo eliminare <b>{groupName}<b/>'?",
@@ -244,8 +246,8 @@
"group-preferences": "Preferenze Gruppo",
"private-group": "Gruppo Privato",
"private-group-description": "Impostando il tuo gruppo su privato verranno ripristinate tutte le opzioni di visualizzazione pubblica. Questa opzione sovrascrive le impostazioni di visualizzazione pubblica della singola ricetta.",
"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": "Abilita accesso pubblico",
"enable-public-access-description": "Rendi pubbliche le ricette di gruppo per impostazione predefinita, e consenti ai visitatori di visualizzare le ricette senza effettuare l'accesso",
"allow-users-outside-of-your-group-to-see-your-recipes": "Consenti agli utenti al di fuori del tuo gruppo di vedere le tue ricette",
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Se abilitato, è possibile utilizzare un link pubblico per condividere ricette specifiche senza autorizzare l'utente. Se disabilitato, puoi condividere ricette solo con gli utenti che sono nel tuo gruppo o con un link privato pre-generato",
"show-nutrition-information": "Mostra informazioni nutrizionali",
@@ -359,11 +361,11 @@
},
"recipe-data-migrations": "Migrazione Dati Ricetta",
"recipe-data-migrations-explanation": "Le ricette possono essere migrate da un'altra applicazione supportata da Mealie. Questo è un ottimo modo per iniziare con Mealie.",
"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": "Arrivi da un'altra applicazione o da una versione più vecchia di Mealie? Scopri le migrazioni e scopri se i tuoi dati possono essere importati.",
"choose-migration-type": "Scegli Il Tipo Di Migrazione",
"tag-all-recipes": "Etichetta tutte le ricette con {tag-name} etichetta",
"nextcloud-text": "Le ricette di Nextcloud possono essere importate da un file zip che contiene i dati memorizzati in Nextcloud. Osserva la struttura della cartella di esempio qui sotto per assicurarti che le tue ricette siano in grado di essere importate.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
"chowdown-text": "Mealie supporta nativamente il formato del repository chowdown. Scarica il repository del codice come file .zip e caricalo qui sotto.",
"recipe-1": "Ricetta 1",
"recipe-2": "Ricetta 2",
"paprika-text": "Mealie può importare ricette dall'applicazione Paprika. Esporta le tue ricette da paprika, rinomina l'estensione di esportazione in .zip e caricala qui sotto.",
@@ -373,8 +375,8 @@
"description-long": "Mealie può importare le ricette da Plan to Eat."
},
"myrecipebox": {
"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."
"title": "Il mio ricettario",
"description-long": "Mealie può importare ricette da My Recipe Box. Esporta le tue ricette in formato HTML, quindi carica il .zip qui sotto."
}
},
"new-recipe": {
@@ -547,8 +549,8 @@
"looking-for-migrations": "Stai Cercando Le Migrazioni?",
"import-with-url": "Importa da URL",
"create-recipe": "Crea Ricetta",
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"create-recipe-description": "Crea una nuova ricetta da zero.",
"create-recipes": "Crea Ricette",
"import-with-zip": "Importa da .zip",
"create-recipe-from-an-image": "Crea ricetta da un'immagine",
"bulk-url-import": "Importazione multipla URL",
@@ -582,7 +584,22 @@
"upload-image": "Carica immagine",
"screen-awake": "Mantieni lo schermo acceso",
"remove-image": "Rimuovi immagine",
"nextStep": "Passo successivo"
"nextStep": "Passo successivo",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Ricerca Avanzata",
@@ -745,9 +762,9 @@
"ldap-ready-success-text": "Le variabili LDAP richieste sono tutte configurate.",
"build": "Versione",
"recipe-scraper-version": "Versione Recipe Scraper",
"oidc-ready": "OIDC Ready",
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
"oidc-ready-success-text": "Required OIDC variables are all set."
"oidc-ready": "Pronto per OIDC",
"oidc-ready-error-text": "I valori OIDC non sono configurati. Questo può essere ignorato se non si utilizza Autenticazione OIDC.",
"oidc-ready-success-text": "Le variabili OIDC richieste sono tutte impostate."
},
"shopping-list": {
"all-lists": "Tutte le Liste",
@@ -855,11 +872,11 @@
"link-id": "Link ID",
"link-name": "Link Nome",
"login": "Login",
"login-oidc": "Login with",
"or": "or",
"login-oidc": "Accedi con",
"or": "oppure",
"logout": "Logout",
"manage-users": "Gestisci Utenti",
"manage-users-description": "Create and manage users.",
"manage-users-description": "Crea e gestisci gli utenti.",
"new-password": "Nuova Password",
"new-user": "Nuovo Utente",
"password-has-been-reset-to-the-default-password": "La password è stata reimpostata a quella di default",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Elimina Ricette",
"source-unit-will-be-deleted": "L'unità di origine verrà eliminata"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Crea Alias",
"manage-aliases": "Gestisci Alias",
"seed-data": "Dati Predefiniti",
@@ -1157,32 +1180,32 @@
"no-logs-found": "Nessun Log Trovato",
"tasks": "Compiti",
"setup": {
"first-time-setup": "First Time Setup",
"welcome-to-mealie-get-started": "Welcome to Mealie! Let's get started",
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage",
"common-settings-for-new-sites": "Here are some common settings for new sites",
"setup-complete": "Setup Complete!",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."
"first-time-setup": "Configurazione iniziale",
"welcome-to-mealie-get-started": "Un benvenuto su Mealie! Iniziamo",
"already-set-up-bring-to-homepage": "Ho già configurato tutto, portami alla pagina iniziale",
"common-settings-for-new-sites": "Ecco alcune impostazioni comuni per i nuovi siti",
"setup-complete": "Configurazione completata!",
"here-are-a-few-things-to-help-you-get-started": "Qui ci sono alcune cose per aiutarvi a iniziare con Mealie",
"restore-from-v1-backup": "Hai un backup da un'istanza precedente di Mealie v1? Puoi ripristinarlo qui.",
"manage-profile-or-get-invite-link": "Gestisci il tuo profilo, o parti da un link di invito per condividere con gli altri."
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "👋 Benvenutǝ, {0}!",
"description": "Gestisci il tuo profilo, le ricette e le impostazioni di gruppo.",
"get-invite-link": "Ottieni Link Di Invito",
"get-public-link": "Ottieni link pubblico",
"account-summary": "Riepilogo Account",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Ecco un riepilogo delle informazioni del tuo gruppo.",
"group-statistics": "Statistiche Gruppo",
"group-statistics-description": "Le statistiche di gruppo forniscono alcune informazioni su come stai usando Mealie.",
"storage-capacity": "Capacità di Archiviazione",
"storage-capacity-description": "La capacità di archiviazione è data dall'insieme delle immagini e delle risorse caricate.",
"personal": "Personale",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Queste sono le tue impostazioni personali. Le modifiche non influenzeranno gli altri utenti.",
"user-settings": "Impostazioni Utente",
"user-settings-description": "Manage your preferences, change your password, and update your email.",
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"user-settings-description": "Gestisci le tue preferenze, modifica la tua password e aggiorna la tua email.",
"api-tokens-description": "Gestisci i tuoi Token API per l'accesso da applicazioni esterne.",
"group-description": "Questi elementi sono condivisi all'interno del tuo gruppo. Modificando uno di essi cambierà per l'intero gruppo!",
"group-settings": "Impostazioni Gruppo",
"group-settings-description": "Gestisci le impostazioni comuni del gruppo, come il piano alimentare e impostazioni di privacy.",
@@ -1193,9 +1216,9 @@
"notifiers": "Notifiche",
"notifiers-description": "Imposta email e notifiche push che si attivano per eventi specifici.",
"manage-data": "Gestisci Dati",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Gestisci i tuoi dati di Mealie; Alimenti, Unità, Categorie, Tag e altro ancora.",
"data-migrations": "Migrazioni Dati",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"data-migrations-description": "Migra i dati esistenti da altre applicazioni come Nextcloud Recipes e Chowdown.",
"email-sent": "Email Inviata",
"error-sending-email": "Errore Nell'Invio Email",
"personal-information": "Informazioni Personali",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "問題が発生しました",
"subscribed-events": "購読中のイベント",
"test-message-sent": "テストメッセージを送信しました",
"message-sent": "Message Sent",
"new-notification": "新着通知",
"event-notifiers": "イベント通知",
"apprise-url-skipped-if-blank": "通知用URL (空欄の場合はスキップ)",
@@ -160,6 +161,7 @@
"test": "テスト",
"themes": "テーマ",
"thursday": "木曜日",
"title": "Title",
"token": "トークン",
"tuesday": "火曜日",
"type": "タイプ",
@@ -582,7 +584,22 @@
"upload-image": "画像をアップロード",
"screen-awake": "画面をスリープ状態にしない",
"remove-image": "画像を削除",
"nextStep": "次のステップ"
"nextStep": "次のステップ",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "詳細検索",
@@ -1001,6 +1018,12 @@
"delete-recipes": "レシピを削除",
"source-unit-will-be-deleted": "元の単位が削除されます"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "エイリアスを作成",
"manage-aliases": "エイリアスの管理",
"seed-data": "シードデータ",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "구독한 이벤트",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "새 알림",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Themes",
"thursday": "목요일",
"title": "Title",
"token": "Token",
"tuesday": "화요일",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "이미지 업로드",
"screen-awake": "화면을 항상 켠 상태로 유지",
"remove-image": "이미지 제거",
"nextStep": "다음 단계"
"nextStep": "다음 단계",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "고급 검색",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Įvyko klaida!",
"subscribed-events": "Prenumeruojami įvykiai",
"test-message-sent": "Testinė žinutė išsiųsta",
"message-sent": "Message Sent",
"new-notification": "Naujas pranešimas",
"event-notifiers": "Įvykių pranešimai",
"apprise-url-skipped-if-blank": "Apprise URL (praleidžiama, jei tuščia)",
@@ -160,6 +161,7 @@
"test": "Tikrinti",
"themes": "Temos",
"thursday": "Ketvirtadienis",
"title": "Title",
"token": "Prieeigos raktas",
"tuesday": "Antradienis",
"type": "Tipas",
@@ -582,7 +584,22 @@
"upload-image": "Įkelti nuotrauką",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Išplėstinė paieška",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Ištrinti receptus",
"source-unit-will-be-deleted": "Pirminis vienetas bus ištrintas"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Pradiniai duomenys",

View File

@@ -1,18 +1,18 @@
{
"about": {
"about": "About",
"about-mealie": "About Mealie",
"api-docs": "API Docs",
"api-port": "API Port",
"application-mode": "Application Mode",
"database-type": "Database Type",
"database-url": "Database URL",
"default-group": "Default Group",
"about": "Par",
"about-mealie": "Par Mealie",
"api-docs": "API Dokumentācija",
"api-port": "API ports",
"application-mode": "Lietojumprogrammas režīms",
"database-type": "Datubāzes tips",
"database-url": "Datubāzes URL",
"default-group": "Noklusētā grupa",
"demo": "Demo",
"demo-status": "Demo Status",
"development": "Development",
"docs": "Docs",
"download-log": "Download Log",
"demo-status": "Demonstrācijas statuss",
"development": "Izstrāde",
"docs": "Dokumentācija",
"download-log": "Lejupielādēt žurnālu",
"download-recipe-json": "Last Scraped JSON",
"github": "Github",
"log-lines": "Log Lines",
@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Themes",
"thursday": "Thursday",
"title": "Title",
"token": "Token",
"tuesday": "Tuesday",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Er is iets fout gegaan!",
"subscribed-events": "Geabonneerde gebeurtenissen",
"test-message-sent": "Testbericht verzonden",
"message-sent": "Bericht verstuurd",
"new-notification": "Nieuwe melding",
"event-notifiers": "Meldingen van gebeurtenissen",
"apprise-url-skipped-if-blank": "URL van Apprise (overgeslagen als veld leeg is)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Thema's",
"thursday": "donderdag",
"title": "Titel",
"token": "Token",
"tuesday": "dinsdag",
"type": "Soort",
@@ -582,7 +584,22 @@
"upload-image": "Afbeelding uploaden",
"screen-awake": "Scherm aan laten staan",
"remove-image": "Afbeelding verwijderen",
"nextStep": "Volgende stap"
"nextStep": "Volgende stap",
"recipe-actions": "Acties met recepten ",
"parser": {
"experimental-alert-text": "Mealie gebruikt natuurlijke taalverwerking om te ontleden en maakt eenheden en levensmiddelen voor de ingrediënten van je recept. Deze functie is experimenteel en werkt misschien niet altijd zoals verwacht. Als u liever niet de bewerkte resultaten gebruikt, kunt u 'Annuleren' selecteren en de wijzigingen worden niet opgeslagen.",
"ingredient-parser": "",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Geavanceerd zoeken",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Verwijder recepten",
"source-unit-will-be-deleted": "Broneenheid zal worden verwijderd"
},
"recipe-actions": {
"recipe-actions-data": "Gegevens voor acties met recepten",
"new-recipe-action": "Nieuwe actie met recept",
"edit-recipe-action": "Pas actie met recept aan",
"action-type": "Soort actie"
},
"create-alias": "Maak alias",
"manage-aliases": "Beheer aliassen ",
"seed-data": "Voorbeeldgegevens",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Noe gikk galt!",
"subscribed-events": "Abonnerte hendelser",
"test-message-sent": "Testmelding sendt",
"message-sent": "Melding sendt",
"new-notification": "Ny varsel",
"event-notifiers": "Hendelsesvarsler",
"apprise-url-skipped-if-blank": "Apprise URL (hoppes over hvis tom)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Temaer",
"thursday": "Torsdag",
"title": "Tittel",
"token": "Token",
"tuesday": "Tirsdag",
"type": "Type",
@@ -208,7 +210,7 @@
"unsaved-changes": "Du har ulagrede endringer. Ønsker du å lagre før du forlater? Trykk 'OK' for å lagre, 'Avbryt' for å forkaste endringene.",
"clipboard-copy-failure": "Kunne ikke kopiere til utklippstavlen.",
"confirm-delete-generic-items": "Er du sikker på at du vil følgende elementer?",
"organizers": "Organizers"
"organizers": "Organisatorer"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Er du sikker på at du vil slette <b>{groupName}<b/>?",
@@ -363,7 +365,7 @@
"choose-migration-type": "Velg type overføring",
"tag-all-recipes": "Merk alle oppskrifter med emneordet {tag-name}",
"nextcloud-text": "Oppskrifter fra Nextcloud kan importeres fra en zip-fil som inneholder dataene lagret i Nextcloud. Se eksempelet på mappestrukture nedenfor for å sikre at oppskriftene kan importeres.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
"chowdown-text": "Mealie støtter Chowdown-arkivformatet. Last ned kodearkivet som en .zip-fil og last den opp nedenfor.",
"recipe-1": "Oppskrift 1",
"recipe-2": "Oppskrift 2",
"paprika-text": "Mealie kan importere oppskrifter fra Paprika. Eksporter oppskriftene fra Paprika, endre filnavnutvidelsen til .zip og last den opp nedenfor.",
@@ -374,7 +376,7 @@
},
"myrecipebox": {
"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 kan importere oppskrifter fra My Recipe Box. Eksporter oppskrifter i CSV-format, og last deretter opp .csv-filen nedenfor."
}
},
"new-recipe": {
@@ -582,7 +584,22 @@
"upload-image": "Last opp bilde",
"screen-awake": "Hold skjermen på",
"remove-image": "Slett bilde",
"nextStep": "Neste trinn"
"nextStep": "Neste trinn",
"recipe-actions": "Oppskriftshandlinger",
"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.",
"ingredient-parser": "Ingrediens-parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Avansert søk",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Slett oppskrifter",
"source-unit-will-be-deleted": "Kildeenheten vil bli slettet"
},
"recipe-actions": {
"recipe-actions-data": "Opppgavehandlings-data",
"new-recipe-action": "Ny oppskriftshandling",
"edit-recipe-action": "Rediger oppskriftshandling",
"action-type": "Handlingstype"
},
"create-alias": "Opprett alias",
"manage-aliases": "Administrer aliaser",
"seed-data": "Tilføringsdata",
@@ -1168,21 +1191,21 @@
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "Velkommen, {0}!",
"description": "Administrer din profil, oppskrifter og gruppeinnstillinger.",
"get-invite-link": "Få invitasjonslenke",
"get-public-link": "Få offentlig lenke",
"account-summary": "Kontosammendrag",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Her er en oppsummering av informasjonen til gruppen din.",
"group-statistics": "Gruppestatistikk",
"group-statistics-description": "Gruppestatistikken din gir deg et innblikk i hvordan du bruker Mealie.",
"storage-capacity": "Lagringskapasitet",
"storage-capacity-description": "Lagringskapasiteten er en beregning av bildene og ressursene du har lastet opp.",
"personal": "Personlig",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Dette er personlige innstillinger. Endringer gjort her påvirker ikke andre brukere.",
"user-settings": "Brukerinnstillinger",
"user-settings-description": "Manage your preferences, change your password, and update your email.",
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"user-settings-description": "Administrer innstillingene, endre passordet og oppdater e-postadressen din.",
"api-tokens-description": "Administrer dine API-tokens for tilgang fra eksterne programmer.",
"group-description": "Disse elementene deles innad i gruppen din. Redigering av elementenee vil føre til endringer for hele gruppen!",
"group-settings": "Gruppeinnstillinger",
"group-settings-description": "Administrer felles gruppeinnstillinger som innstillinger for måltidsplaner og personvern.",
@@ -1193,9 +1216,9 @@
"notifiers": "Varslingsagenter",
"notifiers-description": "Sett opp e-post- og pushvarsler som utløses av spesifikke hendelser.",
"manage-data": "Administrer data",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Administrer dine Mealie-data; Ingredienser, enheter, kategorier, emneknagger og mer.",
"data-migrations": "Dataoverføringer",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"data-migrations-description": "Overfør eksisterende data fra andre programmer som Nextcloud Recipes og Chowdown.",
"email-sent": "E-post sendt",
"error-sending-email": "Feil ved sending av e-post",
"personal-information": "Personlig informasjon",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Coś poszło nie tak!",
"subscribed-events": "Zasubskrybowane wydarzenia",
"test-message-sent": "Wiadomość została wysłana",
"message-sent": "Message Sent",
"new-notification": "Nowe powiadomienie",
"event-notifiers": "Powiadomienia o zdarzeniach",
"apprise-url-skipped-if-blank": "URL Apprise (pominięty, jeśli puste)",
@@ -160,6 +161,7 @@
"test": "Testuj",
"themes": "Motywy",
"thursday": "Czwartek",
"title": "Title",
"token": "Token",
"tuesday": "Wtorek",
"type": "Typ",
@@ -582,7 +584,22 @@
"upload-image": "Prześlij obraz",
"screen-awake": "Pozostaw ekran włączony",
"remove-image": "Usuń obraz",
"nextStep": "Następny krok"
"nextStep": "Następny krok",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Wyszukiwanie zaawansowane",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Usuń Przepisy",
"source-unit-will-be-deleted": "Jednostka źródłowa zostanie usunięta"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Utwórz alias",
"manage-aliases": "Zarządzaj aliasami",
"seed-data": "Dane przykładowe",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Algo deu errado!",
"subscribed-events": "Eventos Inscritos",
"test-message-sent": "Mensagem de teste enviada",
"message-sent": "Message Sent",
"new-notification": "Nova Notificação",
"event-notifiers": "Notificações de Eventos",
"apprise-url-skipped-if-blank": "URL Apprise (ignorado se estiver em branco)",
@@ -160,6 +161,7 @@
"test": "Teste",
"themes": "Temas",
"thursday": "Quinta-feira",
"title": "Title",
"token": "Token",
"tuesday": "Terça-feira",
"type": "Tipo",
@@ -582,7 +584,22 @@
"upload-image": "Enviar imagem",
"screen-awake": "Manter a tela ligada",
"remove-image": "Remover imagem",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Pesquisa avançada",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Excluir Receitas",
"source-unit-will-be-deleted": "Unidade de origem será excluída"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Criar Apelido",
"manage-aliases": "Gerenciar apelidos",
"seed-data": "Semear dados",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Algo correu mal!",
"subscribed-events": "Eventos Subscritos",
"test-message-sent": "Mensagem de teste enviada",
"message-sent": "Mensagem Enviada",
"new-notification": "Nova Notificação",
"event-notifiers": "Notificadores de eventos",
"apprise-url-skipped-if-blank": "URL da Apprise (ignorado se vazio)",
@@ -160,6 +161,7 @@
"test": "Teste",
"themes": "Temas",
"thursday": "Quinta-feira",
"title": "Título",
"token": "Token",
"tuesday": "Terça-feira",
"type": "Tipo",
@@ -582,7 +584,22 @@
"upload-image": "Carregar imagem",
"screen-awake": "Manter ecrã ligado",
"remove-image": "Remover imagem",
"nextStep": "Próximo passo"
"nextStep": "Próximo passo",
"recipe-actions": "Ações da Receita",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Pesquisa Avançada",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Eliminar Receitas",
"source-unit-will-be-deleted": "Unidade de origem será eliminada"
},
"recipe-actions": {
"recipe-actions-data": "Dados das Ações da Receita",
"new-recipe-action": "Nova Ação da Receita",
"edit-recipe-action": "Editar Ação da Receita",
"action-type": "Tipo de Ação"
},
"create-alias": "Criar Pseudónimo",
"manage-aliases": "Gerir Pseudónimos",
"seed-data": "Gerar dados",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Ceva nu a funcţionat corect!",
"subscribed-events": "Evenimente la care ești Abonat",
"test-message-sent": "Mesaj de test trimis",
"message-sent": "Message Sent",
"new-notification": "Notificare nouă",
"event-notifiers": "Notificatori de evenimente",
"apprise-url-skipped-if-blank": "URL Apprise (ignorat daca e gol)",
@@ -160,6 +161,7 @@
"test": "Testează",
"themes": "Teme",
"thursday": "Joi",
"title": "Title",
"token": "Token",
"tuesday": "Marţi",
"type": "Tip",
@@ -582,7 +584,22 @@
"upload-image": "Încărcare imagine",
"screen-awake": "Păstrare ecran aprins",
"remove-image": "Șterge Imaginea",
"nextStep": "Pasul următor"
"nextStep": "Pasul următor",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Căutare avansată",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Что-то пошло не так!",
"subscribed-events": "События с подпиской",
"test-message-sent": "Тестовое сообщение отправлено",
"message-sent": "Message Sent",
"new-notification": "Новое уведомление",
"event-notifiers": "Уведомления о событии",
"apprise-url-skipped-if-blank": "URL-адрес (пропущен, если пусто)",
@@ -160,6 +161,7 @@
"test": "Тест",
"themes": "Темы",
"thursday": "Четверг",
"title": "Title",
"token": "Токен",
"tuesday": "Вторник",
"type": "Тип",
@@ -582,7 +584,22 @@
"upload-image": "Загрузить изображение",
"screen-awake": "Держать экран включенным",
"remove-image": "Удалить изображение",
"nextStep": "След. шаг"
"nextStep": "След. шаг",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Расширенный поиск",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Удалить рецепты",
"source-unit-will-be-deleted": "Первая единица измерения будет удалена"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Создать псевдоним",
"manage-aliases": "Управление псевдонимами",
"seed-data": "Заполнить данные",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Vyskytla sa chyba",
"subscribed-events": "Prihlásené akcie",
"test-message-sent": "Testovacia správa bola odoslaná",
"message-sent": "Message Sent",
"new-notification": "Nové upozornenie",
"event-notifiers": "Upozornenia udalostí",
"apprise-url-skipped-if-blank": "Informačná URL (preskočená, ak je prázdna)",
@@ -160,6 +161,7 @@
"test": "Otestovať",
"themes": "Motívy",
"thursday": "Štvrtok",
"title": "Title",
"token": "Token",
"tuesday": "Utorok",
"type": "Typ",
@@ -582,7 +584,22 @@
"upload-image": "Nahrať obrázok",
"screen-awake": "Ponechať obrazovku stále zapnutú",
"remove-image": "Odstrániť obrázok",
"nextStep": "Ďalší krok"
"nextStep": "Ďalší krok",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Rozšírené vyhľadávanie",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Odstrániť recepty",
"source-unit-will-be-deleted": "Zdroj bude vymazaný"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Vytvoriť alias",
"manage-aliases": "Spravovať aliasy",
"seed-data": "Naplniť dáta",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Nekaj je šlo narobe!",
"subscribed-events": "Naročeni dogodki",
"test-message-sent": "Testno sporočilo je bilo poslano",
"message-sent": "Sporočilo poslano",
"new-notification": "Novo obvestilo",
"event-notifiers": "Obvestila o dogodkih",
"apprise-url-skipped-if-blank": "Apprise URL (preskočeno, če je prazno)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Teme",
"thursday": "Četrtek",
"title": "Naslov",
"token": "Žeton",
"tuesday": "Torek",
"type": "Tip",
@@ -460,7 +462,7 @@
"recipe-deleted": "Recept je izbrisan",
"recipe-image": "Slika recepta",
"recipe-image-updated": "Slika recepta je posodobljena",
"recipe-name": "Ime recepta",
"recipe-name": "Naslov recepta",
"recipe-settings": "Nastavitve recepta",
"recipe-update-failed": "Napaka pri posodobitvi recepta",
"recipe-updated": "Recept je posodobljen",
@@ -582,7 +584,22 @@
"upload-image": "Naloži sliko",
"screen-awake": "Ohranjanje budnega zaslona",
"remove-image": "Odstrani sliko",
"nextStep": "Naslednji korak"
"nextStep": "Naslednji korak",
"recipe-actions": "Opravila na receptu",
"parser": {
"experimental-alert-text": "Mealie uporablja procesiranje naravnega jezika za razčlenjevanje in ustvarjanje živil in enot v seznamu sestavin. Ta storitev je eksperimentalna in včasih ne deluje kot pričakovano. Če dobljenega rezultata ne želiš uporabiti, izberi 'Prekliči' in tvoje spremembe ne bodo shranjene.",
"ingredient-parser": "Razčlenjevalnik sestavin",
"explanation": "Če želiš uporabiti razčlenjevalnik sestavin, izberi 'Razčleni vse', da pričneš s postopkom. Ko bodo sestavine na voljo, lahko pregledaš podatke in preveriš, če so bili pravilno razčlenjeni. Stopnja zaupanja je prikazana desno od podatka. Ta vrednost je povprečje vseh posameznih vrednosti in ni nujno popolnoma natančna.",
"alerts-explainer": "Opozorila bodo prikazana v primeru, da obstaja ujemajoče živilo ali enota, ampak še ne obstaja v podatkovni bazi.",
"select-parser": "Izberi razčlenjevalnik",
"natural-language-processor": "Procesor naravnega jezika",
"brute-parser": "Brute Parser",
"parse-all": "Razčleni vse",
"no-unit": "Ni enote",
"missing-unit": "Ustvari manjkajočo enoto: {unit}",
"missing-food": "Ustvari manjkajoče živilo: {food}",
"no-food": "Ni živila"
}
},
"search": {
"advanced-search": "Napredno iskanje",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Izbriši recepte",
"source-unit-will-be-deleted": "Izvorna enota bo izbrisana"
},
"recipe-actions": {
"recipe-actions-data": "Podatki o opravilu na receptu",
"new-recipe-action": "Novo opravilo na receptu",
"edit-recipe-action": "Urejaj opravilo na receptu",
"action-type": "Tip opravila"
},
"create-alias": "Ustvari alias",
"manage-aliases": "Upravljanje z aliasi",
"seed-data": "Napolni podatke",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Нешто је кренуло погрешно!",
"subscribed-events": "Догађаји на које сте претплаћени",
"test-message-sent": "Тест порука је послата",
"message-sent": "Message Sent",
"new-notification": "Ново обавештење",
"event-notifiers": "Обавештавач о догађају",
"apprise-url-skipped-if-blank": "Apprise URL (прескочено ако је празно)",
@@ -160,6 +161,7 @@
"test": "Тест",
"themes": "Теме",
"thursday": "четвртак",
"title": "Title",
"token": "Токен",
"tuesday": "уторак",
"type": "Тип",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Напредна претрага",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Någonting gick fel",
"subscribed-events": "Prenumererade händelser",
"test-message-sent": "Testmeddelande Skickat",
"message-sent": "Message Sent",
"new-notification": "Ny avisering",
"event-notifiers": "Händelseavisering",
"apprise-url-skipped-if-blank": "Apprise-URL (hoppa över om tom)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Tema",
"thursday": "Torsdag",
"title": "Title",
"token": "Token",
"tuesday": "Tisdag",
"type": "Typ",
@@ -582,7 +584,22 @@
"upload-image": "Ladda upp bild",
"screen-awake": "Håll skärmen vaken",
"remove-image": "Ta bort bild",
"nextStep": "Nästa steg"
"nextStep": "Nästa steg",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Avancerad sökning",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Radera recept",
"source-unit-will-be-deleted": "Källenheten kommer att raderas"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Skapa alias",
"manage-aliases": "Hantera alias",
"seed-data": "Exempeldata",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Bir sorun oluştu!",
"subscribed-events": "Abone Olunan Etkinlikler",
"test-message-sent": "Test Mesajı Gönderildi",
"message-sent": "Message Sent",
"new-notification": "Yeni bildirim",
"event-notifiers": "Etkinlik Bildirimleri",
"apprise-url-skipped-if-blank": "Apprise URL'si (boşsa geçilir)",
@@ -160,6 +161,7 @@
"test": "Dene",
"themes": "Temalar",
"thursday": "Perşembe",
"title": "Title",
"token": "Anahtar",
"tuesday": "Salı",
"type": "Tür",
@@ -582,7 +584,22 @@
"upload-image": "Resim yükleyin",
"screen-awake": "Ekranıık Tut",
"remove-image": "Resmi kaldır",
"nextStep": "Sonraki adım"
"nextStep": "Sonraki adım",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Takma Ad Oluştur",
"manage-aliases": "Takma Adları Yönet",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Щось пішло не так!",
"subscribed-events": "Події, на які підписано",
"test-message-sent": "Тестове повідомлення надіслано",
"message-sent": "Повідомлення надіслано",
"new-notification": "Нове сповіщення",
"event-notifiers": "Сповіщувачі",
"apprise-url-skipped-if-blank": "Apprise URL (пропущено якщо порожній)",
@@ -160,6 +161,7 @@
"test": "Тест",
"themes": "Теми",
"thursday": "Четвер",
"title": "Назва",
"token": "Токен",
"tuesday": "Вівторок",
"type": "Тип",
@@ -582,7 +584,22 @@
"upload-image": "Вивантажити зображення",
"screen-awake": "Тримати екран активним",
"remove-image": "Видалити зображення",
"nextStep": "Наступний крок"
"nextStep": "Наступний крок",
"recipe-actions": "Дії рецепту",
"parser": {
"experimental-alert-text": "Mealie використовує аналіз природної мови для аналізу та створення інгредієнтів та одиниць виміру. Це експериментальна функція і може не завжди працювати належним чином. Якщо ви не хочете використовувати результати аналізу виберіть \"Скасувати\", і зміни не будуть збережені.",
"ingredient-parser": "Аналізатор інгредієнтів",
"explanation": "Щоб використати аналізатор інгредієнтів, натисніть кнопку 'Аналізувати все', щоб запустити процес. Після того, як інгредієнти проаналізовані, ви можете їх переглянути та переконатися, що вони були проаналізовані правильно. Оцінка надійності аналізу відображена праворуч від назви елемента. Ця оцінка розраховується як середнє значення усіх індивідуальних оцінок і не завжди може бути абсолютно точним.",
"alerts-explainer": "Оповіщення будуть відображатися, якщо знайдені продукти або одиниці знайдені яких не існує в базі даних.",
"select-parser": "Вибрати аналізатор",
"natural-language-processor": "Аналізатор природної мови",
"brute-parser": "Простий аналізатор",
"parse-all": "Аналізувати все",
"no-unit": "Без одиниці",
"missing-unit": "Створити відсутню одиниці: {unit}",
"missing-food": "Створити відсутню їжу: {food}",
"no-food": "Немає їжі"
}
},
"search": {
"advanced-search": "Розширений пошук",
@@ -794,7 +811,7 @@
"language": "Мова",
"maintenance": "Обслуговування",
"background-tasks": "Фонові завдання",
"parser": "Парсер",
"parser": "Синтаксичний аналізатор (парсер)",
"developer": "Розробник",
"cookbook": "Кулінарна книга",
"create-cookbook": "Створити нову кулінарну книгу"
@@ -1001,6 +1018,12 @@
"delete-recipes": "Видалити рецепти",
"source-unit-will-be-deleted": "Початкову одиницю вимірювання буде видалено"
},
"recipe-actions": {
"recipe-actions-data": "Дані дій рецепта",
"new-recipe-action": "Нова дія рецепту",
"edit-recipe-action": "Редагувати дії рецепта",
"action-type": "Тип Дії"
},
"create-alias": "Створити псевдонім",
"manage-aliases": "Керувати псевдонімами",
"seed-data": "Підготовлені дані",
@@ -1151,7 +1174,7 @@
"ingredient-text": "Текст інгредієнта",
"average-confident": "Впевненість {0}",
"try-an-example": "Спробувати приклад",
"parser": "Парсер",
"parser": "Синтаксичний аналізатор (парсер)",
"background-tasks": "Фонові задачі",
"background-tasks-description": "Тут ви можете переглянути всі запущені фонові задачі та їх статус",
"no-logs-found": "Журналів не знайдено",
@@ -1168,21 +1191,21 @@
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "👋 Ласкаво просимо, {0}!",
"description": "Керування вашим профілем, рецептами та налаштуваннями групи.",
"get-invite-link": "Отримати посилання-запрошення",
"get-public-link": "Отримати публічне посилання",
"account-summary": "Аккаунт",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Ось підсумок інформації про вашу групу.",
"group-statistics": "Статистика групи",
"group-statistics-description": "Статистика вашої групи дає можливість зрозуміти, як ви користуєтеся Mealie.",
"storage-capacity": "Обсяг сховища",
"storage-capacity-description": "Об'єм сховища це сума зображені та відвантажених медіаресурсів.",
"personal": "Особисте",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Це особисті налаштування. Зміни тут не впливають на інших користувачів.",
"user-settings": "Налаштування користувача",
"user-settings-description": "Manage your preferences, change your password, and update your email.",
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"user-settings-description": "Керуйте вашими налаштуваннями, змінюйте пароль і оновлюйте адресу електронної пошти.",
"api-tokens-description": "Керуйте своїми ключами API для доступу із зовнішніх програм.",
"group-description": "Ці елементи є спільними для вашої групи. Редагування одного з них змінить його для всієї групи!",
"group-settings": "Налаштування групи",
"group-settings-description": "Керуйте спільними налаштуванням груп, такими як плани харчування і налаштування конфіденційності.",
@@ -1193,9 +1216,9 @@
"notifiers": "Сповіщувачі",
"notifiers-description": "Налаштуйте email та push сповіщення, що спрацьовують для певних подій.",
"manage-data": "Керування даними",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Керуйте своїми даними Mealie; їжа, одиниці, категорії, мітки та багато іншого.",
"data-migrations": "Міграції даних",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"data-migrations-description": "Перенести наявні дані з таких програм, як Nextcloud Recipes і Chowdown.",
"email-sent": "Лист надіслано",
"error-sending-email": "Помилка надсилання листа",
"personal-information": "Персональні данні",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent",
"message-sent": "Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "Test",
"themes": "Themes",
"thursday": "Thursday",
"title": "Title",
"token": "Token",
"tuesday": "Tuesday",
"type": "Type",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "Advanced Search",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "出错了\t#",
"subscribed-events": "订阅事件",
"test-message-sent": "测试消息已发送",
"message-sent": "Message Sent",
"new-notification": "新通知",
"event-notifiers": "事件通知器",
"apprise-url-skipped-if-blank": "Apprise URL (如果为空则跳过)",
@@ -160,6 +161,7 @@
"test": "测试",
"themes": "布景主题",
"thursday": "周四",
"title": "Title",
"token": "密钥",
"tuesday": "周二",
"type": "类型",
@@ -582,7 +584,22 @@
"upload-image": "上传图片",
"screen-awake": "保持屏幕唤醒",
"remove-image": "删除图片",
"nextStep": "下一步"
"nextStep": "下一步",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "高级搜索",
@@ -1001,6 +1018,12 @@
"delete-recipes": "删除食谱",
"source-unit-will-be-deleted": "“待合并单位”将会被删除"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "创建别名",
"manage-aliases": "管理别名",
"seed-data": "初始数据",

View File

@@ -64,6 +64,7 @@
"something-went-wrong": "出了點問題...",
"subscribed-events": "關注的事件",
"test-message-sent": "測試訊息已發送",
"message-sent": "Message Sent",
"new-notification": "新通知",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
@@ -160,6 +161,7 @@
"test": "測試",
"themes": "佈景主題",
"thursday": "星期四",
"title": "Title",
"token": "密鑰",
"tuesday": "星期二",
"type": "類型",
@@ -582,7 +584,22 @@
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
"remove-image": "Remove image",
"nextStep": "Next step"
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"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.",
"ingredient-parser": "Ingredient Parser",
"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.",
"alerts-explainer": "Alerts will be displayed if a matching foods or unit is found but does not exists in the database.",
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"no-food": "No Food"
}
},
"search": {
"advanced-search": "進階搜尋",
@@ -1001,6 +1018,12 @@
"delete-recipes": "Delete Recipes",
"source-unit-will-be-deleted": "Source Unit will be deleted"
},
"recipe-actions": {
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"seed-data": "Seed Data",

View File

@@ -9,6 +9,7 @@ import { UtilsAPI } from "./user/utils";
import { FoodAPI } from "./user/recipe-foods";
import { UnitAPI } from "./user/recipe-units";
import { CookbookAPI } from "./user/group-cookbooks";
import { GroupRecipeActionsAPI } from "./user/group-recipe-actions";
import { WebhooksAPI } from "./user/group-webhooks";
import { RegisterAPI } from "./user/user-registration";
import { MealPlanAPI } from "./user/group-mealplan";
@@ -36,6 +37,7 @@ export class UserApiClient {
public foods: FoodAPI;
public units: UnitAPI;
public cookbooks: CookbookAPI;
public groupRecipeActions: GroupRecipeActionsAPI;
public groupWebhooks: WebhooksAPI;
public register: RegisterAPI;
public mealplans: MealPlanAPI;
@@ -65,6 +67,7 @@ export class UserApiClient {
this.users = new UserApi(requests);
this.groups = new GroupAPI(requests);
this.cookbooks = new CookbookAPI(requests);
this.groupRecipeActions = new GroupRecipeActionsAPI(requests);
this.groupWebhooks = new WebhooksAPI(requests);
this.register = new RegisterAPI(requests);
this.mealplans = new MealPlanAPI(requests);

View File

@@ -5,6 +5,9 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type RecipeActionType =
| "link"
| "post";
export type WebhookType = "mealplan";
export type SupportedMigrations =
| "nextcloud"
@@ -26,6 +29,11 @@ export interface CreateGroupPreferences {
recipeDisableAmount?: boolean;
groupId: string;
}
export interface CreateGroupRecipeAction {
actionType: RecipeActionType;
title: string;
url: string;
}
export interface CreateInviteToken {
uses: number;
}
@@ -191,6 +199,13 @@ export interface GroupEventNotifierUpdate {
options?: GroupEventNotifierOptions;
id: string;
}
export interface GroupRecipeActionOut {
actionType: RecipeActionType;
title: string;
url: string;
groupId: string;
id: string;
}
export interface GroupStatistics {
totalRecipes: number;
totalUsers: number;
@@ -230,6 +245,12 @@ export interface ReadWebhook {
groupId: string;
id: string;
}
export interface SaveGroupRecipeAction {
actionType: RecipeActionType;
title: string;
url: string;
groupId: string;
}
export interface SaveInviteToken {
usesLeft: number;
groupId: string;

View File

@@ -0,0 +1,14 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/group";
const prefix = "/api";
const routes = {
groupRecipeActions: `${prefix}/groups/recipe-actions`,
groupRecipeActionsId: (id: string | number) => `${prefix}/groups/recipe-actions/${id}`,
};
export class GroupRecipeActionsAPI extends BaseCRUDAPI<CreateGroupRecipeAction, GroupRecipeActionOut> {
baseRoute = routes.groupRecipeActions;
itemRoute = routes.groupRecipeActionsId;
}

View File

@@ -1,10 +1,9 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { QueryValue, route } from "~/lib/api/base/route";
import { PaginationData, RequestResponse } from "~/lib/api/types/non-generated";
import { PaginationData } from "~/lib/api/types/non-generated";
import {
ChangePassword,
DeleteTokenResponse,
GroupInDB,
LongLiveTokenIn,
LongLiveTokenOut,
ResetPassword,
@@ -30,7 +29,6 @@ const routes = {
groupUsers: `${prefix}/users/group-users`,
usersSelf: `${prefix}/users/self`,
ratingsSelf: `${prefix}/users/self/ratings`,
groupsSelf: `${prefix}/users/self/group`,
passwordReset: `${prefix}/users/reset-password`,
passwordChange: `${prefix}/users/password`,
users: `${prefix}/users`,
@@ -57,10 +55,6 @@ export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> {
return await this.requests.get<PaginationData<UserSummary>>(route(routes.groupUsers, { page, perPage, ...params }));
}
async getSelfGroup(): Promise<RequestResponse<GroupInDB>> {
return await this.requests.get(routes.groupsSelf, {});
}
async addFavorite(id: string, slug: string) {
return await this.requests.post(routes.usersIdFavoritesSlug(id, slug), {});
}

View File

@@ -156,6 +156,7 @@ export default {
propertyName: "access_token",
},
refresh: { url: "api/auth/refresh", method: "post" },
logout: { url: "api/auth/logout", method: "post" },
user: { url: "api/users/self", method: "get" },
},
},
@@ -260,6 +261,12 @@ export default {
"en-GB": require("./lang/dateTimeFormats/en-GB.json"),
"fi-FI": require("./lang/dateTimeFormats/fi-FI.json"),
"vi-VN": require("./lang/dateTimeFormats/vi-VN.json"),
"sl-SI": require("./lang/dateTimeFormats/sl-SI.json"),
"lv-LV": require("./lang/dateTimeFormats/lv-LV.json"),
"is-IS": require("./lang/dateTimeFormats/is-IS.json"),
"gl-ES": require("./lang/dateTimeFormats/gl-ES.json"),
"lt-LT": require("./lang/dateTimeFormats/lt-LT.json"),
"hr-HR": require("./lang/dateTimeFormats/hr-HR.json"),
// END: DATE_LOCALES
},
fallbackLocale: "en-US",

View File

@@ -2,35 +2,30 @@
<v-container v-if="recipe">
<v-container>
<v-alert dismissible border="left" colored-border type="warning" elevation="2" :icon="$globals.icons.alert">
<b>Experimental Feature</b>
<b>{{ $tc("banner-experimental.title") }}</b>
<div>
Mealie can use natural language processing to attempt to parse and create units, and foods for your Recipe
ingredients. This is experimental and may not work as expected. If you choose to not use the parsed results
you can select cancel and your changes will not be saved.
{{ $tc("recipe.parser.experimental-alert-text") }}
</div>
</v-alert>
<BaseCardSectionTitle title="Ingredients Processor">
To use the ingredient parser, click the "Parse All" button and the process will start. When the processed
ingredients are available, you can look through the items and verify that they were parsed correctly. The models
confidence score is displayed on the right of the title item. This is an average of all scores and may not be
wholely accurate.
<BaseCardSectionTitle :title="$tc('recipe.parser.ingredient-parser')">
<div class="mt-4">{{ $tc("recipe.parser.explanation") }}</div>
<div class="my-4">
Alerts will be displayed if a matching foods or unit is found but does not exists in the database.
{{ $tc("recipe.parser.alerts-explainer") }}
</div>
<div class="d-flex align-center mb-n4">
<div class="mb-4">Select Parser</div>
<div class="mb-4">{{ $tc("recipe.parser.select-parser") }}</div>
<BaseOverflowButton
v-model="parser"
btn-class="mx-2 mb-4"
:items="[
{
text: 'Natural Language Processor ',
text: $tc('recipe.parser.natural-language-processor'),
value: 'nlp',
},
{
text: 'Brute Parser',
text: $tc('recipe.parser.brute-parser'),
value: 'brute',
},
]"
@@ -42,9 +37,9 @@
<BaseButton cancel class="mr-auto" @click="$router.go(-1)"></BaseButton>
<BaseButton color="info" @click="fetchParsed">
<template #icon> {{ $globals.icons.foods }}</template>
Parse All
{{ $tc("recipe.parser.parse-all") }}
</BaseButton>
<BaseButton save @click="saveAll"> Save All </BaseButton>
<BaseButton save @click="saveAll" />
</div>
<v-expansion-panels v-model="panels" multiple>
@@ -145,6 +140,8 @@ export default defineComponent({
const slug = route.value.params.slug;
const api = useUserApi();
const { i18n } = useContext();
const { recipe, loading } = useRecipe(slug);
invoke(async () => {
@@ -170,13 +167,15 @@ export default defineComponent({
if (unitError || foodError) {
if (unitError) {
if (ing?.ingredient?.unit?.name) {
unitErrorMessage = `Create missing unit '${ing?.ingredient?.unit?.name || "No unit"}'`;
const unit = ing.ingredient.unit.name || i18n.tc("recipe.parser.no-unit");
unitErrorMessage = i18n.t("recipe.parser.missing-unit", { unit }).toString();
}
}
if (foodError) {
if (ing?.ingredient?.food?.name) {
foodErrorMessage = `Create missing food '${ing.ingredient.food.name || "No food"}'?`;
const food = ing.ingredient.food.name || i18n.tc("recipe.parser.no-food");
foodErrorMessage = i18n.t("recipe.parser.missing-food", { food }).toString();
}
}
}
@@ -364,7 +363,7 @@ export default defineComponent({
},
head() {
return {
title: "Parser",
title: this.$tc("recipe.parser.ingredient-parser"),
};
},
});

View File

@@ -41,6 +41,7 @@ export default defineComponent({
const { i18n } = useContext();
const buttonLookup: { [key: string]: string } = {
recipes: i18n.tc("general.recipes"),
recipeActions: i18n.tc("recipe.recipe-actions"),
foods: i18n.tc("general.foods"),
units: i18n.tc("general.units"),
labels: i18n.tc("data-pages.labels.labels"),
@@ -56,6 +57,11 @@ export default defineComponent({
text: i18n.t("general.recipes"),
value: "new",
to: "/group/data/recipes",
},
{
text: i18n.t("recipe.recipe-actions"),
value: "new",
to: "/group/data/recipe-actions",
divider: true,
},
{
@@ -92,7 +98,13 @@ export default defineComponent({
]);
const buttonText = computed(() => {
const last = route.value.path.split("/").pop();
const last = route.value.path
.split("/")
.pop()
// convert hypenated-values to camelCase
?.replace(/-([a-z])/g, function (g) {
return g[1].toUpperCase();
})
if (last) {
return buttonLookup[last];

View File

@@ -17,6 +17,7 @@
v-model="state.editDialog"
:icon="$globals.icons.tags"
:title="$t('data-pages.labels.edit-label')"
:submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')"
@submit="editSaveLabel"
>

View File

@@ -0,0 +1,265 @@
<template>
<div>
<!-- Create Dialog -->
<BaseDialog
v-model="state.createDialog"
:title="$t('data-pages.recipe-actions.new-recipe-action')"
:icon="$globals.icons.primary"
@submit="createAction"
>
<v-card-text>
<v-form ref="domNewActionForm">
<v-text-field
v-model="createTarget.title"
autofocus
:label="$t('general.title')"
:rules="[validators.required]"
/>
<v-text-field
v-model="createTarget.url"
:label="$t('general.url')"
:rules="[validators.required]"
/>
<v-select
v-model="createTarget.actionType"
:items="actionTypeOptions"
:label="$t('data-pages.recipe-actions.action-type')"
:rules="[validators.required]"
/>
</v-form>
</v-card-text>
</BaseDialog>
<!-- Edit Dialog -->
<BaseDialog
v-model="state.editDialog"
:icon="$globals.icons.primary"
:title="$t('data-pages.recipe-actions.edit-recipe-action')"
:submit-text="$tc('general.save')"
@submit="editSaveAction"
>
<v-card-text v-if="editTarget">
<div class="mt-4">
<v-text-field v-model="editTarget.title" :label="$t('general.title')"/>
</div>
<div class="mt-4">
<v-text-field v-model="editTarget.url" :label="$t('general.url')"/>
</div>
<div class="mt-4">
<v-select
v-model="editTarget.actionType"
:items="actionTypeOptions"
:label="$t('data-pages.recipe-actions.action-type')"
/>
</div>
</v-card-text>
</BaseDialog>
<!-- Delete Dialog -->
<BaseDialog
v-model="state.deleteDialog"
:title="$tc('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error"
@confirm="deleteAction"
>
<v-card-text>
{{ $t("general.confirm-delete-generic") }}
<p v-if="deleteTarget" class="mt-4 ml-4">{{ deleteTarget.title }}</p>
</v-card-text>
</BaseDialog>
<!-- Bulk Delete Dialog -->
<BaseDialog
v-model="state.bulkDeleteDialog"
width="650px"
:title="$tc('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error"
@confirm="deleteSelected"
>
<v-card-text>
<p class="h4">{{ $t('general.confirm-delete-generic-items') }}</p>
<v-card outlined>
<v-virtual-scroll height="400" item-height="25" :items="bulkDeleteTarget">
<template #default="{ item }">
<v-list-item class="pb-2">
<v-list-item-content>
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-virtual-scroll>
</v-card>
</v-card-text>
</BaseDialog>
<!-- Data Table -->
<BaseCardSectionTitle :icon="$globals.icons.primary" section :title="$tc('data-pages.recipe-actions.recipe-actions-data')"> </BaseCardSectionTitle>
<CrudTable
:table-config="tableConfig"
:headers.sync="tableHeaders"
:data="actions || []"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"
>
<template #button-row>
<BaseButton create @click="state.createDialog = true">{{ $t("general.create") }}</BaseButton>
</template>
<template #item.onHand="{ item }">
<v-icon :color="item.onHand ? 'success' : undefined">
{{ item.onHand ? $globals.icons.check : $globals.icons.close }}
</v-icon>
</template>
</CrudTable>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
import { validators } from "~/composables/use-validators";
import { useGroupRecipeActions, useGroupRecipeActionData } from "~/composables/use-group-recipe-actions";
import { GroupRecipeActionOut } from "~/lib/api/types/group";
export default defineComponent({
setup() {
const { i18n } = useContext();
const tableConfig = {
hideColumns: true,
canExport: true,
};
const tableHeaders = [
{
text: i18n.t("general.id"),
value: "id",
show: false,
},
{
text: i18n.t("general.title"),
value: "title",
show: true,
},
{
text: i18n.t("general.url"),
value: "url",
show: true,
},
{
text: i18n.t("data-pages.recipe-actions.action-type"),
value: "actionType",
show: true,
},
];
const state = reactive({
createDialog: false,
editDialog: false,
deleteDialog: false,
bulkDeleteDialog: false,
});
const actionData = useGroupRecipeActionData();
const actionStore = useGroupRecipeActions(null, null);
const actionTypeOptions = ["link", "post"]
// ============================================================
// Create Action
async function createAction() {
// @ts-ignore groupId isn't required
await actionStore.actions.createOne({
actionType: actionData.data.actionType,
title: actionData.data.title,
url: actionData.data.url,
});
actionData.reset();
state.createDialog = false;
}
// ============================================================
// Edit Action
const editTarget = ref<GroupRecipeActionOut | null>(null);
function editEventHandler(item: GroupRecipeActionOut) {
state.editDialog = true;
editTarget.value = item;
}
async function editSaveAction() {
if (!editTarget.value) {
return;
}
await actionStore.actions.updateOne(editTarget.value);
state.editDialog = false;
}
// ============================================================
// Delete Action
const deleteTarget = ref<GroupRecipeActionOut | null>(null);
function deleteEventHandler(item: GroupRecipeActionOut) {
state.deleteDialog = true;
deleteTarget.value = item;
}
async function deleteAction() {
if (!deleteTarget.value || deleteTarget.value.id === undefined) {
return;
}
await actionStore.actions.deleteOne(deleteTarget.value.id);
state.deleteDialog = false;
}
// ============================================================
// Bulk Delete Action
const bulkDeleteTarget = ref<GroupRecipeActionOut[]>([]);
function bulkDeleteEventHandler(selection: GroupRecipeActionOut[]) {
bulkDeleteTarget.value = selection;
state.bulkDeleteDialog = true;
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
await actionStore.actions.deleteOne(item.id);
}
bulkDeleteTarget.value = [];
}
return {
state,
tableConfig,
tableHeaders,
actionTypeOptions,
actions: actionStore.recipeActions,
validators,
// create
createTarget: actionData.data,
createAction,
// edit
editTarget,
editEventHandler,
editSaveAction,
// delete
deleteTarget,
deleteEventHandler,
deleteAction,
// bulk delete
bulkDeleteTarget,
bulkDeleteEventHandler,
deleteSelected,
};
},
});
</script>

View File

@@ -201,7 +201,7 @@ export default defineComponent({
}
function isDirectLogin() {
return router.currentRoute.query.direct
return Object.keys(router.currentRoute.query).includes("direct")
}
async function oidcAuthenticate() {

View File

@@ -58,11 +58,18 @@
</div>
<!-- Reorder Labels -->
<BaseDialog v-model="reorderLabelsDialog" :icon="$globals.icons.tagArrowUp" :title="$t('shopping-list.reorder-labels')">
<BaseDialog
v-model="reorderLabelsDialog"
:icon="$globals.icons.tagArrowUp"
:title="$t('shopping-list.reorder-labels')"
:submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')"
@submit="saveLabelOrder"
@close="cancelLabelOrder">
<v-card height="fit-content" max-height="70vh" style="overflow-y: auto;">
<draggable :value="shoppingList.labelSettings" handle=".handle" class="my-2" @start="loadingCounter += 1" @end="loadingCounter -= 1" @input="updateLabelOrder">
<div v-for="(labelSetting, index) in shoppingList.labelSettings" :key="labelSetting.id">
<MultiPurposeLabelSection v-model="shoppingList.labelSettings[index]" use-color />
<draggable v-if="localLabels" :value="localLabels" handle=".handle" class="my-2" @input="updateLabelOrder">
<div v-for="(labelSetting, index) in localLabels" :key="labelSetting.id">
<MultiPurposeLabelSection v-model="localLabels[index]" use-color />
</div>
</draggable>
</v-card>
@@ -103,7 +110,9 @@
/>
</div>
<div v-else class="mt-4 d-flex justify-end">
<BaseButton v-if="preferences.viewByLabel" edit class="mr-2" @click="reorderLabelsDialog = true">
<BaseButton
v-if="preferences.viewByLabel" edit class="mr-2"
@click="toggleReorderLabelsDialog">
<template #icon> {{ $globals.icons.tags }} </template>
{{ $t('shopping-list.reorder-labels') }}
</BaseButton>
@@ -279,6 +288,7 @@ export default defineComponent({
const edit = ref(false);
const reorderLabelsDialog = ref(false);
const settingsDialog = ref(false);
const preserveItemOrder = ref(false);
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
@@ -299,8 +309,19 @@ export default defineComponent({
loadingCounter.value -= 1;
// only update the list with the new value if we're not loading, to prevent UI jitter
if (!loadingCounter.value) {
shoppingList.value = newListValue;
if (loadingCounter.value) {
return;
}
shoppingList.value = newListValue;
updateListItemOrder();
}
function updateListItemOrder() {
if (!preserveItemOrder.value) {
groupAndSortListItemsByFood();
updateItemsByLabel();
} else {
sortListItems();
updateItemsByLabel();
}
@@ -480,6 +501,8 @@ export default defineComponent({
// Labels, Units, Foods
// TODO: Extract to Composable
const localLabels = ref<ShoppingListMultiPurposeLabelOut[]>()
const { labels: allLabels } = useLabelStore();
const { units: allUnits } = useUnitStore();
const { foods: allFoods } = useFoodStore();
@@ -493,7 +516,10 @@ export default defineComponent({
}
function toggleReorderLabelsDialog() {
// stop polling and populate localLabels
loadingCounter.value += 1
reorderLabelsDialog.value = !reorderLabelsDialog.value
localLabels.value = shoppingList.value?.labelSettings
}
async function toggleSettingsDialog() {
@@ -503,7 +529,7 @@ export default defineComponent({
settingsDialog.value = !settingsDialog.value;
}
async function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) {
function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) {
if (!shoppingList.value) {
return;
}
@@ -513,16 +539,31 @@ export default defineComponent({
return labelSetting;
});
// setting this doesn't have any effect on the data since it's refreshed automatically, but it makes the ux feel smoother
shoppingList.value.labelSettings = labelSettings;
updateItemsByLabel();
localLabels.value = labelSettings
}
function cancelLabelOrder() {
loadingCounter.value -= 1
if (!shoppingList.value) {
return;
}
// restore original state
localLabels.value = shoppingList.value.labelSettings
}
async function saveLabelOrder() {
if (!shoppingList.value || !localLabels.value || (localLabels.value === shoppingList.value.labelSettings)) {
return;
}
loadingCounter.value += 1;
const { data } = await userApi.shopping.lists.updateLabelSettings(shoppingList.value.id, labelSettings);
const { data } = await userApi.shopping.lists.updateLabelSettings(shoppingList.value.id, localLabels.value);
loadingCounter.value -= 1;
if (data) {
refresh();
// update shoppingList labels using the API response
shoppingList.value.labelSettings = (data as ShoppingListOut).labelSettings;
updateItemsByLabel();
}
}
@@ -543,12 +584,62 @@ export default defineComponent({
const itemsByLabel = ref<{ [key: string]: ShoppingListItemOut[] }>({});
interface ListItemGroup {
position: number;
createdAt: string;
items: ShoppingListItemOut[];
}
function groupAndSortListItemsByFood() {
if (!shoppingList.value?.listItems?.length) {
return;
}
const checkedItemKey = "__checkedItem"
const listItemGroupsMap = new Map<string, ListItemGroup>();
listItemGroupsMap.set(checkedItemKey, {position: Number.MAX_SAFE_INTEGER, createdAt: "", items: []});
// group items by checked status, food, or note
shoppingList.value.listItems.forEach((item) => {
const key = item.checked ? checkedItemKey : item.isFood && item.food?.name
? item.food.name
: item.note || ""
const group = listItemGroupsMap.get(key);
if (!group) {
listItemGroupsMap.set(key, {position: item.position || 0, createdAt: item.createdAt || "", items: [item]});
} else {
group.items.push(item);
}
});
// sort group items by position ascending, then createdAt descending
const listItemGroups = Array.from(listItemGroupsMap.values());
listItemGroups.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1));
// sort group items by position ascending, then createdAt descending, and aggregate them
const sortedItems: ShoppingListItemOut[] = [];
let nextPosition = 0;
listItemGroups.forEach((listItemGroup) => {
// @ts-ignore none of these fields are undefined
listItemGroup.items.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1));
listItemGroup.items.forEach((item) => {
item.position = nextPosition;
nextPosition += 1;
sortedItems.push(item);
})
});
shoppingList.value.listItems = sortedItems;
}
function sortListItems() {
if (!shoppingList.value?.listItems?.length) {
return;
}
// sort by position ascending, then createdAt descending
// @ts-ignore none of these fields are undefined
shoppingList.value.listItems.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1))
}
@@ -682,8 +773,7 @@ export default defineComponent({
});
}
sortListItems();
updateItemsByLabel();
updateListItemOrder();
loadingCounter.value += 1;
const { data } = await userApi.shopping.items.updateOne(item.id, item);
@@ -759,6 +849,9 @@ export default defineComponent({
shoppingList.value.listItems = uncheckedItems.concat(listItems.value.checked);
}
// since the user has manually reordered the list, we should preserve this order
preserveItemOrder.value = true;
updateListItems();
}
@@ -776,6 +869,9 @@ export default defineComponent({
allUncheckedItems.push(...itemsByLabel.value[labelName]);
}
// since the user has manually reordered the list, we should preserve this order
preserveItemOrder.value = true;
// save changes
return updateIndexUnchecked(allUncheckedItems);
}
@@ -873,7 +969,10 @@ export default defineComponent({
toggleReorderLabelsDialog,
settingsDialog,
toggleSettingsDialog,
localLabels,
updateLabelOrder,
cancelLabelOrder,
saveLabelOrder,
saveListItem,
shoppingList,
showChecked,

View File

@@ -190,7 +190,6 @@
<script lang="ts">
import { computed, defineComponent, useContext, ref, toRefs, reactive, useAsync, useRoute } from "@nuxtjs/composition-api";
import { invoke, until } from "@vueuse/core";
import UserProfileLinkCard from "@/components/Domain/User/UserProfileLinkCard.vue";
import { useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators";
@@ -198,7 +197,7 @@ import { alert } from "~/composables/use-toast";
import UserAvatar from "@/components/Domain/User/UserAvatar.vue";
import { useAsyncKey } from "~/composables/use-utils";
import StatsCards from "~/components/global/StatsCards.vue";
import { GroupInDB, UserOut } from "~/lib/api/types/user";
import { UserOut } from "~/lib/api/types/user";
export default defineComponent({
name: "UserProfile",
@@ -216,7 +215,6 @@ export default defineComponent({
// @ts-ignore $auth.user is typed as unknown, but it's a user
const user = computed<UserOut | null>(() => $auth.user);
const group = ref<GroupInDB | null>(null);
const showPublicLink = ref(false);
const publicLink = ref("");
@@ -225,16 +223,6 @@ export default defineComponent({
const token = ref("");
const api = useUserApi();
invoke(async () => {
await until(user.value).not.toBeNull();
if (!user.value) {
return;
}
const { data } = await api.users.getSelfGroup();
group.value = data;
});
async function getSignupLink() {
const { data } = await api.groups.createInvitation({ uses: 1 });
if (data) {
@@ -333,7 +321,6 @@ export default defineComponent({
getStatsTitle,
getStatsIcon,
getStatsTo,
group,
stats,
user,
constructLink,

View File

@@ -9,14 +9,12 @@ export default class DynamicOpenIDConnectScheme extends OpenIDConnectScheme {
async mounted() {
await this.getConfiguration();
this.options.scope = ["openid", "profile", "email", "groups"]
this.configurationDocument = new ConfigurationDocument(
this,
this.$auth.$storage
)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return await super.mounted()
}
@@ -78,7 +76,10 @@ export default class DynamicOpenIDConnectScheme extends OpenIDConnectScheme {
})
// Update tokens with mealie token
this.updateTokens(response)
} catch {
} catch (e) {
if (e.response?.status === 401 || e.response?.status === 500) {
this.$auth.reset()
}
const currentUrl = new URL(window.location.href)
if (currentUrl.pathname === "/login" && currentUrl.searchParams.has("direct")) {
return
@@ -109,6 +110,11 @@ export default class DynamicOpenIDConnectScheme extends OpenIDConnectScheme {
const data = await response.json();
this.options.endpoints.configuration = data.configurationUrl;
this.options.clientId = data.clientId;
this.options.scope = ["openid", "profile", "email"]
if (data.groupsClaim !== null) {
this.options.scope.push(data.groupsClaim)
}
console.log(this.options.scope)
} catch (error) {
// pass
}

View File

@@ -5,9 +5,10 @@ from pathlib import Path
from uuid import uuid4
import fastapi
import jwt
from fastapi import BackgroundTasks, Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from jwt.exceptions import PyJWTError
from sqlalchemy.orm.session import Session
from mealie.core import root_logger
@@ -96,8 +97,8 @@ async def get_current_user(
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
user_id: str = payload.get("sub")
long_token: str = payload.get("long_token")
user_id: str | None = payload.get("sub")
long_token: str | None = payload.get("long_token")
if long_token is not None:
return validate_long_live_token(session, token, payload.get("id"))
@@ -106,7 +107,7 @@ async def get_current_user(
raise credentials_exception
token_data = TokenData(user_id=user_id)
except JWTError as e:
except PyJWTError as e:
raise credentials_exception from e
repos = get_repositories(session)
@@ -126,7 +127,7 @@ async def get_integration_id(token: str = Depends(oauth2_scheme)) -> str:
decoded_token = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
return decoded_token.get("integration_id", DEFAULT_INTEGRATION_ID)
except JWTError as e:
except PyJWTError as e:
raise credentials_exception from e
@@ -162,7 +163,7 @@ def validate_file_token(token: str | None = None) -> Path:
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
file_path = Path(payload.get("file"))
except JWTError as e:
except PyJWTError as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="could not validate file token",
@@ -181,7 +182,7 @@ def validate_recipe_token(token: str | None = None) -> str:
Raises:
HTTPException: 400 Bad Request when no token or the recipe doesn't exist
HTTPException: 401 JWTError when token is invalid
HTTPException: 401 PyJWTError when token is invalid
Returns:
str: token data
@@ -192,7 +193,7 @@ def validate_recipe_token(token: str | None = None) -> str:
try:
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
slug: str | None = payload.get("slug")
except JWTError as e:
except PyJWTError as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="could not validate file token",

View File

@@ -2,7 +2,7 @@ import abc
from datetime import datetime, timedelta, timezone
from typing import Generic, TypeVar
from jose import jwt
import jwt
from sqlalchemy.orm.session import Session
from mealie.core.config import get_app_settings

View File

@@ -35,17 +35,19 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
repos = get_repositories(self.session)
user = self.try_get_user(claims.get(settings.OIDC_USER_CLAIM))
group_claim = claims.get("groups", [])
is_admin = settings.OIDC_ADMIN_GROUP in group_claim if settings.OIDC_ADMIN_GROUP else False
is_valid_user = settings.OIDC_USER_GROUP in group_claim if settings.OIDC_USER_GROUP else True
is_admin = False
if settings.OIDC_USER_GROUP or settings.OIDC_ADMIN_GROUP:
group_claim = claims.get(settings.OIDC_GROUPS_CLAIM, [])
is_admin = settings.OIDC_ADMIN_GROUP in group_claim if settings.OIDC_ADMIN_GROUP else False
is_valid_user = settings.OIDC_USER_GROUP in group_claim if settings.OIDC_USER_GROUP else True
if not is_valid_user:
self._logger.debug(
"[OIDC] User does not have the required group. Found: %s - Required: %s",
group_claim,
settings.OIDC_USER_GROUP,
)
return None
if not is_valid_user:
self._logger.debug(
"[OIDC] User does not have the required group. Found: %s - Required: %s",
group_claim,
settings.OIDC_USER_GROUP,
)
return None
if not user:
if not settings.OIDC_SIGNUP_ENABLED:
@@ -68,18 +70,18 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
return self.get_access_token(user, settings.OIDC_REMEMBER_ME) # type: ignore
if user:
if user.admin != is_admin:
if settings.OIDC_ADMIN_GROUP and user.admin != is_admin:
self._logger.debug(f"[OIDC] {'Setting' if is_admin else 'Removing'} user as admin")
user.admin = is_admin
repos.users.update(user.id, user)
return self.get_access_token(user, settings.OIDC_REMEMBER_ME)
self._logger.info("[OIDC] Found user but their AuthMethod does not match OIDC")
self._logger.warning("[OIDC] Found user but their AuthMethod does not match OIDC")
return None
def get_claims(self, settings: AppSettings) -> JWTClaims | None:
"""Get the claims from the ID token and check if the required claims are present"""
required_claims = {"preferred_username", "name", "email"}
required_claims = {"preferred_username", "name", "email", settings.OIDC_USER_CLAIM}
jwks = OpenIDProvider.get_jwks()
if not jwks:
return None
@@ -91,15 +93,18 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
self._logger.error(
f"[OIDC] Unsupported algorithm '{algorithm}'. Unable to decode id token due to mismatched algorithm."
)
return None
try:
claims.validate()
except ExpiredTokenError as e:
self._logger.debug(f"[OIDC] {e.error}: {e.description}")
self._logger.error(f"[OIDC] {e.error}: {e.description}")
return None
except Exception as e:
self._logger.error("[OIDC] Exception while validating id_token claims", e)
if not claims:
self._logger.warning("[OIDC] Claims not found")
self._logger.error("[OIDC] Claims not found")
return None
if not required_claims.issubset(claims.keys()):
self._logger.error(
@@ -116,20 +121,27 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
if not (settings.OIDC_READY and settings.OIDC_CONFIGURATION_URL):
return None
configuration = None
with requests.get(settings.OIDC_CONFIGURATION_URL, timeout=5) as config_response:
config_response.raise_for_status()
configuration = config_response.json()
session = requests.Session()
if settings.OIDC_TLS_CACERTFILE:
session.verify = settings.OIDC_TLS_CACERTFILE
config_response = session.get(settings.OIDC_CONFIGURATION_URL, timeout=5)
config_response.raise_for_status()
configuration = config_response.json()
if not configuration:
OpenIDProvider._logger.warning("[OIDC] Unable to fetch configuration from the OIDC_CONFIGURATION_URL")
session.close()
return None
jwks_uri = configuration.get("jwks_uri", None)
if not jwks_uri:
OpenIDProvider._logger.warning("[OIDC] Unable to find the jwks_uri from the OIDC_CONFIGURATION_URL")
session.close()
return None
with requests.get(jwks_uri, timeout=5) as response:
response.raise_for_status()
return JsonWebKey.import_key_set(response.json())
response = session.get(jwks_uri, timeout=5)
response.raise_for_status()
session.close()
return JsonWebKey.import_key_set(response.json())

View File

@@ -2,8 +2,8 @@ import secrets
from datetime import datetime, timedelta, timezone
from pathlib import Path
import jwt
from fastapi import Request
from jose import jwt
from sqlalchemy.orm.session import Session
from mealie.core import root_logger

View File

@@ -192,16 +192,20 @@ class AppSettings(BaseSettings):
OIDC_REMEMBER_ME: bool = False
OIDC_SIGNING_ALGORITHM: str = "RS256"
OIDC_USER_CLAIM: str = "email"
OIDC_GROUPS_CLAIM: str | None = "groups"
OIDC_TLS_CACERTFILE: str | None = None
@property
def OIDC_READY(self) -> bool:
"""Validates OIDC settings are all set"""
required = {self.OIDC_CLIENT_ID, self.OIDC_CONFIGURATION_URL}
required = {self.OIDC_CLIENT_ID, self.OIDC_CONFIGURATION_URL, self.OIDC_USER_CLAIM}
not_none = None not in required
valid_user_claim = self.OIDC_USER_CLAIM in ["email", "preferred_username"]
valid_group_claim = True
if (not self.OIDC_USER_GROUP or not self.OIDC_ADMIN_GROUP) and not self.OIDC_GROUPS_CLAIM:
valid_group_claim = False
return self.OIDC_AUTH_ENABLED and not_none and valid_user_claim
return self.OIDC_AUTH_ENABLED and not_none and valid_group_claim
# ===============================================
# Testing Config

View File

@@ -27,10 +27,10 @@ class GUID(TypeDecorator):
return str(value)
else:
if not isinstance(value, uuid.UUID):
return "%.32x" % uuid.UUID(value).int
return f"{uuid.UUID(value).int:032x}"
else:
# hexstring
return "%.32x" % value.int
return f"{value.int:032x}"
def load_dialect_impl(self, dialect):
if dialect.name == "postgresql":

View File

@@ -5,6 +5,7 @@ from .group import *
from .invite_tokens import *
from .mealplan import *
from .preferences import *
from .recipe_action import *
from .report import *
from .shopping_list import *
from .webhooks import *

View File

@@ -25,6 +25,7 @@ if TYPE_CHECKING:
from ..users import User
from .events import GroupEventNotifierModel
from .exports import GroupDataExportsModel
from .recipe_action import GroupRecipeAction
from .report import ReportModel
from .shopping_list import ShoppingList
@@ -64,6 +65,7 @@ class Group(SqlAlchemyBase, BaseMixins):
GroupMealPlan, order_by="GroupMealPlan.date", **common_args
)
webhooks: Mapped[list[GroupWebhooksModel]] = orm.relationship(GroupWebhooksModel, **common_args)
recipe_actions: Mapped[list["GroupRecipeAction"]] = orm.relationship("GroupRecipeAction", **common_args)
cookbooks: Mapped[list[CookBook]] = orm.relationship(CookBook, **common_args)
server_tasks: Mapped[list[ServerTaskModel]] = orm.relationship(ServerTaskModel, **common_args)
data_exports: Mapped[list["GroupDataExportsModel"]] = orm.relationship("GroupDataExportsModel", **common_args)
@@ -82,6 +84,7 @@ class Group(SqlAlchemyBase, BaseMixins):
exclude={
"users",
"webhooks",
"recipe_actions",
"shopping_lists",
"cookbooks",
"preferences",

View File

@@ -0,0 +1,25 @@
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID, auto_init
if TYPE_CHECKING:
from group import Group
class GroupRecipeAction(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipe_actions"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), index=True)
group: Mapped["Group"] = relationship("Group", back_populates="recipe_actions", single_parent=True)
action_type: Mapped[str] = mapped_column(String, index=True)
title: Mapped[str] = mapped_column(String, index=True)
url: Mapped[str] = mapped_column(String)
@auto_init()
def __init__(self, **_) -> None:
pass

View File

@@ -260,13 +260,12 @@ def receive_description(target: RecipeModel, value: str, oldvalue, initiator):
@event.listens_for(RecipeModel, "before_update")
def calculate_rating(mapper, connection, target: RecipeModel):
session = object_session(target)
if not session:
if not (session and session.is_modified(target, "rating")):
return
if session.is_modified(target, "rating"):
history = get_history(target, "rating")
old_value = history.deleted[0] if history.deleted else None
new_value = history.added[0] if history.added else None
history = get_history(target, "rating")
old_value = history.deleted[0] if history.deleted else None
new_value = history.added[0] if history.added else None
if old_value == new_value:
return

View File

@@ -3,7 +3,11 @@
"server-error": "Iets het skeefgeloop"
},
"recipe": {
"unique-name-error": "Nuwe resepname moet uniek wees"
"unique-name-error": "Nuwe resepname moet uniek wees",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Geen resepte voldoen aan jou reëls nie"

View File

@@ -3,7 +3,11 @@
"server-error": "حدث خطأ غير متوقع"
},
"recipe": {
"unique-name-error": "يجب أن تكون أسماء الوصفات فريدة"
"unique-name-error": "يجب أن تكون أسماء الوصفات فريدة",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "لا توجد وصفات تتطابق مع المرشحات الخاصة بك"

View File

@@ -3,7 +3,11 @@
"server-error": "Възникна неочаквана грешка"
},
"recipe": {
"unique-name-error": "Името на рецептата трябва да е уникално"
"unique-name-error": "Името на рецептата трябва да е уникално",
"recipe-defaults": {
"ingredient-note": "1 чаша брашно",
"step-text": "Стъпките на рецептата, както и други полета в страницата с рецепти поддържат синтаксис за маркиране.\n\n **Добавяне на връзка**\n\n [Моята връзка](https://demo.mealie.io)\n"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Няма рецепти отговарящи на Вашите условия"

View File

@@ -3,7 +3,11 @@
"server-error": "S'ha produït un error inesperat"
},
"recipe": {
"unique-name-error": "El nom de la recepta ha de ser únic"
"unique-name-error": "El nom de la recepta ha de ser únic",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Cap recepta coincideix amb les teues regles"

View File

@@ -3,7 +3,11 @@
"server-error": "Došlo k nečekané chybě"
},
"recipe": {
"unique-name-error": "Názvy receptů musí být jedinečné"
"unique-name-error": "Názvy receptů musí být jedinečné",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Žádné recepty neodpovídají vašim pravidlům"

View File

@@ -3,7 +3,11 @@
"server-error": "Der opstod en uventet fejl"
},
"recipe": {
"unique-name-error": "Opskriftsnavnet er allerede i brug"
"unique-name-error": "Opskriftsnavnet er allerede i brug",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Ingen opskrifter matcher dine regler"

View File

@@ -3,7 +3,11 @@
"server-error": "Ein unerwarteter Fehler ist aufgetreten"
},
"recipe": {
"unique-name-error": "Rezeptnamen müssen einzigartig sein"
"unique-name-error": "Rezeptnamen müssen einzigartig sein",
"recipe-defaults": {
"ingredient-note": "250 g Mehl",
"step-text": "Zubereitungs-Schritte und andere Felder der Rezeptseite unterstützen Markdown Syntax.\n\n**Füge einen Link hinzu**\n\n[Mein Link](https://demo.mealie.io)\n"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Keine Rezepte entsprechen deinen Regeln"
@@ -37,7 +41,7 @@
"day": "Tag|Tage",
"hour": "Stunde|Stunden",
"minute": "Minute|Minuten",
"second": "sekunde|sekunden",
"second": "Sekunde|Sekunden",
"millisecond": "Millisekunde|Millisekunden",
"microsecond": "Mikrosekunde|Mikrosekunden"
}

View File

@@ -3,7 +3,11 @@
"server-error": "An unexpected error occurred"
},
"recipe": {
"unique-name-error": "Recipe names must be unique"
"unique-name-error": "Recipe names must be unique",
"recipe-defaults": {
"ingredient-note": "1 Κύπελλο Αλεύρι",
"step-text": "Βήματα συνταγής, καθώς και άλλα πεδία στη σύνταξη σήμανσης της σελίδας συνταγής.\n\n**Προσθήκη συνδέσμου**\n\n[Ο σύνδεσμος μου](https://demo.mealie.io)\n"
}
},
"mealplan": {
"no-recipes-match-your-rules": "No recipes match your rules"

View File

@@ -3,7 +3,11 @@
"server-error": "An unexpected error occurred"
},
"recipe": {
"unique-name-error": "Recipe names must be unique"
"unique-name-error": "Recipe names must be unique",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "No recipes match your rules"

View File

@@ -3,7 +3,11 @@
"server-error": "An unexpected error occurred"
},
"recipe": {
"unique-name-error": "Recipe names must be unique"
"unique-name-error": "Recipe names must be unique",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "No recipes match your rules"

View File

@@ -3,7 +3,11 @@
"server-error": "Se ha producido un error inesperado"
},
"recipe": {
"unique-name-error": "El nombre de la receta debe ser único"
"unique-name-error": "El nombre de la receta debe ser único",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "No hay recetas que coincidan con tus reglas"

View File

@@ -3,7 +3,11 @@
"server-error": "Tapahtui odottamaton virhe"
},
"recipe": {
"unique-name-error": "Reseptien nimien täytyy olla yksilöllisiä"
"unique-name-error": "Reseptien nimien täytyy olla yksilöllisiä",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Määritysten mukaisia reseptejä ei löytynyt"

View File

@@ -3,7 +3,11 @@
"server-error": "Une erreur inattendue s'est produite"
},
"recipe": {
"unique-name-error": "Les noms de recette doivent être uniques"
"unique-name-error": "Les noms de recette doivent être uniques",
"recipe-defaults": {
"ingredient-note": "1 tasse de Farine",
"step-text": "Les étapes de la recette ainsi que les autres champs de la page de recette supportent la syntaxe markdown.\n\n**Ajouter un lien**\n\n[Mon lien](https://demo.mealie.io)\n"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Aucune recette ne correspond à vos règles"
@@ -33,12 +37,12 @@
"generic-deleted": "{name} a été supprimé"
},
"datetime": {
"year": "year|years",
"day": "day|days",
"hour": "hour|hours",
"year": "année|années",
"day": "jour|jours",
"hour": "heure|heures",
"minute": "minute|minutes",
"second": "second|seconds",
"millisecond": "millisecond|milliseconds",
"microsecond": "microsecond|microseconds"
"second": "seconde|secondes",
"millisecond": "milliseconde|millisecondes",
"microsecond": "microseconde|microsecondes"
}
}

View File

@@ -3,7 +3,11 @@
"server-error": "Une erreur inattendue sest produite"
},
"recipe": {
"unique-name-error": "Les noms de recette doivent être uniques"
"unique-name-error": "Les noms de recette doivent être uniques",
"recipe-defaults": {
"ingredient-note": "100g de farine",
"step-text": "Les étapes de la recette ainsi que les autres champs de la page de recette supportent la syntaxe markdown.\n\n**Ajouter un lien**\n\n[Mon lien](https://demo.mealie.io)\n"
}
},
"mealplan": {
"no-recipes-match-your-rules": "Aucune recette ne correspond à vos règles"

View File

@@ -3,7 +3,11 @@
"server-error": "An unexpected error occurred"
},
"recipe": {
"unique-name-error": "Recipe names must be unique"
"unique-name-error": "Recipe names must be unique",
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"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"
}
},
"mealplan": {
"no-recipes-match-your-rules": "No recipes match your rules"

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