Compare commits

...

36 Commits

Author SHA1 Message Date
Hayden
d1c5a6ed8c New Crowdin updates (#3635) 2024-05-23 07:48:06 +02:00
Michael Genson
ca26639525 feat: Data Management from Shopping List (#3603)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-05-22 21:58:16 +00:00
renovate[bot]
89982f3e5f fix(deps): update dependency requests to v2.32.2 (#3632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-22 20:32:00 +10:00
renovate[bot]
8485b17490 fix(deps): update dependency openai to v1.30.1 (#3633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-22 09:57:12 +00:00
Michael Genson
5c57b3dd1a feat: OpenAI Ingredient Parsing (#3581) 2024-05-22 09:45:07 +00:00
renovate[bot]
4c8bbdcde2 chore(deps): update dependency mkdocs-material to v9.5.24 (#3629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 21:59:17 +10:00
renovate[bot]
2607066570 fix(deps): update dependency apprise to v1.8.0 (#3588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 23:19:03 +00:00
renovate[bot]
8b7c8be51d fix(deps): update dependency requests to v2.32.1 (#3631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 18:08:33 -05:00
nephlm
c70a5cb72c fix: Fix file not found error with individual recipe export/download. (#3579) 2024-05-20 17:53:14 -05:00
Michael Genson
c610ec1344 fix: Broken Import (#3630) 2024-05-20 07:55:01 -08:00
Michael Genson
61becdbec7 chore: Remove Server Tasks (#3592) 2024-05-20 14:51:37 +00:00
renovate[bot]
78d2a3b8aa chore(deps): update dependency pylint to v3.2.2 (#3625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 20:37:03 +10:00
Michael Genson
a4c3b0da71 fix: NLP Ingredient Parser Misses Some Fractions (#3618) 2024-05-20 10:18:11 +00:00
Hayden
3d3279738b New Crowdin updates (#3622) 2024-05-20 00:36:33 +00:00
renovate[bot]
495d643ed9 fix(deps): update dependency rapidfuzz to v3.9.1 (#3623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 19:28:23 -05:00
renovate[bot]
9094d24e50 chore(deps): update dependency pytest to v8.2.1 (#3621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 15:53:19 -05:00
renovate[bot]
aa4d0f9958 chore(deps): update dependency pytest-asyncio to v0.23.7 (#3620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 10:59:31 -05:00
renovate[bot]
cb821994ae chore(deps): update dependency pylint to v3.2.1 (#3616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-18 12:53:27 -05:00
Hayden
b07a3a31f7 New Crowdin updates (#3614) 2024-05-17 17:48:33 -05:00
Zac Warham
68ff5f4b1c Fixed comment describing method (#3611) 2024-05-17 00:30:01 +00:00
renovate[bot]
cde9d166a4 chore(deps): update dependency mkdocs-material to v9.5.23 (#3605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-05-16 22:29:23 +00:00
Hayden
041145423f New Crowdin updates (#3604) 2024-05-16 17:11:54 -05:00
renovate[bot]
c227519fb7 chore(deps): update dependency pylint to v3.2.0 (#3598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 09:04:29 -05:00
Hayden
94223d2903 New Crowdin updates (#3596) 2024-05-13 12:24:07 -05:00
renovate[bot]
e015c65d92 chore(deps): update dependency pylint to v3.1.1 (#3595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 15:30:03 +00:00
renovate[bot]
53916badf3 chore(deps): update dependency mkdocs-material to v9.5.22 (#3589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-12 20:00:30 -05:00
Michael Genson
c82549ccb4 feat: Default To Fractions When Unit Is Empty (#3587)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-05-12 14:15:26 -05:00
Hayden
554b3fa749 New Crowdin updates (#3590) 2024-05-12 17:59:58 +01:00
Carter
3f263281e7 Add time-based caching for JWKS fetching (#3586) 2024-05-11 21:21:55 -05:00
renovate[bot]
dc47145af6 chore(deps): update dependency pre-commit to v3.7.1 (#3583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-11 17:24:29 +00:00
Hayden
0ccee3584c New Crowdin updates (#3585) 2024-05-11 12:15:17 -05:00
Hayden
dc8aadc327 New Crowdin updates (#3580) 2024-05-10 15:28:14 +00:00
renovate[bot]
efbb571bc2 chore(deps): update dependency ruff to v0.4.4 (#3577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-09 18:39:31 -05:00
Hayden
1df75328d7 New Crowdin updates (#3576) 2024-05-09 14:45:26 +02:00
Hayden
85e402ccc3 New Crowdin updates (#3575) 2024-05-08 14:18:40 +02:00
github-actions[bot]
53a1f04562 docs(auto): Update image tag, for release v1.6.0 (#3571)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-05-07 18:04:46 +02:00
113 changed files with 1746 additions and 1163 deletions

View File

@@ -26,7 +26,7 @@ Do the following for each recipe you want to intelligently handle ingredients.
6. Click the Edit button/icon again
7. Scroll to the ingredients and you should see new fields for Amount, Unit, Food, and Note. The Note in particular will contain the original text of the Recipe.
8. Click `Parse` and you will be taken to the ingredient parsing page.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`.
9. Choose your parser. The `Natural Language Parser` works very well, but you can also use the `Brute Parser`, or the `OpenAI Parser` if you've [enabled OpenAI support](./installation/backend-config.md#openai).
10. Click `Parse All`, and your ingredients should be separated out into Units and Foods based on your seeding in Step 1 above.
11. For ingredients where the Unit or Food was not found, you can click a button to accept an automatically suggested Food to add to the database. Or, manually enter the Unit/Food and hit `Enter` (or click `Create`) to add it to the database
12. When done, click `Save All` and you will be taken back to the recipe. Now the Unit and Food fields of the recipe should be filled out.

View File

@@ -102,6 +102,20 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
| 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`) |
### OpenAI
:octicons-tag-24: v1.7.0
Mealie supports various integrations using OpenAI. To enable OpenAI, [you must provide your OpenAI API key](https://platform.openai.com/api-keys). You can tweak how OpenAI is used using these backend settings. Please note that while OpenAI usage is optimized to reduce API costs, you're unlikely to be able to use OpenAI features with the free tier limits.
| Variables | Default | Description |
| ------------------------- | :------: | ------------------------------------------------------------------------------------------------------------------------------ |
| OPENAI_BASE_URL | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
| OPENAI_API_KEY | None | Your OpenAI API Key. Enables OpenAI-related features |
| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
### Themeing
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.

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.5.1 # (3)
image: ghcr.io/mealie-recipes/mealie:v1.6.0 # (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.5.1 # (3)
image: ghcr.io/mealie-recipes/mealie:v1.6.0 # (3)
container_name: mealie
restart: always
ports:

File diff suppressed because one or more lines are too long

View File

@@ -29,6 +29,7 @@
</v-col>
<v-col v-if="!disableAmount" sm="12" md="3" cols="12">
<v-autocomplete
ref="unitAutocomplete"
v-model="value.unit"
:search-input.sync="unitSearch"
auto-select-first
@@ -57,6 +58,7 @@
<!-- Foods Input -->
<v-col v-if="!disableAmount" m="12" md="3" cols="12" class="">
<v-autocomplete
ref="foodAutocomplete"
v-model="value.food"
:search-input.sync="foodSearch"
auto-select-first
@@ -200,11 +202,13 @@ export default defineComponent({
const foodStore = useFoodStore();
const foodData = useFoodData();
const foodSearch = ref("");
const foodAutocomplete = ref<HTMLInputElement>();
async function createAssignFood() {
foodData.data.name = foodSearch.value;
props.value.food = await foodStore.actions.createOne(foodData.data) || undefined;
foodData.reset();
foodAutocomplete.value?.blur();
}
// ==================================================
@@ -212,11 +216,13 @@ export default defineComponent({
const unitStore = useUnitStore();
const unitsData = useUnitData();
const unitSearch = ref("");
const unitAutocomplete = ref<HTMLInputElement>();
async function createAssignUnit() {
unitsData.data.name = unitSearch.value;
props.value.unit = await unitStore.actions.createOne(unitsData.data) || undefined;
unitsData.reset();
unitAutocomplete.value?.blur();
}
const state = reactive({
@@ -269,7 +275,9 @@ export default defineComponent({
contextMenuOptions,
handleUnitEnter,
handleFoodEnter,
foodAutocomplete,
createAssignFood,
unitAutocomplete,
createAssignUnit,
foods: foodStore.foods,
foodSearch,

View File

@@ -9,6 +9,7 @@
:item-id.sync="listItem.foodId"
:label="$t('shopping-list.food')"
:icon="$globals.icons.foods"
@create="createAssignFood"
/>
<InputLabelType
v-model="listItem.unit"
@@ -16,6 +17,7 @@
:item-id.sync="listItem.unitId"
:label="$t('general.units')"
:icon="$globals.icons.units"
@create="createAssignUnit"
/>
</div>
<div class="d-md-flex align-center" style="gap: 20px">
@@ -28,37 +30,49 @@
@keypress="handleNoteKeyPress"
></v-textarea>
</div>
<div class="d-flex align-end" style="gap: 20px">
<div>
<InputQuantity v-model="listItem.quantity" />
</div>
<div style="max-width: 300px" class="mt-3 mr-auto">
<InputLabelType
v-model="listItem.label"
:items="labels"
:item-id.sync="listItem.labelId"
:label="$t('shopping-list.label')"
/>
</div>
<div class="d-flex flex-wrap align-end" style="gap: 20px">
<div class="d-flex align-end">
<div>
<InputQuantity v-model="listItem.quantity" />
</div>
<div style="max-width: 300px" class="mt-3 mr-auto">
<InputLabelType
v-model="listItem.label"
:items="labels"
:item-id.sync="listItem.labelId"
:label="$t('shopping-list.label')"
/>
</div>
<v-menu
v-if="listItem.recipeReferences && listItem.recipeReferences.length > 0"
open-on-hover
offset-y
left
top
>
<template #activator="{ on, attrs }">
<v-icon class="mt-auto" icon v-bind="attrs" color="warning" v-on="on">
{{ $globals.icons.alert }}
</v-icon>
</template>
<v-card max-width="350px" class="left-warning-border">
<v-card-text>
{{ $t("shopping-list.linked-item-warning") }}
</v-card-text>
</v-card>
</v-menu>
<v-menu
v-if="listItem.recipeReferences && listItem.recipeReferences.length > 0"
open-on-hover
offset-y
left
top
>
<template #activator="{ on, attrs }">
<v-icon class="mt-auto" icon v-bind="attrs" color="warning" v-on="on">
{{ $globals.icons.alert }}
</v-icon>
</template>
<v-card max-width="350px" class="left-warning-border">
<v-card-text>
{{ $t("shopping-list.linked-item-warning") }}
</v-card-text>
</v-card>
</v-menu>
</div>
<BaseButton
v-if="listItem.labelId && listItem.food && listItem.labelId !== listItem.food.labelId"
small
color="info"
:icon="$globals.icons.tagArrowRight"
:text="$tc('shopping-list.save-label')"
class="mt-2 align-items-flex-start"
@click="assignLabelToFood"
/>
<v-spacer />
</div>
</v-card-text>
</v-card>
@@ -100,6 +114,7 @@ import { defineComponent, computed, watch } from "@nuxtjs/composition-api";
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
export default defineComponent({
props: {
@@ -121,6 +136,12 @@ export default defineComponent({
},
},
setup(props, context) {
const foodStore = useFoodStore();
const foodData = useFoodData();
const unitStore = useUnitStore();
const unitData = useUnitData();
const listItem = computed({
get: () => {
return props.value;
@@ -139,8 +160,47 @@ export default defineComponent({
}
);
async function createAssignFood(val: string) {
// keep UI reactive
listItem.value.food ? listItem.value.food.name = val : listItem.value.food = { name: val };
foodData.data.name = val;
const newFood = await foodStore.actions.createOne(foodData.data);
if (newFood) {
listItem.value.food = newFood;
listItem.value.foodId = newFood.id;
}
foodData.reset();
}
async function createAssignUnit(val: string) {
// keep UI reactive
listItem.value.unit ? listItem.value.unit.name = val : listItem.value.unit = { name: val };
unitData.data.name = val;
const newUnit = await unitStore.actions.createOne(unitData.data);
if (newUnit) {
listItem.value.unit = newUnit;
listItem.value.unitId = newUnit.id;
}
unitData.reset();
}
async function assignLabelToFood() {
if (!(listItem.value.food && listItem.value.foodId && listItem.value.labelId)) {
return;
}
listItem.value.food.labelId = listItem.value.labelId;
// @ts-ignore the food will have an id, even though TS says it might not
await foodStore.actions.updateOne(listItem.value.food);
}
return {
listItem,
createAssignFood,
createAssignUnit,
assignLabelToFood,
};
},
methods: {

View File

@@ -14,15 +14,15 @@
>
<v-icon v-if="!iconRight" left>
<slot name="icon">
{{ btnAttrs.icon }}
{{ icon || btnAttrs.icon }}
</slot>
</v-icon>
<slot name="default">
{{ btnAttrs.text }}
{{ text || btnAttrs.text }}
</slot>
<v-icon v-if="iconRight" right>
<slot name="icon">
{{ btnAttrs.icon }}
{{ icon || btnAttrs.icon }}
</slot>
</v-icon>
</v-btn>
@@ -103,6 +103,14 @@ export default defineComponent({
type: String,
default: null,
},
text: {
type: String,
default: null,
},
icon: {
type: String,
default: null,
},
iconRight: {
type: Boolean,
default: false,

View File

@@ -11,44 +11,50 @@
</v-icon>
</v-btn>
</template>
<!-- Model -->
<!-- Model -->
<v-list v-if="mode === MODES.model" dense>
<v-list-item-group v-model="itemGroup">
<template v-for="(item, index) in items">
<v-list-item :key="index" @click="setValue(item)">
<div v-if="!item.hide" :key="index">
<v-list-item @click="setValue(item)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
</div>
</template>
</v-list-item-group>
</v-list>
<!-- Links -->
<v-list v-else-if="mode === MODES.link" dense>
<v-list-item-group v-model="itemGroup">
<template v-for="(item, index) in items">
<div v-if="!item.hide" :key="index">
<v-list-item :to="item.to">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>\
</div>
</template>
</v-list-item-group>
</v-list>
<!-- Event -->
<v-list v-else-if="mode === MODES.event" dense>
<template v-for="(item, index) in items">
<div v-if="!item.hide" :key="index">
<v-list-item @click="$emit(item.event)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
</template>
</v-list-item-group>
</v-list>
<!-- Links -->
<v-list v-else-if="mode === MODES.link" dense>
<v-list-item-group v-model="itemGroup">
<template v-for="(item, index) in items">
<v-list-item :key="index" :to="item.to">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
</template>
</v-list-item-group>
</v-list>
<!-- Event -->
<v-list v-else-if="mode === MODES.event" dense>
<template v-for="(item, index) in items">
<v-list-item :key="index" @click="$emit(item.event)">
<v-list-item-icon v-if="item.icon">
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
<v-divider v-if="item.divider" :key="`divider-${index}`" class="my-1" ></v-divider>
</div>
</template>
</v-list>
</v-menu>
@@ -74,6 +80,7 @@ export interface MenuItem {
value?: string;
event?: string;
divider?: boolean;
hide?:boolean;
}
export default defineComponent({

View File

@@ -1,14 +1,27 @@
<template>
<v-autocomplete
ref="autocompleteRef"
v-model="itemVal"
v-bind="$attrs"
:search-input.sync="searchInput"
item-text="name"
return-object
:items="items"
:prepend-icon="icon || $globals.icons.tags"
auto-select-first
clearable
hide-details
/>
@keyup.enter="emitCreate"
>
<template v-if="$listeners.create" #no-data>
<div class="caption text-center pb-2">{{ $t("recipe.press-enter-to-create") }}</div>
</template>
<template v-if="$listeners.create" #append-item>
<div class="px-2">
<BaseButton block small @click="emitCreate"></BaseButton>
</div>
</template>
</v-autocomplete>
</template>
<script lang="ts">
@@ -31,7 +44,7 @@
* Both the ID and Item can be synced. The item can be synced using the v-model syntax and the itemId can be synced
* using the .sync syntax `item-id.sync="item.labelId"`
*/
import { defineComponent, computed } from "@nuxtjs/composition-api";
import { computed, defineComponent, ref } from "@nuxtjs/composition-api";
import { MultiPurposeLabelSummary } from "~/lib/api/types/labels";
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
@@ -59,6 +72,8 @@ export default defineComponent({
},
},
setup(props, context) {
const autocompleteRef = ref<HTMLInputElement>();
const searchInput = ref("");
const itemIdVal = computed({
get: () => {
return props.itemId || undefined;
@@ -78,9 +93,20 @@ export default defineComponent({
},
});
function emitCreate() {
if (props.items.some(item => item.name === searchInput.value)) {
return;
}
context.emit("create", searchInput.value);
autocompleteRef.value?.blur();
}
return {
autocompleteRef,
itemVal,
itemIdVal,
searchInput,
emitCreate,
};
},
});

View File

@@ -34,6 +34,12 @@ describe(parseIngredientText.name, () => {
expect(parseIngredientText(ingredient, false, 1, true)).contain("1 <sup>1</sup>").and.to.contain("<sub>2</sub>");
});
test("ingredient text with fraction when unit is null", () => {
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: undefined });
expect(parseIngredientText(ingredient, false, 1, true)).contain("1 <sup>1</sup>").and.to.contain("<sub>2</sub>");
});
test("ingredient text with fraction no formatting", () => {
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: { fraction: true, id: "1", name: "cup" } });
const result = parseIngredientText(ingredient, false, 1, false);

View File

@@ -53,7 +53,9 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
// casting to number is required as sometimes quantity is a string
if (quantity && Number(quantity) !== 0) {
if (unit?.fraction) {
if (unit && !unit.fraction) {
returnQty = (quantity * scale).toString();
} else {
const fraction = frac(quantity * scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
returnQty += fraction[0];
@@ -64,8 +66,6 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
` <sup>${fraction[1]}</sup>&frasl;<sub>${fraction[2]}</sub>` :
` ${fraction[1]}/${fraction[2]}`;
}
} else {
returnQty = (quantity * scale).toString();
}
}

View File

@@ -10,7 +10,7 @@ const storeLoading = ref(false);
/**
* useFoodData returns a template reactive object
* for managing the creation of units. It also provides a
* for managing the creation of foods. It also provides a
* function to reset the data back to the initial state.
*/
export const useFoodData = function () {

View File

@@ -1,6 +1,6 @@
import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
import { TimelineEventType } from "~/lib/api/types/recipe";
import { RegisteredParser, TimelineEventType } from "~/lib/api/types/recipe";
export interface UserPrintPreferences {
imagePosition: string;
@@ -36,6 +36,10 @@ export interface UserTimelinePreferences {
types: TimelineEventType[];
}
export interface UserParsingPreferences {
parser: RegisteredParser;
}
export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
const fromStorage = useLocalStorage(
"recipe-print-preferences",
@@ -116,3 +120,17 @@ export function useTimelinePreferences(): Ref<UserTimelinePreferences> {
return fromStorage;
}
export function useParsingPreferences(): Ref<UserParsingPreferences> {
const fromStorage = useLocalStorage(
"parsing-preferences",
{
parser: "nlp",
},
{ 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<UserParsingPreferences>;
return fromStorage;
}

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Resep skraper weergawe",
"oidc-ready": "OIDC Klar",
"oidc-ready-error-text": "Ikke alle OIDC værdier er konfigureret. Dette kan ignoreres hvis du ikke bruger OIDC godkendelse.",
"oidc-ready-success-text": "Krævede OIDC variabler er udfyldt."
"oidc-ready-success-text": "Krævede OIDC variabler er udfyldt.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Alle lyste",
@@ -778,6 +782,7 @@
"food": "Voedsel",
"note": "Nota",
"label": "Etiket",
"save-label": "Save Label",
"linked-item-warning": "Hierdie item is gekoppel aan een of meer resepte. Die aanpassing van die eenhede of bestanddele sal onverwagte resultate lewer wanneer die resep van hierdie lys bygevoeg of verwyder word.",
"toggle-food": "Voedsel skakelling",
"manage-labels": "Bestuur etikette",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Dit is nie perfek nie, maar dit lewer oor die algemeen uitstekende resultate en is 'n goeie beginpunt vir die handverwerking van bestanddele in individuele velde. Jy kan ook die \"Brute\" verwerker gebruik wat 'n patroonpastegniek gebruik om bestanddele te identifiseer.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Wys individuele oortuiging",
"ingredient-text": "Bestanddeel teks",
"average-confident": "{0} oortuig",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Версия на скрепер на рецепти",
"oidc-ready": "Готов за OIDC",
"oidc-ready-error-text": "Не всички OIDC стойности са конфигурирани. Това може да бъде игнорирано, ако не използвате OIDC удостоверяване.",
"oidc-ready-success-text": "Задължителните OIDC променливи са зададени."
"oidc-ready-success-text": "Задължителните OIDC променливи са зададени.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Всички списъци",
@@ -778,6 +782,7 @@
"food": "Продукт",
"note": "Бележка",
"label": "Етикет",
"save-label": "Save Label",
"linked-item-warning": "Елементът е добавен към една или повече рецепти. Редактиране на единиците или храните ще се отрази с непредвидими резултати когато добавяте или премахвате рецепта от списъка.",
"toggle-food": "Превключване на храна",
"manage-labels": "Управление на етикети",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Не е перфектно, но като цяло дава страхотни резултати и е добра отправна точка за ръчно анализиране на съставки в отделни полета. Като алтернатива можете също да използвате процесора \"Груб\", който използва техника за съвпадение на шаблони, за да идентифицира съставките.",
"nlp": "ПЕЕ",
"brute": "Груб",
"openai": "OpenAI",
"show-individual-confidence": "Покажи индивидуална увереност",
"ingredient-text": "Текст на съставката",
"average-confident": "{0} Уверен",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "Analitzador d'OpenAI",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "Llest per OpenAI",
"openai-ready-error-text": "No tots els valors d'OpenAI estan configurats. Es pot ignorar si no s'estan utilitzant les funcionalitats d'OpenAI.",
"openai-ready-success-text": "Les variables requerides per OpenAI estan establertes."
},
"shopping-list": {
"all-lists": "Totes les llistes",
@@ -778,6 +782,7 @@
"food": "Aliments",
"note": "Nota",
"label": "Etiqueta",
"save-label": "Save Label",
"linked-item-warning": "Aquest element està enllaçat amb una o més receptes. Modificar les unitats o els aliments pot provocar resultats inesperats en afegir o elimina la recepta del llistat.",
"toggle-food": "Mostra el nom de l'aliment",
"manage-labels": "Gestiona etiquetes",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -340,7 +340,7 @@
},
"nextcloud": {
"description": "Migrovat data z instance Nextcloud Cookbook",
"description-long": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
"description-long": "Nextcloud recepty lze importovat ze souboru zip, který obsahuje data uložená v Nextcloudu. Podívejte se na příklad struktury složek níže, abyste se ujistili, že vaše recepty lze importovat.",
"title": "Nextcloud Cookbook"
},
"copymethat": {
@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Všechny seznamy",
@@ -778,6 +782,7 @@
"food": "Jídlo",
"note": "Poznámka",
"label": "Popisek",
"save-label": "Save Label",
"linked-item-warning": "Tato položka je propojena s jedním nebo více recepty. Úprava jednotky nebo jídla bude mít neočekávané důsledky při přidání nebo odebrání receptu z tohoto seznamu.",
"toggle-food": "Přepnout typ položky",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Není to dokonalé, ale obecně to přináší skvělé výsledky a je dobrým výchozím bodem pro ruční zpracování ingrediencí do jednotlivých polí. Alternativně můžete také použít procesor \"Brute\", který používá metodu porovnávání vzorců pro idenfikaci ingrediencí.",
"nlp": "ZPJ",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Zobrazit individuální důvěru",
"ingredient-text": "Text přísady",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Version på opskrift-indsamler",
"oidc-ready": "OIDC er Klar",
"oidc-ready-error-text": "Ikke alle OIDC værdier er konfigureret. Dette kan ignoreres, hvis du ikke bruger OIDC godkendelse.",
"oidc-ready-success-text": "Alle påkrævede OIDC værdier er angivet."
"oidc-ready-success-text": "Alle påkrævede OIDC værdier er angivet.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Alle lister",
@@ -778,6 +782,7 @@
"food": "Fødevarer",
"note": "Note",
"label": "Etiket",
"save-label": "Save Label",
"linked-item-warning": "Dette element er tilknyttet en eller flere opskrifter. Justering af enheder eller fødevarer vil give uventede resultater, når du tilføjer eller fjerner opskriften fra denne liste.",
"toggle-food": "Slå fødevarer til/fra",
"manage-labels": "Håndter etiketter",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Det er ikke perfekt, men giver generelt gode resultater og er et godt udgangspunkt for manuel redigering af ingredienser i individuelle felter. Alternativt kan du også bruge \"Brute\" metoden, der bruger en mønstermatchende teknik til at identificere ingredienser.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Vis individual konfidensscore",
"ingredient-text": "Ingredienstekst",
"average-confident": "{0} konfidens",

View File

@@ -594,6 +594,7 @@
"select-parser": "Parser auswählen",
"natural-language-processor": "Natürliche Sprachverarbeitung (NLP)",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Alles parsen",
"no-unit": "Keine Einheit",
"missing-unit": "Fehlende Einheit erstellen: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Rezept Scraper Version",
"oidc-ready": "OIDC bereit",
"oidc-ready-error-text": "Es sind nicht alle OIDC-Werte konfiguriert. Wenn du keine OIDC-Authentifizierung benutzt, kannst du das ignorieren.",
"oidc-ready-success-text": "Alle erforderlichen OIDC-Variablen sind gesetzt."
"oidc-ready-success-text": "Alle erforderlichen OIDC-Variablen sind gesetzt.",
"openai-ready": "OpenAI bereit",
"openai-ready-error-text": "Es sind nicht alle OpenAI-Werte konfiguriert. Wenn du die OpenAI Funktionen nicht benutzt, kannst du das ignorieren.",
"openai-ready-success-text": "Alle erforderlichen OpenAI-Variablen sind hinterlegt."
},
"shopping-list": {
"all-lists": "Alle Listen",
@@ -778,6 +782,7 @@
"food": "Lebensmittel",
"note": "Notiz",
"label": "Etikett",
"save-label": "Save Label",
"linked-item-warning": "Dieser Eintrag ist mit einem oder mehreren Rezepten verknüpft. Das Ändern der Einheiten oder Lebensmittel führt zu unerwarteten Ergebnissen, wenn das Rezept von dieser Einkaufsliste entfernt oder hinzugefügt wird.",
"toggle-food": "Lebensmittel-Eingabe umschalten",
"manage-labels": "Etiketten verwalten",
@@ -1170,6 +1175,7 @@
"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",
"openai": "OpenAI",
"show-individual-confidence": "Zeige individuelle Zuverlässigkeitswerte an",
"ingredient-text": "Zutaten-Angabe",
"average-confident": "{0} zuverlässig",

View File

@@ -73,7 +73,7 @@
"user-events": "Συμβάντα Χρήστη",
"mealplan-events": "Mealplan Events",
"when-a-user-in-your-group-creates-a-new-mealplan": "When a user in your group creates a new mealplan",
"shopping-list-events": "Shopping List Events",
"shopping-list-events": "Συμβάντα Λιστών Αγορών",
"cookbook-events": "Cookbook Events",
"tag-events": "Tag Events",
"category-events": "Category Events",
@@ -314,7 +314,7 @@
"random-side": "Τυχαίο Συνοδευτικό",
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
"to-all-days": "σε όλες τις ημέρες",
"on-days": "on {0}s",
"on-days": "κάθε {0}",
"for-all-meal-types": "για όλα τα είδη γεύματος",
"for-type-meal-types": "for {0} meal types",
"meal-plan-rules": "Κανόνες Προγράμματος Γευμάτων",
@@ -594,6 +594,7 @@
"select-parser": "Επιλέξτε Αναλυτή",
"natural-language-processor": "Επεξεργαστής Φυσικής Γλώσσας",
"brute-parser": "Αναλυτής Ωμής Βίας",
"openai-parser": "Αναλυτής OpenAI",
"parse-all": "Ανάλυση Ολων",
"no-unit": "Καμία μονάδα",
"missing-unit": "Δημιουργία μονάδας που λείπει: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Δεν έχουν ρυθμιστεί όλες οι τιμές OpenAI. Αυτό μπορεί να αγνοηθεί αν δεν χρησιμοποιείτε τα χαρακτηριστικά του OpenAI.",
"openai-ready-success-text": "Ολες οι απαιτούμενες μεταβλητές OpenAI έχουν οριστεί."
},
"shopping-list": {
"all-lists": "Όλες οι λίστες",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Αποθήκεύση ετικέτας",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -64,7 +64,7 @@
"something-went-wrong": "¡Algo ha salido mal!",
"subscribed-events": "Eventos suscritos",
"test-message-sent": "Mensaje Enviado",
"message-sent": "Message Sent",
"message-sent": "Mensaje Enviado",
"new-notification": "Nueva notificación",
"event-notifiers": "Notificaciones de eventos",
"apprise-url-skipped-if-blank": "URL de Apprise (omitida si está en blanco)",
@@ -81,12 +81,12 @@
"recipe-events": "Eventos de receta"
},
"general": {
"add": "Add",
"add": "Agregar",
"cancel": "Cancelar",
"clear": "Eliminar",
"close": "Cerrar",
"confirm": "Confirmar",
"confirm-how-does-everything-look": "How does everything look?",
"confirm-how-does-everything-look": "¿Cómo se ve todo?",
"confirm-delete-generic": "¿Estás seguro de que quieres eliminarlo?",
"copied_message": "¡Copiado!",
"create": "Crear",
@@ -145,23 +145,23 @@
"save": "Guardar",
"settings": "Ajustes",
"share": "Compartir",
"show-all": "Show All",
"show-all": "Mostrar Todo",
"shuffle": "Aleatorio",
"sort": "Ordenar",
"sort-ascending": "Sort Ascending",
"sort-descending": "Sort Descending",
"sort-ascending": "Orden Ascendente",
"sort-descending": "Orden Descendente",
"sort-alphabetically": "Alfabéticamente",
"status": "Estado",
"subject": "Asunto",
"submit": "Enviar",
"success-count": "Éxito: {count}",
"sunday": "Domingo",
"system": "System",
"system": "Sistema",
"templates": "Plantillas:",
"test": "Prueba",
"themes": "Temas",
"thursday": "Jueves",
"title": "Title",
"title": "Título",
"token": "Token",
"tuesday": "Martes",
"type": "Tipo",
@@ -176,7 +176,7 @@
"units": "Unidades",
"back": "Volver",
"next": "Siguiente",
"start": "Start",
"start": "Comenzar",
"toggle-view": "Cambiar vista",
"date": "Fecha",
"id": "Id",
@@ -210,7 +210,7 @@
"unsaved-changes": "Tienes cambios sin guardar. ¿Quieres guardar antes de salir? Aceptar para guardar, Cancelar para descartar cambios.",
"clipboard-copy-failure": "No se pudo copiar al portapapeles.",
"confirm-delete-generic-items": "¿Estás seguro que quieres eliminar los siguientes elementos?",
"organizers": "Organizers"
"organizers": "Organizadores"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar <b>{groupName}<b/>",
@@ -246,8 +246,8 @@
"group-preferences": "Preferencias de grupo",
"private-group": "Grupo privado",
"private-group-description": "Establecer el grupo como privado, cambiará todas las opciones de visualización a las opciones predeterminadas. Puede cambiarse individualmente en la configuración de cada receta.",
"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": "Habilitar acceso público",
"enable-public-access-description": "Habilitar recetas públicas por defecto y permitir que usuarios anónimos vean recetas",
"allow-users-outside-of-your-group-to-see-your-recipes": "Permite a los usuarios fuera de tu grupo ver tus recetas",
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Cuando esté habilitado, puede utilizar un enlace público para compartir recetas específicas sin autorizar al usuario. Cuando está desactivado, sólo puedes compartir recetas con usuarios que estén en tu grupo o generando un enlace privado de forma previa",
"show-nutrition-information": "Mostrar la información nutricional",
@@ -361,11 +361,11 @@
},
"recipe-data-migrations": "Migración de recetas",
"recipe-data-migrations-explanation": "Las recetas pueden migrarse desde otra aplicación soportada a Mealie. Esta es una excelente manera de empezar 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": "¿Vienes de otra app o una versión previa de Mealie? Revisa migraciones para ver si tus datos pueden ser importados.",
"choose-migration-type": "Elegir tipo de migración",
"tag-all-recipes": "Etiqueta todas las recetas con la etiqueta {tag-name}",
"nextcloud-text": "Las recetas Nextcloud se pueden importar desde un archivo zip que contiene los datos almacenados en Nextcloud. Consulte la estructura de carpetas de ejemplo a continuación para asegurarse de que sus recetas pueden ser importadas.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
"chowdown-text": "Mealie soporta el formato de repositorio chowndown de manera nativa. Descarga el código fuente como archivo .zip y súbelo a continuación.",
"recipe-1": "Receta 1",
"recipe-2": "Receta 2",
"paprika-text": "Mealie puede importar recetas de la aplicación Paprika. Exporta tus recetas de paprika, renombra la extensión del fichero a .zip y súbelo a continuación.",
@@ -376,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 puede importar recetas de My Recipe Box. Exporta tus recetas en formato CSV y sube el archivo a continuación."
}
},
"new-recipe": {
@@ -512,7 +512,7 @@
"link-ingredients": "Vincular ingredientes",
"merge-above": "Combinar por encima",
"move-to-bottom": "Mover al fondo",
"move-to-top": "Move To Top",
"move-to-top": "Mover al Inicio",
"reset-scale": "Reiniciar",
"decrease-scale-label": "Disminuir escala en 1",
"increase-scale-label": "Aumentar escala en 1",
@@ -528,7 +528,7 @@
"edit-timeline-event": "Editar evento en la cronología",
"timeline": "Cronología",
"timeline-is-empty": "Aún no hay nada en la línea de tiempo. ¡Intenta hacer esta receta!",
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
"timeline-no-events-found-try-adjusting-filters": "No se encontraron eventos. Intenta ajustar los filtros de búsqueda.",
"group-global-timeline": "Línea de tiempo global de {groupName}",
"open-timeline": "Abrir línea de tiempo",
"made-this": "Lo hice",
@@ -549,8 +549,8 @@
"looking-for-migrations": "¿Buscas las Migraciones?",
"import-with-url": "Importar por url",
"create-recipe": "Crear receta",
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"create-recipe-description": "Crear nueva receta desde cero.",
"create-recipes": "Crear Recetas",
"import-with-zip": "Importar desde .zip",
"create-recipe-from-an-image": "Crear receta a partir de una imagen",
"bulk-url-import": "Importación masiva desde URL",
@@ -585,20 +585,21 @@
"screen-awake": "Mantener la pantalla encendida",
"remove-image": "Eliminar imagen",
"nextStep": "Siguiente paso",
"recipe-actions": "Recipe Actions",
"recipe-actions": "Acciones de Receta",
"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"
"experimental-alert-text": "Mealie usa procesamiento de lenguaje natural para analizar y crear unidades y productos para los ingredientes de tu receta. Esta característica es experimental y puede no ser confiable. Si prefieres no usar los resultados analizados, elige 'Cancelar' y los cambios serán descartados.",
"ingredient-parser": "Analizador de Ingredientes",
"explanation": "Para usar el analizador de ingredientes, haz clic en 'Analizar Todo'. Una vez que los ingredientes procesados estén disponibles, puedes revisar los productos y verificar que fueron analizados correctamente. El puntaje de confianza del modelo se muestra a la derecha del título del producto. Este puntaje es un promedio de todos los puntajes individuales y puede no ser del todo preciso.",
"alerts-explainer": "Las alertas se mostrarán si se encuentra un alimento o unidad que coincida pero que no exista en la base de datos.",
"select-parser": "Seleccionar Analizador",
"natural-language-processor": "Procesador de Lenguaje Natural",
"brute-parser": "Analizador Bruto",
"openai-parser": "OpenAI Parser",
"parse-all": "Analizar Todo",
"no-unit": "Sin unidad",
"missing-unit": "Crear unidad faltante: {unit}",
"missing-food": "Crear comida faltante: {food}",
"no-food": "Sin Comida"
}
},
"search": {
@@ -636,7 +637,7 @@
"import-summary": "Importar resumen",
"partial-backup": "Copia de seguridad parcial",
"unable-to-delete-backup": "No se puede eliminar la copia de seguridad.",
"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": "Las copias de seguridad son guardados completos de las carpetas database y data del sitio. Esto incluye todo y no puede ser configurado para excluir subconjuntos de datos. Puedes pensar en ello como una 'instantánea' de Mealie en un momento en específico. Estas sirven para exportar e importar datos, o clonar el sitio a otra ubicación de manera intercompatible.",
"backup-restore": "Restaurar Copia de Seguridad",
"back-restore-description": "Restaurar esta copia de seguridad sobrescribirá todos los datos actuales de su base de datos y del directorio de datos y los sustituirá por el contenido de esta copia. {cannot-be-undone} Si la restauración se realiza correctamente, se cerrará su sesión.",
"cannot-be-undone": "Esta acción no se puede deshacer, use con precaución.",
@@ -762,9 +763,12 @@
"ldap-ready-success-text": "Las variables LDAP requeridas están todas definidas.",
"build": "Compilación",
"recipe-scraper-version": "Versión de Analizador de Recetas",
"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 Listo",
"oidc-ready-error-text": "No todos los valores OIDC están configurados. Puedes ignorar esto si no estás usando autenticación OIDC.",
"oidc-ready-success-text": "Todas las variables OIDC requeridas están configuradas.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Todas las listas",
@@ -778,6 +782,7 @@
"food": "Alimentos",
"note": "Nota",
"label": "Etiqueta",
"save-label": "Save Label",
"linked-item-warning": "Este elemento está vinculado a una o más recetas. Ajustar las unidades o los alimentos producirá resultados inesperados al añadir o quitar la receta de esta lista.",
"toggle-food": "Mostrar nombre del alimento",
"manage-labels": "Administrar etiquetas",
@@ -872,11 +877,11 @@
"link-id": "ID del enlace",
"link-name": "Nombre del enlace",
"login": "Iniciar sesión",
"login-oidc": "Login with",
"or": "or",
"login-oidc": "Acceder con",
"or": "o",
"logout": "Cerrar Sesión",
"manage-users": "Administrar usuarios",
"manage-users-description": "Create and manage users.",
"manage-users-description": "Crear y gestionar usuarios.",
"new-password": "Nueva contraseña",
"new-user": "Nuevo usuario",
"password-has-been-reset-to-the-default-password": "La contraseña se ha restablecido a su valor por defecto",
@@ -1019,10 +1024,10 @@
"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"
"recipe-actions-data": "Datos de Acciones de Receta",
"new-recipe-action": "Nueva Acción de Receta",
"edit-recipe-action": "Editar Acción de Receta",
"action-type": "Tipo de Acción"
},
"create-alias": "Crear un Alias",
"manage-aliases": "Administrar Alias",
@@ -1113,15 +1118,15 @@
"selection-mode-desc": "El modo de selección es el modo principal que puede utilizarse para introducir datos:",
"selection-mode-steps": {
"draw": "Dibuja un rectángulo sobre el texto que deseas seleccionar.",
"click": "Click on any field on the right and then click back on the rectangle above the image.",
"result": "The selected text will appear inside the previously selected field."
"click": "Haz clic en cualquier campo a la derecha y luego haz clic en el rectángulo sobre la imagen.",
"result": "El texto seleccionado aparecerá dentro del campo elegido."
},
"pan-and-zoom-mode": "Modo Panorámico y Zoom",
"pan-and-zoom-desc": "Select pan and zoom by clicking the icon. This mode allows to zoom inside the image and move around to make using big images easier.",
"pan-and-zoom-desc": "Selecciona, desplaza y haz zoom haciendo clic en el icono. Este modo te permite hacer zoom dentro de la imagen y desplazarte para facilitar el uso de imágenes grandes.",
"split-text-mode": "Modos de división de texto",
"split-modes": {
"line-mode": "Modo de línea (por defecto)",
"line-mode-desc": "In line mode, the text will be propagated by keeping the original line breaks. This mode is useful when using bulk add on a list of ingredients where one ingredient is one line.",
"line-mode-desc": "En el modo línea, el texto será propagado manteniendo los saltos de línea originales. Este modo es útil cuando se usa 'agregar varios' y cada ingrediente corresponde a una línea de texto.",
"block-mode": "Modo en bloque",
"block-mode-desc": "En el modo de bloque, el texto se dividirá en bloques. Este modo es útil cuando se agregan instrucciones que están escritas en párrafos.",
"flat-mode": "Modo texto sin formato",
@@ -1150,7 +1155,7 @@
"action-delete-log-files-name": "Borrar archivos de registro",
"action-delete-log-files-description": "Elimina todos los archivos de registro",
"action-clean-directories-name": "Limpiar directorios",
"action-clean-directories-description": "Removes all the recipe folders that are not valid UUIDs",
"action-clean-directories-description": "Remueve todas las carpetas de receta sin UUID válido",
"action-clean-temporary-files-name": "Eliminar archivos temporales",
"action-clean-temporary-files-description": "Eliminar todos los archivos y carpetas del directorio .temp",
"action-clean-images-name": "Limpiar imágenes",
@@ -1165,11 +1170,12 @@
"mainentance": {
"actions-title": "Acciones"
},
"ingredients-natural-language-processor": "Ingredients Natural Language Processor",
"ingredients-natural-language-processor-explanation": "Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times. Note that as the model is trained in English only, you may have varied results when using the model in other languages. This page is a playground for testing the model.",
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"ingredients-natural-language-processor": "Procesador de Lenguaje Natural de Ingredientes",
"ingredients-natural-language-processor-explanation": "Mealie usa CRFs (Campos Condicionales Aleatorios) para analizar y procesar ingredientes. El modelo para ingredientes está basado en un conjunto de más de 100.000 ingredientes de una base de datos perteneciente a New York Times. Ya que el modelo sólo está entrenado en inglés, puede que obtengas resultados variados al utilizar otros lenguajes. Esta página es un sitio de prueba para probar el modelo.",
"ingredients-natural-language-processor-explanation-2": "Si bien no es perfecto, entrega buenos resultados en general y es un buen punto de partida para empezar a analizar ingredientes a campos individuales. Alternativamente puedes usar el procesador \"bruto\", que utiliza una técnica de reconocimiento de patrones para identificar ingredientes.",
"nlp": "PLN",
"brute": "En bruto",
"openai": "OpenAI",
"show-individual-confidence": "Mostrar confianza individual",
"ingredient-text": "Texto del ingrediente",
"average-confident": "{0} Confianza",
@@ -1180,45 +1186,45 @@
"no-logs-found": "No se encontraron registros",
"tasks": "Tareas",
"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": "Configuración Inicial",
"welcome-to-mealie-get-started": "¡Bienvenido a Mealie! Empecemos",
"already-set-up-bring-to-homepage": "Estoy bien, solo llévame al Inicio",
"common-settings-for-new-sites": "Aquí hay algunos ajustes comunes para sitios nuevos",
"setup-complete": "¡Configuración completada!",
"here-are-a-few-things-to-help-you-get-started": "Aquí hay algunas cosas para ayudarte a empezar con Mealie",
"restore-from-v1-backup": "¿Tienes una copia de seguridad de Mealie v1? Puedes restaurarla aquí.",
"manage-profile-or-get-invite-link": "Gestiona tu perfil, o usa un enlace de invitación para compartir con otros."
}
},
"profile": {
"welcome-user": "👋 Welcome, {0}!",
"welcome-user": "👋 ¡Bienvenido, {0}!",
"description": "Administra tu perfil, recetas y ajustes de grupo.",
"get-invite-link": "Obtener enlace de invitación",
"get-public-link": "Obtener enlace público",
"account-summary": "Información de la cuenta",
"account-summary-description": "Here's a summary of your group's information.",
"account-summary-description": "Aquí hay un resumen de la información del grupo.",
"group-statistics": "Estadísticas del grupo",
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
"group-statistics-description": "Tus estadísticas de grupo proporcionan información sobre cómo utilizas Mealie.",
"storage-capacity": "Capacidad de almacenamiento",
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
"storage-capacity-description": "Tu capacidad de almacenamiento es el cálculo de las imágenes y recursos que has subido.",
"personal": "Personal",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.",
"personal-description": "Estos ajustes son para ti. Cualquier cambio no afectará otros usuarios.",
"user-settings": "Ajustes de usuario",
"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.",
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
"user-settings-description": "Gestiona tus ajustes, cambia tu contraseña y actualiza tu correo electrónico.",
"api-tokens-description": "Administra tus API tokens para el acceso desde apps externas.",
"group-description": "Estos elementos se comparten dentro del grupo. ¡Editar cualquiera de ellos lo modificará para todo el grupo!",
"group-settings": "Ajustes de grupo",
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
"group-settings-description": "Gestiona tus ajustes comunes de grupo, como la configuración de plan de comidas y privacidad.",
"cookbooks-description": "Gestiona un a colección de categorías de receta y genera páginas para estas.",
"members": "Miembros",
"members-description": "Ver quién está en tu grupo y administrar sus permisos.",
"webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.",
"notifiers": "Notificaciones",
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
"manage-data": "Administrar datos",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.",
"manage-data-description": "Gestiona tus datos de Mealie; Comidas, unidades, categorías, etiquetas y más.",
"data-migrations": "Migración de datos",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.",
"data-migrations-description": "Migra tus datos existentes desde otras aplicaciones como Nextcloud Recipes o Chowdown.",
"email-sent": "Email enviado",
"error-sending-email": "Error enviando email",
"personal-information": "Datos Personales",

View File

@@ -64,7 +64,7 @@
"something-went-wrong": "Jotain meni pieleen!",
"subscribed-events": "Tilatut tapahtumat",
"test-message-sent": "Viesti lähetetty",
"message-sent": "Message Sent",
"message-sent": "Viesti lähetetty",
"new-notification": "Uusi ilmoitus",
"event-notifiers": "Tapahtumien ilmoitukset",
"apprise-url-skipped-if-blank": "Ilmoitusverkko-osoite (voi jättää tyhjäksi)",
@@ -161,7 +161,7 @@
"test": "Testi",
"themes": "Teemat",
"thursday": "Torstai",
"title": "Title",
"title": "Otsikko",
"token": "Tunniste",
"tuesday": "Tiistai",
"type": "Tyyppi",
@@ -559,8 +559,8 @@
"new-recipe-names-must-be-unique": "Reseptin nimen on oltava yksilöllinen",
"scrape-recipe": "Reseptin kaappain",
"scrape-recipe-description": "Kaappaa resepti urlin avulla. Anna sen reseptin url-osoite, jonka haluat kaapata, ja Mealie yrittää kaapata reseptin kyseiseltä sivustolta ja lisätä sen kokoelmaasi.",
"scrape-recipe-have-a-lot-of-recipes": "Have a lot of recipes you want to scrape at once?",
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-a-lot-of-recipes": "Haluatko kerätä useamman reseptin kerralla?",
"scrape-recipe-suggest-bulk-importer": "Kokeile massasiirtotyökalua",
"import-original-keywords-as-tags": "Tuo alkuperäiset avainsanat tunnisteiksi",
"stay-in-edit-mode": "Pysy muokkaustilassa",
"import-from-zip": "Tuo zip-arkistosta",
@@ -585,20 +585,21 @@
"screen-awake": "Pidä näyttö aina päällä",
"remove-image": "Poista kuva",
"nextStep": "Seuraava askel",
"recipe-actions": "Recipe Actions",
"recipe-actions": "Reseptin toiminnot",
"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"
"experimental-alert-text": "Mealie käyttää luonnollisen kielen prosessointia jäsentääkseen ja luodakseen yksiköitä ja tarvikkeita reseptiesi ainesosille. Ominaisuus on kokeellinen, eikä välttämättä toimi aina odotetulla tavalla. Jos et halua näitä jäsennettyjä tuloksia, voit painaa \"Peruuta\", ja muutoksiasi ei tallenneta.",
"ingredient-parser": "Ainesosan jäsentäjä",
"explanation": "Paina \"Jäsennä kaikki\" -painiketta käyttääksesi ainesosien jäsennystyökalua. Kun ainesosat ovat saatavilla, voit tarkastella kohteita ja varmistaa, että jäsennys onnistui. Mallin luottamusarvo mainitaan kohteen nimen oikealla puolella. Tämä arvo on yksittäisten arvojen keskiarvo, eikä se ole aina kovin tarkka.",
"alerts-explainer": "Ohjelmisto hälyttää, mikäli jokin sopiva ruoka tai yksikkö löytyy, mutta sitä ei ole olemassa tietokannassa.",
"select-parser": "Valitse jäsentäjätyökalu",
"natural-language-processor": "Luonnollisen kielen prosessointityökalu",
"brute-parser": "Voimakas jäsentäjätyökalu",
"openai-parser": "OpenAI Parser",
"parse-all": "Jäsennä kaikki",
"no-unit": "Ei yksikköä",
"missing-unit": "Luo puuttuva yksikkö: {unit}",
"missing-food": "Luo puuttuva ruoka: {food}",
"no-food": "Ei ruokaa"
}
},
"search": {
@@ -717,7 +718,7 @@
"no-unused-items": "Ei käyttämättömiä kohteita",
"recipes-affected": "Ei vaikuttanut resepteihin|Vaikutti yhteen reseptiin|Vaikutti {count} reseptiin",
"remove-unused": "Poista käyttämättömät",
"title-case-all": "Title Case All",
"title-case-all": "Isot alkukirjaimet kaikille sanoille",
"toolbox": "Työkalut",
"unorganized": "Järjestämätön"
},
@@ -763,8 +764,11 @@
"build": "Koonti",
"recipe-scraper-version": "Reseptikaappaimen versio",
"oidc-ready": "OIDC valmis",
"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-error-text": "Kaikkia OIDC-arvoja ei ole määritelty. Jos et käytä OIDC-todennusta, voidaan asia jättää huomiotta.",
"oidc-ready-success-text": "Kaikki vaaditut OIDC-muuttujat asetettu.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Kaikki ostoslistat",
@@ -778,6 +782,7 @@
"food": "Elintarvikkeet",
"note": "Muistiinpano",
"label": "Tunnus",
"save-label": "Save Label",
"linked-item-warning": "Tämä kohde on linkitetty yhteen tai useampaan reseptiin. Yksikköjen tai elintarvikkeiden muuttaminen tuottaa odottamattomia tuloksia, kun reseptiä lisätään tai poistetaan tästä luettelosta.",
"toggle-food": "Vaihda elintarvike",
"manage-labels": "Hallinnoi nimikkeitä",
@@ -837,7 +842,7 @@
"untagged-count": "Tunnisteettomat {count}",
"create-a-tag": "Luo tunniste",
"tag-name": "Tunnisteen nimi",
"tag": "Tag"
"tag": "Tunniste"
},
"tool": {
"tools": "Työkalut",
@@ -847,7 +852,7 @@
"create-new-tool": "Luo Uusi Työkalu",
"on-hand-checkbox-label": "Näytä työkalut, jotka omistan jo (valittu)",
"required-tools": "Tarvittavat Työkalut",
"tool": "Tool"
"tool": "Työkalu"
},
"user": {
"admin": "Ylläpitäjä",
@@ -872,11 +877,11 @@
"link-id": "Linkin ID",
"link-name": "Linkin nimi",
"login": "Kirjaudu",
"login-oidc": "Login with",
"or": "or",
"login-oidc": "Kirjaudu sisään käyttäen",
"or": "tai",
"logout": "Uloskirjaudu",
"manage-users": "Käyttäjien hallinta",
"manage-users-description": "Create and manage users.",
"manage-users-description": "Luo ja hallitse käyttäjiä.",
"new-password": "Uusi salasana",
"new-user": "Uusi käyttäjä",
"password-has-been-reset-to-the-default-password": "Salasana on palautettu oletussalasanaksi",
@@ -885,7 +890,7 @@
"password-updated": "Salasana päivitetty",
"password": "Salasana",
"password-strength": "Salasana on {strength}",
"please-enter-password": "Please enter your new password.",
"please-enter-password": "Syötä uusi salasanasi.",
"register": "Rekisteröidy",
"reset-password": "Palauta salasana",
"sign-in": "Kirjaudu",
@@ -906,7 +911,7 @@
"username": "Käyttäjänimi",
"users-header": "KÄYTTÄJÄT",
"users": "Käyttäjät",
"user-not-found": "User not found",
"user-not-found": "Käyttäjää ei löytynyt",
"webhook-time": "Webhook-aika",
"webhooks-enabled": "Webhookit käytössä",
"you-are-not-allowed-to-create-a-user": "Sinulla ei ole oikeutta luoda käyttäjää",
@@ -929,7 +934,7 @@
"user-management": "Käyttäjien Hallinta",
"reset-locked-users": "Nollaa Lukitut Käyttäjät",
"admin-user-creation": "Ylläpitokäyttäjän Luonti",
"admin-user-management": "Admin User Management",
"admin-user-management": "Ylläpidon käyttäjien hallinta",
"user-details": "Käyttäjän tiedot",
"user-name": "Käyttäjänimi",
"authentication-method": "Todentamistapa",
@@ -940,11 +945,11 @@
"user-can-manage-group": "Käyttäjä voi hallita ryhmää",
"user-can-organize-group-data": "Käyttäjä voi järjestellä ryhmän tietoja",
"enable-advanced-features": "Salli edistyneemmät ominaisuudet",
"it-looks-like-this-is-your-first-time-logging-in": "It looks like this is your first time logging in.",
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!",
"forgot-password": "Forgot Password",
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.",
"changes-reflected-immediately": "Changes to this user will be reflected immediately."
"it-looks-like-this-is-your-first-time-logging-in": "Tämä vaikuttaa olevan ensimmäinen kirjautumisesi.",
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Vaihda sähköpostisi asetuksista, jos et halua nähdä tätä enää.",
"forgot-password": "Unohditko salasanasi",
"forgot-password-text": "Syötä sähköpostiosoitteesi, niin voit muuttaa salasanaasi linkin kautta.",
"changes-reflected-immediately": "Muutokset tähän käyttäjään astuvat välittömästi voimaan."
},
"language-dialog": {
"translated": "käännetty",
@@ -966,8 +971,8 @@
"food-label": "Elintarvikkeiden nimike",
"edit-food": "Muokkaa elintarviketta",
"food-data": "Elintarvikkeiden tiedot",
"example-food-singular": "ex: Onion",
"example-food-plural": "ex: Onions"
"example-food-singular": "esim. sipuli",
"example-food-plural": "esim. sipulit"
},
"units": {
"seed-dialog-text": "Lisää tietokantaan yksiköt paikallisen kielen perusteella.",
@@ -978,7 +983,7 @@
"merging-unit-into-unit": "Yhdistä {0} ja {1} yhdeksi",
"create-unit": "Luo yksikkö",
"abbreviation": "Lyhenne",
"plural-abbreviation": "Plural Abbreviation",
"plural-abbreviation": "Monikon lyhenne",
"description": "Kuvaus",
"display-as-fraction": "Näytä murtolukuna",
"use-abbreviation": "Käytä Lyhennettä",
@@ -986,10 +991,10 @@
"unit-data": "Yksikkötiedot",
"use-abbv": "Käytä lyhennettä.",
"fraction": "Murtoluku",
"example-unit-singular": "ex: Tablespoon",
"example-unit-plural": "ex: Tablespoons",
"example-unit-abbreviation-singular": "ex: Tbsp",
"example-unit-abbreviation-plural": "ex: Tbsps"
"example-unit-singular": "esim. ruokalusikka",
"example-unit-plural": "esim. ruokalusikat",
"example-unit-abbreviation-singular": "esim. rkl",
"example-unit-abbreviation-plural": "esim. rkl"
},
"labels": {
"seed-dialog-text": "Lisää tietokantaan yleiset tunnisteet paikallisen kielen perusteella.",
@@ -1019,13 +1024,13 @@
"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"
"recipe-actions-data": "Reseptin toimintojen käyttötiedot",
"new-recipe-action": "Uusi reseptin toiminto",
"edit-recipe-action": "Muuta reseptin toimintoa",
"action-type": "Toiminnon tyyppi"
},
"create-alias": "Create Alias",
"manage-aliases": "Manage Aliases",
"create-alias": "Luo alias",
"manage-aliases": "Hallitse aliaksia",
"seed-data": "Tietokannan pohjadata",
"seed": "Lisää pohjadata",
"data-management": "Tietojen hallinta",
@@ -1035,9 +1040,9 @@
"columns": "Sarakkeet",
"combine": "Yhdistä",
"categories": {
"edit-category": "Edit Category",
"new-category": "New Category",
"category-data": "Category Data"
"edit-category": "Muuta luokkaa",
"new-category": "Uusi luokka",
"category-data": "Luokan tiedot"
},
"tags": {
"new-tag": "New Tag",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Se ei ole täydellinen, mutta se tuottaa hyviä tuloksia yleensä ja on hyvä lähtökohta manuaalisesti jäsentää ainesosia yksittäisiin kenttiin. Vaihtoehtoisesti voit myös käyttää Brute-prosessori, joka käyttää kuvion täsmäystekniikkaa tunnistamaan ainesosia.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Näytä yksilöllinen luottamus",
"ingredient-text": "Ainesosan Teksti",
"average-confident": "{0} Luottamus",

View File

@@ -594,6 +594,7 @@
"select-parser": "Sélectionner l'analyseur",
"natural-language-processor": "Traitement du Langage Naturel",
"brute-parser": "Analyseur brut",
"openai-parser": "OpenAI Parser",
"parse-all": "Tout analyser",
"no-unit": "Pas d'unité",
"missing-unit": "Créer une unité manquante : {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Version du Scraper de recette",
"oidc-ready": "Prêt pour OIDC",
"oidc-ready-error-text": "Toutes les valeurs OIDC ne sont pas configurées. Vous pouvez ignorer cet avertissement si vous nutilisez pas lauthentification OIDC.",
"oidc-ready-success-text": "Les variables OIDC obligatoires sont toutes définies."
"oidc-ready-success-text": "Les variables OIDC obligatoires sont toutes définies.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Toutes les listes",
@@ -778,6 +782,7 @@
"food": "Aliments",
"note": "Note",
"label": "Étiquette",
"save-label": "Save Label",
"linked-item-warning": "Cet article est lié à une ou plusieurs recettes. Ajuster les unités ou les aliments donnera des résultats inattendus lors de lajout ou de la suppression de la recette de cette liste.",
"toggle-food": "Activer/Désactiver aliment",
"manage-labels": "Gérer les libellés",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Ce n'est pas parfait, mais cela donne de bons résultats en général et est un bon point de départ pour décomposer manuellement les ingrédients dans des champs individuels. Alternativement, vous pouvez également utiliser le processeur « Brut » qui utilise une technique de correspondance (patterns) pour identifier les ingrédients.",
"nlp": "NLP",
"brute": "Brut",
"openai": "OpenAI",
"show-individual-confidence": "Afficher la confiance individuelle",
"ingredient-text": "Texte de l'ingrédient",
"average-confident": "Confiant à {0}",

View File

@@ -594,6 +594,7 @@
"select-parser": "Sélectionner l'analyseur",
"natural-language-processor": "Traitement du Langage Naturel",
"brute-parser": "Analyseur brut",
"openai-parser": "OpenAI Parser",
"parse-all": "Tout analyser",
"no-unit": "Pas d'unité",
"missing-unit": "Créer une unité manquante : {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Version du Scraper de recette",
"oidc-ready": "Prêt pour OIDC",
"oidc-ready-error-text": "Toutes les valeurs OIDC ne sont pas configurées. Vous pouvez ignorer cet avertissement si vous nutilisez pas lauthentification OIDC.",
"oidc-ready-success-text": "Les variables OIDC obligatoires sont toutes définies."
"oidc-ready-success-text": "Les variables OIDC obligatoires sont toutes définies.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Toutes les listes",
@@ -778,6 +782,7 @@
"food": "Aliment",
"note": "Note",
"label": "Étiquette",
"save-label": "Save Label",
"linked-item-warning": "Cet article est lié à une ou plusieurs recettes. Ajuster les unités ou les aliments donnera des résultats inattendus lors de lajout ou de la suppression de la recette de cette liste.",
"toggle-food": "Activer/Désactiver aliment",
"manage-labels": "Gérer les libellés",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Ce n'est pas parfait, mais cela donne de bons résultats en général et est un bon point de départ pour décomposer manuellement les ingrédients dans des champs individuels. Alternativement, vous pouvez également utiliser le processeur « Brut » qui utilise une technique de correspondance (patterns) pour identifier les ingrédients.",
"nlp": "NLP",
"brute": "Brut",
"openai": "OpenAI",
"show-individual-confidence": "Afficher la confiance individuelle",
"ingredient-text": "Texte de l'ingrédient",
"average-confident": "Confiant à {0}",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "גרסת מפענך המתכונים",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "כל הרשימות",
@@ -778,6 +782,7 @@
"food": "אוכל",
"note": "הערה",
"label": "תווית",
"save-label": "Save Label",
"linked-item-warning": "האובייקט הזה מקושר לאחד או יותר מתכונים. שינוי היחידות או האוכל יוביל לתוצאות בלתי צפויות בהוספה או הסרת מתכונים מהרשימה.",
"toggle-food": "הצג/הסתר אוכל",
"manage-labels": "ניהול תויות",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "זה לא מושלם, אבל מספק תוצאות טובות באופן כללי ומספק נקודת התחלה טובה עבור עיבוד ידני של מרכיבים לשדות נפרדים. לחילופין ניתן להשתמש בעיבוד \"נוקשה\" לזיהוי המרכיבים.",
"nlp": "NLP",
"brute": "נוקשה",
"openai": "OpenAI",
"show-individual-confidence": "הראה תאימות בודדת",
"ingredient-text": "טקסט מרכיב",
"average-confident": "{0} דיוק",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Verzija skraper alata za recepte",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Svi Popisi",
@@ -778,6 +782,7 @@
"food": "Namirnica",
"note": "Bilješka",
"label": "Oznaka",
"save-label": "Save Label",
"linked-item-warning": "Ova stavka je povezana s jednim ili više recepata. Prilagođavanje jedinica ili namirnica može rezultirati neočekivanim rezultatima prilikom dodavanja ili uklanjanja recepta s ove liste.",
"toggle-food": "Prebaci prekidač Namirnice",
"manage-labels": "Upravljanje Oznakama",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Nije savršen, ali općenito daje odlične rezultate i dobra je polazna točka za ručno parsiranje sastojaka u pojedinačna polja. Alternativno, možete koristiti \"Brute\" procesor koji koristi tehniku usklađivanja uzoraka za identifikaciju sastojaka.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Prikaži pojedinačno povjerenje",
"ingredient-text": "Tekst Sastojka",
"average-confident": "{0} - Siguran",

View File

@@ -594,6 +594,7 @@
"select-parser": "Válasszon elemzőt",
"natural-language-processor": "Természetes nyelvi feldolgozó",
"brute-parser": "Brute elemző",
"openai-parser": "OpenAI 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}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Receptkinyerő verziója",
"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."
"oidc-ready-success-text": "A szükséges OIDC változók mind beállítva.",
"openai-ready": "OpenAI készen áll",
"openai-ready-error-text": "Nem minden OpenAI érték van beállítva. Ez figyelmen kívül hagyható, ha nem használja az OpenAI funkcióit.",
"openai-ready-success-text": "Minden szükséges OpenAI változó beállítva."
},
"shopping-list": {
"all-lists": "Összes lista",
@@ -778,6 +782,7 @@
"food": "Étel",
"note": "Megjegyzés",
"label": "Címke",
"save-label": "Save Label",
"linked-item-warning": "Ez a tétel egy vagy több recepthez kapcsolódik. A mennyiségi egységek vagy az alapanyagok átállítása nem várt eredményeket fog eredményezni, amikor hozzáadja vagy eltávolítja a receptet ebből a listából.",
"toggle-food": "Váltás alapanyagokra",
"manage-labels": "Címkék kezelése",
@@ -1022,7 +1027,7 @@
"recipe-actions-data": "Recipe Actions Data",
"new-recipe-action": "New Recipe Action",
"edit-recipe-action": "Edit Recipe Action",
"action-type": "Action Type"
"action-type": "Művelet típusa"
},
"create-alias": "Alias készítése",
"manage-aliases": "Alias kezelése",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Nem tökéletes, de általában nagyszerű eredményeket ad, és jó kiindulópont a hozzávalók kézi elemzésére az egyedi mezőkbe. Alternatívaként használhatja a \"Brute\" feldolgozót is, amely egy mintaillesztési technikát használ a hozzávalók azonosítására.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "",
"ingredient-text": "Hozzávaló szöveg",
"average-confident": "{0}-os bizonyosság",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -587,18 +587,19 @@
"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"
"experimental-alert-text": "Mealie utilizza l'elaborazione del linguaggio naturale per analizzare e creare unità e prodotti alimentari per i vostri ingredienti di ricetta. Questa funzione è sperimentale e potrebbe non funzionare sempre come previsto. Se preferisci non usare i risultati analizzati, puoi selezionare 'Annulla' e le tue modifiche non saranno salvate.",
"ingredient-parser": "Analizzatore ingredienti",
"explanation": "Per utilizzare l'analizzatore degli ingredienti, fare clic sul pulsante 'Analizza tutto' per avviare il processo. Una volta che gli ingredienti elaborati saranno disponibili, sarà possibile rivedere gli elementi e verificare che siano stati analizzati correttamente. Il punteggio di confidenza del modello viene visualizzato alla destra del titolo dell'elemento. Questo punteggio è una media di tutti i singoli punteggi e potrebbe non essere sempre completamente accurato.",
"alerts-explainer": "Gli avvisi verranno visualizzati se si trova un prodotto o un'unità corrispondente ma non esiste nel database.",
"select-parser": "Seleziona Analizzatore",
"natural-language-processor": "Analizzatore di Linguaggio Naturale",
"brute-parser": "Analizzatore brutale",
"openai-parser": "OpenAI Parser",
"parse-all": "Analizza tutto",
"no-unit": "Nessuna unità",
"missing-unit": "Crea unità mancante: {unit}",
"missing-food": "Crea cibo mancante: {food}",
"no-food": "Nessun Alimento"
}
},
"search": {
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Versione Recipe Scraper",
"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."
"oidc-ready-success-text": "Le variabili OIDC richieste sono tutte impostate.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Tutte le Liste",
@@ -778,6 +782,7 @@
"food": "Alimenti",
"note": "Nota",
"label": "Etichetta",
"save-label": "Save Label",
"linked-item-warning": "Questo elemento è collegato a una o più ricette. La modifica delle unità o degli alimenti potrebbe dare risultati inattesi quando si aggiunge o si rimuove la ricetta da questo elenco.",
"toggle-food": "Attiva/Disattiva Alimento",
"manage-labels": "Gestisci Etichette",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Non è perfetto, ma produce ottimi risultati in generale ed è un buon punto di partenza per separare manualmente gli ingredienti in singoli campi. In alternativa, è anche possibile utilizzare il processore \"Bruto\" che utilizza una tecnica di corrispondenza di modello per identificare gli ingredienti.",
"nlp": "NLP",
"brute": "Bruto",
"openai": "OpenAI",
"show-individual-confidence": "Mostra fiducia individuale",
"ingredient-text": "Testo Ingrediente",
"average-confident": "{0} Fiducia",

View File

@@ -18,8 +18,8 @@
"log-lines": "ログの行",
"not-demo": "非デモ",
"portfolio": "ポートフォリオ",
"production": "生産",
"support": "お問い合わせ",
"production": "Production",
"support": "ヘルプ",
"version": "バージョン",
"unknown-version": "不明",
"sponsor": "スポンサー"
@@ -29,7 +29,7 @@
"code": "コード",
"file": "ファイル",
"image": "画像",
"new-asset": "新しい資産",
"new-asset": "新しいアセット",
"pdf": "PDF",
"recipe": "レシピ",
"show-assets": "資産を表示",
@@ -64,7 +64,7 @@
"something-went-wrong": "問題が発生しました",
"subscribed-events": "購読中のイベント",
"test-message-sent": "テストメッセージを送信しました",
"message-sent": "Message Sent",
"message-sent": "メッセージが送信されました",
"new-notification": "新着通知",
"event-notifiers": "イベント通知",
"apprise-url-skipped-if-blank": "通知用URL (空欄の場合はスキップ)",
@@ -72,12 +72,12 @@
"what-events": "この通知はどのイベントを購読すべきですか?",
"user-events": "ユーザーイベント",
"mealplan-events": "献立イベント",
"when-a-user-in-your-group-creates-a-new-mealplan": "グループ内のユーザーが新しい食事プランを作成したとき",
"when-a-user-in-your-group-creates-a-new-mealplan": "グループ内のユーザーが新しい献立を作成したとき",
"shopping-list-events": "買い物リストイベント",
"cookbook-events": "料理本イベント",
"tag-events": "タグイベント",
"category-events": "カテゴリイベント",
"when-a-new-user-joins-your-group": "新しいユーザーがあなたのグループに参加する際",
"when-a-new-user-joins-your-group": "新しいユーザーがグループに参加したとき",
"recipe-events": "レシピイベント"
},
"general": {
@@ -161,7 +161,7 @@
"test": "テスト",
"themes": "テーマ",
"thursday": "木曜日",
"title": "Title",
"title": "タイトル",
"token": "トークン",
"tuesday": "火曜日",
"type": "タイプ",
@@ -585,7 +585,7 @@
"screen-awake": "画面をスリープ状態にしない",
"remove-image": "画像を削除",
"nextStep": "次のステップ",
"recipe-actions": "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",
@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper バージョン",
"oidc-ready": "OIDC対応",
"oidc-ready-error-text": "すべてのOIDC値が設定されていません。OIDC認証を使用していない場合は無視できます。",
"oidc-ready-success-text": "必要なOIDC変数はすべて設定されています。"
"oidc-ready-success-text": "必要なOIDC変数はすべて設定されています。",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "すべてのリスト",
@@ -778,6 +782,7 @@
"food": "食料",
"note": "メモ",
"label": "ラベル",
"save-label": "Save Label",
"linked-item-warning": "このアイテムは 1 つ以上のレシピにリンクされています。このリストにレシピを追加または削除するときに、単位や食品を調整すると予期しない結果が生じることがあります。",
"toggle-food": "食料の切り替え",
"manage-labels": "ラベルの管理",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "これは完璧ではありませんが、一般的に優れた結果が得られ、成分を手動で個別のフィールドに解析するための良い出発点となります。あるいは、パターン マッチング技術を使用して成分を識別する「Brute」プロセッサを使用することもできます。",
"nlp": "NLP",
"brute": "ブルート",
"openai": "OpenAI",
"show-individual-confidence": "個々の信頼性を表示",
"ingredient-text": "材料テキスト",
"average-confident": "{0} 自信あり",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Visi sąrašai",
@@ -778,6 +782,7 @@
"food": "Maistas",
"note": "Pastaba",
"label": "Žyma",
"save-label": "Save Label",
"linked-item-warning": "Šis įrašas susietas su vienu ar keliais receptais. Jei pakeisite vienetus ar produktus, galimi nenumatyti rezultatai pridedant ar pašalinant receptą iš šio sąrašo.",
"toggle-food": "Įjungti produktą",
"manage-labels": "Tvarkyti žymas",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Šiuo metu funkcionalumo veikimas tobulinamas, bet jau duoda pakankamai gerus rezultatus ir yra nebloga pradžia rankiniu būdu skirstant ingredientus į atskirus laukelius. Taip pat galima pasinaudoti funkcija \"Netikslus apdorojimas\", kuri atpažįsta ingredientus pagal šabloninį algoritmą.",
"nlp": "NLP",
"brute": "Netikslus apdorojimas",
"openai": "OpenAI",
"show-individual-confidence": "Rodyti individualų patikimumą",
"ingredient-text": "Ingrediento tekstas",
"average-confident": "{0} patikimumas",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI verwerker",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Versie van de receptenscraper",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI staat klaar",
"openai-ready-error-text": "Niet alle tekstvakken voor OpenAI zijn ingevuld. Als je geen OpenAI gebruikt kun je dit leeg laten.",
"openai-ready-success-text": "Verplichte tekstvakken voor OpenAI zijn ingevuld."
},
"shopping-list": {
"all-lists": "Alle lijsten",
@@ -778,6 +782,7 @@
"food": "Voedsel",
"note": "Notitie",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "Dit element is gekoppeld aan een of meer recepten. Het aanpassen van de eenheden of ingrediënten zal onverwachte resultaten opleveren bij het toevoegen of verwijderen van het recept uit deze lijst.",
"toggle-food": "Voedsel schakelen",
"manage-labels": "Labels beheren",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Het is niet perfect, maar het levert uitstekende resultaten op in het algemeen en is een goed uitgangspunt voor het handmatig verwerken van ingrediënten in afzonderlijke velden. Je kunt ook de \"Brute\" processor gebruiken die een patroonovereenkomende techniek gebruikt om ingrediënten te identificeren.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI kunstmatige intelligentie",
"show-individual-confidence": "Individuele overtuiging tonen",
"ingredient-text": "Ingrediënt tekst",
"average-confident": "{0} overtuigd",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Versjon på oppskrift-scraper",
"oidc-ready": "OIDC klar",
"oidc-ready-error-text": "Ikke alle OIDC-verdier er konfigurert. Dette kan ignoreres hvis du ikke bruker OIDC-autentisering.",
"oidc-ready-success-text": "Alle obligratoriske OIDC-variabler er satt."
"oidc-ready-success-text": "Alle obligratoriske OIDC-variabler er satt.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Alle lister",
@@ -778,6 +782,7 @@
"food": "Matvare",
"note": "Notat",
"label": "Etikett",
"save-label": "Save Label",
"linked-item-warning": "Dette elementet er koblet til én eller flere oppskrifter. Å endre enheter eller matvarer vil føre til uventede resultater når oppskriften legges til eller fjernes fra denne listen.",
"toggle-food": "Vis/skjul matvare",
"manage-labels": "Administrer etiketter",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Modellen er ikke perfekt, men det gir generelt sett gode resultater og er et godt utgangspunkt for å manuelt analysere ingredienser i individuelle felt. Alternativt kan du også bruke 'Brute'-prosessoren som bruker mønstergjenkjennelsesteknikker for å identifisere ingredienser.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Vis individuell konfidens",
"ingredient-text": "Ingredienstekst",
"average-confident": "{0} Troverdig",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Wersja Scrapera Przepisów",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Wszystkie listy",
@@ -778,6 +782,7 @@
"food": "Jedzenie",
"note": "Notatka",
"label": "Etykieta",
"save-label": "Save Label",
"linked-item-warning": "Ten przedmiot jest powiązany z jednym lub kilkoma przepisami. Dostosowanie jednostek lub żywności przyniesie niespodziewane wyniki podczas dodawania lub usuwania przepisu z tej listy.",
"toggle-food": "Przełącz Żywność",
"manage-labels": "Zarządzaj Etykietami",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Nie jest on idealny, ale na ogół daje świetne wyniki i jest dobrym punktem wyjścia do ręcznego przydzielania składników w poszczególne pola. Alternatywnie możesz również użyć procesora \"Brut\", który używa dopasowywujący schemat do identyfikacji składników.",
"nlp": "NLP",
"brute": "Brut",
"openai": "OpenAI",
"show-individual-confidence": "Pokaż pojedyncze pewności",
"ingredient-text": "Tekst składnika",
"average-confident": "Pewność {0}",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Versão do receptor de receita",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Todas as Listas",
@@ -778,6 +782,7 @@
"food": "Comida",
"note": "Nota",
"label": "Marcador",
"save-label": "Save Label",
"linked-item-warning": "Este item está vinculado a uma ou mais receitas. Ajustar as unidades ou alimentos produzirá resultados inesperados ao adicionar ou remover a receita desta lista.",
"toggle-food": "Mostrar nome da Comida",
"manage-labels": "Gerenciar marcadores",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Não é perfeito, mas produz grandes resultados em geral e é um bom ponto de partida para analisar ingredientes manualmente em campos individuais. Como alternativa, você também pode usar o processador \"Brute\" que usa uma técnica de correspondência de padrões para identificar ingredientes.",
"nlp": "PLN",
"brute": "Bruto",
"openai": "OpenAI",
"show-individual-confidence": "Mostrar confiança individual",
"ingredient-text": "Texto de Ingrediente",
"average-confident": "{0} confiante",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Versão do Scraper de receitas",
"oidc-ready": "Suporta OICD",
"oidc-ready-error-text": "Nem todos os valores OICD estão configurados. Pode ignorar isto se não estiver a utilizar autenticação OICD.",
"oidc-ready-success-text": "As variáveis OICD necessárias estão definidas."
"oidc-ready-success-text": "As variáveis OICD necessárias estão definidas.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Todas as Listas",
@@ -778,6 +782,7 @@
"food": "Alimentos",
"note": "Nota",
"label": "Rótulo",
"save-label": "Save Label",
"linked-item-warning": "Este item tem ligação a uma ou mais receitas. Ajustar as unidades ou alimentos irá produzir resultados inesperados quando adicionar ou remover a receita desta lista.",
"toggle-food": "Alternar Alimento",
"manage-labels": "Gerir Rótulos",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Não é perfeito, mas produz bons resultados em geral e é um bom ponto de partida para, manualmente, fazer corresponder ingredientes a campos individuais. Em alternativa, também pode usar o processador \"Brute\" que usa uma técnica de correspondência de padrões para identificar ingredientes.",
"nlp": "PLN",
"brute": "Bruto",
"openai": "OpenAI",
"show-individual-confidence": "Mostrar confiança individual",
"ingredient-text": "Texto de Ingrediente",
"average-confident": "{0} Confiante",

View File

@@ -64,7 +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",
"message-sent": "Mesaj trimis",
"new-notification": "Notificare nouă",
"event-notifiers": "Notificatori de evenimente",
"apprise-url-skipped-if-blank": "URL Apprise (ignorat daca e gol)",
@@ -161,7 +161,7 @@
"test": "Testează",
"themes": "Teme",
"thursday": "Joi",
"title": "Title",
"title": "Titlu",
"token": "Token",
"tuesday": "Marţi",
"type": "Tip",
@@ -210,7 +210,7 @@
"unsaved-changes": "Aveți modificări nesalvate. Doriți să salvați înainte de a închide aplicația? Apăsați \"OK\" pentru a salva sau \"Anulare\" pentru a renunța la modificări.",
"clipboard-copy-failure": "Copierea în clipboard a eșuat.",
"confirm-delete-generic-items": "Sunteți sigur că doriți să ștergeți următoarele?",
"organizers": "Organizers"
"organizers": "Organizatori"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Sunteți sigur că doriți să ștergeți <b>{groupName}<b/>?",
@@ -365,7 +365,7 @@
"choose-migration-type": "Alegeți tipul de migrare",
"tag-all-recipes": "Etichetați toate rețetele cu eticheta {tag-name}",
"nextcloud-text": "Rețetele din Nextcloud pot fi importate dintr-un fișier de tip \".ZIP\" ce conține toate datele stocate în Nextcloud. Vedeți structura directorului din exemplul de mai jos pentru a vă asigura că rețetele pot fi importate cu succes.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below.",
"chowdown-text": "Mealie acceptă nativ formatul repo-ului Chowdown. Descărcați repository-ul de cod ca fișier \".zip\" și încărcați-l mai jos.",
"recipe-1": "Rețeta 1",
"recipe-2": "Rețeta 2",
"paprika-text": "Mealie poate importa rețete din aplicația Paprika. Exportă rețetele din Paprika, redenumește extensia de export în \".zip\" și încarcă-o folosind câmpul de mai jos.",
@@ -375,7 +375,7 @@
"description-long": "Mealie poate importa rețete din Planul de Masă."
},
"myrecipebox": {
"title": "My Recipe Box",
"title": "Cutia mea de rețete",
"description-long": "Mealie can import recipes from My Recipe Box. Export your recipes in CSV format, then upload the .csv file below."
}
},
@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Версия скрейпера рецептов",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Все списки",
@@ -778,6 +782,7 @@
"food": "Продукты",
"note": "Заметка",
"label": "Метка",
"save-label": "Save Label",
"linked-item-warning": "Этот предмет связан с одним или несколькими рецептами. Обновление единиц измерения или продуктов приведет к неожиданным результатам при добавлении или удалении рецепта из этого списка.",
"toggle-food": "Переключить продукты",
"manage-labels": "Настройки меток",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Он не совершенен, но, в целом, дает хорошие результаты и является хорошей отправной точкой для ручного разбора ингредиентов в отдельных полях. Иначе, для определения ингредиентов можно использовать процессор \"Brute\", использующий технику сопоставления образцов (pattern matching).",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Показать уверенность каждого поля",
"ingredient-text": "Описание ингредиента",
"average-confident": "Уверенность {0}",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Verzia scrapera receptov",
"oidc-ready": "OIDC pripravené",
"oidc-ready-error-text": "Niektoré z OIDC hodnôt nie sú nakonfigurované. Toto varovanie je možné ignorovať, ak nepoužívate OIDC autentifikáciu.",
"oidc-ready-success-text": "Všetky potrebné OIDC premenné sú nastavené."
"oidc-ready-success-text": "Všetky potrebné OIDC premenné sú nastavené.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Všetky zoznamy",
@@ -778,6 +782,7 @@
"food": "Jedlo",
"note": "Poznámka",
"label": "Štítok",
"save-label": "Save Label",
"linked-item-warning": "Táto položka je prepojená s jedným alebo viacerými receptami. Zmena jednotiek alebo jedál bude viesť k neželaným zmenám pri pridávaní alebo odoberaní receptov z tohto zoznamu.",
"toggle-food": "Prepnúť jedlo",
"manage-labels": "Spravovať štítky",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Napriek tomu, že nie je bezchybným, vo všeobecnosti poskytuje veľmi dobré výsledky, ktoré môžu slúžiť ako výborný štartovací bod manuálneho parsovania textov prísad do príslušných polí. Alternatívne tiež môžete využiť procesor založený na využití \"hrubej sily\", ktorý využíva na identifikáciu jednotlivých prísad metódu vyhľadávania vzorov v poskytnutom texte.",
"nlp": "NLP",
"brute": "Hrubá sila",
"openai": "OpenAI",
"show-individual-confidence": "Zobraziť individuálne miery spoľahlivosti",
"ingredient-text": "Text prísady",
"average-confident": "{0} Spoľahlivý",

View File

@@ -594,6 +594,7 @@
"select-parser": "Izberi razčlenjevalnik",
"natural-language-processor": "Procesor naravnega jezika",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Razčleni vse",
"no-unit": "Ni enote",
"missing-unit": "Ustvari manjkajočo enoto: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Verzija strgalnika receptov",
"oidc-ready": "OIDC Ready",
"oidc-ready-error-text": "Nekatere OIDC vrednosti niso nastavljene. To lahko ignoriraš, če ne uporabljaš OIDC avtentikacije.",
"oidc-ready-success-text": "Vse zahtevane OIDC spremenljivke so nastavljene."
"oidc-ready-success-text": "Vse zahtevane OIDC spremenljivke so nastavljene.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Vsi seznami",
@@ -778,6 +782,7 @@
"food": "Živilo",
"note": "Opomba",
"label": "Oznaka",
"save-label": "Save Label",
"linked-item-warning": "Ta sestavina je povezana na en ali več receptov. Če spremenite enote količin, se lahko kaj podre, ko dodajate ali odstranjujete iz seznama.",
"toggle-food": "Preklopi med hrano",
"manage-labels": "Upravljanje oznak",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Rezultati niso popolni, ampak so v splošnem uporabni in služijo kot dobro izhodišče za ročno urejanje sestavin v posamezna polja. Kot alternativo lahko uporabiš \"Brute\" procesor, ki deluje na osnovi ujemanja vzorcev za identifikacijo sestavin.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Pokaži zaupanje v posamezne postavke",
"ingredient-text": "Besedilo s sestavino",
"average-confident": "{0} zanesljivost",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Сви спискови",
@@ -778,6 +782,7 @@
"food": "Храна",
"note": "Note",
"label": "Natpis",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Управљај натписима",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Није савршено, али даје одличне резултате и добра је почетна тачка за ручно разлагање саставних делова у појединачна поља. Као алтернативу, такође можете користити \"Брут\" процесор који користи технику усклађивања шаблона за идентификацију састојака.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -64,7 +64,7 @@
"something-went-wrong": "Någonting gick fel",
"subscribed-events": "Prenumererade händelser",
"test-message-sent": "Testmeddelande Skickat",
"message-sent": "Message Sent",
"message-sent": "Meddelandet skickat",
"new-notification": "Ny avisering",
"event-notifiers": "Händelseavisering",
"apprise-url-skipped-if-blank": "Apprise-URL (hoppa över om tom)",
@@ -145,11 +145,11 @@
"save": "Spara",
"settings": "Inställningar",
"share": "Dela",
"show-all": "Show All",
"show-all": "Visa allt",
"shuffle": "Blanda",
"sort": "Sortering",
"sort-ascending": "Sort Ascending",
"sort-descending": "Sort Descending",
"sort-ascending": "Sortera stigande",
"sort-descending": "Sortera fallande",
"sort-alphabetically": "Alfabetisk",
"status": "Status",
"subject": "Ämne",
@@ -161,7 +161,7 @@
"test": "Test",
"themes": "Tema",
"thursday": "Torsdag",
"title": "Title",
"title": "Rubrik",
"token": "Token",
"tuesday": "Tisdag",
"type": "Typ",
@@ -210,7 +210,7 @@
"unsaved-changes": "Du har osparade ändringar. Vill du spara innan du lämnar? Tryck Okej att spara, Avbryt för att ignorera ändringar.",
"clipboard-copy-failure": "Det gick inte att kopiera till urklipp.",
"confirm-delete-generic-items": "Är du säker på att du vill radera följande objekt?",
"organizers": "Organizers"
"organizers": "Organisatörer"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Är du säker på att du vill radera <b>{groupName}<b/>?",
@@ -246,8 +246,8 @@
"group-preferences": "Gruppinställningar",
"private-group": "Privat grupp",
"private-group-description": "Sätta gruppen till privat kommer att sätta alla publika visningslägen till standard. Det skriver över publika inställningar för individuella recept.",
"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": "Tillåt offentlig åtkomst",
"enable-public-access-description": "Gör recept i grupp, offentliga som standard och tillåt besökare att se recept utan att logga in",
"allow-users-outside-of-your-group-to-see-your-recipes": "Tillåt användare utanför din grupp att se dina recept",
"allow-users-outside-of-your-group-to-see-your-recipes-description": "Om aktiverad kan du dela en publik länk för specifika recept utan att användaren behöver auktorisera sig. Om avaktiverad kan du bara dela med användare i din grupp eller med en systemskapad länk",
"show-nutrition-information": "Visa näringsinnehåll",
@@ -361,7 +361,7 @@
},
"recipe-data-migrations": "Migrering av receptdata",
"recipe-data-migrations-explanation": "Recept kan migreras från en annan applikation som stöds till Mealie. Detta är ett bra sätt att komma igång med 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": "Kommer från en annan app, program eller tidigare version av Mealie? Kika på migrationer och se om din data kan bli importerad.",
"choose-migration-type": "Välj migrationstyp",
"tag-all-recipes": "Tagga alla recept med {tag-name} tagg",
"nextcloud-text": "Nextcloud-recept kan importeras från en zip-fil som innehåller data som lagras i Nextcloud. Se exempelmappens struktur nedan för att säkerställa att dina recept kan importeras.",
@@ -375,8 +375,8 @@
"description-long": "Mealie kan importera recept från 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": "Min receptlåda",
"description-long": "Mealie kan importera recept från appen 'My Recipe Box'. Exportera dina recept i CSV format, och sen ladda upp .csv filen nedan."
}
},
"new-recipe": {
@@ -528,7 +528,7 @@
"edit-timeline-event": "Redigera tidslinjehändelse",
"timeline": "Tidslinje",
"timeline-is-empty": "Inget på tidslinjen än. Försök att göra detta recept!",
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
"timeline-no-events-found-try-adjusting-filters": "Inga händelser hittades. Försök justera filtren angivna i sökningen.",
"group-global-timeline": "{groupName} Global tidslinje",
"open-timeline": "Öppna tidslinje",
"made-this": "Jag lagade den här",
@@ -549,7 +549,7 @@
"looking-for-migrations": "Letar du efter migreringar?",
"import-with-url": "Importera från URL",
"create-recipe": "Skapa recept",
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipe-description": "Skapa nytt recept från grunden.",
"create-recipes": "Skapa recept",
"import-with-zip": "Importera från .zip",
"create-recipe-from-an-image": "Skapa recept från en bild",
@@ -587,17 +587,18 @@
"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",
"experimental-alert-text": "Mealie använder naturligt språk för att tolka enheter och livsmedel som behövs för dina recept. Denna funktion är experimentell och kanske inte alltid funkar som förväntat. Om du föredrar att inte använda de tolkade resultatet, kan du välja 'Avbryt' och förändringarna kommer då inte sparas.",
"ingredient-parser": "Ingrediensanalysator",
"explanation": "För att använda ingrediensen parser, klicka på \"Parse All\" knappen för att starta processen. När de bearbetade ingredienserna är tillgängliga, kan du granska objekten och kontrollera att de tolkades korrekt. Modellens självförtroende poäng visas till höger om artikelns titel. Denna poäng är ett genomsnitt av alla individuella poäng och kanske inte alltid vara helt korrekt.",
"alerts-explainer": "Varning kommer visas om en matchande ingrediens eller enhet hittas, men inte existerar i databasen.",
"select-parser": "Välj tolk",
"natural-language-processor": "Naturlig språkbehandlare",
"brute-parser": "Brute Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"openai-parser": "OpenAI Parser",
"parse-all": "Analysera allt",
"no-unit": "Ingen enhet",
"missing-unit": "Skapa saknad enhet: {unit}",
"missing-food": "Skapa saknad ingrediens: {food}",
"no-food": "No Food"
}
},
@@ -762,9 +763,12 @@
"ldap-ready-success-text": "Alla obligatoriska LDAP-variabler är satta.",
"build": "Bygg",
"recipe-scraper-version": "Version av Recept-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": "OIDC Klar",
"oidc-ready-error-text": "Alla OIDC-värden är inte konfigurerade. Detta kan ignoreras om du inte använder OIDC-autentisering.",
"oidc-ready-success-text": "Alla obligatoriska OIDC-variabler är satta.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Visa alla listor",
@@ -778,6 +782,7 @@
"food": "Mat",
"note": "Anteckning",
"label": "Etikett",
"save-label": "Save Label",
"linked-item-warning": "Denna artikel är länkad till ett eller flera recept. Justering av enheter eller livsmedel ger oväntade resultat när du lägger till eller tar bort receptet från denna lista.",
"toggle-food": "Växla mat",
"manage-labels": "Hantera etiketter",
@@ -876,7 +881,7 @@
"or": "eller",
"logout": "Logga ut",
"manage-users": "Hantera användare",
"manage-users-description": "Create and manage users.",
"manage-users-description": "Skapa och hantera användare.",
"new-password": "Nytt lösenord",
"new-user": "Ny användare",
"password-has-been-reset-to-the-default-password": "Lösenordet har återställts till standardlösenordet",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Det är inte perfekt, men det ger bra resultat i allmänhet och är en bra utgångspunkt för att manuellt tolka ingredienser i enskilda områden. Alternativt kan du också använda \"Brute\" processor som använder en mönstermatchningsteknik för att identifiera ingredienser.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Visa individuella självförtroende",
"ingredient-text": "Ingrediens text",
"average-confident": "{0} Säker",

View File

@@ -64,7 +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",
"message-sent": "Mesaj Gönderildi",
"new-notification": "Yeni bildirim",
"event-notifiers": "Etkinlik Bildirimleri",
"apprise-url-skipped-if-blank": "Apprise URL'si (boşsa geçilir)",
@@ -78,7 +78,7 @@
"tag-events": "Etiket Etkinlikleri",
"category-events": "Kategori Etkinlikleri",
"when-a-new-user-joins-your-group": "Grubunuza yeni bir kullanıcı katıldığında",
"recipe-events": "Recipe Events"
"recipe-events": "Tarif Etkinlikleri"
},
"general": {
"add": "Ekle",
@@ -161,7 +161,7 @@
"test": "Dene",
"themes": "Temalar",
"thursday": "Perşembe",
"title": "Title",
"title": "Başlık",
"token": "Anahtar",
"tuesday": "Salı",
"type": "Tür",
@@ -332,7 +332,7 @@
"no-file-selected": "Dosya seçilmedi",
"no-migration-data-available": "No Migration Data Available",
"previous-migrations": "Önceki Yer Değiştirme",
"recipe-migration": "Recipe Migration",
"recipe-migration": "Tarif Taşıma",
"chowdown": {
"description": "Migrate data from Chowdown",
"description-long": "Mealie yerel olarak chowdown veri havuzu biçimini destekler. Kod havuzunu bir .zip dosyası olarak indirin ve aşağıya yükleyin.",
@@ -362,7 +362,7 @@
"recipe-data-migrations": "Recipe Data Migrations",
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
"coming-from-another-application-or-an-even-older-version-of-mealie": "Başka bir uygulamadan mı yoksa Mealie'nin daha eski bir sürümünden mi geliyorsunuz? Taşıma işlemlerine göz atın ve verilerinizin içe aktarılıp aktarılamayacağını görün.",
"choose-migration-type": "Choose Migration Type",
"choose-migration-type": "Taşıma Türünü Seçin",
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
"chowdown-text": "Mealie yerel olarak chowdown veri havuzu formatını destekler. Kod deposunu .zip dosyası olarak indirin ve aşağıya yükleyin.",
@@ -537,7 +537,7 @@
"last-made-date": "En Son {date} Yapıldı",
"api-extras-description": "Tarif ekstraları Mealie API'nin önemli bir özelliğidir. Üçüncü taraf uygulamalardan referans almak üzere bir tarif içinde özel JSON anahtar/değer çiftleri oluşturmanıza olanak tanır. Bu tuşları, örneğin otomasyonları tetiklemek veya istediğiniz cihaza iletilecek özel mesajları bilgi sağlamak için kullanabilirsiniz.",
"message-key": "İleti Anahtarı",
"parse": "Parse",
"parse": "Ayrıştırma",
"attach-images-hint": "Düzenleyiciye sürükleyip bırakarak görselleri ekleyin",
"drop-image": "Yüklenecek resimi sürükleyip bırakın",
"enable-ingredient-amounts-to-use-this-feature": "Enable ingredient amounts to use this feature",
@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"oidc-ready": "OIDC Hazır",
"oidc-ready-error-text": "Tüm OIDC Değerleri yapılandırılmamıştır. OIDC Kimlik Doğrulamasını kullanmıyorsanız bu göz ardı edilebilir.",
"oidc-ready-success-text": "Gerekli OIDC değişkenlerinin tümü ayarlanmıştır."
"oidc-ready-success-text": "Gerekli OIDC değişkenlerinin tümü ayarlanmıştır.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Not",
"label": "Etiket",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Mükemmel değil, ancak genel olarak harika sonuçlar veriyor ve malzemeleri manuel olarak ayrı alanlara ayrıştırmak için iyi bir başlangıç noktası. Alternatif olarak, malzemeleri tanımlamak için bir model eşleştirme tekniği kullanan \"Brute\" işlemciyi de kullanabilirsiniz.",
"nlp": "NLP",
"brute": "Kaba",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "İçerik Metni",
"average-confident": "{0} Confident",

View File

@@ -594,6 +594,7 @@
"select-parser": "Вибрати аналізатор",
"natural-language-processor": "Аналізатор природної мови",
"brute-parser": "Простий аналізатор",
"openai-parser": "OpenAI Parser",
"parse-all": "Аналізувати все",
"no-unit": "Без одиниці",
"missing-unit": "Створити відсутню одиниці: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Версія парсера рецептів",
"oidc-ready": "OIDC готово",
"oidc-ready-error-text": "Не всі значення OIDC налаштовано. Це можна ігнорувати, якщо ви не використовуєте авторизацію OIDC.",
"oidc-ready-success-text": "Всі необхідні змінні OIDC встановлені."
"oidc-ready-success-text": "Всі необхідні змінні OIDC встановлені.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "Всі списки",
@@ -778,6 +782,7 @@
"food": "Продукт",
"note": "Нотатка",
"label": "Етикетка",
"save-label": "Save Label",
"linked-item-warning": "Цей предмет зв'язано з одним або більше рецептами. Зміна одиниць виміру або продуктів дасть неочікувані результати при додаванні або видаленні рецепту з цього списку.",
"toggle-food": "Перемкнути продукт",
"manage-labels": "Керування етикетками",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "Модель не ідеальна, але дає непогані результати в загальному і є хорошим початковим пунктом для ручного парсингу інгредієнтів в окремі поля. Крім того, ви можете використовувати \"Brute\" процесор, який використовує метод підбору для визначення інгредієнтів.",
"nlp": "ОПМ",
"brute": "Брут",
"openai": "OpenAI",
"show-individual-confidence": "Показати індивідуальну впевненість",
"ingredient-text": "Текст інгредієнта",
"average-confident": "Впевненість {0}",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -64,7 +64,7 @@
"something-went-wrong": "出错了\t#",
"subscribed-events": "订阅事件",
"test-message-sent": "测试消息已发送",
"message-sent": "Message Sent",
"message-sent": "已发送消息",
"new-notification": "新通知",
"event-notifiers": "事件通知器",
"apprise-url-skipped-if-blank": "Apprise URL (如果为空则跳过)",
@@ -161,7 +161,7 @@
"test": "测试",
"themes": "布景主题",
"thursday": "周四",
"title": "Title",
"title": "标题",
"token": "密钥",
"tuesday": "周二",
"type": "类型",
@@ -210,7 +210,7 @@
"unsaved-changes": "你有未保存的更改。你希望现在离开前保存吗?保存选择“是”,不保存选择“取消”。",
"clipboard-copy-failure": "未能复制到剪切板。",
"confirm-delete-generic-items": "你确定删除以下条目吗?",
"organizers": "组织者"
"organizers": "管理器"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "您确定要删除<b>{groupName}<b/>吗?",
@@ -585,20 +585,21 @@
"screen-awake": "保持屏幕唤醒",
"remove-image": "删除图片",
"nextStep": "下一步",
"recipe-actions": "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"
"experimental-alert-text": "Mealie使用自然语言处理解析食材,并生成对应的计量单位和食物条目。此项功能尚在试验阶段,并非每次都能达到预期效果。如果你不想使用解析结果,可以选择取消,此时修改将不会被保存。",
"ingredient-parser": "食材解析器",
"explanation": "若要使用食材解析器,请单击“全部解析”按钮。当解析结果出现时,你可以检查结果是否正确。解析模型的置信分数显示在条目名称的右侧。该分数是各项分数的平均值,且不总是准确的。",
"alerts-explainer": "当匹配到食物或计量单位,而该结果不在数据库中时,会显示提醒。",
"select-parser": "选取解析器",
"natural-language-processor": "自然语言处理器",
"brute-parser": "暴力解析器",
"openai-parser": "OpenAI Parser",
"parse-all": "全部解析",
"no-unit": "没有计量单位",
"missing-unit": "创建缺失的计量单位:{unit}",
"missing-food": "创建缺失的食物:{food}",
"no-food": "没有食物"
}
},
"search": {
@@ -764,7 +765,10 @@
"recipe-scraper-version": "食谱刮削器版本",
"oidc-ready": "OIDC 已就绪",
"oidc-ready-error-text": "某些OIDC环境变量尚未配置。如果你不使用OIDC验证可以忽略该报错",
"oidc-ready-success-text": "OIDC所需的环境变量均已配置。"
"oidc-ready-success-text": "OIDC所需的环境变量均已配置。",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "所有购物清单",
@@ -778,6 +782,7 @@
"food": "食品",
"note": "备注",
"label": "标注",
"save-label": "Save Label",
"linked-item-warning": "此条目已经与一个或多个食谱有关。若强行变更它的单位或食品,会在增减下方关联食谱份数时产生意外结果。",
"toggle-food": "是否为数据库中的食品",
"manage-labels": "管理标签",
@@ -876,7 +881,7 @@
"or": "或",
"logout": "登出",
"manage-users": "管理用户",
"manage-users-description": "Create and manage users.",
"manage-users-description": "创建并管理用户。",
"new-password": "新密码",
"new-user": "新建用户",
"password-has-been-reset-to-the-default-password": "密码已被重置为默认密码",
@@ -1019,10 +1024,10 @@
"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"
"recipe-actions-data": "食谱行为数据",
"new-recipe-action": "新建食谱行为",
"edit-recipe-action": "编辑食谱行为",
"action-type": "行为种类"
},
"create-alias": "创建别名",
"manage-aliases": "管理别名",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "它不完美但通常来说结果还不错。推荐用它上手来把食材手动解析成独立字段。或者你也可以选择“Brute解析器“它使用一种模式匹配技术来识别食材。",
"nlp": "自然语言处理",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "显示个体置信度",
"ingredient-text": "食材文本",
"average-confident": "{0}置信度",
@@ -1185,9 +1191,9 @@
"already-set-up-bring-to-homepage": "我已经配置好了,直接跳转到主页",
"common-settings-for-new-sites": "这有一些新站点的常见设置",
"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."
"here-are-a-few-things-to-help-you-get-started": "以下这些可以帮助你开始使用Mealie",
"restore-from-v1-backup": "有之前Mealie v1实例的备份数据你可以在这里恢复它们。",
"manage-profile-or-get-invite-link": "管理你自己的个人资料,或者获取邀请链接分享给其他人。"
}
},
"profile": {
@@ -1196,16 +1202,16 @@
"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": "管理常见的群组设置,如饮食计划和隐私设置。",
@@ -1216,9 +1222,9 @@
"notifiers": "通知方案",
"notifiers-description": "设置基于特定事件触发的邮件提醒和通知推送。",
"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 RecipesChowdown的现有数据迁移至Mealie。",
"email-sent": "邮件已发送",
"error-sending-email": "发送邮件出错",
"personal-information": "个人信息",

View File

@@ -594,6 +594,7 @@
"select-parser": "Select Parser",
"natural-language-processor": "Natural Language Processor",
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}",
@@ -764,7 +765,10 @@
"recipe-scraper-version": "Recipe Scraper Version",
"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-success-text": "Required OIDC variables are all set.",
"openai-ready": "OpenAI Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
"shopping-list": {
"all-lists": "所有清單",
@@ -778,6 +782,7 @@
"food": "Food",
"note": "Note",
"label": "Label",
"save-label": "Save Label",
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
"toggle-food": "Toggle Food",
"manage-labels": "Manage Labels",
@@ -1170,6 +1175,7 @@
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",

View File

@@ -85,12 +85,6 @@ export default defineComponent({
title: i18n.tc("sidebar.maintenance"),
restricted: true,
},
{
icon: $globals.icons.check,
to: "/admin/background-tasks",
title: i18n.tc("sidebar.background-tasks"),
restricted: true,
},
{
icon: $globals.icons.slotMachine,
to: "/admin/parser",

View File

@@ -1,19 +0,0 @@
import { BaseAPI } from "../base/base-clients";
import { ServerTask } from "~/lib/api/types/server";
import { PaginationData } from "~/lib/api/types/non-generated";
const prefix = "/api";
const routes = {
base: `${prefix}/admin/server-tasks`,
};
export class AdminTaskAPI extends BaseAPI {
async testTask() {
return await this.requests.post<ServerTask>(`${routes.base}`, {});
}
async getAll() {
return await this.requests.get<PaginationData<ServerTask>>(routes.base);
}
}

View File

@@ -1,5 +1,4 @@
import { AdminAboutAPI } from "./admin/admin-about";
import { AdminTaskAPI } from "./admin/admin-tasks";
import { AdminUsersApi } from "./admin/admin-users";
import { AdminGroupsApi } from "./admin/admin-groups";
import { AdminBackupsApi } from "./admin/admin-backups";
@@ -9,7 +8,6 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class AdminAPI {
public about: AdminAboutAPI;
public serverTasks: AdminTaskAPI;
public users: AdminUsersApi;
public groups: AdminGroupsApi;
public backups: AdminBackupsApi;
@@ -18,7 +16,6 @@ export class AdminAPI {
constructor(requests: ApiRequestInstance) {
this.about = new AdminAboutAPI(requests);
this.serverTasks = new AdminTaskAPI(requests);
this.users = new AdminUsersApi(requests);
this.groups = new AdminGroupsApi(requests);
this.backups = new AdminBackupsApi(requests);

View File

@@ -15,7 +15,6 @@ import { RegisterAPI } from "./user/user-registration";
import { MealPlanAPI } from "./user/group-mealplan";
import { EmailAPI } from "./user/email";
import { BulkActionsAPI } from "./user/recipe-bulk-actions";
import { GroupServerTaskAPI } from "./user/group-tasks";
import { ToolsApi } from "./user/organizer-tools";
import { GroupMigrationApi } from "./user/group-migrations";
import { GroupReportsApi } from "./user/group-reports";
@@ -46,7 +45,6 @@ export class UserApiClient {
public bulk: BulkActionsAPI;
public groupMigration: GroupMigrationApi;
public groupReports: GroupReportsApi;
public grouperServerTasks: GroupServerTaskAPI;
public tools: ToolsApi;
public shopping: ShoppingApi;
public multiPurposeLabels: MultiPurposeLabelsApi;
@@ -72,7 +70,6 @@ export class UserApiClient {
this.register = new RegisterAPI(requests);
this.mealplans = new MealPlanAPI(requests);
this.mealplanRules = new MealPlanRulesApi(requests);
this.grouperServerTasks = new GroupServerTaskAPI(requests);
// Group
this.groupMigration = new GroupMigrationApi(requests);

View File

@@ -13,6 +13,7 @@ export interface AdminAboutInfo {
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
enableOpenai: boolean;
versionLatest: string;
apiPort: number;
apiDocs: boolean;
@@ -40,6 +41,7 @@ export interface AppInfo {
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
enableOpenai: boolean;
}
export interface AppStartupInfo {
isFirstLogin: boolean;
@@ -80,6 +82,7 @@ export interface CheckAppConfig {
emailReady: boolean;
ldapReady: boolean;
oidcReady: boolean;
enableOpenai: boolean;
baseUrlSet: boolean;
isUpToDate: boolean;
}

View File

@@ -6,7 +6,7 @@
*/
export type ExportTypes = "json";
export type RegisteredParser = "nlp" | "brute";
export type RegisteredParser = "nlp" | "brute" | "openai";
export type TimelineEventType = "system" | "info" | "comment";
export type TimelineEventImage = "has image" | "does not have image";

View File

@@ -1,25 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type ServerTaskNames = "Background Task" | "Database Backup" | "Bulk Recipe Import";
export type ServerTaskStatus = "running" | "finished" | "failed";
export interface ServerTask {
groupId: string;
name?: ServerTaskNames & string;
createdAt?: string;
status?: ServerTaskStatus & string;
log?: string;
id: number;
}
export interface ServerTaskCreate {
groupId: string;
name?: ServerTaskNames & string;
createdAt?: string;
status?: ServerTaskStatus & string;
log?: string;
}

View File

@@ -1,13 +0,0 @@
import { BaseAPI } from "../base/base-clients";
import { ServerTask } from "~/lib/api/types/server";
const prefix = "/api";
const routes = {
base: `${prefix}/groups/server-tasks`,
};
export class GroupServerTaskAPI extends BaseAPI {
async getAll() {
return await this.requests.get<ServerTask[]>(routes.base);
}
}

View File

@@ -17,7 +17,7 @@ import {
} from "~/lib/api/types/recipe";
import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
export type Parser = "nlp" | "brute";
export type Parser = "nlp" | "brute" | "openai";
export interface CreateAsset {
name: string;

View File

@@ -9,6 +9,7 @@ import {
mdiSquareEditOutline,
mdiClose,
mdiTagArrowUpOutline,
mdiTagArrowRight,
mdiTagMultipleOutline,
mdiShapeOutline,
mdiBookOutline,
@@ -293,6 +294,7 @@ export const icons = {
// Organization
tags: mdiTagMultipleOutline,
tagArrowUp: mdiTagArrowUpOutline,
tagArrowRight: mdiTagArrowRight,
categories: mdiShapeOutline,
pages: mdiBookOutline,
book: mdiBookOpenPageVariant,

View File

@@ -1,87 +0,0 @@
<template>
<v-container class="narrow-container">
<BasePageTitle divider>
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-tasks.svg')"></v-img>
</template>
<template #title> {{ $t('admin.background-tasks') }} </template>
{{ $t('admin.background-tasks-description') }}
</BasePageTitle>
<v-card-actions>
<BaseButton color="info" :loading="loading" @click="refreshTasks">
<template #icon> {{ $globals.icons.refresh }} </template>
{{ $t('general.refresh') }}
</BaseButton>
<BaseButton color="info" @click="testTask">
<template #icon> {{ $globals.icons.testTube }} </template>
{{ $t('general.test') }}
</BaseButton>
</v-card-actions>
<v-expansion-panels class="mt-2">
<v-expansion-panel v-for="(task, i) in tasks" :key="i">
<v-expansion-panel-header>
<span>
<v-progress-circular v-if="task.status === 'running'" indeterminate color="info"></v-progress-circular>
<v-icon v-else-if="task.status === 'finished'" large color="success"> {{ $globals.icons.check }}</v-icon>
<v-icon v-else-if="task.status === 'failed'" large color="error"> {{ $globals.icons.close }}</v-icon>
<v-icon v-else-if="task.status === 'pending'" large color="gray"> {{ $globals.icons.pending }}</v-icon>
<span class="ml-2">
{{ task.name }}
</span>
</span>
{{ $d(Date.parse(task.createdAt), "short") }}
</v-expansion-panel-header>
<v-expansion-panel-content style="white-space: pre-line">
{{ task.log === "" ? $t('admin.no-logs-found') : task.log }}
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-container>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import { ServerTask } from "~/lib/api/types/server";
import { useAdminApi } from "~/composables/api";
export default defineComponent({
layout: "admin",
setup() {
const api = useAdminApi();
const tasks = ref<ServerTask[]>([]);
const loading = ref(false);
async function refreshTasks() {
loading.value = true;
const { data } = await api.serverTasks.getAll();
if (data) {
tasks.value = data.items;
}
loading.value = false;
}
async function testTask() {
await api.serverTasks.testTask();
refreshTasks();
}
onMounted(async () => {
await refreshTasks();
});
return {
loading,
refreshTasks,
testTask,
tasks,
};
},
head() {
return {
title: this.$t("admin.tasks"),
};
},
});
</script>

View File

@@ -13,6 +13,7 @@
<v-btn-toggle v-model="parser" dense mandatory @change="processIngredient">
<v-btn value="nlp"> {{ $t('admin.nlp') }} </v-btn>
<v-btn value="brute"> {{ $t('admin.brute') }} </v-btn>
<v-btn value="openai"> {{ $t('admin.openai') }} </v-btn>
</v-btn-toggle>
<v-checkbox v-model="showConfidence" class="ml-5" :label="$t('admin.show-individual-confidence')"></v-checkbox>
@@ -63,8 +64,8 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { IngredientConfidence } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api";
import { IngredientConfidence } from "~/lib/api/types/recipe";
import { Parser } from "~/lib/api/user/recipes/recipe";
type ConfidenceAttribute = "average" | "comment" | "name" | "unit" | "quantity" | "food";

View File

@@ -268,6 +268,15 @@ export default defineComponent({
color: appConfig.value.oidcReady ? goodColor : warningColor,
icon: appConfig.value.oidcReady ? goodIcon : warningIcon,
},
{
id: "openai-ready",
text: i18n.t("settings.openai-ready"),
status: appConfig.value.enableOpenai,
errorText: i18n.t("settings.openai-ready-error-text"),
successText: i18n.t("settings.openai-ready-success-text"),
color: appConfig.value.enableOpenai ? goodColor : warningColor,
icon: appConfig.value.enableOpenai ? goodIcon : warningIcon,
},
];
return data;
});

View File

@@ -19,87 +19,93 @@
<BaseOverflowButton
v-model="parser"
btn-class="mx-2 mb-4"
:items="[
{
text: $tc('recipe.parser.natural-language-processor'),
value: 'nlp',
},
{
text: $tc('recipe.parser.brute-parser'),
value: 'brute',
},
]"
:items="availableParsers"
/>
</div>
</BaseCardSectionTitle>
<div class="d-flex mt-n3 mb-4 justify-end" style="gap: 5px">
<BaseButton cancel class="mr-auto" @click="$router.go(-1)"></BaseButton>
<BaseButton color="info" @click="fetchParsed">
<BaseButton color="info" :disabled="parserLoading" @click="fetchParsed">
<template #icon> {{ $globals.icons.foods }}</template>
{{ $tc("recipe.parser.parse-all") }}
</BaseButton>
<BaseButton save @click="saveAll" />
<BaseButton save :disabled="parserLoading" @click="saveAll" />
</div>
<v-expansion-panels v-model="panels" multiple>
<draggable
v-if="parsedIng.length > 0"
v-model="parsedIng"
handle=".handle"
:style="{ width: '100%' }"
ghost-class="ghost"
>
<v-expansion-panel v-for="(ing, index) in parsedIng" :key="index">
<v-expansion-panel-header class="my-0 py-0" disable-icon-rotate>
<template #default="{ open }">
<v-fade-transition>
<span v-if="!open" key="0"> {{ ing.input }} </span>
</v-fade-transition>
</template>
<template #actions>
<v-icon left :color="isError(ing) ? 'error' : 'success'">
{{ isError(ing) ? $globals.icons.alert : $globals.icons.check }}
</v-icon>
<div class="my-auto" :color="isError(ing) ? 'error-text' : 'success-text'">
{{ ing.confidence ? asPercentage(ing.confidence.average) : "" }}
</div>
</template>
</v-expansion-panel-header>
<v-expansion-panel-content class="pb-0 mb-0">
<RecipeIngredientEditor v-model="parsedIng[index].ingredient" allow-insert-ingredient @insert-ingredient="insertIngredient(index)" @delete="deleteIngredient(index)" />
{{ ing.input }}
<v-card-actions>
<v-spacer />
<BaseButton
v-if="errors[index].unitError && errors[index].unitErrorMessage !== ''"
color="warning"
small
@click="createUnit(ing.ingredient.unit, index)"
>
{{ errors[index].unitErrorMessage }}
</BaseButton>
<BaseButton
v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''"
color="warning"
small
@click="createFood(ing.ingredient.food, index)"
>
{{ errors[index].foodErrorMessage }}
</BaseButton>
</v-card-actions>
</v-expansion-panel-content>
</v-expansion-panel>
</draggable>
</v-expansion-panels>
<div v-if="parserLoading">
<AppLoader
v-if="parserLoading"
:loading="parserLoading"
waiting-text=""
/>
</div>
<div v-else>
<v-expansion-panels v-model="panels" multiple>
<draggable
v-if="parsedIng.length > 0"
v-model="parsedIng"
handle=".handle"
:style="{ width: '100%' }"
ghost-class="ghost"
>
<v-expansion-panel v-for="(ing, index) in parsedIng" :key="index">
<v-expansion-panel-header class="my-0 py-0" disable-icon-rotate>
<template #default="{ open }">
<v-fade-transition>
<span v-if="!open" key="0"> {{ ing.input }} </span>
</v-fade-transition>
</template>
<template #actions>
<v-icon left :color="isError(ing) ? 'error' : 'success'">
{{ isError(ing) ? $globals.icons.alert : $globals.icons.check }}
</v-icon>
<div class="my-auto" :color="isError(ing) ? 'error-text' : 'success-text'">
{{ ing.confidence ? asPercentage(ing.confidence.average) : "" }}
</div>
</template>
</v-expansion-panel-header>
<v-expansion-panel-content class="pb-0 mb-0">
<RecipeIngredientEditor v-model="parsedIng[index].ingredient" allow-insert-ingredient @insert-ingredient="insertIngredient(index)" @delete="deleteIngredient(index)" />
{{ ing.input }}
<v-card-actions>
<v-spacer />
<BaseButton
v-if="errors[index].unitError && errors[index].unitErrorMessage !== ''"
color="warning"
small
@click="createUnit(ing.ingredient.unit, index)"
>
{{ errors[index].unitErrorMessage }}
</BaseButton>
<BaseButton
v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''"
color="warning"
small
@click="createFood(ing.ingredient.food, index)"
>
{{ errors[index].foodErrorMessage }}
</BaseButton>
</v-card-actions>
</v-expansion-panel-content>
</v-expansion-panel>
</draggable>
</v-expansion-panels>
</div>
</v-container>
</v-container>
</template>
<script lang="ts">
import { computed, defineComponent, ref, useContext, useRoute, useRouter } from "@nuxtjs/composition-api";
import { computed, defineComponent, ref, useContext, useRoute, useRouter, watch } from "@nuxtjs/composition-api";
import { invoke, until } from "@vueuse/core";
import draggable from "vuedraggable";
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
import { useAppInfo, useUserApi } from "~/composables/api";
import { useRecipe } from "~/composables/recipes";
import { useFoodData, useFoodStore, useUnitData, useUnitStore } from "~/composables/store";
import { useParsingPreferences } from "~/composables/use-users/preferences";
import { uuid4 } from "~/composables/use-utils";
import {
CreateIngredientFood,
CreateIngredientUnit,
@@ -108,12 +114,7 @@ import {
ParsedIngredient,
RecipeIngredient,
} from "~/lib/api/types/recipe";
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
import { useUserApi } from "~/composables/api";
import { useRecipe } from "~/composables/recipes";
import { useFoodData, useFoodStore, useUnitStore, useUnitData } from "~/composables/store";
import { Parser } from "~/lib/api/user/recipes/recipe";
import { uuid4 } from "~/composables/use-utils";
interface Error {
ingredientIndex: number;
@@ -130,7 +131,7 @@ export default defineComponent({
},
middleware: ["auth", "group-only"],
setup() {
const { $auth } = useContext();
const { $auth, i18n } = useContext();
const panels = ref<number[]>([]);
const route = useRoute();
@@ -139,10 +140,10 @@ export default defineComponent({
const router = useRouter();
const slug = route.value.params.slug;
const api = useUserApi();
const { i18n } = useContext();
const appInfo = useAppInfo();
const { recipe, loading } = useRecipe(slug);
const parserLoading = ref(false);
invoke(async () => {
await until(recipe).not.toBeNull();
@@ -152,10 +153,32 @@ export default defineComponent({
const ingredients = ref<any[]>([]);
const availableParsers = computed(() => {
return [
{
"text": i18n.tc("recipe.parser.natural-language-processor"),
"value": "nlp",
},
{
"text": i18n.tc("recipe.parser.brute-parser"),
"value": "brute",
},
{
"text": i18n.tc("recipe.parser.openai-parser"),
"value": "openai",
"hide": !appInfo.value?.enableOpenai,
},
]
});
// =========================================================
// Parser Logic
const parser = ref<Parser>("nlp");
const parserPreferences = useParsingPreferences();
const parser = ref<Parser>(parserPreferences.value.parser || "nlp");
const parsedIng = ref<ParsedIngredient[]>([]);
watch(parser, (val) => {
parserPreferences.value.parser = val;
});
function processIngredientError(ing: ParsedIngredient, index: number): Error {
const unitError = !checkForUnit(ing.ingredient.unit);
@@ -195,7 +218,10 @@ export default defineComponent({
return;
}
const raw = recipe.value.recipeIngredient.map((ing) => ing.note ?? "");
parserLoading.value = true;
const { data } = await api.recipes.parseIngredients(parser.value, raw);
parserLoading.value = false;
if (data) {
// When we send the recipe ingredient text to be parsed, we lose the reference to the original unparsed ingredient.
@@ -343,6 +369,7 @@ export default defineComponent({
return {
parser,
availableParsers,
saveAll,
createFood,
createUnit,
@@ -358,6 +385,7 @@ export default defineComponent({
parsedIng,
recipe,
loading,
parserLoading,
ingredients,
};
},

View File

@@ -1,12 +1,13 @@
import shutil
import tempfile
from collections.abc import AsyncGenerator, Callable, Generator
from collections.abc import Callable, Generator
from contextlib import contextmanager
from pathlib import Path
from shutil import rmtree
from uuid import uuid4
import fastapi
import jwt
from fastapi import BackgroundTasks, Depends, HTTPException, Request, status
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordBearer
from jwt.exceptions import PyJWTError
from sqlalchemy.orm.session import Session
@@ -205,24 +206,26 @@ def validate_recipe_token(token: str | None = None) -> str:
return slug
async def temporary_zip_path() -> AsyncGenerator[Path, None]:
@contextmanager
def get_temporary_zip_path(auto_unlink=True) -> Generator[Path, None, None]:
app_dirs.TEMP_DIR.mkdir(exist_ok=True, parents=True)
temp_path = app_dirs.TEMP_DIR.joinpath("my_zip_archive.zip")
try:
yield temp_path
finally:
temp_path.unlink(missing_ok=True)
if auto_unlink:
temp_path.unlink(missing_ok=True)
async def temporary_dir(background_tasks: BackgroundTasks) -> AsyncGenerator[Path, None]:
@contextmanager
def get_temporary_path(auto_unlink=True) -> Generator[Path, None, None]:
temp_path = app_dirs.TEMP_DIR.joinpath(uuid4().hex)
temp_path.mkdir(exist_ok=True, parents=True)
try:
yield temp_path
finally:
background_tasks.add_task(shutil.rmtree, temp_path)
if auto_unlink:
rmtree(temp_path)
def temporary_file(ext: str = "") -> Callable[[], Generator[tempfile._TemporaryFileWrapper, None, None]]:

View File

@@ -1,3 +1,4 @@
import time
from datetime import timedelta
from functools import lru_cache
@@ -82,7 +83,7 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
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", settings.OIDC_USER_CLAIM}
jwks = OpenIDProvider.get_jwks()
jwks = OpenIDProvider.get_jwks(self.get_ttl_hash()) # cache the key set for 30 minutes
if not jwks:
return None
@@ -115,8 +116,9 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
@lru_cache
@staticmethod
def get_jwks() -> KeySet | None:
"""Get the key set from the open id configuration"""
def get_jwks(ttl_hash=None) -> KeySet | None:
"""Get the key set from the openid configuration"""
del ttl_hash # ttl_hash is used for caching only
settings = get_app_settings()
if not (settings.OIDC_READY and settings.OIDC_CONFIGURATION_URL):
@@ -145,3 +147,6 @@ class OpenIDProvider(AuthProvider[OIDCRequest]):
response.raise_for_status()
session.close()
return JsonWebKey.import_key_set(response.json())
def get_ttl_hash(self, seconds=1800):
return time.time() // seconds

View File

@@ -207,6 +207,31 @@ class AppSettings(BaseSettings):
return self.OIDC_AUTH_ENABLED and not_none and valid_group_claim
# ===============================================
# OpenAI Configuration
OPENAI_BASE_URL: str | None = None
"""The base URL for the OpenAI API. Leave this unset for most usecases"""
OPENAI_API_KEY: str | None = None
"""Your OpenAI API key. Required to enable OpenAI features"""
OPENAI_MODEL: str = "gpt-4o"
"""Which OpenAI model to send requests to. Leave this unset for most usecases"""
OPENAI_WORKERS: int = 2
"""
Number of OpenAI workers per request. Higher values may increase
processing speed, but will incur additional API costs
"""
OPENAI_SEND_DATABASE_DATA: bool = True
"""
Sending database data may increase accuracy in certain requests,
but will incur additional API costs
"""
@property
def OPENAI_ENABLED(self) -> bool:
"""Validates OpenAI settings are all set"""
return bool(self.OPENAI_API_KEY and self.OPENAI_MODEL)
# ===============================================
# Testing Config

View File

@@ -21,7 +21,13 @@ from .mealplan import GroupMealPlan
from .preferences import GroupPreferencesModel
if TYPE_CHECKING:
from ..recipe import IngredientFoodModel, IngredientUnitModel, RecipeModel, Tag, Tool
from ..recipe import (
IngredientFoodModel,
IngredientUnitModel,
RecipeModel,
Tag,
Tool,
)
from ..users import User
from .events import GroupEventNotifierModel
from .exports import GroupDataExportsModel
@@ -67,7 +73,7 @@ class Group(SqlAlchemyBase, BaseMixins):
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)
server_tasks: Mapped[list["ServerTaskModel"]] = orm.relationship("ServerTaskModel", **common_args)
data_exports: Mapped[list["GroupDataExportsModel"]] = orm.relationship("GroupDataExportsModel", **common_args)
shopping_lists: Mapped[list["ShoppingList"]] = orm.relationship("ShoppingList", **common_args)
group_reports: Mapped[list["ReportModel"]] = orm.relationship("ReportModel", **common_args)

View File

@@ -14,6 +14,8 @@ if TYPE_CHECKING:
class ServerTaskModel(SqlAlchemyBase, BaseMixins):
# Server Tasks are deprecated, but the table still exists in the database
__tablename__ = "server_tasks"
name: Mapped[str] = mapped_column(String, nullable=False)
completed_date: Mapped[datetime] = mapped_column(DateTime, nullable=True)

View File

@@ -5,8 +5,8 @@
"recipe": {
"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"
"ingredient-note": "1 Taza de harina",
"step-text": "Los pasos de receta, así como otros campos en la página de recetas, soportan sintaxis Markdown.\n\n**Agregar un enlace**\n\n[Mi Enlace](https://demo.mealie.io)\n"
}
},
"mealplan": {

View File

@@ -3,19 +3,19 @@
"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"
"ingredient-note": "小麦粉1カップ",
"step-text": "レシピの手順など、レシピページはmarkdown構文に対応しています\n\n例\n[ミーリー](https://demo.mealie.io)\n"
}
},
"mealplan": {
"no-recipes-match-your-rules": "ルールに致するレシピはありません"
"no-recipes-match-your-rules": "ルールに致するレシピはありません"
},
"user": {
"user-updated": "ユーザを更新しました",
"password-updated": "パスワードを更新しました",
"invalid-current-password": "現在のパスワードが無効です",
"user-updated": "ユーザを更新しました",
"password-updated": "パスワードを更新しました",
"invalid-current-password": "現在のパスワードと一致しません",
"ldap-update-password-unavailable": "パスワードを更新できません。ユーザーはLDAPによって制御されています"
},
"group": {
@@ -26,15 +26,15 @@
"no-entry-found": "リクエストされた情報は見つかりませんでした",
"integrity-error": "データベースの整合性エラー",
"username-conflict-error": "この ユーザー名はすでに使用されています。",
"email-conflict-error": "この Eメイルはすでに使用されています。"
"email-conflict-error": "このメールアドレスはすでに使用されています。"
},
"notifications": {
"generic-created": "{name} が作成されました",
"generic-updated": "{name} が更新されました",
"generic-created-with-url": "{name} 作成されています。 {url}",
"generic-updated-with-url": "{name} 更新されています。 {url}",
"generic-created-with-url": "{name} 作成されました: {url}",
"generic-updated-with-url": "{name} 更新されました: {url}",
"generic-duplicated": "{name} が複製されました",
"generic-deleted": "{name} 削除されています"
"generic-deleted": "{name} 削除されました"
},
"datetime": {
"year": "年",

View File

@@ -5,8 +5,8 @@
"recipe": {
"unique-name-error": "Receptnamn måste vara unika",
"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"
"ingredient-note": "1 kopp mjöl",
"step-text": "Receptsteg samt andra fält i receptsidan stöder markdown syntax.\n\n**Lägg till en länk**\n\n[Min länk](https://demo.mealie.io)\n"
}
},
"mealplan": {

View File

@@ -5,8 +5,8 @@
"recipe": {
"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"
"ingredient-note": "1杯面粉",
"step-text": "食谱步骤以及食谱页面的其他字段支持markdown语法。\n\n**添加一个链接**\n\n[我的链接](https://demo.mealie.io)\n"
}
},
"mealplan": {

View File

@@ -29,7 +29,6 @@ from mealie.db.models.recipe.recipe_timeline import RecipeTimelineEvent
from mealie.db.models.recipe.shared import RecipeShareTokenModel
from mealie.db.models.recipe.tag import Tag
from mealie.db.models.recipe.tool import Tool
from mealie.db.models.server.task import ServerTaskModel
from mealie.db.models.users import LongLiveToken, User
from mealie.db.models.users.password_reset import PasswordResetModel
from mealie.db.models.users.user_to_recipe import UserToRecipe
@@ -59,7 +58,6 @@ from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUni
from mealie.schema.recipe.recipe_share_token import RecipeShareToken
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventOut
from mealie.schema.reports.reports import ReportEntryOut, ReportOut
from mealie.schema.server import ServerTask
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser
from mealie.schema.user.user import UserRatingOut
from mealie.schema.user.user_passwords import PrivatePasswordResetToken
@@ -162,10 +160,6 @@ class AllRepositories:
# ================================================================
# Group
@cached_property
def server_tasks(self) -> RepositoryGeneric[ServerTask, ServerTaskModel]:
return RepositoryGeneric(self.session, PK_ID, ServerTaskModel, ServerTask)
@cached_property
def groups(self) -> RepositoryGroup:
return RepositoryGroup(self.session, PK_ID, Group, GroupInDB)

View File

@@ -1,6 +1,6 @@
{
"acorn-squash": "橡果南瓜",
"alfalfa-sprouts": "alfalfa sprouts",
"alfalfa-sprouts": "紫花苜蓿芽",
"anchovies": "凤尾鱼",
"apples": "苹果",
"artichoke": "洋蓟",
@@ -18,7 +18,7 @@
"blackberries": "黑莓",
"brassicas": "甘蓝",
"bok-choy": "小白菜",
"broccoflower": "broccoflower",
"broccoflower": "西蓝花",
"broccoli": "西兰花",
"broccolini": "broccolini",
"broccoli-rabe": "broccoli rabe",

View File

@@ -1,6 +1,6 @@
[
{
"name": "農産物"
"name": "青果物"
},
{
"name": "穀物"
@@ -42,7 +42,7 @@
"name": "健康食品"
},
{
"name": "世帯"
"name": "家庭"
},
{
"name": "食肉製品"

View File

@@ -70,12 +70,12 @@
"abbreviation": "mg"
},
"splash": {
"name": "しぶき",
"name": "少量",
"description": "",
"abbreviation": ""
},
"dash": {
"name": "ダッシュ",
"name": "少量",
"description": "",
"abbreviation": ""
},
@@ -90,7 +90,7 @@
"abbreviation": ""
},
"clove": {
"name": "クローブ",
"name": "欠片",
"description": "",
"abbreviation": ""
},

View File

@@ -70,12 +70,12 @@
"abbreviation": "毫克"
},
"splash": {
"name": "splash",
"name": "适量",
"description": "",
"abbreviation": ""
},
"dash": {
"name": "dash",
"name": "少许",
"description": "",
"abbreviation": ""
},

View File

@@ -8,7 +8,6 @@ from . import (
admin_maintenance,
admin_management_groups,
admin_management_users,
admin_server_tasks,
)
router = AdminAPIRouter(prefix="/admin")
@@ -17,7 +16,6 @@ router.include_router(admin_about.router, tags=["Admin: About"])
router.include_router(admin_management_users.router, tags=["Admin: Manage Users"])
router.include_router(admin_management_groups.router, tags=["Admin: Manage Groups"])
router.include_router(admin_email.router, tags=["Admin: Email"])
router.include_router(admin_server_tasks.router, tags=["Admin: Server Tasks"])
router.include_router(admin_backups.router, tags=["Admin: Backups"])
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])
router.include_router(admin_analytics.router, tags=["Admin: Analytics"])

View File

@@ -33,6 +33,7 @@ class AdminAboutController(BaseAdminController):
enable_oidc=settings.OIDC_AUTH_ENABLED,
oidc_redirect=settings.OIDC_AUTO_REDIRECT,
oidc_provider_name=settings.OIDC_PROVIDER_NAME,
enable_openai=settings.OPENAI_ENABLED,
)
@router.get("/statistics", response_model=AppStatistics)
@@ -55,4 +56,5 @@ class AdminAboutController(BaseAdminController):
base_url_set=settings.BASE_URL != "http://localhost:8080",
is_up_to_date=APP_VERSION == "develop" or APP_VERSION == "nightly" or get_latest_version() == APP_VERSION,
oidc_ready=settings.OIDC_READY,
enable_openai=settings.OPENAI_ENABLED,
)

View File

@@ -1,27 +0,0 @@
from fastapi import BackgroundTasks, Depends
from mealie.routes._base import BaseAdminController, controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.server.tasks import ServerTask, ServerTaskNames, ServerTaskPagination
from mealie.services.server_tasks import BackgroundExecutor, test_executor_func
router = UserAPIRouter()
@controller(router)
class AdminServerTasksController(BaseAdminController):
@router.get("/server-tasks", response_model=ServerTaskPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
response = self.repos.server_tasks.page_all(
pagination=q,
override=ServerTask,
)
response.set_pagination_guides(router.url_path_for("get_all"), q.model_dump())
return response
@router.post("/server-tasks", response_model=ServerTask, status_code=201)
def create_test_tasks(self, bg_tasks: BackgroundTasks):
bg_executor = BackgroundExecutor(self.group.id, self.repos, bg_tasks)
return bg_executor.dispatch(ServerTaskNames.default, test_executor_func)

View File

@@ -32,6 +32,7 @@ def get_app_info(session: Session = Depends(generate_session)):
enable_oidc=settings.OIDC_READY,
oidc_redirect=settings.OIDC_AUTO_REDIRECT,
oidc_provider_name=settings.OIDC_PROVIDER_NAME,
enable_openai=settings.OPENAI_ENABLED,
)

View File

@@ -1,10 +1,9 @@
import shutil
from pathlib import Path
from fastapi import Depends, File, Form
from fastapi import File, Form
from fastapi.datastructures import UploadFile
from mealie.core.dependencies import temporary_zip_path
from mealie.core.dependencies import get_temporary_zip_path
from mealie.routes._base import BaseUserController, controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.group.group_migration import SupportedMigrations
@@ -32,38 +31,39 @@ class GroupMigrationController(BaseUserController):
add_migration_tag: bool = Form(False),
migration_type: SupportedMigrations = Form(...),
archive: UploadFile = File(...),
temp_path: Path = Depends(temporary_zip_path),
):
# Save archive to temp_path
with temp_path.open("wb") as buffer:
shutil.copyfileobj(archive.file, buffer)
with get_temporary_zip_path() as temp_path:
# Save archive to temp_path
with temp_path.open("wb") as buffer:
shutil.copyfileobj(archive.file, buffer)
args = {
"archive": temp_path,
"db": self.repos,
"session": self.session,
"user_id": self.user.id,
"group_id": self.group_id,
"add_migration_tag": add_migration_tag,
"translator": self.translator,
}
args = {
"archive": temp_path,
"db": self.repos,
"session": self.session,
"user_id": self.user.id,
"group_id": self.group_id,
"add_migration_tag": add_migration_tag,
"translator": self.translator,
}
table: dict[SupportedMigrations, type[BaseMigrator]] = {
SupportedMigrations.chowdown: ChowdownMigrator,
SupportedMigrations.copymethat: CopyMeThatMigrator,
SupportedMigrations.mealie_alpha: MealieAlphaMigrator,
SupportedMigrations.nextcloud: NextcloudMigrator,
SupportedMigrations.paprika: PaprikaMigrator,
SupportedMigrations.tandoor: TandoorMigrator,
SupportedMigrations.plantoeat: PlanToEatMigrator,
SupportedMigrations.myrecipebox: MyRecipeBoxMigrator,
}
table: dict[SupportedMigrations, type[BaseMigrator]] = {
SupportedMigrations.chowdown: ChowdownMigrator,
SupportedMigrations.copymethat: CopyMeThatMigrator,
SupportedMigrations.mealie_alpha: MealieAlphaMigrator,
SupportedMigrations.nextcloud: NextcloudMigrator,
SupportedMigrations.paprika: PaprikaMigrator,
SupportedMigrations.tandoor: TandoorMigrator,
SupportedMigrations.plantoeat: PlanToEatMigrator,
SupportedMigrations.myrecipebox: MyRecipeBoxMigrator,
}
constructor = table.get(migration_type, None)
constructor = table.get(migration_type, None)
if constructor is None:
raise ValueError(f"Unsupported migration type: {migration_type}")
if constructor is None:
raise ValueError(f"Unsupported migration type: {migration_type}")
migrator = constructor(**args)
migrator = constructor(**args)
return migrator.migrate(f"{migration_type.value.title()} Migration")
migration_result = migrator.migrate(f"{migration_type.value.title()} Migration")
return migration_result

View File

@@ -11,11 +11,12 @@ router = APIRouter(prefix="/parser")
@controller(router)
class IngredientParserController(BaseUserController):
@router.post("/ingredients", response_model=list[ParsedIngredient])
def parse_ingredients(self, ingredients: IngredientsRequest):
async def parse_ingredients(self, ingredients: IngredientsRequest):
parser = get_parser(ingredients.parser, self.group_id, self.session)
return parser.parse(ingredients.ingredients)
return await parser.parse(ingredients.ingredients)
@router.post("/ingredient", response_model=ParsedIngredient)
def parse_ingredient(self, ingredient: IngredientRequest):
async def parse_ingredient(self, ingredient: IngredientRequest):
parser = get_parser(ingredient.parser, self.group_id, self.session)
return parser.parse([ingredient.ingredient])[0]
response = await parser.parse([ingredient.ingredient])
return response[0]

View File

@@ -1,9 +1,9 @@
from functools import cached_property
from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, HTTPException
from mealie.core.dependencies.dependencies import temporary_zip_path
from mealie.core.dependencies.dependencies import get_temporary_zip_path
from mealie.core.security import create_file_token
from mealie.routes._base import BaseUserController, controller
from mealie.schema.group.group_exports import GroupDataExport
@@ -44,8 +44,9 @@ class RecipeBulkActionsController(BaseUserController):
self.service.delete_recipes(delete_recipes.recipes)
@router.post("/export", status_code=202)
def bulk_export_recipes(self, export_recipes: ExportRecipes, temp_path=Depends(temporary_zip_path)):
self.service.export_recipes(temp_path, export_recipes.recipes)
def bulk_export_recipes(self, export_recipes: ExportRecipes):
with get_temporary_zip_path() as temp_path:
self.service.export_recipes(temp_path, export_recipes.recipes)
@router.get("/export/download")
def get_exported_data_token(self, path: Path):

View File

@@ -1,5 +1,5 @@
from functools import cached_property
from shutil import copyfileobj
from shutil import copyfileobj, rmtree
from uuid import UUID
from zipfile import ZipFile
@@ -10,11 +10,11 @@ from fastapi.datastructures import UploadFile
from fastapi.responses import JSONResponse
from pydantic import UUID4, BaseModel, Field
from slugify import slugify
from starlette.background import BackgroundTask
from starlette.responses import FileResponse
from mealie.core import exceptions
from mealie.core.dependencies import temporary_zip_path
from mealie.core.dependencies.dependencies import temporary_dir, validate_recipe_token
from mealie.core.dependencies import get_temporary_path, get_temporary_zip_path, validate_recipe_token
from mealie.core.security import create_recipe_slug_token
from mealie.db.models.group.cookbook import CookBook
from mealie.pkgs import cache
@@ -103,7 +103,7 @@ class RecipeExportController(BaseRecipeController):
return RecipeZipTokenResponse(token=create_recipe_slug_token(slug))
@router_exports.get("/{slug}/exports", response_class=FileResponse)
def get_recipe_as_format(self, slug: str, template_name: str, temp_dir=Depends(temporary_dir)):
def get_recipe_as_format(self, slug: str, template_name: str):
"""
## Parameters
`template_name`: The name of the template to use to use in the exports listed. Template type will automatically
@@ -111,27 +111,31 @@ class RecipeExportController(BaseRecipeController):
names and formats in the /api/recipes/exports endpoint.
"""
recipe = self.mixins.get_one(slug)
file = self.service.render_template(recipe, temp_dir, template_name)
return FileResponse(file)
with get_temporary_path(auto_unlink=False) as temp_path:
recipe = self.mixins.get_one(slug)
file = self.service.render_template(recipe, temp_path, template_name)
return FileResponse(file, background=BackgroundTask(rmtree, temp_path))
@router_exports.get("/{slug}/exports/zip")
def get_recipe_as_zip(self, slug: str, token: str, temp_path=Depends(temporary_zip_path)):
"""Get a Recipe and It's Original Image as a Zip File"""
slug = validate_recipe_token(token)
def get_recipe_as_zip(self, slug: str, token: str):
"""Get a Recipe and Its Original Image as a Zip File"""
with get_temporary_zip_path(auto_unlink=False) as temp_path:
validated_slug = validate_recipe_token(token)
if slug != slug:
raise HTTPException(status_code=400, detail="Invalid Slug")
if validated_slug != slug:
raise HTTPException(status_code=400, detail="Invalid Slug")
recipe: Recipe = self.mixins.get_one(slug)
image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
with ZipFile(temp_path, "w") as myzip:
myzip.writestr(f"{slug}.json", recipe.model_dump_json())
recipe: Recipe = self.mixins.get_one(validated_slug)
image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
with ZipFile(temp_path, "w") as myzip:
myzip.writestr(f"{slug}.json", recipe.model_dump_json())
if image_asset.is_file():
myzip.write(image_asset, arcname=image_asset.name)
if image_asset.is_file():
myzip.write(image_asset, arcname=image_asset.name)
return FileResponse(temp_path, filename=f"{slug}.zip")
return FileResponse(
temp_path, filename=f"{recipe.slug}.zip", background=BackgroundTask(temp_path.unlink, missing_ok=True)
)
router = UserAPIRouter(prefix="/recipes", tags=["Recipe: CRUD"], route_class=MealieCrudRoute)
@@ -219,13 +223,14 @@ class RecipeController(BaseRecipeController):
return "recipe_scrapers was unable to scrape this URL"
@router.post("/create-from-zip", status_code=201)
def create_recipe_from_zip(self, temp_path=Depends(temporary_zip_path), archive: UploadFile = File(...)):
def create_recipe_from_zip(self, archive: UploadFile = File(...)):
"""Create recipe from archive"""
recipe = self.service.create_from_zip(archive, temp_path)
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=recipe.slug),
)
with get_temporary_zip_path() as temp_path:
recipe = self.service.create_from_zip(archive, temp_path)
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=recipe.slug),
)
return recipe.slug

View File

@@ -1,10 +1,9 @@
import shutil
from pathlib import Path
from fastapi import Depends, File, HTTPException, UploadFile, status
from fastapi import File, HTTPException, UploadFile, status
from pydantic import UUID4
from mealie.core.dependencies.dependencies import temporary_dir
from mealie.core.dependencies import get_temporary_path
from mealie.pkgs import cache, img
from mealie.routes._base import BaseUserController, controller
from mealie.routes._base.routers import UserAPIRouter
@@ -21,19 +20,19 @@ class UserImageController(BaseUserController):
self,
id: UUID4,
profile: UploadFile = File(...),
temp_dir: Path = Depends(temporary_dir),
):
"""Updates a User Image"""
assert_user_change_allowed(id, self.user)
temp_img = temp_dir.joinpath(profile.filename)
with get_temporary_path() as temp_path:
assert_user_change_allowed(id, self.user)
temp_img = temp_path.joinpath(profile.filename)
with temp_img.open("wb") as buffer:
shutil.copyfileobj(profile.file, buffer)
with temp_img.open("wb") as buffer:
shutil.copyfileobj(profile.file, buffer)
image = img.PillowMinifier.to_webp(temp_img)
dest = PrivateUser.get_directory(id) / "profile.webp"
image = img.PillowMinifier.to_webp(temp_img)
dest = PrivateUser.get_directory(id) / "profile.webp"
shutil.copyfile(image, dest)
shutil.copyfile(image, dest)
self.repos.users.patch(id, {"cache_key": cache.new_key()})

View File

@@ -18,6 +18,7 @@ class AppInfo(MealieModel):
enable_oidc: bool
oidc_redirect: bool
oidc_provider_name: str
enable_openai: bool
class AppTheme(MealieModel):
@@ -64,6 +65,7 @@ class CheckAppConfig(MealieModel):
email_ready: bool
ldap_ready: bool
oidc_ready: bool
enable_openai: bool
base_url_set: bool
is_up_to_date: bool

View File

@@ -187,7 +187,7 @@ class RecipeIngredientBase(MealieModel):
qty: float | Fraction
# decimal
if not self.unit or not self.unit.fraction:
if self.unit and not self.unit.fraction:
qty = round(self.quantity or 0, INGREDIENT_QTY_PRECISION)
if qty.is_integer():
return str(int(qty))
@@ -327,6 +327,7 @@ class ParsedIngredient(MealieModel):
class RegisteredParser(str, enum.Enum):
nlp = "nlp"
brute = "brute"
openai = "openai"
class IngredientsRequest(MealieModel):

View File

@@ -1,10 +0,0 @@
# This file is auto-generated by gen_schema_exports.py
from .tasks import ServerTask, ServerTaskCreate, ServerTaskNames, ServerTaskPagination, ServerTaskStatus
__all__ = [
"ServerTask",
"ServerTaskCreate",
"ServerTaskNames",
"ServerTaskPagination",
"ServerTaskStatus",
]

View File

@@ -1,50 +0,0 @@
import datetime
import enum
from uuid import UUID
from pydantic import ConfigDict, Field
from mealie.schema._mealie import MealieModel
from mealie.schema.response.pagination import PaginationBase
class ServerTaskNames(str, enum.Enum):
default = "Background Task"
backup_task = "Database Backup"
bulk_recipe_import = "Bulk Recipe Import"
class ServerTaskStatus(str, enum.Enum):
running = "running"
finished = "finished"
failed = "failed"
class ServerTaskCreate(MealieModel):
group_id: UUID
name: ServerTaskNames = ServerTaskNames.default
created_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
status: ServerTaskStatus = ServerTaskStatus.running
log: str = ""
def set_running(self) -> None:
self.status = ServerTaskStatus.running
def set_finished(self) -> None:
self.status = ServerTaskStatus.finished
def set_failed(self) -> None:
self.status = ServerTaskStatus.failed
def append_log(self, message: str) -> None:
# Prefix with Timestamp and append new line and join to log
self.log += f"{datetime.datetime.now()}: {message}\n"
class ServerTask(ServerTaskCreate):
id: int
model_config = ConfigDict(from_attributes=True)
class ServerTaskPagination(PaginationBase):
items: list[ServerTask]

View File

@@ -0,0 +1,6 @@
from .openai import OpenAIDataInjection, OpenAIService
__all__ = [
"OpenAIDataInjection",
"OpenAIService",
]

View File

@@ -0,0 +1,129 @@
import inspect
import json
import os
from pathlib import Path
from textwrap import dedent
from openai import NOT_GIVEN, AsyncOpenAI
from openai.resources.chat.completions import ChatCompletion
from pydantic import BaseModel, field_validator
from mealie.core.config import get_app_settings
from .._base_service import BaseService
class OpenAIDataInjection(BaseModel):
description: str
value: str
@field_validator("value", mode="before")
def parse_value(cls, value):
if not value:
raise ValueError("Value cannot be empty")
if isinstance(value, str):
return value
# convert Pydantic models to JSON
if isinstance(value, BaseModel):
return value.model_dump_json()
# convert Pydantic types to their JSON schema definition
if inspect.isclass(value) and issubclass(value, BaseModel):
value = value.model_json_schema()
# attempt to convert object to JSON
try:
return json.dumps(value, separators=(",", ":"))
except TypeError:
return value
class OpenAIService(BaseService):
PROMPTS_DIR = Path(os.path.dirname(os.path.abspath(__file__))) / "prompts"
def __init__(self) -> None:
settings = get_app_settings()
if not settings.OPENAI_ENABLED:
raise ValueError("OpenAI is not enabled")
self.model = settings.OPENAI_MODEL
self.workers = settings.OPENAI_WORKERS
self.send_db_data = settings.OPENAI_SEND_DATABASE_DATA
self.get_client = lambda: AsyncOpenAI(
base_url=settings.OPENAI_BASE_URL,
api_key=settings.OPENAI_API_KEY,
)
super().__init__()
@classmethod
def get_prompt(cls, name: str, data_injections: list[OpenAIDataInjection] | None = None) -> str:
"""
Load stored prompt and inject data into it.
Access prompts with dot notation.
For example, to access `prompts/recipes/parse-recipe-ingredients.txt`, use
`recipes.parse-recipe-ingredients`
"""
if not name:
raise ValueError("Prompt name cannot be empty")
tree = name.split(".")
prompt_dir = os.path.join(cls.PROMPTS_DIR, *tree[:-1], tree[-1] + ".txt")
try:
with open(prompt_dir) as f:
content = f.read()
except OSError as e:
raise OSError(f"Unable to load prompt {name}") from e
if not data_injections:
return content
content_parts = [content]
for data_injection in data_injections:
content_parts.append(
dedent(
f"""
###
{data_injection.description}
---
{data_injection.value}
"""
)
)
return "\n".join(content_parts)
async def _get_raw_response(
self, prompt: str, message: str, temperature=0.2, force_json_response=True
) -> ChatCompletion:
client = self.get_client()
return await client.chat.completions.create(
messages=[
{
"role": "system",
"content": prompt,
},
{
"role": "user",
"content": message,
},
],
model=self.model,
temperature=temperature,
response_format={"type": "json_object"} if force_json_response else NOT_GIVEN,
)
async def get_response(self, prompt: str, message: str, temperature=0.2, force_json_response=True) -> str | None:
"""Send data to OpenAI and return the response message content"""
try:
response = await self._get_raw_response(prompt, message, temperature, force_json_response)
if not response.choices:
return None
return response.choices[0].message.content
except Exception:
self.logger.exception("OpenAI Request Failed")
return None

View File

@@ -0,0 +1,24 @@
You are a bot that parses user input into recipe ingredients. You will receive a list of one or more ingredients, each containing one or more of the following components:
- Food: the actual physical ingredient used in the recipe. For instance, if you receive "3 cups of onions, chopped", the food is "onions"
- Unit: the unit of measurement for this ingredient. For instance, if you receive "2 lbs chicken breast", the unit is "lbs" (short for "pounds")
- Quantity: the numerical representation of how much of this ingredient. For instance, if you receive "3 1/2 grams of minced garlic", the quantity is "3 1/2". Quantity may be represented as a whole number (integer), a float or decimal, or a fraction. You should output quantity in only whole numbers or floats, converting fractions into floats. Floats longer than 10 decimal places should be rounded to 10 decimal places.
- Note: the rest of the text that represents more detail on how to prepare the ingredient. Anything that is not one of the above should be the note. For instance, if you receive "one can of butter beans, drained" the note would be "drained". If you receive "3 cloves of garlic peeled and finely chopped", the note would be "peeled and finely chopped"
- Input: The input is simply the ingredient string you are processing as-is. It is forbidden to modify this at all, you must provide the input exactly as you received it
While parsing the ingredients, there are some things to keep in mind:
- If you cannot accurately determine the quantity, unit, food, or note, you should place everything into the note field and leave everything else empty. It's better to err on the side of putting everything in the note field than being wrong
- You may receive recipe ingredients from multiple different languages. You should adhere to the grammar rules of the input language when trying to parse the ingredient string
- Sometimes foods or units will be in their singular, plural, or other grammatical forms. You must interpret all of them appropriately
- Sometimes ingredients will have text in parenthesis (like this). Parenthesis typically indicate something that should appear in the notes. For example: an input of "3 potatoes (roughly chopped)" would parse "roughly chopped" into the notes. Notice that when this occurs, the parenthesis are dropped, and you should use "roughly chopped" instead of "(roughly chopped)" in the note
- It's possible for the input to contain typos. For instance, you might see the word "potatos" instead of "potatoes". If it is a common misspelling, you may correct it
- Pay close attention to what can be considered a unit of measurement. There are common measurements such as tablespoon, teaspoon, and gram, abbreviations such as tsp, tbsp, and oz, and others such as sprig, can, bundle, bunch, unit, cube, package, and pinch
- Sometimes quantities can be given a range, such as "3-5" or "1 to 2" or "three or four". In this instance, choose the lower quantity; do not try to average or otherwise calculate the quantity. For instance, if the input it "2-3 lbs of chicken breast" the quantity should be "2"
- Any text that does not appear in the unit or food must appear in the notes. No text should be left off. The only exception for this is if a quantity is converted from text into a number. For instance, if you convert "2 dozen" into the number "24", you should not put the word "dozen" into any other field
It is imperative that you do not create any data or otherwise make up any information. Failure to adhere to this rule is illegal and will result in harsh punishment. If you are unsure, place the entire string into the note section of the response. Do not make things up.
In addition to calculating the recipe ingredient fields, you are also responsible for including a confidence value. This value is a float between 0 - 1, where 1 is full confidence that the result is correct, and 0 is no confidence that the result is correct. If you're unable to parse anything, and you put the entire string in the notes, you should return 0 confidence. If you can easily parse the string into each component, then you should return a confidence of 1. If you have to guess which part is the unit and which part is the food, your confidence should be lower, such as 0.6. Even if there is no unit or note, if you're able to determine the food, you may use a higher confidence. If the entire ingredient consists of only a food, you can use a confidence of 1.
Below you will receive the JSON schema for your response. Your response must be in valid JSON in the below schema as provided. You must respond in this JSON schema; failure to do so is illegal. It is imperative that you follow the schema precisely to avoid punishment. You must follow the JSON schema.
The user message that you receive will be the list of one or more recipe ingredients for you to parse. Your response should have exactly one item for each item provided. For instance, if you receive 12 items to parse, then your response should be an array of 12 parsed items.

View File

@@ -0,0 +1,161 @@
from abc import ABC, abstractmethod
from typing import TypeVar
from pydantic import UUID4, BaseModel
from rapidfuzz import fuzz, process
from sqlalchemy.orm import Session
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
from mealie.repos.all_repositories import get_repositories
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_ingredient import (
CreateIngredientFood,
CreateIngredientUnit,
IngredientFood,
IngredientUnit,
ParsedIngredient,
)
from mealie.schema.response.pagination import PaginationQuery
T = TypeVar("T", bound=BaseModel)
class ABCIngredientParser(ABC):
"""
Abstract class for ingredient parsers.
"""
def __init__(self, group_id: UUID4, session: Session) -> None:
self.group_id = group_id
self.session = session
self._foods_by_alias: dict[str, IngredientFood] | None = None
self._units_by_alias: dict[str, IngredientUnit] | None = None
@property
def _repos(self) -> AllRepositories:
return get_repositories(self.session)
@property
def foods_by_alias(self) -> dict[str, IngredientFood]:
if self._foods_by_alias is None:
foods_repo = self._repos.ingredient_foods.by_group(self.group_id)
query = PaginationQuery(page=1, per_page=-1)
all_foods = foods_repo.page_all(query).items
foods_by_alias: dict[str, IngredientFood] = {}
for food in all_foods:
if food.name:
foods_by_alias[IngredientFoodModel.normalize(food.name)] = food
if food.plural_name:
foods_by_alias[IngredientFoodModel.normalize(food.plural_name)] = food
for alias in food.aliases or []:
if alias.name:
foods_by_alias[IngredientFoodModel.normalize(alias.name)] = food
self._foods_by_alias = foods_by_alias
return self._foods_by_alias
@property
def units_by_alias(self) -> dict[str, IngredientUnit]:
if self._units_by_alias is None:
units_repo = self._repos.ingredient_units.by_group(self.group_id)
query = PaginationQuery(page=1, per_page=-1)
all_units = units_repo.page_all(query).items
units_by_alias: dict[str, IngredientUnit] = {}
for unit in all_units:
if unit.name:
units_by_alias[IngredientUnitModel.normalize(unit.name)] = unit
if unit.plural_name:
units_by_alias[IngredientUnitModel.normalize(unit.plural_name)] = unit
if unit.abbreviation:
units_by_alias[IngredientUnitModel.normalize(unit.abbreviation)] = unit
if unit.plural_abbreviation:
units_by_alias[IngredientUnitModel.normalize(unit.plural_abbreviation)] = unit
for alias in unit.aliases or []:
if alias.name:
units_by_alias[IngredientUnitModel.normalize(alias.name)] = unit
self._units_by_alias = units_by_alias
return self._units_by_alias
@property
def food_fuzzy_match_threshold(self) -> int:
"""Minimum threshold to fuzzy match against a database food search"""
return 85
@property
def unit_fuzzy_match_threshold(self) -> int:
"""Minimum threshold to fuzzy match against a database unit search"""
return 70
@abstractmethod
async def parse_one(self, ingredient_string: str) -> ParsedIngredient: ...
@abstractmethod
async def parse(self, ingredients: list[str]) -> list[ParsedIngredient]: ...
@classmethod
def find_match(cls, match_value: str, *, store_map: dict[str, T], fuzzy_match_threshold: int = 0) -> T | None:
# check for literal matches
if match_value in store_map:
return store_map[match_value]
# fuzzy match against food store
fuzz_result = process.extractOne(
match_value, store_map.keys(), scorer=fuzz.ratio, score_cutoff=fuzzy_match_threshold
)
if fuzz_result is None:
return None
return store_map[fuzz_result[0]]
def find_food_match(self, food: IngredientFood | CreateIngredientFood | str) -> IngredientFood | None:
if isinstance(food, IngredientFood):
return food
food_name = food if isinstance(food, str) else food.name
match_value = IngredientFoodModel.normalize(food_name)
return self.find_match(
match_value,
store_map=self.foods_by_alias,
fuzzy_match_threshold=self.food_fuzzy_match_threshold,
)
def find_unit_match(self, unit: IngredientUnit | CreateIngredientUnit | str) -> IngredientUnit | None:
if isinstance(unit, IngredientUnit):
return unit
unit_name = unit if isinstance(unit, str) else unit.name
match_value = IngredientUnitModel.normalize(unit_name)
return self.find_match(
match_value,
store_map=self.units_by_alias,
fuzzy_match_threshold=self.unit_fuzzy_match_threshold,
)
def find_ingredient_match(self, ingredient: ParsedIngredient) -> ParsedIngredient:
if ingredient.ingredient.food and (food_match := self.find_food_match(ingredient.ingredient.food)):
ingredient.ingredient.food = food_match
if ingredient.ingredient.unit and (unit_match := self.find_unit_match(ingredient.ingredient.unit)):
ingredient.ingredient.unit = unit_match
# Parser might have wrongly split a food into a unit and food.
if isinstance(ingredient.ingredient.food, CreateIngredientFood) and isinstance(
ingredient.ingredient.unit, CreateIngredientUnit
):
if food_match := self.find_food_match(
f"{ingredient.ingredient.unit.name} {ingredient.ingredient.food.name}"
):
ingredient.ingredient.food = food_match
ingredient.ingredient.unit = None
return ingredient

View File

@@ -29,24 +29,24 @@ class CRFIngredient(BaseModel):
input: str = ""
name: str = ""
other: str = ""
qty: str = ""
qty: Annotated[str, Field(validate_default=True)] = ""
comment: str = ""
unit: str = ""
confidence: CRFConfidence
@field_validator("qty", mode="before")
def validate_qty(qty, info: ValidationInfo): # sourcery skip: merge-nested-ifs
if qty is None or qty == "":
# Check if other contains a fraction
try:
if info.data["other"] is not None and info.data["other"].find("/") != -1:
return round(float(Fraction(info.data["other"])), 3)
else:
return 1
except Exception:
pass
def validate_qty(cls, qty, info: ValidationInfo):
if qty is not None and qty != "":
return qty
return qty
# Check if other contains a fraction
try:
if info.data["other"] is not None and info.data["other"].find("/") != -1:
return str(round(float(Fraction(info.data["other"])), 3))
else:
return "0"
except Exception:
return ""
def _exec_crf_test(input_text):

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