Compare commits

...

37 Commits

Author SHA1 Message Date
Hayden
f644ee1879 fix: sort recipe names accent-folded for locale-aware ordering (#6853)
Recipe name sorting applied lower() to the raw name, so accented and
umlaut characters sorted by raw code point and landed after "Z"
(e.g. "Über" after "Zebra"). Sort by the existing unidecode-normalized
name column instead, so accented characters sort next to their base
letter. Works identically on SQLite and Postgres since the sort key is
a pre-computed ASCII column rather than a DB collation.
2026-05-24 11:43:49 -05:00
Hayden
8eb00c3dc0 chore(l10n): New Crowdin updates (#7649) 2026-05-22 11:06:43 -05:00
Michael Genson
642c826f2b fix: Protect sensitive data in query filter API (GHSA-8m57-7cv5-rjp8) (#7629)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2026-05-21 21:08:41 +00:00
Hayden
493154caa8 chore(l10n): New Crowdin updates (#7646) 2026-05-21 15:27:16 -05:00
Hayden
71e0d99a46 chore(l10n): New Crowdin updates (#7643) 2026-05-20 22:35:33 -05:00
Michael Genson
c52a4e10c9 fix: Inconsistent "from an image" vs "from images" translation (#7642) 2026-05-20 13:45:42 -05:00
mealie-commit-bot[bot]
8b9149a1ce chore: bump version to v3.18.0 2026-05-20 14:26:21 +00:00
Hayden
c8ff75c02a chore(l10n): New Crowdin updates (#7617)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-20 04:06:39 +00:00
d 🔹
3d6ff52358 fix: return HTTP 400 for duplicate tag and label creation (#7638)
Co-authored-by: voidborne-d <voidborne-d@users.noreply.github.com>
2026-05-17 15:50:32 +00:00
mealie-actions[bot]
f04b0c741c chore(l10n): Crowdin locale sync (#7637)
Co-authored-by: GitHub Action <action@github.com>
2026-05-17 03:11:13 +00:00
Hayden
742b498c1d fix: enforce ownership check on recipe deletion (GHSA-x5v9-9jvh-7c7q) (#7625) 2026-05-14 17:04:04 +00:00
Hayden
eddb0c30e0 fix: block scriptable asset extensions and force Content-Disposition: attachment (GHSA-gfwc-pjx4-mg9p) (#7626) 2026-05-14 16:08:16 +00:00
Hayden
1cebfd56ab fix: use locale for Recipe Created timeline event (#4497) (#7623) 2026-05-14 14:07:15 +00:00
Hayden
074ec7aab2 fix: downgrade OIDC missing-claims log from ERROR to DEBUG (#6801) (#7620) 2026-05-14 02:35:11 +00:00
Michael Genson
af75c5f39d fix: Infinite API request loop on empty stores (#7613) 2026-05-12 12:25:48 -05:00
Zdenek Stursa
703db2931f fix: prevent double-scaling of sub-recipe ingredients in shopping list (#7537)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:29:30 +00:00
Hayden
52399547d6 chore: update SECURITY.md for GitHub private vulnerability reporting (#7612) 2026-05-12 01:54:52 +00:00
Hayden
be4ff86c57 chore(l10n): New Crowdin updates (#7608)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-11 20:52:46 +00:00
Michael Genson
8a054b1be8 feat: Remember screen lock preference (#7609) 2026-05-11 14:12:50 -05:00
Zdenek Stursa
2dbfc7f72b fix: redirect to new slug URL after recipe rename (#7522)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:13:16 +00:00
Hayden
e492da67e2 chore(l10n): New Crowdin updates (#7605)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-11 13:40:52 +00:00
renovate[bot]
811be08996 fix(deps): update dependency authlib to v1.7.2 (#7606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-11 10:01:42 +00:00
Michael Genson
fdd17182d8 fix: Update OpenAI recipe parse prompt to return the same number of ingredients as given (#7604) 2026-05-10 22:24:47 -05:00
Michael Genson
d340fdd9df fix: Update backend normalization to match search normalization logic (#7603)
Co-authored-by: Copilot <copilot@github.com>
2026-05-10 21:23:57 -05:00
Zdenek Stursa
551a92a031 fix: redirect to login and validate input on password reset flow (#7521)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
2026-05-10 13:37:46 -05:00
Zdenek Stursa
8c06f49b02 fix: make PWA share target functional on Android Chrome (#7468)
Co-authored-by: Zdenek <tvuj-email@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 18:21:16 +00:00
Michael Genson
9fd3fbca8b feat: Improve new shopping list UI (#7600)
Co-authored-by: Copilot <copilot@github.com>
2026-05-10 13:15:20 -05:00
Hayden
a242aea9f2 chore(l10n): New Crowdin updates (#7589)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-10 17:02:45 +00:00
Michael Genson
6e9ad5fef1 fix: Query Filter Builder "Advanced" bug (#7599)
Co-authored-by: Copilot <copilot@github.com>
2026-05-10 11:51:27 -05:00
mealie-actions[bot]
ee181a598b chore(l10n): Crowdin locale sync (#7595)
Co-authored-by: GitHub Action <action@github.com>
2026-05-10 03:10:29 +00:00
renovate[bot]
3a84b3f262 fix(deps): update dependency openai to v2.34.0 (#7594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 17:48:09 +00:00
renovate[bot]
a616e14bf9 fix(deps): update dependency authlib to v1.7.1 (#7593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 13:01:06 +00:00
renovate[bot]
b902d2cd98 chore(deps): update node.js to 050bf2b (#7592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 13:00:23 +00:00
renovate[bot]
565736e116 chore(deps): update node.js to 34f0eb9 (#7590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 00:58:25 +00:00
renovate[bot]
7f29efc0e4 chore(deps): update dependency types-requests to v2.33.0.20260503 (#7587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-08 05:46:11 +00:00
Hayden
743c15a981 chore(l10n): New Crowdin updates (#7571)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-05-06 22:29:26 -05:00
renovate[bot]
3be9193590 chore(deps): update dependency mypy to v2 (#7584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-06 21:06:18 +00:00
138 changed files with 1518 additions and 707 deletions

View File

@@ -6,4 +6,6 @@ Since this software is still considered beta/WIP support is always only given fo
## Reporting a Vulnerability
For general security vulnerabilities you're welcome to open a GitHub issues or contribute a fix. If you feel the vulnerability should not be disclosed you can open a generic issue on GitHub and email to the details to [ob92oy0sl@mozmail.com](mailto:ob92oy0sl@mozmail.com) which is monitored by the maintainer.
This repository has [private vulnerability reporting](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/privately-reporting-a-security-vulnerability) enabled. To confidentially report a security issue, click the **"Report a vulnerability"** button on the [Security tab](../../security/advisories/new) of this repository. This allows you to submit details directly to the maintainers without public disclosure.
For non-sensitive issues or general feedback, feel free to open a GitHub issue or contribute a fix via pull request.

View File

@@ -1,7 +1,7 @@
###############################################
# Frontend Build
###############################################
FROM node:24@sha256:e9891237dfbb1de60ce19e9ff9fac5d73ad9c37da303ad72ff2a425ad1057e71 \
FROM node:24@sha256:050bf2bbe33c1d6754e060bec89378a79ed831f04a7bb1a53fe45e997df7b3bb \
AS frontend-builder
WORKDIR /frontend

View File

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

View File

@@ -10,7 +10,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
```yaml
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:v3.17.0 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.18.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:v3.17.0 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.18.0 # (3)
container_name: mealie
restart: always
ports:

View File

@@ -258,11 +258,15 @@
class="text-center"
@update:model-value="setRightParenthesisValue(field, index, $event)"
/>
</v-col>
<!-- field actions -->
<v-col
v-if="!$vuetify.display.smAndDown || index === fields.length - 1"
:cols="config.items.fieldActions.cols(index)"
:sm="config.items.fieldActions.sm(index)"
:class="config.col.class"
>
>
<BaseButtonGroup
:buttons="[
{

View File

@@ -321,7 +321,7 @@ async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
const householdsWithFood = subIng.food?.householdsWithIngredientFood || [];
ownIngs.push({
checked: !householdsWithFood.includes(currentHouseholdSlug.value),
ingredient: { ...subIng, quantity: (ing.quantity || 1) * (subIng.quantity || 1) },
ingredient: subIng,
});
}
}

View File

@@ -336,8 +336,16 @@ onMounted(() => {
}
});
// When set, the isEditMode watcher skips its URL cleanup because saveRecipe
// is navigating to a new slug that naturally omits ?edit=true.
const isNavigatingAfterRename = ref(false);
watch(isEditMode, (newVal) => {
if (!newVal) {
if (isNavigatingAfterRename.value) {
isNavigatingAfterRename.value = false;
return;
}
paramsEdit.value = undefined;
}
});
@@ -355,13 +363,17 @@ watch(isParsing, () => {
async function saveRecipe() {
const { data, error } = await api.recipes.updateOne(recipe.value.slug, recipe.value);
if (!error) {
if (data?.slug && data.slug !== route.params.slug) {
isNavigatingAfterRename.value = true;
}
setMode(PageMode.VIEW);
}
if (data?.slug) {
router.push(`/g/${groupSlug.value}/r/` + data.slug);
recipe.value = data as NoUndefinedField<Recipe>;
// Update the snapshot after successful save
originalRecipe.value = deepCopy(recipe.value);
if (data.slug !== route.params.slug) {
router.replace(`/g/${groupSlug.value}/r/` + data.slug);
}
}
}

View File

@@ -11,18 +11,28 @@
>
<div class="d-flex flex-column ga-3">
<v-card-actions class="pa-0">
<InputLabelType
v-model="listItem.food"
v-model:item-id="listItem.foodId!"
:items="foods"
:label="rail ? $t('shopping-list.add-item') : $t('shopping-list.food')"
:icon="$globals.icons.foods"
:style="rail ? 'margin-inline: 3px;' : undefined"
:search="rail"
create
@create="createAssignFood"
@focus="rail = false"
/>
<div class="position-relative" style="flex: 1;">
<InputLabelType
ref="foodInputRef"
v-model="listItem.food"
v-model:item-id="listItem.foodId!"
:items="foods"
:label="rail ? $t('shopping-list.add-item') : $t('shopping-list.food')"
:icon="$globals.icons.foods"
:style="rail ? 'margin-inline: 3px;' : undefined"
:search="rail"
:menu-props="{ location: menuDirection }"
create
@create="createAssignFood"
/>
<!-- Intercept clicks when collapsed so the drawer expands before the autocomplete opens -->
<div
v-if="rail"
class="position-absolute"
style="inset: 0; cursor: text;"
@click="expandAndFocus"
/>
</div>
<BaseButtonGroup
v-if="!rail"
:buttons="[
@@ -84,6 +94,20 @@ defineEmits<{
const { createAssignFood } = useShoppingListItemEditor(listItem);
const { smAndDown } = useDisplay();
const menuDirection = computed(() => smAndDown.value ? "top" : "bottom");
const foodInputRef = ref<{ focus: () => void } | null>(null);
const rail = ref(true);
async function expandAndFocus() {
rail.value = false;
await nextTick();
setTimeout(() => {
foodInputRef.value?.focus();
}, 200);
}
watch(
() => listItem.value.quantity,
(newQty) => {
@@ -100,6 +124,4 @@ watch(
listItem.value.labelId = listItem.value.label?.id || null;
},
);
const rail = ref(true);
</script>

View File

@@ -11,11 +11,13 @@
>
<v-row
v-touch="{
move: ({ originalEvent: { touches: [{ screenX }] } }) => {
move: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
swipeInfo.touchendX = screenX;
swipeInfo.touchendY = screenY;
},
start: ({ originalEvent: { touches: [{ screenX }] } }) => {
start: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
swipeInfo.touchstartX = screenX;
swipeInfo.touchstartY = screenY;
},
end: () => {
if (swiping < SWIPE_THRESHOLD) {
@@ -212,6 +214,7 @@ const emit = defineEmits<{
}>();
const SWIPE_THRESHOLD = 50;
const SCROLL_THRESHOLD = 50;
const { isRtl } = useRtl();
const i18n = useI18n();
@@ -264,14 +267,22 @@ function save() {
edit.value = false;
}
const swipeInfo: Ref<{ touchstartX?: number; touchendX?: number }> = ref({ touchstartX: undefined, touchendX: undefined });
const swipeInfo: Ref<{ touchstartX?: number; touchendX?: number; touchstartY?: number; touchendY?: number }> = ref({});
const swiping = computed(() => {
const { touchstartX, touchendX } = swipeInfo.value ?? {};
const { touchstartX, touchendX, touchstartY, touchendY } = swipeInfo.value ?? {};
if (touchstartX === undefined || touchendX === undefined) {
return 0;
}
const delta = isRtl.value ? touchstartX - touchendX : touchendX - touchstartX;
return Math.min(Math.max(0, delta), 100);
const deltaX = isRtl.value ? touchstartX - touchendX : touchendX - touchstartX;
// If there's significant vertical movement, treat as a scroll gesture and ignore
if (touchstartY !== undefined && touchendY !== undefined) {
const deltaY = Math.abs(touchendY - touchstartY);
if (deltaY > SCROLL_THRESHOLD) {
return 0;
}
}
return Math.min(Math.max(0, deltaX), 100);
});
const recipeList = computed<RecipeSummary[]>(() => {

View File

@@ -16,6 +16,7 @@
:items="units"
:label="$t('recipe.unit')"
:icon="$globals.icons.units"
:menu-props="{ location: menuDirection }"
style="flex: 3"
create
@create="createAssignUnit"
@@ -35,6 +36,7 @@
v-model:item-id="listItem.labelId!"
:items="labels"
:label="$t('shopping-list.label')"
:menu-props="{ location: menuDirection }"
style="flex: 1 0 200px"
/>
<BaseButton
@@ -75,6 +77,9 @@ const emit = defineEmits<{ (e: "save"): void }>();
const { assignLabelToFood, createAssignUnit } = useShoppingListItemEditor(listItem);
const { smAndDown } = useDisplay();
const menuDirection = computed(() => smAndDown.value ? "top" : "bottom");
function handleNoteKeyPress(event: KeyboardEvent) {
// Save on Enter
if (!event.shiftKey && event.key === "Enter") {

View File

@@ -205,7 +205,7 @@ const createLinks = computed(() => [
insertDivider: false,
icon: $globals.icons.fileImage,
title: i18n.t("recipe.create-from-images"),
subtitle: i18n.t("recipe.create-recipe-from-an-image"),
subtitle: i18n.t("recipe.create-recipe-from-images"),
to: `/g/${groupSlug.value}/r/create/image`,
restricted: true,
hide: !showImageImport.value,

View File

@@ -93,4 +93,8 @@ function emitCreate() {
emit("create", searchInput.value);
autocompleteRef.value?.blur();
}
defineExpose({
focus: () => autocompleteRef.value?.focus(),
});
</script>

View File

@@ -14,17 +14,25 @@
<script setup lang="ts">
import { useWakeLock } from "@vueuse/core";
import { useUserExperiencePreferences } from "~/composables/use-users/preferences";
const { isSupported: wakeIsSupported, isActive, request, release } = useWakeLock();
const userExperiencePreferences = useUserExperiencePreferences();
function handleLock() {
if (userExperiencePreferences.value.lockScreen) {
lockScreen();
}
else {
unlockScreen();
}
}
const wakeLock = computed({
get: () => isActive.value,
get: () => userExperiencePreferences.value.lockScreen,
set: () => {
if (isActive.value) {
unlockScreen();
}
else {
lockScreen();
}
userExperiencePreferences.value.lockScreen = !userExperiencePreferences.value.lockScreen;
handleLock();
},
});
async function lockScreen() {
@@ -34,11 +42,11 @@ async function lockScreen() {
}
}
async function unlockScreen() {
if (wakeIsSupported || isActive) {
if (wakeIsSupported || isActive.value) {
console.debug("Wake Lock Released");
await release();
}
}
onMounted(() => lockScreen());
onMounted(() => handleLock());
onUnmounted(() => unlockScreen());
</script>

View File

@@ -13,9 +13,10 @@ describe("useStoreActions", () => {
const mockStore = ref([]);
const mockLoading = ref(false);
const mockInitialized = ref(false);
test("deleteMany calls deleteOne for each ID and refreshes once", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading, mockInitialized);
mockApi.deleteOne = vi.fn().mockResolvedValue({ response: { data: {} } });
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
@@ -32,7 +33,7 @@ describe("useStoreActions", () => {
});
test("deleteMany handles empty array", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading, mockInitialized);
mockApi.deleteOne = vi.fn();
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
@@ -44,7 +45,7 @@ describe("useStoreActions", () => {
});
test("deleteMany sets loading state", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading, mockInitialized);
mockApi.deleteOne = vi.fn().mockResolvedValue({});
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
@@ -55,4 +56,25 @@ describe("useStoreActions", () => {
await promise;
expect(mockLoading.value).toBe(false);
});
test("refresh sets initialized to true even when store returns empty results", async () => {
const localInitialized = ref(false);
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading, localInitialized);
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
expect(localInitialized.value).toBe(false);
await actions.refresh();
expect(localInitialized.value).toBe(true);
});
test("refresh sets initialized to true when store returns items", async () => {
const localInitialized = ref(false);
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading, localInitialized);
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [{ id: "1", name: "item" }] } });
await actions.refresh();
expect(localInitialized.value).toBe(true);
});
});

View File

@@ -26,6 +26,7 @@ export function useReadOnlyActions<T extends BoundT>(
api: BaseCRUDAPIReadOnly<T>,
allRef: Ref<T[] | null> | null,
loading: Ref<boolean>,
initialized: Ref<boolean>,
defaultQueryParams: Record<string, QueryValue> = {},
): ReadOnlyStoreActions<T> {
function getAll(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
@@ -69,6 +70,7 @@ export function useReadOnlyActions<T extends BoundT>(
allRef.value = data.items;
}
initialized.value = true;
loading.value = false;
}
@@ -89,6 +91,7 @@ export function useStoreActions<T extends BoundT>(
api: BaseCRUDAPI<unknown, T, unknown>,
allRef: Ref<T[] | null> | null,
loading: Ref<boolean>,
initialized: Ref<boolean>,
defaultQueryParams: Record<string, QueryValue> = {},
): StoreActions<T> {
function getAll(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
@@ -132,6 +135,7 @@ export function useStoreActions<T extends BoundT>(
allRef.value = data.items;
}
initialized.value = true;
loading.value = false;
}

View File

@@ -16,10 +16,11 @@ export const useReadOnlyStore = function <T extends BoundT>(
storeKey: string,
store: Ref<T[]>,
loading: Ref<boolean>,
initialized: Ref<boolean>,
api: BaseCRUDAPIReadOnly<T>,
params = {} as Record<string, QueryValue>,
) {
const storeActions = useReadOnlyActions(`${storeKey}-store-readonly`, api, store, loading);
const storeActions = useReadOnlyActions(`${storeKey}-store-readonly`, api, store, loading, initialized);
const actions = {
...storeActions,
async refresh() {
@@ -27,11 +28,12 @@ export const useReadOnlyStore = function <T extends BoundT>(
},
flushStore() {
store.value = [];
initialized.value = false;
},
};
// initial hydration
if (!loading.value && !store.value.length) {
if (!loading.value && !initialized.value) {
actions.refresh();
}
@@ -42,10 +44,11 @@ export const useStore = function <T extends BoundT>(
storeKey: string,
store: Ref<T[]>,
loading: Ref<boolean>,
initialized: Ref<boolean>,
api: BaseCRUDAPI<unknown, T, unknown>,
params = {} as Record<string, QueryValue>,
) {
const storeActions = useStoreActions(`${storeKey}-store`, api, store, loading);
const storeActions = useStoreActions(`${storeKey}-store`, api, store, loading, initialized);
const actions = {
...storeActions,
async refresh() {
@@ -53,11 +56,12 @@ export const useStore = function <T extends BoundT>(
},
flushStore() {
store.value = [];
initialized.value = false;
},
};
// initial hydration
if (!loading.value && !store.value.length) {
if (!loading.value && !initialized.value) {
actions.refresh();
}

View File

@@ -5,12 +5,16 @@ import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref<RecipeCategory[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
const publicLoading = ref(false);
const publicInitialized = ref(false);
export function resetCategoryStore() {
store.value = [];
loading.value = false;
initialized.value = false;
publicLoading.value = false;
publicInitialized.value = false;
}
export const useCategoryData = function () {
@@ -23,10 +27,10 @@ export const useCategoryData = function () {
export const useCategoryStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<RecipeCategory>("category", store, loading, api.categories);
return useStore<RecipeCategory>("category", store, loading, initialized, api.categories);
};
export const usePublicCategoryStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeCategory>("category", store, publicLoading, api.categories);
return useReadOnlyStore<RecipeCategory>("category", store, publicLoading, publicInitialized, api.categories);
};

View File

@@ -5,17 +5,21 @@ import { usePublicExploreApi, useUserApi } from "~/composables/api";
const cookbooks: Ref<ReadCookBook[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
const publicLoading = ref(false);
const publicInitialized = ref(false);
export function resetCookbookStore() {
cookbooks.value = [];
loading.value = false;
initialized.value = false;
publicLoading.value = false;
publicInitialized.value = false;
}
export const useCookbookStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
const store = useStore<ReadCookBook>("cookbook", cookbooks, loading, api.cookbooks);
const store = useStore<ReadCookBook>("cookbook", cookbooks, loading, initialized, api.cookbooks);
const updateAll = async function (updateData: UpdateCookBook[]) {
loading.value = true;
@@ -31,5 +35,5 @@ export const useCookbookStore = function (i18n?: Composer) {
export const usePublicCookbookStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<ReadCookBook>("cookbook", cookbooks, publicLoading, api.cookbooks);
return useReadOnlyStore<ReadCookBook>("cookbook", cookbooks, publicLoading, publicInitialized, api.cookbooks);
};

View File

@@ -5,12 +5,16 @@ import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref<IngredientFood[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
const publicLoading = ref(false);
const publicInitialized = ref(false);
export function resetFoodStore() {
store.value = [];
loading.value = false;
initialized.value = false;
publicLoading.value = false;
publicInitialized.value = false;
}
export const useFoodData = function () {
@@ -24,10 +28,10 @@ export const useFoodData = function () {
export const useFoodStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<IngredientFood>("food", store, loading, api.foods);
return useStore<IngredientFood>("food", store, loading, initialized, api.foods);
};
export const usePublicFoodStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<IngredientFood>("food", store, publicLoading, api.foods);
return useReadOnlyStore<IngredientFood>("food", store, publicLoading, publicInitialized, api.foods);
};

View File

@@ -5,20 +5,24 @@ import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref<HouseholdSummary[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
const publicLoading = ref(false);
const publicInitialized = ref(false);
export function resetHouseholdStore() {
store.value = [];
loading.value = false;
initialized.value = false;
publicLoading.value = false;
publicInitialized.value = false;
}
export const useHouseholdStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useReadOnlyStore<HouseholdSummary>("household", store, loading, api.households);
return useReadOnlyStore<HouseholdSummary>("household", store, loading, initialized, api.households);
};
export const usePublicHouseholdStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<HouseholdSummary>("household-public", store, publicLoading, api.households);
return useReadOnlyStore<HouseholdSummary>("household-public", store, publicLoading, publicInitialized, api.households);
};

View File

@@ -5,10 +5,12 @@ import { useUserApi } from "~/composables/api";
const store: Ref<MultiPurposeLabelOut[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
export function resetLabelStore() {
store.value = [];
loading.value = false;
initialized.value = false;
}
export const useLabelData = function () {
@@ -22,5 +24,5 @@ export const useLabelData = function () {
export const useLabelStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<MultiPurposeLabelOut>("label", store, loading, api.multiPurposeLabels);
return useStore<MultiPurposeLabelOut>("label", store, loading, initialized, api.multiPurposeLabels);
};

View File

@@ -5,12 +5,16 @@ import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref<RecipeTag[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
const publicLoading = ref(false);
const publicInitialized = ref(false);
export function resetTagStore() {
store.value = [];
loading.value = false;
initialized.value = false;
publicLoading.value = false;
publicInitialized.value = false;
}
export const useTagData = function () {
@@ -23,10 +27,10 @@ export const useTagData = function () {
export const useTagStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<RecipeTag>("tag", store, loading, api.tags);
return useStore<RecipeTag>("tag", store, loading, initialized, api.tags);
};
export const usePublicTagStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeTag>("tag", store, publicLoading, api.tags);
return useReadOnlyStore<RecipeTag>("tag", store, publicLoading, publicInitialized, api.tags);
};

View File

@@ -9,12 +9,16 @@ interface RecipeToolWithOnHand extends RecipeTool {
const store: Ref<RecipeTool[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
const publicLoading = ref(false);
const publicInitialized = ref(false);
export function resetToolStore() {
store.value = [];
loading.value = false;
initialized.value = false;
publicLoading.value = false;
publicInitialized.value = false;
}
export const useToolData = function () {
@@ -29,10 +33,10 @@ export const useToolData = function () {
export const useToolStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<RecipeTool>("tool", store, loading, api.tools);
return useStore<RecipeTool>("tool", store, loading, initialized, api.tools);
};
export const usePublicToolStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeTool>("tool", store, publicLoading, api.tools);
return useReadOnlyStore<RecipeTool>("tool", store, publicLoading, publicInitialized, api.tools);
};

View File

@@ -5,10 +5,12 @@ import { useUserApi } from "~/composables/api";
const store: Ref<IngredientUnit[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
export function resetUnitStore() {
store.value = [];
loading.value = false;
initialized.value = false;
}
export const useUnitData = function () {
@@ -23,5 +25,5 @@ export const useUnitData = function () {
export const useUnitStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<IngredientUnit>("unit", store, loading, api.units);
return useStore<IngredientUnit>("unit", store, loading, initialized, api.units);
};

View File

@@ -6,10 +6,12 @@ import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
const store: Ref<UserSummary[]> = ref([]);
const loading = ref(false);
const initialized = ref(false);
export function resetUserStore() {
store.value = [];
loading.value = false;
initialized.value = false;
}
class GroupUserAPIReadOnly extends BaseCRUDAPIReadOnly<UserSummary> {
@@ -21,5 +23,5 @@ export const useUserStore = function (i18n?: Composer) {
const requests = useRequests(i18n);
const api = new GroupUserAPIReadOnly(requests);
return useReadOnlyStore<UserSummary>("user", store, loading, api, { orderBy: "full_name" });
return useReadOnlyStore<UserSummary>("user", store, loading, initialized, api, { orderBy: "full_name" });
};

View File

@@ -10,7 +10,7 @@ export const LOCALES = [
{
name: "简体中文 (Chinese simplified)",
value: "zh-CN",
progress: 55,
progress: 54,
dir: "ltr",
pluralFoodHandling: "never",
},
@@ -38,7 +38,7 @@ export const LOCALES = [
{
name: "Svenska (Swedish)",
value: "sv-SE",
progress: 74,
progress: 75,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -101,14 +101,14 @@ export const LOCALES = [
{
name: "Norsk (Norwegian)",
value: "no-NO",
progress: 59,
progress: 60,
dir: "ltr",
pluralFoodHandling: "always",
},
{
name: "Nederlands (Dutch)",
value: "nl-NL",
progress: 97,
progress: 98,
dir: "ltr",
pluralFoodHandling: "always",
},
@@ -143,7 +143,7 @@ export const LOCALES = [
{
name: "Italiano (Italian)",
value: "it-IT",
progress: 72,
progress: 73,
dir: "ltr",
pluralFoodHandling: "always",
},

View File

@@ -73,6 +73,10 @@ export interface UserActivityPreferences {
defaultActivity: ActivityKey;
}
export interface UserExperiencePreferences {
lockScreen: boolean;
}
export function useUserMealPlanPreferences(): Ref<UserMealPlanPreferences> {
const fromStorage = useLocalStorage(
"meal-planner-preferences",
@@ -81,9 +85,7 @@ export function useUserMealPlanPreferences(): Ref<UserMealPlanPreferences> {
numberOfDays: 7,
},
{ 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<UserMealPlanPreferences>;
);
return fromStorage;
}
@@ -92,15 +94,14 @@ export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
const fromStorage = useLocalStorage(
"recipe-print-preferences",
{
imagePosition: "left",
imagePosition: "left" as ImagePosition,
showDescription: true,
showNotes: true,
showNutrition: false,
expandChildRecipes: false,
},
{ 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<UserPrintPreferences>;
);
return fromStorage;
}
@@ -118,9 +119,7 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
useMobileCards: false,
},
{ 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<UserRecipePreferences>;
);
return fromStorage;
}
@@ -132,9 +131,7 @@ export function useUserActivityPreferences(): Ref<UserActivityPreferences> {
defaultActivity: ActivityKey.RECIPES,
},
{ 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 Ref<UserActivityPreferences>;
);
return fromStorage;
}
@@ -146,9 +143,7 @@ export function useUserSearchQuerySession(): Ref<UserSearchQuery> {
recipe: "",
},
{ mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref<UserSearchQuery>;
);
return fromStorage;
}
@@ -160,9 +155,7 @@ export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
viewAllLists: false,
},
{ 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<UserShoppingListPreferences>;
);
return fromStorage;
}
@@ -175,9 +168,7 @@ export function useTimelinePreferences(): Ref<UserTimelinePreferences> {
types: ["info", "system", "comment"] as TimelineEventType[],
},
{ 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<UserTimelinePreferences>;
);
return fromStorage;
}
@@ -186,12 +177,10 @@ export function useParsingPreferences(): Ref<UserParsingPreferences> {
const fromStorage = useLocalStorage(
"parsing-preferences",
{
parser: "nlp",
parser: "nlp" as RegisteredParser,
},
{ 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;
}
@@ -203,9 +192,7 @@ export function useCookbookPreferences(): Ref<UserCookbooksPreferences> {
hideOtherHouseholds: false,
},
{ 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<UserCookbooksPreferences>;
);
return fromStorage;
}
@@ -224,9 +211,7 @@ export function useRecipeFinderPreferences(): Ref<UserRecipeFinderPreferences> {
includeToolsOnHand: true,
},
{ 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<UserRecipeFinderPreferences>;
);
return fromStorage;
}
@@ -241,9 +226,19 @@ export function useRecipeCreatePreferences(): Ref<UserRecipeCreatePreferences> {
parseRecipe: true,
},
{ 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<UserRecipeCreatePreferences>;
);
return fromStorage;
}
export function useUserExperiencePreferences(): Ref<UserExperiencePreferences> {
const fromStorage = useLocalStorage(
"user-experience-preferences",
{
lockScreen: true,
},
{ mergeDefaults: true },
);
return fromStorage;
}

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Dinsdag",
"type": "Tipe",
"undo": "Undo",
"update": "Wysig",
"updated": "Opgedateer",
"upload": "Laai op",
@@ -627,7 +628,7 @@
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"import-with-zip": "Voer in met .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
"create-from-images": "Create from Images",
@@ -916,6 +917,7 @@
"quantity": "Hoeveelheid: {0}",
"shopping-list": "Inkopielys",
"shopping-lists": "Inkopielyste",
"add-item": "Add item",
"food": "Voedsel",
"note": "Nota",
"label": "Etiket",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Are you sure you want to check all items?",
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
"no-shopping-lists-found": "No Shopping Lists Found"
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Alle resepte",

View File

@@ -169,6 +169,7 @@
"token": "الرمز التعريفي",
"tuesday": "الثلاثاء",
"type": "النوع",
"undo": "Undo",
"update": "تحديث",
"updated": "محدث",
"upload": "تحميل",
@@ -627,7 +628,7 @@
"create-recipe-description": "إنشاء وصفة جديدة من الصفر.",
"create-recipes": "إنشاء الوصفات",
"import-with-zip": "الاستيراد باستخدام zip.",
"create-recipe-from-an-image": "إنشاء وصفة عن طريق صورة",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "قم بقص الصورة وتدويرها بحيث يظهر النص فقط، ويكون في الاتجاه الصحيح.",
"create-from-images": "إنشاء عن طريق صور",
@@ -916,6 +917,7 @@
"quantity": "الكَمّيَّة: {0}",
"shopping-list": "قائمة التسوق",
"shopping-lists": "قوائم التسوق",
"add-item": "إضافة عنصر",
"food": "الطعام",
"note": "ملاحظة",
"label": "Label",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "هل أنت متأكد من أنك تريد تحديد جميع العناصر؟",
"are-you-sure-you-want-to-uncheck-all-items": "هل أنت متأكد من أنك تريد إلغاء تحديد جميع العناصر؟",
"are-you-sure-you-want-to-delete-checked-items": "هل أنت متأكد أنك تريد حذف جميع العناصر المحددة؟",
"no-shopping-lists-found": "لم يتم العثور على قوائم تسوق"
"no-shopping-lists-found": "لم يتم العثور على قوائم تسوق",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "جميع الوصفات",
@@ -1477,7 +1480,7 @@
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"mark-all-as-read": "وضع علامة مقروء على الكل",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
}

View File

@@ -169,6 +169,7 @@
"token": "Токен",
"tuesday": "Вторник",
"type": "Тип",
"undo": "Undo",
"update": "Актуализация",
"updated": "Последно обновени",
"upload": "Качи",
@@ -627,7 +628,7 @@
"create-recipe-description": "Създайте нова рецепта от чернова.",
"create-recipes": "Създайте рецепти",
"import-with-zip": "Импортирай от .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Изрежете и завъртете изображението, така че да се вижда само текстът и той да е в правилната ориентация.",
"create-from-images": "Създаване от изображения",
@@ -916,6 +917,7 @@
"quantity": "Количество: {0}",
"shopping-list": "Списък за пазаруване",
"shopping-lists": "Списъци за пазаруване",
"add-item": "Add item",
"food": "Продукт",
"note": "Бележка",
"label": "Етикет",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Сигурни ли сте, че искате да изберете всички елементи?",
"are-you-sure-you-want-to-uncheck-all-items": "Сигурни ли сте, че искате да премахнете отметката от всички елементи?",
"are-you-sure-you-want-to-delete-checked-items": "Сигурни ли сте, че искате да изтриете всички отметнати елементи?",
"no-shopping-lists-found": "Не са намерени списъци за пазаруване"
"no-shopping-lists-found": "Не са намерени списъци за пазаруване",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Всички рецепти",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Dimarts",
"type": "Tipus",
"undo": "Undo",
"update": "Actualitza",
"updated": "S'ha actualitzat",
"upload": "Puja",
@@ -627,7 +628,7 @@
"create-recipe-description": "Crea una nova recepta des de zero.",
"create-recipes": "Crea Receptes",
"import-with-zip": "Importar amb un .zip",
"create-recipe-from-an-image": "Crear una recepta a partir d'una imatge",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Crear una recepta pujant una imatge d'ella. Mealie intentarà extreure el text de la imatge mitjançant IA i crear-ne la recepta.",
"crop-and-rotate-the-image": "Retalla i rota la imatge, per tal que només el text sigui visible, i estigui orientat correctament.",
"create-from-images": "Crear una recepta a partir d'una imatge",
@@ -916,6 +917,7 @@
"quantity": "Quantitat: {0}",
"shopping-list": "Llista de la compra",
"shopping-lists": "Llistes de la compra",
"add-item": "Add item",
"food": "Aliments",
"note": "Nota",
"label": "Etiqueta",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Estàs segur que vols marcar tots els elements?",
"are-you-sure-you-want-to-uncheck-all-items": "Estàs segur que vols desmarcar tots els elements?",
"are-you-sure-you-want-to-delete-checked-items": "Estàs segur que vols eliminar tots els elements marcats?",
"no-shopping-lists-found": "No s'han trobat llistes de la compra"
"no-shopping-lists-found": "No s'han trobat llistes de la compra",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Receptes",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Úterý",
"type": "Typ",
"undo": "Undo",
"update": "Aktualizace",
"updated": "Aktualizováno",
"upload": "Nahrát",
@@ -627,7 +628,7 @@
"create-recipe-description": "Vytvořit nový recept od nuly.",
"create-recipes": "Vytvořit recepty",
"import-with-zip": "Importovat pomocí .zip",
"create-recipe-from-an-image": "Vytvořit recept z obrázku",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Vytvořte recept nahráním obrázku. Mealie se pokusí z obrázku extrahovat text pomocí AI a vytvořit z něj recept.",
"crop-and-rotate-the-image": "Oříznout a otočit obrázek tak, aby byl viditelný pouze text a aby byl ve správné orientaci.",
"create-from-images": "Vytvořit z obrázků",
@@ -916,6 +917,7 @@
"quantity": "Množství: {0}",
"shopping-list": "Nákupní seznam",
"shopping-lists": "Nákupní seznamy",
"add-item": "Add item",
"food": "Jídlo",
"note": "Poznámka",
"label": "Popisek",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Opravdu chcete vybrat všechny položky?",
"are-you-sure-you-want-to-uncheck-all-items": "Opravdu chcete zrušit výběr všech položek?",
"are-you-sure-you-want-to-delete-checked-items": "Opravdu chcete odstranit všechny vybrané položky?",
"no-shopping-lists-found": "Nebyly nalezeny žádné nákupní seznamy"
"no-shopping-lists-found": "Nebyly nalezeny žádné nákupní seznamy",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Všechny recepty",

View File

@@ -169,6 +169,7 @@
"token": "Nøgle",
"tuesday": "Tirsdag",
"type": "Type",
"undo": "Fortryd",
"update": "Gem",
"updated": "Ændret",
"upload": "Upload",
@@ -627,7 +628,7 @@
"create-recipe-description": "Opret ny opskrift fra bunden.",
"create-recipes": "Opret opskrift",
"import-with-zip": "Importér fra ZIP-fil",
"create-recipe-from-an-image": "Opret opskrift fra et billede",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Opret en opskrift ved at overføre et billede af den. Mealie vil forsøge at udtrække teksten fra billedet med AI og oprette en opskrift fra det.",
"crop-and-rotate-the-image": "Beskær og roter billedet, så kun teksten er synlig, og det vises i den rigtige retning.",
"create-from-images": "Opret fra billede",
@@ -916,6 +917,7 @@
"quantity": "Antal: {0}",
"shopping-list": "Indkøbsliste",
"shopping-lists": "Indkøbslister",
"add-item": "Tilføj vare",
"food": "Fødevarer",
"note": "Note",
"label": "Etiket",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Er du sikker på, at du vil markere alle elementer?",
"are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på, at du vil fjerne markeringen af alle elementer?",
"are-you-sure-you-want-to-delete-checked-items": "Er du sikker på, at du vil sletter de valgte elementer?",
"no-shopping-lists-found": "Ingen Indkøbslister fundet"
"no-shopping-lists-found": "Ingen Indkøbslister fundet",
"item-checked-off": "Krydsede {item} af"
},
"sidebar": {
"all-recipes": "Alle opskrifter",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Dienstag",
"type": "Typ",
"undo": "Rückgängig",
"update": "Aktualisieren",
"updated": "Aktualisiert",
"upload": "Hochladen",
@@ -627,7 +628,7 @@
"create-recipe-description": "Erstelle ein neues Rezept von Grund auf.",
"create-recipes": "Rezepte erstellen",
"import-with-zip": "Von .zip importieren",
"create-recipe-from-an-image": "Rezept von einem Bild erstellen",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Erstelle ein Rezept, indem du ein Bild hochlädst. Mealie wird versuchen, den Text aus dem Bild mit Hilfe von KI zu extrahieren und ein Rezept daraus zu erstellen.",
"crop-and-rotate-the-image": "Beschneide und drehe das Bild so, dass nur der Text zu sehen ist und die Ausrichtung stimmt.",
"create-from-images": "Aus Bildern erstellen",
@@ -916,6 +917,7 @@
"quantity": "Menge: {0}",
"shopping-list": "Einkaufsliste",
"shopping-lists": "Einkaufslisten",
"add-item": "Eintrag hinzufügen",
"food": "Lebensmittel",
"note": "Notiz",
"label": "Kategorie",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Bist du sicher, dass du alle Elemente markieren möchtest?",
"are-you-sure-you-want-to-uncheck-all-items": "Bist du sicher, dass du die Auswahl aller Elemente aufheben möchtest?",
"are-you-sure-you-want-to-delete-checked-items": "Bist du sicher, dass du alle ausgewählten Elemente löschen möchtest?",
"no-shopping-lists-found": "Keine Einkaufslisten gefunden"
"no-shopping-lists-found": "Keine Einkaufslisten gefunden",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Alle Rezepte",
@@ -1475,10 +1478,10 @@
"max-length": "Muss maximal {max} Zeichen haben|Muss maximal {max} Zeichen haben"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"announcements": "Ankündigungen",
"all-announcements": "Alle Ankündigungen",
"mark-all-as-read": "Alle als gelesen markieren",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"show-announcements-from-mealie": "Ankündigung von Mealie anzeigen",
"show-announcements-setting-description": "Lege fest, ob Benutzer Ankündigungen von Mealie sehen dürfen. Wenn aktiviert, können Benutzer die Anzeige in ihren Benutzereinstellungen immer noch deaktivieren"
}
}

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Τρίτη",
"type": "Τύπος",
"undo": "Undo",
"update": "Ενημέρωση",
"updated": "Ενημερώθηκε",
"upload": "Ανέβασμα",
@@ -627,7 +628,7 @@
"create-recipe-description": "Δημιουργήστε μια νέα συνταγή από το μηδέν.",
"create-recipes": "Δημιουργία Συνταγών",
"import-with-zip": "Εισαγωγή μέσω .zip",
"create-recipe-from-an-image": "Δημιουργία συνταγής από μια εικόνα",
"create-recipe-from-images": "Δημιουργία συνταγής από εικόνες",
"create-recipe-from-an-image-description": "Δημιουργήστε μια συνταγή ανεβάζοντας μια εικόνα της. Το Mealie θα προσπαθήσει να εξάγει το κείμενο από την εικόνα χρησιμοποιώντας τεχνητή νοημοσύνη και να δημιουργήσει μια συνταγή από αυτό.",
"crop-and-rotate-the-image": "Περικοπή και περιστροφή της εικόνας, έτσι ώστε να είναι μόνο το κείμενο ορατό και να είναι στο σωστό προσανατολισμό.",
"create-from-images": "Δημιουργία από εικόνες",
@@ -916,6 +917,7 @@
"quantity": "Ποσότητα: {0}",
"shopping-list": "Λίστα για ψώνια",
"shopping-lists": "Λίστες για ψώνια",
"add-item": "Add item",
"food": "Τρόφιμο",
"note": "Σημείωση",
"label": "Ετικέτα",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Θέλετε σίγουρα να επιλέξετε όλα τα αντικείμενα;",
"are-you-sure-you-want-to-uncheck-all-items": "Θέλετε σίγουρα να αποεπιλέξετε όλα τα αντικείμενα;",
"are-you-sure-you-want-to-delete-checked-items": "Θέλετε σίγουρα να διαγράψετε όλα τα επιλεγμένα αντικείμενα;",
"no-shopping-lists-found": "Δεν βρέθηκαν λίστες για ψώνια"
"no-shopping-lists-found": "Δεν βρέθηκαν λίστες για ψώνια",
"item-checked-off": "Το {item} επισημάνθηκε ως ολοκληρωμένο"
},
"sidebar": {
"all-recipes": "Συνταγές όλες",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Tuesday",
"type": "Type",
"undo": "Undo",
"update": "Update",
"updated": "Updated",
"upload": "Upload",
@@ -627,7 +628,7 @@
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"import-with-zip": "Import with .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
"create-from-images": "Create from Images",
@@ -916,6 +917,7 @@
"quantity": "Quantity: {0}",
"shopping-list": "Shopping List",
"shopping-lists": "Shopping Lists",
"add-item": "Add item",
"food": "Food",
"note": "Note",
"label": "Label",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Are you sure you want to check all items?",
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
"no-shopping-lists-found": "No Shopping Lists Found"
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "All Recipes",

View File

@@ -628,7 +628,7 @@
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"import-with-zip": "Import with .zip",
"create-recipe-from-an-image": "Create Recipe from Images",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading images of the recipe text. Mealie will attempt to extract the text from the images using AI and create a new recipe from it.",
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
"create-from-images": "Create from Images",
@@ -943,7 +943,7 @@
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "{item} was checked off"
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "All Recipes",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Martes",
"type": "Tipo",
"undo": "Undo",
"update": "Actualizar",
"updated": "Actualizado",
"upload": "Subir",
@@ -627,7 +628,7 @@
"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",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Crea una receta cargando una imagen de ella. Mealie intentará extraer el texto de la imagen usando IA y crear una receta de ella.",
"crop-and-rotate-the-image": "Recortar y rotar la imagen de manera que sólo el texto sea visible, y esté en la orientación correcta.",
"create-from-images": "Crear a partir de imágenes",
@@ -916,6 +917,7 @@
"quantity": "Cantidad: {0}",
"shopping-list": "Lista de la compra",
"shopping-lists": "Listas de la compra",
"add-item": "Add item",
"food": "Alimentos",
"note": "Nota",
"label": "Etiqueta",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "¿Seguro que quieres seleccionar todos los elementos?",
"are-you-sure-you-want-to-uncheck-all-items": "¿Seguro que quieres de-seleccionar todos los elementos?",
"are-you-sure-you-want-to-delete-checked-items": "¿Está seguro que deseas eliminar los elementos seleccionados?",
"no-shopping-lists-found": "No hay listas de la compra"
"no-shopping-lists-found": "No hay listas de la compra",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Recetas",

View File

@@ -169,6 +169,7 @@
"token": "Identifikaator",
"tuesday": "Teisipäev",
"type": "Tüüp",
"undo": "Undo",
"update": "Uuenda",
"updated": "Uuendatud",
"upload": "Lae üles",
@@ -627,7 +628,7 @@
"create-recipe-description": "Loo uus retsept algusest",
"create-recipes": "Loo retseptid",
"import-with-zip": "Impordi .zip failist",
"create-recipe-from-an-image": "Retsepti loomine pildist",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Retsepti loomiseks lae üles selle pilt. Mealie üritab ekstraheerida pildil oleva teksti ning luua retsepti sellest kasutades AI-d.",
"crop-and-rotate-the-image": "Kärpige ja pöörake pilti nii, et ainult tekst oleks nähtaval ja see oleks suunatud ülespoole.",
"create-from-images": "Retsepti loomine pildist",
@@ -916,6 +917,7 @@
"quantity": "Kogus: {0}",
"shopping-list": "Ostunimekiri",
"shopping-lists": "Ostunimekirjad",
"add-item": "Add item",
"food": "Toit",
"note": "Märkus",
"label": "Silt",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Kas oled kindel, et tahad valida kõik üksused?",
"are-you-sure-you-want-to-uncheck-all-items": "Kas oled kindel, et tahad tühistada kõik valikud?",
"are-you-sure-you-want-to-delete-checked-items": "Kas oled kindel, et tahad kustutada kõik valitud üksused?",
"no-shopping-lists-found": "Poenimekirja ei leitud"
"no-shopping-lists-found": "Poenimekirja ei leitud",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Kõik retseptid",

View File

@@ -51,7 +51,7 @@
"category": "Kategoria"
},
"events": {
"apprise-url": "Apprise URL",
"apprise-url": "Apprise-url",
"database": "Tietokanta",
"delete-event": "Poista tapahtuma",
"event-delete-confirmation": "Haluatko varmasti poistaa tämän tapahtuman?",
@@ -98,7 +98,7 @@
"dashboard": "Hallintanäkymä",
"delete": "Poista",
"disabled": "Poistettu käytöstä",
"done": "Done",
"done": "Valmis",
"download": "Lataa",
"duplicate": "Monista",
"edit": "Muokkaa",
@@ -169,6 +169,7 @@
"token": "Tunniste",
"tuesday": "Tiistai",
"type": "Tyyppi",
"undo": "Peru",
"update": "Päivitä",
"updated": "Päivitetty",
"upload": "Lähetä",
@@ -332,8 +333,8 @@
"any-household": "Mikä tahansa kotitalous",
"no-meal-plan-defined-yet": "Ateriasuunnitelmaa ei ole vielä määritelty",
"no-meal-planned-for-today": "Ei ateriasuunnitelmaa tälle päivälle",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-hint": "Menneisyydestä ladattujen päivien määrä",
"numberOfDaysPast-label": "Oletusarvo menneiden päivien lataukselle",
"numberOfDays-hint": "Sivun latauspäivien lukumäärä",
"numberOfDays-label": "Oletuspäivät",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Vain näiden luokkien reseptejä käytetään ateriasuunnitelmissa",
@@ -391,7 +392,7 @@
"nextcloud": {
"description": "Tuo tiedot Nextcloudin Cookbookista",
"description-long": "Nextcloud reseptejä voidaan tuoda zip-tiedostosta, joka sisältää Nextcloudin tallennetut tiedot. Katso esimerkkikansiorakenne alla varmistaaksesi, että reseptisi voidaan tuoda.",
"title": "Nextcloud Cookbook"
"title": "Nextcloud-keittokirja"
},
"copymethat": {
"description-long": "Mealie voi tuoda reseptejä Copy Me That -sovelluksesta. Vie reseptisi HTML-muodossa ja lataa sitten zip-tiedosto.",
@@ -627,7 +628,7 @@
"create-recipe-description": "Luo resepti alusta.",
"create-recipes": "Luo reseptejä",
"import-with-zip": "Tuo .zip:llä",
"create-recipe-from-an-image": "Luo resepti kuvasta",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Luo resepti tuomalla siitä kuva. Mealie pyrkii poimimaan tekstin kuvasta tekoälyllä ja luomaan siitä reseptin.",
"crop-and-rotate-the-image": "Rajaa ja kierrä kuvaa niin, että vain teksti näkyy, ja että se on oikein päin.",
"create-from-images": "Luo resepti kuvasta",
@@ -701,7 +702,7 @@
"confidence-score": "Varmuuspisteet",
"ingredient-parser-description": "Ainesosat on haettu onnistuneesti. Ole hyvä ja tarkista ainesosat joista emme ole varmoja.",
"ingredient-parser-final-review-description": "Kun kaikki ainesosat on tarkistettu, sinulla on vielä yksi mahdollisuus tarkistaa kaikki ainesosat ennen kuin muokkaat reseptiäsi.",
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
"add-text-as-alias-for-item": "Lisää \"{text}\" kohteen {item} aliakseksi",
"delete-item": "Poista kohde"
},
"reset-servings-count": "Palauta Annoksien Määrä",
@@ -892,17 +893,17 @@
"server-side-base-url-error-text": "`BASE_URL` on API-palvelimen oletusarvo. Tämä aiheuttaa ongelmia ilmoitusten linkkien kanssa, jotka on luotu palvelimella sähköposteja varten jne.",
"server-side-base-url-success-text": "Palvelimen nettiosoite ei vastaa oletusta",
"ldap-ready": "LDAP Valmis",
"ldap-not-ready": "LDAP Not Ready",
"ldap-not-ready": "LDAP ei valmis",
"ldap-ready-error-text": "Kaikkia LDAP-arvoja ei ole määritetty. Tämä voidaan ohittaa, jos et käytä LDAP-todennusta.",
"ldap-ready-success-text": "Kaikki vaaditut LDAP-muuttujat on asetettu.",
"build": "Koonti",
"recipe-scraper-version": "Reseptikaappaimen versio",
"oidc-ready": "OIDC valmis",
"oidc-not-ready": "OIDC Not Ready",
"oidc-not-ready": "OIDC ei ole valmis",
"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 valmis",
"openai-not-ready": "OpenAI Not Ready",
"openai-not-ready": "OpenAI ei ole valmis",
"openai-ready-error-text": "Kaikkia OpenAI:n arvoja ei ole määritelty. Tämä voidaan sivuuttaa, mikäli et käytä OpenAI:n toimintoja.",
"openai-ready-success-text": "Vaadittavat OpenAI-muuttujat ovat asetetut."
},
@@ -916,6 +917,7 @@
"quantity": "Määrä: {0}",
"shopping-list": "Ostoslista",
"shopping-lists": "Ostoslistat",
"add-item": "Lisää kohde",
"food": "Elintarvikkeet",
"note": "Muistiinpano",
"label": "Tunnus",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Haluatko varmasti valita kaikki kohteet?",
"are-you-sure-you-want-to-uncheck-all-items": "Haluatko varmasti poistaa kaikki valinnat?",
"are-you-sure-you-want-to-delete-checked-items": "Haluatko varmasti poistaa kaikki valitut kohteet?",
"no-shopping-lists-found": "Ostoslistoja ei löytynyt"
"no-shopping-lists-found": "Ostoslistoja ei löytynyt",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Reseptit",
@@ -959,7 +962,7 @@
"language": "Kieli",
"maintenance": "Ylläpito",
"background-tasks": "Taustatehtävät",
"parser": "Parser",
"parser": "Jäsentäjä",
"developer": "Kehittäjä",
"cookbook": "Keittokirja",
"create-cookbook": "Luo uusi keittokirja"
@@ -1348,7 +1351,7 @@
"ingredient-text": "Ainesosan Teksti",
"average-confident": "{0} Luottamus",
"try-an-example": "Kokeile esimerkkiä",
"parser": "Parser",
"parser": "Jäsentäjä",
"background-tasks": "Taustatehtävät",
"background-tasks-description": "Täältä voit tarkastella kaikkia käynnissä olevia taustatehtäviä ja niiden tilaa",
"no-logs-found": "Lokeja Ei Löytynyt",
@@ -1478,7 +1481,7 @@
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-from-mealie": "Näytä Mealien ilmoitukset",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
}
}

View File

@@ -169,6 +169,7 @@
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
"undo": "Undo",
"update": "Mettre à jour",
"updated": "Mis à jour",
"upload": "Importer",
@@ -627,7 +628,7 @@
"create-recipe-description": "Créer une nouvelle recette de zéro.",
"create-recipes": "Créer des recettes",
"import-with-zip": "Importer un .zip",
"create-recipe-from-an-image": "Créer une recette à partir dune image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Créez une recette en téléchargeant une image de celle-ci. Mealie utilisera lIA pour tenter dextraire le texte et de créer une recette.",
"crop-and-rotate-the-image": "Rogner et pivoter limage pour que seul le texte soit visible, et quil soit dans la bonne orientation.",
"create-from-images": "Créer à partir dune image",
@@ -916,6 +917,7 @@
"quantity": "Quantité: {0}",
"shopping-list": "Liste de courses",
"shopping-lists": "Listes de courses",
"add-item": "Ajouter un article",
"food": "Aliment",
"note": "Remarque",
"label": "Étiquette",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Voulez-vous vraiment sélectionner tous les éléments ?",
"are-you-sure-you-want-to-uncheck-all-items": "Voulez-vous vraiment désélectionner tous les éléments ?",
"are-you-sure-you-want-to-delete-checked-items": "Voulez-vous vraiment supprimer tous les éléments sélectionnés ?",
"no-shopping-lists-found": "Aucune liste de courses trouvée"
"no-shopping-lists-found": "Aucune liste de courses trouvée",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Recettes",

View File

@@ -169,6 +169,7 @@
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
"undo": "Undo",
"update": "Mettre à jour",
"updated": "Mis à jour",
"upload": "Importer",
@@ -627,7 +628,7 @@
"create-recipe-description": "Créer une nouvelle recette à partir de zéro.",
"create-recipes": "Créer des recettes",
"import-with-zip": "Importer un .zip",
"create-recipe-from-an-image": "Créer une recette à partir dune image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Créez une recette en téléversant une image de celle-ci. Mealie utilisera lIA pour tenter dextraire le texte et de créer une recette.",
"crop-and-rotate-the-image": "Rogner et pivoter limage pour que seul le texte soit visible et quil soit dans la bonne orientation.",
"create-from-images": "Créer à partir dimages",
@@ -916,6 +917,7 @@
"quantity": "Quantité : {0}",
"shopping-list": "Liste d'épicerie",
"shopping-lists": "Listes d'épicerie",
"add-item": "Ajouter un article",
"food": "Aliments",
"note": "Remarque",
"label": "Étiquette",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Voulez-vous vraiment sélectionner tous les éléments ?",
"are-you-sure-you-want-to-uncheck-all-items": "Voulez-vous vraiment désélectionner tous les éléments ?",
"are-you-sure-you-want-to-delete-checked-items": "Voulez-vous vraiment supprimer tous les éléments sélectionnés ?",
"no-shopping-lists-found": "Aucune liste de courses trouvée"
"no-shopping-lists-found": "Aucune liste de courses trouvée",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Les recettes",

View File

@@ -169,6 +169,7 @@
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
"undo": "Annuler",
"update": "Mettre à jour",
"updated": "Mis à jour",
"upload": "Importer",
@@ -627,7 +628,7 @@
"create-recipe-description": "Créer une nouvelle recette de zéro.",
"create-recipes": "Créer des recettes",
"import-with-zip": "Importer un .zip",
"create-recipe-from-an-image": "Créer une recette à partir dune image",
"create-recipe-from-images": "Créer une recette depuis une image",
"create-recipe-from-an-image-description": "Créez une recette en téléchargeant une image de celle-ci. Mealie utilisera lIA pour tenter dextraire le texte et de créer une recette.",
"crop-and-rotate-the-image": "Rogner et pivoter limage pour que seul le texte soit visible, et quil soit dans la bonne orientation.",
"create-from-images": "Créer à partir dimages",
@@ -916,6 +917,7 @@
"quantity": "Quantité: {0}",
"shopping-list": "Liste de courses",
"shopping-lists": "Listes de courses",
"add-item": "Ajouter un article",
"food": "Aliment",
"note": "Remarque",
"label": "Étiquette",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Voulez-vous vraiment sélectionner tous les éléments ?",
"are-you-sure-you-want-to-uncheck-all-items": "Voulez-vous vraiment désélectionner tous les éléments ?",
"are-you-sure-you-want-to-delete-checked-items": "Voulez-vous vraiment supprimer tous les éléments sélectionnés ?",
"no-shopping-lists-found": "Aucune liste de courses trouvée"
"no-shopping-lists-found": "Aucune liste de courses trouvée",
"item-checked-off": "{item} coché"
},
"sidebar": {
"all-recipes": "Recettes",

View File

@@ -169,6 +169,7 @@
"token": "Identificador",
"tuesday": "Martes",
"type": "Tipo",
"undo": "Undo",
"update": "Actualizar",
"updated": "Actualizado",
"upload": "Subir",
@@ -627,7 +628,7 @@
"create-recipe-description": "Crear unha receita en branco.",
"create-recipes": "Crear Receitas",
"import-with-zip": "Importar con .zip",
"create-recipe-from-an-image": "Crear receita a partir dunha imaxen",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Cree unha receita cargando unha imaxen da mesma. O Mealie tentará extrair o texto da imaxen utilizando IA e creará unha receita a partir da mesma.",
"crop-and-rotate-the-image": "Recorte e vire a imaxen de modo a que só o texto sexa visível e na orientación correta.",
"create-from-images": "Crear a partir de imaxens",
@@ -916,6 +917,7 @@
"quantity": "Cantidade: {0}",
"shopping-list": "Lista de Compras",
"shopping-lists": "Listas de Compras",
"add-item": "Add item",
"food": "Comida",
"note": "Nota",
"label": "Rótulo",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Ten a certeza de que pretende selecionar todos os itens?",
"are-you-sure-you-want-to-uncheck-all-items": "Ten a certeza de que pretende desmarcar todos os itens?",
"are-you-sure-you-want-to-delete-checked-items": "Ten a certeza de que pretende eliminar todos os itens selecionados?",
"no-shopping-lists-found": "Nengunha Lista de Compras Encontrada"
"no-shopping-lists-found": "Nengunha Lista de Compras Encontrada",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Todas as Receitas",

View File

@@ -169,6 +169,7 @@
"token": "טוקן",
"tuesday": "שלישי",
"type": "סוג",
"undo": "Undo",
"update": "עדכון",
"updated": "עודכן",
"upload": "העלאה",
@@ -627,7 +628,7 @@
"create-recipe-description": "יצירת מתכון חדש מאפס.",
"create-recipes": "יצירת מתכונים",
"import-with-zip": "ייבא באמצעות zip",
"create-recipe-from-an-image": "יצירת מתכון מתמונה",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "יצירת מתכון ע\"י העלאת תמונה שלו. Mealie תנסה לחלץ את הטקסט מהתמונה באמצעות AI ותייצר ממנו מתכון.",
"crop-and-rotate-the-image": "נא לחתוך ולסובב את התמונה כך שרואים רק את הטקסט, והוא בכיוון הנכון.",
"create-from-images": "יצירה מתמונה",
@@ -916,6 +917,7 @@
"quantity": "כמות: {0}",
"shopping-list": "רשימת קניות",
"shopping-lists": "רשימות קניות",
"add-item": "Add item",
"food": "אוכל",
"note": "הערה",
"label": "תווית",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "לסמן את כל הפריטים?",
"are-you-sure-you-want-to-uncheck-all-items": "לבטל את סימון כל הפריטים?",
"are-you-sure-you-want-to-delete-checked-items": "למחוק את כל הפריטים המסומנים?",
"no-shopping-lists-found": "לא נמצאה רשימת קניות"
"no-shopping-lists-found": "לא נמצאה רשימת קניות",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "כל המתכונים",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Utorak",
"type": "Tip",
"undo": "Undo",
"update": "Ažuriraj",
"updated": "Ažurirano",
"upload": "Prenesi",
@@ -627,7 +628,7 @@
"create-recipe-description": "Izradi novi recept od početka",
"create-recipes": "Kreiraj recept",
"import-with-zip": "Učitaj pomoću .zip-a",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Obreži i rotiraj sliku tako da bude vidljiv samo tekst i da bude u ispravnoj orijentaciji.",
"create-from-images": "Izradi na temelju fotografije",
@@ -916,6 +917,7 @@
"quantity": "Količina: {0}",
"shopping-list": "Popis za Kupovinu",
"shopping-lists": "Popis za Kupovinu",
"add-item": "Add item",
"food": "Namirnica",
"note": "Bilješka",
"label": "Oznaka",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Are you sure you want to check all items?",
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
"no-shopping-lists-found": "No Shopping Lists Found"
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Svi Recepti",

View File

@@ -98,7 +98,7 @@
"dashboard": "Vezérlőpult",
"delete": "Törlés",
"disabled": "Letiltva",
"done": "Done",
"done": "Kész",
"download": "Letöltés",
"duplicate": "Duplikálás",
"edit": "Szerkesztés",
@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Kedd",
"type": "Típus",
"undo": "Visszavonás",
"update": "Frissítés",
"updated": "Frissítve",
"upload": "Feltöltés",
@@ -213,7 +214,7 @@
"upload-file": "Fájl feltöltése",
"created-on-date": "Létrehozva: {0}",
"unsaved-changes": "El nem mentett módosításai vannak. Szeretné elmenteni, mielőtt kilép? A mentéshez kattintson az Ok, a módosítások elvetéséhez a Mégsem gombra.",
"discard-changes": "Discard Changes",
"discard-changes": "Változtatások elvetése",
"discard-changes-description": "Nem mentett módosításai vannak, biztos, hogy elveti?",
"clipboard-copy-failure": "Nem sikerült a vágólapra másolás.",
"confirm-delete-generic-items": "Biztos benne, hogy törölni szeretné az alábbi tételeket?",
@@ -442,7 +443,7 @@
"error-details": "Csak ld+json vagy microdata-t tartalmazó oldalakat tudunk importálni. Nagyobb weboldalak támogatják ezen adatstruktúrákat. Ha az importálás sikertelen, de van json adat a naplókban, jelentsd a hibát a github issue-kban az URL-el és az naplózott adattal.",
"error-title": "Úgy tűnik nem találtunk semmit",
"from-url": "Recept importálása",
"github-issues": "GitHub Issues",
"github-issues": "GitHub-problémák",
"google-ld-json-info": "ld+json információ keresése",
"must-be-a-valid-url": "Érvényes URL-nek kell lennie",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Másold be a receptedet. Minden sor egy új elemként lesz kezelve a listában",
@@ -627,7 +628,7 @@
"create-recipe-description": "Adj hozzá egy új receptet a nulláról kezdve.",
"create-recipes": "Receptek létrehozása",
"import-with-zip": "Importálás .zip formátummal",
"create-recipe-from-an-image": "Recept készítése képről",
"create-recipe-from-images": "Recept létrehozása képek alapján",
"create-recipe-from-an-image-description": "Hozzon létre egy receptet egy kép feltöltésével. A Mealie megpróbálja a kép szövegét mesterséges intelligencia segítségével kinyerni, és létrehozni belőle a receptet.",
"crop-and-rotate-the-image": "Vágja ki és forgassa el a képet úgy, hogy csak a szöveg legyen látható, és megfelelő tájolásban legyen.",
"create-from-images": "Létrehozás képekről",
@@ -640,7 +641,7 @@
"new-recipe-names-must-be-unique": "Az új recept nevének egyedinek kell lennie",
"scrape-recipe": "Recept kinyerése",
"scrape-recipe-description": "Recept kinyerése URL alapján. Adja meg annak az oldalnak az URLjét, amelyről szeretné a receptet kinyerni, és a Mealie megpróbálja beolvasni a receptet, majd hozzáadja a gyűjteményéhez.",
"scrape-recipe-description-transcription": "You can also provide the url to a video and Mealie will attempt to transcribe it into a recipe.",
"scrape-recipe-description-transcription": "Megadhat egy videóUrlt is, és a Mealie megpróbálja receptté átírni.",
"scrape-recipe-have-a-lot-of-recipes": "Sok receptje van, amit egyszerre szeretne átvenni?",
"scrape-recipe-suggest-bulk-importer": "Próbálja ki a tömeges importálót",
"scrape-recipe-have-raw-html-or-json-data": "Nyers HTML vagy JSON adatai vannak?",
@@ -916,6 +917,7 @@
"quantity": "Mennyiség: {0}",
"shopping-list": "Bevásárlólista",
"shopping-lists": "Bevásárlólisták",
"add-item": "Tétel hozzáadása",
"food": "Étel",
"note": "Megjegyzés",
"label": "Címke",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Biztos, hogy minden elemet be akar jelölni?",
"are-you-sure-you-want-to-uncheck-all-items": "Biztos, hogy minden elem kijelölését visszavonja?",
"are-you-sure-you-want-to-delete-checked-items": "Biztosan törölni akarja az összes bejelölt elemet?",
"no-shopping-lists-found": "Nem találhatók bevásárlólisták"
"no-shopping-lists-found": "Nem találhatók bevásárlólisták",
"item-checked-off": "{item} leellenőrzve"
},
"sidebar": {
"all-recipes": "Minden recept",
@@ -1344,7 +1347,7 @@
"nlp": "NLP",
"brute": "Brute",
"openai": "OpenAI",
"show-individual-confidence": "Show individual confidence",
"show-individual-confidence": "Mutasd az egyes elemek bizalmi szintjét",
"ingredient-text": "Hozzávaló szöveg",
"average-confident": "{0}-os bizonyosság",
"try-an-example": "Próbáljon ki egy példát",
@@ -1475,10 +1478,10 @@
"max-length": "Legfeljebb {max} karakter lehet|Legfeljebb {max} karakter lehet"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"announcements": "Közlemények",
"all-announcements": "Minden közlemény",
"mark-all-as-read": "Összes megjelölése olvasottként",
"show-announcements-from-mealie": "Mutasd a Mealie értesítéseit",
"show-announcements-setting-description": "Szeretnée engedélyezni, hogy a felhasználók lássák a Mealie bejelentéseit. Ha engedélyezi, a felhasználók a saját beállításaikban továbbra is kikapcsolhatják azokat"
}
}

View File

@@ -169,6 +169,7 @@
"token": "Tóki",
"tuesday": "Þriðjudag",
"type": "Tegund",
"undo": "Undo",
"update": "Uppfæra",
"updated": "Uppfært",
"upload": "Hlaða upp",
@@ -332,8 +333,8 @@
"any-household": "Öll heimili",
"no-meal-plan-defined-yet": "Ekkert matarplan hefur verið skilgreint",
"no-meal-planned-for-today": "Ekkert matarplan skipulagt í dag",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-hint": "Fjöldi liðina daga við síðuhleðslu",
"numberOfDaysPast-label": "Sjálfgefnir liðnir dagar",
"numberOfDays-hint": "Fjöldi daga við síðuhleðslu",
"numberOfDays-label": "Sjálfgefnir dagar",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Aðeins uppskriftir í þessum flokkum verða notaðir í matarplan",
@@ -627,7 +628,7 @@
"create-recipe-description": "Stofna nýja uppskrift frá grunni.",
"create-recipes": "Stofna uppskriftir",
"import-with-zip": "Hlaða inn með .zip",
"create-recipe-from-an-image": "Stofna uppskrift út frá mynd",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Stofna uppskrift með því hlaða inn myndum af uppskriftartextanum. Mealie mun reyna að vinna texta úr myndunum með gervigreind og stofna nýja uppskrift út frá textanum.",
"crop-and-rotate-the-image": "Sníða og snúa mynd svo bara textinn sé sýnilegur og að myndin snúi rétt.",
"create-from-images": "Stofna uppskrift frá mynd",
@@ -639,8 +640,8 @@
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Stofnaðu uppskrift með því að gefa henni nafn, allar uppskriftir þurfa að hafa einstakt nafn.",
"new-recipe-names-must-be-unique": "Nöfn uppskrifta þurfa að vera einstök",
"scrape-recipe": "Vinna uppskrift",
"scrape-recipe-description": "Scrape a recipe by url. Provide the url for the site you want to scrape, and Mealie will attempt to scrape the recipe from that site and add it to your collection.",
"scrape-recipe-description-transcription": "You can also provide the url to a video and Mealie will attempt to transcribe it into a recipe.",
"scrape-recipe-description": "Sækja uppskrift af vefslóð. Settu inn vefslóð fyrir síðuna þar sem þú vilt sækja uppskrift og Mealie mun reyna að vinna uppskriftina þaðan og bæta henni við safnið þitt.",
"scrape-recipe-description-transcription": "Þú getur einnig sett inn slóð á video og Mealie mun reyna að umrita það yfir í uppskrift.",
"scrape-recipe-have-a-lot-of-recipes": "Ertu með margar uppskriftir sem þú villt setja inn í einu?",
"scrape-recipe-suggest-bulk-importer": "Prófaðu að setja inn margar uppskriftir í einu",
"scrape-recipe-have-raw-html-or-json-data": "Ertu með hrá HTML eða JSON gögn?",
@@ -892,17 +893,17 @@
"server-side-base-url-error-text": "'BASE_URL' er enn sjálfgefið gildi á API netþjóns. Þetta getur valdið vandræðum með tilkynninga tengla sem netþjónninn býr til fyrir tölvupósta og annað.",
"server-side-base-url-success-text": "Slóð netþjóns samsvarar ekki sjálfgefnu gildi",
"ldap-ready": "LDAP klár",
"ldap-not-ready": "LDAP Not Ready",
"ldap-not-ready": "LDAP er ekki tilbúið",
"ldap-ready-error-text": "Ekki öll LDAP-gildi eru stillt. Þetta má hunsa ef þú notar ekki LDAP-auðkenningu.",
"ldap-ready-success-text": "Öll nauðsynleg LDAP-gildi eru stillt.",
"build": "Build",
"recipe-scraper-version": "Recipe Scraper útgáfa",
"oidc-ready": "OIDC klár",
"oidc-not-ready": "OIDC Not Ready",
"oidc-not-ready": "OIDC er ekki tilbúið",
"oidc-ready-error-text": "Ekki öll OIDC gildi eru stillt. Þetta má hunsa ef þú notar ekki OIDC-auðkenningu.",
"oidc-ready-success-text": "Öll nauðsynleg OIDC-gildi eru stillt.",
"openai-ready": "OpenAI klár",
"openai-not-ready": "OpenAI Not Ready",
"openai-not-ready": "OpenAI er ekki tilbúið",
"openai-ready-error-text": "Ekki öll OpenAI gildi eru stillt. Þetta má hunsa ef þú notar ekki OpenAI.",
"openai-ready-success-text": "Öll nauðsynleg OpenAI-gildi eru stillt."
},
@@ -916,6 +917,7 @@
"quantity": "Fjöldi: {0}",
"shopping-list": "Innkaupalisti",
"shopping-lists": "Innkaupalistar",
"add-item": "Bæta við vöru",
"food": "Matvara",
"note": "Minnispunktur",
"label": "Merkimiði",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Ertu viss um að þú viljir merkja við allar matvörurnar?",
"are-you-sure-you-want-to-uncheck-all-items": "Ertu viss um að þú viljir taka merkingu af öllum matvörunum?",
"are-you-sure-you-want-to-delete-checked-items": "Ertu viss um að þú viljir eyða öllum merktum matvörum?",
"no-shopping-lists-found": "Enginn innkaupalisti fannst"
"no-shopping-lists-found": "Enginn innkaupalisti fannst",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Allar uppskriftir",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Martedì",
"type": "Tipo",
"undo": "Undo",
"update": "Aggiorna",
"updated": "Aggiornato",
"upload": "Carica",
@@ -347,9 +348,9 @@
"breakfast": "Colazione",
"lunch": "Pranzo",
"dinner": "Cena",
"snack": "Snack",
"snack": "Spuntino",
"drink": "Bevanda",
"dessert": "Dessert",
"dessert": "Dolce",
"type-any": "Qualsiasi",
"day-any": "Qualsiasi",
"editor": "Modifica",
@@ -425,7 +426,7 @@
"paprika-text": "Mealie può importare ricette dall'applicazione Paprika. Esporta le tue ricette da paprika, rinomina l'estensione di esportazione in .zip e caricala qui sotto.",
"mealie-text": "Mealie può importare ricette dall'applicazione Mealie da un versione pre v1.0. Esporta le tue ricette dalla tua vecchia istanza e carica il file zip qui sotto. Nota che solo le ricette possono essere importate dall'esportazione.",
"plantoeat": {
"title": "Plan to Eat",
"title": "Pianifico di mangiare",
"description-long": "Mealie può importare le ricette da Plan to Eat."
},
"myrecipebox": {
@@ -442,7 +443,7 @@
"error-details": "Solo i siti web contenenti ld+json o microdata possono essere importati da Mealie. Se il tuo sito non può essere importato, ma ci sono dati json nel log, per favore crea una issue su github con l'URL e i dati.",
"error-title": "Sembra che non riusciamo a trovare nulla",
"from-url": "Importa ricetta",
"github-issues": "GitHub Issues",
"github-issues": "Github Issues",
"google-ld-json-info": "Google ld+json Info",
"must-be-a-valid-url": "Deve essere un URL valido",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Inserisci i dati della tua ricetta. Ogni riga sarà trattata come uno step della ricetta.",
@@ -627,7 +628,7 @@
"create-recipe-description": "Crea una nuova ricetta da zero.",
"create-recipes": "Crea Ricette",
"import-with-zip": "Importa da .zip",
"create-recipe-from-an-image": "Crea ricetta da un'immagine",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Crea una ricetta caricando un'immagine di essa. Mealie tenterà di estrarre il testo dall'immagine usando l'IA e creare una ricetta da esso.",
"crop-and-rotate-the-image": "Ritaglia e ruota l'immagine in modo che solo il testo sia visibile e che sia orientato correttamente.",
"create-from-images": "Crea da immagini",
@@ -635,7 +636,7 @@
"please-wait-image-procesing": "Attendere, l'immagine è in fase di elaborazione. Potrebbe volerci un po' di tempo.",
"please-wait-images-processing": "Attendere, le immagini sono in fase di elaborazione. Potrebbe volerci un po' di tempo.",
"bulk-url-import": "Importazione multipla URL",
"debug-scraper": "Debug Scraper",
"debug-scraper": "Scraper Debug",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Crea una ricetta fornendo il nome. Tutte le ricette devono avere nomi univoci.",
"new-recipe-names-must-be-unique": "I nuovi nomi delle ricette devono essere univoci",
"scrape-recipe": "Recupera Ricetta",
@@ -868,7 +869,7 @@
"bug-report-information": "Usa queste informazioni per segnalare un bug. Fornire i dettagli della tua istanza agli sviluppatori è il modo migliore per risolvere rapidamente i tuoi problemi.",
"tracker": "Invia",
"configuration": "Configurazione",
"docker-volume": "Docker Volume",
"docker-volume": "Volume di Docker",
"docker-volume-help": "Mealie richiede che il frontend e il backend condividano lo stesso volume docker o archiviazione. Ciò assicura che il frontend possa accedere correttamente alle immagini e alle risorse memorizzate sul disco.",
"volumes-are-misconfigured": "I volumi sono configurati male.",
"volumes-are-configured-correctly": "I volumi sono stati configurati correttamente.",
@@ -916,6 +917,7 @@
"quantity": "Quantità: {0}",
"shopping-list": "Lista della Spesa",
"shopping-lists": "Liste della Spesa",
"add-item": "Add item",
"food": "Alimenti",
"note": "Nota",
"label": "Etichetta",
@@ -940,14 +942,15 @@
"are-you-sure-you-want-to-check-all-items": "Sei sicuro di voler tutti gli elementi?",
"are-you-sure-you-want-to-uncheck-all-items": "Sei sicuro di voler deselezionare tutti gli elementi?",
"are-you-sure-you-want-to-delete-checked-items": "Sei sicuro di voler rimuovere tutti gli articoli selezionati?",
"no-shopping-lists-found": "Nessuna lista della spesa trovata"
"no-shopping-lists-found": "Nessuna lista della spesa trovata",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Ricette",
"backups": "Backup",
"categories": "Categorie",
"cookbooks": "Ricettari",
"dashboard": "Dashboard",
"dashboard": "Pannello iniziale",
"home-page": "Pagina iniziale",
"manage-users": "Gestire Utenti",
"migrations": "Migrazioni",
@@ -959,7 +962,7 @@
"language": "Lingua",
"maintenance": "Manutenzione",
"background-tasks": "Attività in Background",
"parser": "Parser",
"parser": "Analizzatore",
"developer": "Sviluppatore",
"cookbook": "Ricettario",
"create-cookbook": "Crea un nuovo ricettario"
@@ -1017,12 +1020,12 @@
"full-name": "Nome",
"generate-password-reset-link": "Genera Link Di Reset Password",
"invite-only": "Solo su invito",
"link-id": "Link ID",
"link-id": "Link-ID",
"link-name": "Link Nome",
"login": "Login",
"login": "Accedi",
"login-oidc": "Accedi con",
"or": "oppure",
"logout": "Logout",
"logout": "Esci",
"manage-users": "Gestisci Utenti",
"manage-users-description": "Crea e gestisci gli utenti.",
"new-password": "Nuova Password",
@@ -1434,7 +1437,7 @@
"require-all-tools": "Richiedi Tutti Gli Strumenti",
"cookbook-name": "Nome Ricettario",
"cookbook-with-name": "Ricettario {0}",
"household-cookbook-name": "{0} Cookbook {1}",
"household-cookbook-name": "{0} Ricettario {1}",
"create-a-cookbook": "Crea un libro di cucina",
"cookbook": "Libro di cucina"
},
@@ -1475,10 +1478,10 @@
"max-length": "Deve Essere Al Più {max} Carattere|Deve Essere Al Più {max} Caratteri"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"announcements": "Annunci",
"all-announcements": "Tutti gli annunci",
"mark-all-as-read": "Segna tutti come letti",
"show-announcements-from-mealie": "Mostra annunci da Mealie",
"show-announcements-setting-description": "Consentire o meno agli utenti di vedere gli annunci da Mealie. Quando abilitato gli utenti potranno comunque scegliere di non vederli dalle impostazioni utente."
}
}

View File

@@ -169,6 +169,7 @@
"token": "トークン",
"tuesday": "火曜日",
"type": "タイプ",
"undo": "Undo",
"update": "更新",
"updated": "更新日時",
"upload": "アップロード",
@@ -627,7 +628,7 @@
"create-recipe-description": "新しいレシピを一から作成します。",
"create-recipes": "レシピを作成する",
"import-with-zip": ".zip でインポート",
"create-recipe-from-an-image": "画像からレシピを作成",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "画像をアップロードしてレシピを作成します。 Mealieは、AIを使用して画像からテキストを抽出し、そこからレシピを作成しようとします。",
"crop-and-rotate-the-image": "テキストのみが表示され、正しい方向になるように画像をトリミングして回転します。",
"create-from-images": "Create from Images",
@@ -916,6 +917,7 @@
"quantity": "数量: {0}",
"shopping-list": "買い物リスト",
"shopping-lists": "買い物リスト",
"add-item": "Add item",
"food": "食料",
"note": "メモ",
"label": "ラベル",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "すべての項目をチェックしてもよろしいですか?",
"are-you-sure-you-want-to-uncheck-all-items": "すべてのアイテムのチェックを外してもよろしいですか?",
"are-you-sure-you-want-to-delete-checked-items": "チェックされた項目をすべて削除してもよろしいですか?",
"no-shopping-lists-found": "ショッピングリストが見つかりません"
"no-shopping-lists-found": "ショッピングリストが見つかりません",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "すべてのレシピ",

View File

@@ -169,6 +169,7 @@
"token": "토큰",
"tuesday": "화요일",
"type": "유형",
"undo": "Undo",
"update": "업데이트",
"updated": "업데이트됨",
"upload": "업로드",
@@ -627,7 +628,7 @@
"create-recipe-description": "처음부터 새로운 레시피를 만드세요.",
"create-recipes": "레시피 생성",
"import-with-zip": ".zip 파일로 가져오기",
"create-recipe-from-an-image": "이미지에서 레시피 생성",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "레시피 텍스트 이미지를 업로드하여 레시피를 생성하세요. Mealie는 AI를 사용하여 이미지에서 텍스트를 추출하고 이를 통해 새로운 레시피를 생성하려고 시도합니다.",
"crop-and-rotate-the-image": "이미지를 잘라내고 회전시켜 텍스트만 보이도록 하고 올바른 방향으로 배치하십시오.",
"create-from-images": "이미지에서 생성",
@@ -916,6 +917,7 @@
"quantity": "양: {0}",
"shopping-list": "장보기 목록",
"shopping-lists": "장보기 목록",
"add-item": "Add item",
"food": "식품",
"note": "메모",
"label": "라벨",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "모든 항목을 선택하시겠습니까?",
"are-you-sure-you-want-to-uncheck-all-items": "모든 항목을 선택 해제하시겠습니까?",
"are-you-sure-you-want-to-delete-checked-items": "선택한 항목을 모두 삭제하시겠습니까?",
"no-shopping-lists-found": "장보기 목록이 없습니다."
"no-shopping-lists-found": "장보기 목록이 없습니다.",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "모든 레시피",

View File

@@ -169,6 +169,7 @@
"token": "Prieeigos raktas",
"tuesday": "Antradienis",
"type": "Tipas",
"undo": "Undo",
"update": "Atnaujinti",
"updated": "Atnaujinta",
"upload": "Įkelti",
@@ -627,7 +628,7 @@
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"import-with-zip": "Įkelti naudojant .zip failus",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
"create-from-images": "Kurti iš vaizdų",
@@ -916,6 +917,7 @@
"quantity": "Kiekis: {0}",
"shopping-list": "Pirkinių sąrašas",
"shopping-lists": "Pirkinių sąrašai",
"add-item": "Add item",
"food": "Maistas",
"note": "Pastaba",
"label": "Žyma",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Are you sure you want to check all items?",
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
"no-shopping-lists-found": "No Shopping Lists Found"
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Visi receptai",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Otrdiena",
"type": "Tips",
"undo": "Undo",
"update": "Atjaunināt",
"updated": "Atjaunināts",
"upload": "Augšupielādēt",
@@ -627,7 +628,7 @@
"create-recipe-description": "Izveidojiet jaunu recepti no nulles.",
"create-recipes": "Izveidojiet receptes",
"import-with-zip": "Importēt ar .zip",
"create-recipe-from-an-image": "Izveidojiet recepti no attēla",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Izveidojiet recepti, augšupielādējot tās attēlu. Mealie mēģinās iegūt tekstu no attēla, izmantojot AI, un no tā izveidot recepti.",
"crop-and-rotate-the-image": "Apgrieziet un pagrieziet attēlu tā, lai būtu redzams tikai teksts un tas būtu pareizajā orientācijā.",
"create-from-images": "Create from Images",
@@ -916,6 +917,7 @@
"quantity": "Daudzums: {0}",
"shopping-list": "Iepirkumu saraksts",
"shopping-lists": "Iepirkumu saraksti",
"add-item": "Add item",
"food": "Pārtika",
"note": "piezīme",
"label": "etiķete",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Vai esat pārliecināts, ka vēlaties pārbaudīt visus vienumus?",
"are-you-sure-you-want-to-uncheck-all-items": "Vai esat pārliecināts, ka vēlaties noņemt atzīmi no visiem vienumiem?",
"are-you-sure-you-want-to-delete-checked-items": "Vai tiešām vēlaties dzēst visus pārbaudītos vienumus?",
"no-shopping-lists-found": "No Shopping Lists Found"
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Visas receptes",

View File

@@ -51,7 +51,7 @@
"category": "Categorie"
},
"events": {
"apprise-url": "Apprise URL",
"apprise-url": "Kennisgevings-url",
"database": "Database",
"delete-event": "Gebeurtenis verwijderen",
"event-delete-confirmation": "Weet je zeker dat je deze gebeurtenis wilt verwijderen?",
@@ -98,7 +98,7 @@
"dashboard": "Dashboard",
"delete": "Verwijderen",
"disabled": "Uitgeschakeld",
"done": "Done",
"done": "Gereed",
"download": "Downloaden",
"duplicate": "Dupliceren",
"edit": "Bewerken",
@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "dinsdag",
"type": "Soort",
"undo": "Ongedaan maken",
"update": "Bijwerken",
"updated": "Bijgewerkt",
"upload": "Uploaden",
@@ -332,8 +333,8 @@
"any-household": "Elk huishouden",
"no-meal-plan-defined-yet": "Nog geen maaltijdplan opgesteld",
"no-meal-planned-for-today": "Geen maaltijd gepland voor vandaag",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-hint": "Aantal dagen in het verleden bij laden pagina",
"numberOfDaysPast-label": "Standaard dagen in het verleden",
"numberOfDays-hint": "Aantal dagen bij laden van de pagina",
"numberOfDays-label": "Standaard aantal dagen",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Alleen recepten met deze categorieën zullen worden gebruikt in maaltijdplannen",
@@ -442,7 +443,7 @@
"error-details": "Alleen websites met ld+json of microdata kunnen worden geïmporteerd door Mealie. De meeste grote receptenwebsites ondersteunen deze gegevensstructuur. Als je site niet kan worden geïmporteerd, maar er zijn json-gegevens in de log, maak dan een github issue aan met de URL en gegevens.",
"error-title": "Het lijkt erop dat we niets konden vinden",
"from-url": "Recept importeren",
"github-issues": "GitHub Issues",
"github-issues": "GitHubproblemen",
"google-ld-json-info": "Google ld+json Info",
"must-be-a-valid-url": "Moet een geldige URL zijn",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Plak je receptgegevens. Elke regel wordt behandeld als een item in een lijst",
@@ -627,7 +628,7 @@
"create-recipe-description": "Maak een nieuw recept.",
"create-recipes": "Recepten aanmaken",
"import-with-zip": "Importeer met .zip",
"create-recipe-from-an-image": "Maak recept van de tekst op een afbeelding",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Maak een recept door een afbeelding ervan te uploaden. Mealie probeert de tekst met behulp van AI uit de afbeelding te halen en er een recept uit te maken.",
"crop-and-rotate-the-image": "Snijd de afbeelding bij zodat alleen tekst zichtbaar is. En draai t plaatje zodat het leesbaar is.",
"create-from-images": "Maak recept van een afbeelding",
@@ -892,17 +893,17 @@
"server-side-base-url-error-text": "`BASE_URL` is nog steeds de standaard waarde op de API Server. Dit geeft problemen met notificatielinks in e-mails etc.",
"server-side-base-url-success-text": "Server-side URL komt niet overeen met de standaard",
"ldap-ready": "LDAP klaar",
"ldap-not-ready": "LDAP Not Ready",
"ldap-not-ready": "LDAP niet gereed",
"ldap-ready-error-text": "Niet alle LDAP-waarden zijn geconfigureerd. Dit kan worden genegeerd als je geen LDAP-authenticatie gebruikt.",
"ldap-ready-success-text": "Vereiste LDAP variabelen zijn helemaal ingesteld.",
"build": "Build",
"recipe-scraper-version": "Versie van de receptenscraper",
"oidc-ready": "OIDC klaar",
"oidc-not-ready": "OIDC Not Ready",
"oidc-not-ready": "OIDC niet gereed",
"oidc-ready-error-text": "Niet alle OIDC-waarden zijn geconfigureerd. Dit kan worden genegeerd als je geen OIDC-authenticatie gebruikt.",
"oidc-ready-success-text": "Vereiste OIDC-variabelen zijn allemaal ingesteld.",
"openai-ready": "OpenAI staat klaar",
"openai-not-ready": "OpenAI Not Ready",
"openai-not-ready": "OpenAI niet gereed",
"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."
},
@@ -916,6 +917,7 @@
"quantity": "Hoeveelheid: {0}",
"shopping-list": "Boodschappenlijst",
"shopping-lists": "Boodschappenlijsten",
"add-item": "Item toevoegen",
"food": "Levensmiddelen",
"note": "Notitie",
"label": "Label",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Weet je zeker dat je alle items wilt selecteren?",
"are-you-sure-you-want-to-uncheck-all-items": "Weet je zeker dat je alle items wilt deselecteren?",
"are-you-sure-you-want-to-delete-checked-items": "Weet je zeker dat je de geselecteerde items wilt verwijderen?",
"no-shopping-lists-found": "Geen boodschappenlijsten gevonden"
"no-shopping-lists-found": "Geen boodschappenlijsten gevonden",
"item-checked-off": "Uitgevinkt {item}"
},
"sidebar": {
"all-recipes": "Alle Recepten",
@@ -1280,7 +1283,7 @@
"split-by-block": "Splits per tekstblok",
"flatten": "Plat maken ongeacht originele opmaak",
"help": {
"help": "Help",
"help": "Hulp",
"mouse-modes": "Muismodus",
"selection-mode": "Selectiemodus (standaard)",
"selection-mode-desc": "De selectiemodus is de hoofdmodus die gebruikt kan worden om gegevens in te voeren:",
@@ -1475,10 +1478,10 @@
"max-length": "Moet maximaal {max} tekens bevatten|Moet maximaal {max} tekens bevatten"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"announcements": "Aankondigingen",
"all-announcements": "Alle aankondigingen",
"mark-all-as-read": "Alles markeren als gelezen",
"show-announcements-from-mealie": "Aankondigingen van Mealie weergeven",
"show-announcements-setting-description": "Of je gebruikers wel of niet meldingen van Mealie wilt laten zien. Wanneer ingeschakeld kunnen gebruikers nog steeds afzien van het bekijken van hen in hun gebruikersinstellingen"
}
}

View File

@@ -98,7 +98,7 @@
"dashboard": "Kontrollpanel",
"delete": "Slett",
"disabled": "Deaktivert",
"done": "Done",
"done": "Ferdig",
"download": "Last ned",
"duplicate": "Dupliser",
"edit": "Rediger",
@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Tirsdag",
"type": "Type",
"undo": "Angre",
"update": "Oppdater",
"updated": "Oppdatert",
"upload": "Last opp",
@@ -333,7 +334,7 @@
"no-meal-plan-defined-yet": "Ingen måltidsplan er definert ennå",
"no-meal-planned-for-today": "Ingen måltid planlagt i dag",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-label": "Standard antall dager tilbake",
"numberOfDays-hint": "Antall dager på sideinnlasting",
"numberOfDays-label": "Standard antall dager",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Kun oppskrifter med disse kategoriene vil bli brukt i måltidsplaner",
@@ -391,7 +392,7 @@
"nextcloud": {
"description": "Overfør data fra en Nextcloud Cookbook-instans",
"description-long": "Oppskrifter fra Nextcloud kan importeres fra en zip-fil som inneholder dataene lagret i Nextcloud. Se eksempelet på mappestrukture nedenfor for å sikre at oppskriftene kan importeres.",
"title": "Nextcloud Cookbook"
"title": "Nextcloud kokebok"
},
"copymethat": {
"description-long": "Mealie kan importere oppskrifter fra Copy Me That. Eksporter oppskrifter i HTML-format, last deretter opp .zip-filen under.",
@@ -627,7 +628,7 @@
"create-recipe-description": "Opprett en ny oppskrift fra bunnen av.",
"create-recipes": "Opprett oppskrifter",
"import-with-zip": "Importer fra .zip-fil",
"create-recipe-from-an-image": "Opprett oppskrift fra et bilde",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Opprett en oppskrift ved å laste opp et bilde av den. Mealie vil forsøke å hente ut teksten fra bildet ved bruk av AI, og lage en ny oppskrift.",
"crop-and-rotate-the-image": "Beskjær og roter bildet slik at bare teksten er synlig, og at det er i riktig retning.",
"create-from-images": "Opprett fra bilde",
@@ -916,6 +917,7 @@
"quantity": "Antall: {0}",
"shopping-list": "Handleliste",
"shopping-lists": "Handlelister",
"add-item": "Legg til produkt",
"food": "Matvare",
"note": "Notat",
"label": "Etikett",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Er du sikker på at du vil velge alle elementer?",
"are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på at du vil fjerne valg av alle elementer?",
"are-you-sure-you-want-to-delete-checked-items": "Er du sikker på at du vil slette alle valgte elementer?",
"no-shopping-lists-found": "Ingen handlelister funnet"
"no-shopping-lists-found": "Ingen handlelister funnet",
"item-checked-off": "Avkrysset av {item}"
},
"sidebar": {
"all-recipes": "Alle oppskrifter",
@@ -1475,10 +1478,10 @@
"max-length": "Må være minst minst {max} tegn må bestå av maks {max} tegn"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"announcements": "Kunngjøringer",
"all-announcements": "Alle kunngjøringer",
"mark-all-as-read": "Marker alle som lest",
"show-announcements-from-mealie": "Vis kunngjøringer fra Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
}
}

View File

@@ -51,7 +51,7 @@
"category": "Kategoria"
},
"events": {
"apprise-url": "Apprise URL",
"apprise-url": "URL Apprise",
"database": "Baza danych",
"delete-event": "Usuń wydarzenie",
"event-delete-confirmation": "Czy na pewno chcesz usunąć to zdarzenie?",
@@ -98,7 +98,7 @@
"dashboard": "Panel główny",
"delete": "Usuń",
"disabled": "Wyłączone",
"done": "Done",
"done": "Gotowe",
"download": "Pobierz",
"duplicate": "Duplikuj",
"edit": "Edytuj",
@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Wtorek",
"type": "Typ",
"undo": "Cofnij",
"update": "Zaktualizuj",
"updated": "Zaktualizowano",
"upload": "Prześlij",
@@ -627,7 +628,7 @@
"create-recipe-description": "Utwórz nowy przepis od zera.",
"create-recipes": "Utwórz przepisy",
"import-with-zip": "Importuj z pliku .zip",
"create-recipe-from-an-image": "Utwórz przepis z obrazów",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Utwórz przepis poprzez przesłanie obrazów tekstu przepisu. Mealie spróbuje wyodrębnić tekst z obrazów za pomocą AI i utworzyć z niego przepis.",
"crop-and-rotate-the-image": "Przytnij i obróć obraz, tak aby był w odpowiedniej orientacji i był widoczny tylko tekst.",
"create-from-images": "Utwórz przepis z obrazów",
@@ -916,6 +917,7 @@
"quantity": "Ilość: {0}",
"shopping-list": "Lista zakupów",
"shopping-lists": "Listy zakupów",
"add-item": "Dodaj element",
"food": "Jedzenie",
"note": "Notatka",
"label": "Etykieta",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Czy na pewno chcesz zaznaczyć wszystkie elementy?",
"are-you-sure-you-want-to-uncheck-all-items": "Czy na pewno chcesz odznaczyć wszystkie elementy?",
"are-you-sure-you-want-to-delete-checked-items": "Czy jesteś pewien, że chcesz usunąć wszystkie zaznaczone elementy?",
"no-shopping-lists-found": "Nie znaleziono list zakupów"
"no-shopping-lists-found": "Nie znaleziono list zakupów",
"item-checked-off": "Zaznaczono {item}"
},
"sidebar": {
"all-recipes": "Wszystkie",
@@ -1475,10 +1478,10 @@
"max-length": "Może zawierać co najwyżej {max} znak|Może zawierać co najwyżej {max} znaki|Może zawierać co najwyżej {max} znaków"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"announcements": "Ogłoszenia",
"all-announcements": "Wszystkie ogłoszenia",
"mark-all-as-read": "Oznacz wszystkie jako przeczytane",
"show-announcements-from-mealie": "Pokazuj ogłoszenia z Mealie",
"show-announcements-setting-description": "Czy chcesz by użytkownicy widzieli ogłoszenia z Mealie? Użytkownicy będą w dalszym ciągu mogli wyłączyć ogłoszenia w swoich ustawieniach użytkownika"
}
}

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Terça-feira",
"type": "Tipo",
"undo": "Undo",
"update": "Atualizar",
"updated": "Atualizado",
"upload": "Enviar",
@@ -627,7 +628,7 @@
"create-recipe-description": "Criar uma receita do zero.",
"create-recipes": "Criar Receitas",
"import-with-zip": "Importar a partir de .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Corte e gire a imagem para que apenas o texto esteja visível e esteja na posição correta.",
"create-from-images": "Criar a partir de imagens",
@@ -916,6 +917,7 @@
"quantity": "Quantidade: {0}",
"shopping-list": "Lista de compras",
"shopping-lists": "Listas de compras",
"add-item": "Add item",
"food": "Comida",
"note": "Nota",
"label": "Marcador",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Tem certeza que deseja marcar todos os itens?",
"are-you-sure-you-want-to-uncheck-all-items": "Tem certeza que deseja desmarcar todos os itens?",
"are-you-sure-you-want-to-delete-checked-items": "Tem certeza que deseja apagar todos os itens marcados?",
"no-shopping-lists-found": "Nenhuma lista de compras encontrada"
"no-shopping-lists-found": "Nenhuma lista de compras encontrada",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Todas as Receitas",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Terça-feira",
"type": "Tipo",
"undo": "Undo",
"update": "Atualizar",
"updated": "Atualizado",
"upload": "Enviar",
@@ -627,7 +628,7 @@
"create-recipe-description": "Criar uma receita em branco.",
"create-recipes": "Criar Receitas",
"import-with-zip": "Importar com .zip",
"create-recipe-from-an-image": "Criar receita a partir de uma imagem",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Crie uma receita carregando uma imagem da mesma. O Mealie tentará extrair o texto da imagem utilizando IA e criará uma receita a partir da mesma.",
"crop-and-rotate-the-image": "Recorte e rode a imagem de modo a que apenas o texto seja visível e esteja na orientação correta.",
"create-from-images": "Criar a partir de Imagens",
@@ -916,6 +917,7 @@
"quantity": "Quantidade: {0}",
"shopping-list": "Lista de Compras",
"shopping-lists": "Listas de Compras",
"add-item": "Add item",
"food": "Alimentos",
"note": "Nota",
"label": "Rótulo",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Tem a certeza de que pretende selecionar todos os itens?",
"are-you-sure-you-want-to-uncheck-all-items": "Tem a certeza de que pretende desmarcar todos os itens?",
"are-you-sure-you-want-to-delete-checked-items": "Tem a certeza de que pretende eliminar todos os itens selecionados?",
"no-shopping-lists-found": "Nenhuma lista de compras encontrada"
"no-shopping-lists-found": "Nenhuma lista de compras encontrada",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Todas as Receitas",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Marţi",
"type": "Tip",
"undo": "Undo",
"update": "Actualizează",
"updated": "Actualizat",
"upload": "Încarcă",
@@ -627,7 +628,7 @@
"create-recipe-description": "Creează o rețetă nouă de la zero.",
"create-recipes": "Crează rețetă",
"import-with-zip": "Importă cu .zip",
"create-recipe-from-an-image": "Creează o rețetă dintr-o imagine",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Creează o rețetă prin încărcarea unei imagini a acesteia. Mealie va încerca să extragă textul din imagine folosind AI și să creeze o rețetă din el.",
"crop-and-rotate-the-image": "Decupați și rotiți imaginea astfel încât numai textul să fie vizibil, iar orientarea să fie corectă.",
"create-from-images": "Creează din Imagini",
@@ -916,6 +917,7 @@
"quantity": "Cantitate: {0}",
"shopping-list": "Listă de cumpărături",
"shopping-lists": "Liste de cumpărături",
"add-item": "Add item",
"food": "Aliment",
"note": "Note",
"label": "Etichetă",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Sunteți sigur că doriți să bifați toate elementele?",
"are-you-sure-you-want-to-uncheck-all-items": "Sunteți sigur că doriți să debifați toate elementele?",
"are-you-sure-you-want-to-delete-checked-items": "Sunteți sigur că doriți să ștergeți toate elementele selectate?",
"no-shopping-lists-found": "Nu s-au găsit liste de cumpărături"
"no-shopping-lists-found": "Nu s-au găsit liste de cumpărături",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Toate reţetele",

View File

@@ -169,6 +169,7 @@
"token": "Токен",
"tuesday": "Вторник",
"type": "Тип",
"undo": "Undo",
"update": "Обновить",
"updated": "Обновлено",
"upload": "Загрузить",
@@ -627,7 +628,7 @@
"create-recipe-description": "Создать новый рецепт с нуля.",
"create-recipes": "Создать Рецепт",
"import-with-zip": "Импорт из .zip",
"create-recipe-from-an-image": "Создать рецепт из изображения",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Создайте рецепт, загрузив изображение. Mealie попытается извлечь текст из изображения с помощью ИИ и создать рецепт из него.",
"crop-and-rotate-the-image": "Обрежьте и поверните изображение так, чтобы виден был только текст в нужной ориентации.",
"create-from-images": "Создать из изображений",
@@ -916,6 +917,7 @@
"quantity": "Количество: {0}",
"shopping-list": "Список покупок",
"shopping-lists": "Списки покупок",
"add-item": "Add item",
"food": "Продукты",
"note": "Заметка",
"label": "Метка",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Вы уверены, что хотите выбрать все элементы?",
"are-you-sure-you-want-to-uncheck-all-items": "Вы уверены, что хотите снять отметку со всех элементов?",
"are-you-sure-you-want-to-delete-checked-items": "Вы уверены, что хотите удалить все отмеченные элементы?",
"no-shopping-lists-found": "Списки покупок не найдены"
"no-shopping-lists-found": "Списки покупок не найдены",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Все рецепты",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Utorok",
"type": "Typ",
"undo": "Undo",
"update": "Aktualizovať",
"updated": "Aktualizované",
"upload": "Nahrať",
@@ -627,7 +628,7 @@
"create-recipe-description": "Vytvoriť nový recept od začiatku.",
"create-recipes": "Vytvoriť recept",
"import-with-zip": "Importovať .zip súbor",
"create-recipe-from-an-image": "Vytvoriť recept z obrázka",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Vytvoriť recept nahraním fotografie jedla. Mealie sa pokúsi previesť obrázok na text pomocou AI a vytvorí k nemu recept.",
"crop-and-rotate-the-image": "Orežte a otočte obrázok tak, aby bol viditeľný iba text a aby mal správnu orientáciu.",
"create-from-images": "Vytvoriť z obrázka",
@@ -916,6 +917,7 @@
"quantity": "Množstvo: {0}",
"shopping-list": "Nákupný zoznam",
"shopping-lists": "Nákupné zoznamy",
"add-item": "Add item",
"food": "Jedlo",
"note": "Poznámka",
"label": "Štítok",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Naozaj chcete označiť všetky položky?",
"are-you-sure-you-want-to-uncheck-all-items": "Naozaj chcete zrušiť označenie všetkých položiek?",
"are-you-sure-you-want-to-delete-checked-items": "Naozaj chcete odstrániť všetky označené položky?",
"no-shopping-lists-found": "Nenašli sa žiadne nákupné zoznamy"
"no-shopping-lists-found": "Nenašli sa žiadne nákupné zoznamy",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Všetky recepty",

View File

@@ -169,6 +169,7 @@
"token": "Žeton",
"tuesday": "Torek",
"type": "Tip",
"undo": "Razveljavi",
"update": "Posodobi",
"updated": "Posodobljen",
"upload": "Naloži",
@@ -627,7 +628,7 @@
"create-recipe-description": "Ustvari nov recept.",
"create-recipes": "Ustvari recepte",
"import-with-zip": "Uvozi z .zip",
"create-recipe-from-an-image": "Ustvari recept iz slike",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Ustvarite recept tako, da naložite njegovo sliko. Mealie bo s pomočjo umetne inteligence poskušal izluščiti besedilo iz slike in iz njega ustvariti recept.",
"crop-and-rotate-the-image": "Obrežite in zasukajte sliko, tako da bo vidno samo besedilo in da bo v pravilnem položaju.",
"create-from-images": "Ustvari iz slik",
@@ -916,6 +917,7 @@
"quantity": "Količina: {0}",
"shopping-list": "Nakupovalni seznam",
"shopping-lists": "Nakupovalni seznami",
"add-item": "Dodaj element",
"food": "Živilo",
"note": "Opomba",
"label": "Oznaka",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Ali res želite izbrati vse elemente?",
"are-you-sure-you-want-to-uncheck-all-items": "Ali res ne želite izbrati vseh elementov?",
"are-you-sure-you-want-to-delete-checked-items": "Ali ste prepričani, da želite izbrisati vse izbrane elemente?",
"no-shopping-lists-found": "Ni nakupovalnih seznamov"
"no-shopping-lists-found": "Ni nakupovalnih seznamov",
"item-checked-off": "Odkljukano {item}"
},
"sidebar": {
"all-recipes": "Vsi recepti",

View File

@@ -169,6 +169,7 @@
"token": "Токен",
"tuesday": "уторак",
"type": "Тип",
"undo": "Undo",
"update": "Ажурирај",
"updated": "Ажурирано",
"upload": "Отпреми",
@@ -425,15 +426,15 @@
"paprika-text": "Mealie може да увезе рецепте из апликације Paprika. Извезите ваше рецепте из Paprika апликације, промените екстензију извоза у .zip и отпремите је испод.",
"mealie-text": "Mealie може да увезе рецепте из Mealie апликације пре верзије 1.0. Извезите ваше рецепте са ваше старе инстанце и отпремите zip датотеку испод. Напомена: само рецепти се могу увести из овог извоза.",
"plantoeat": {
"title": "Plan to Eat",
"title": "Планирајте јело",
"description-long": "Mealie може да увезе рецепте из Plan to Eat."
},
"myrecipebox": {
"title": "My Recipe Box",
"title": "Моја кутија за рецепте",
"description-long": "Mealie може да увезе рецепте из My Recipe Box. Извезите ваше рецепте у CSV формату, затим отпремите .csv датотеку испод."
},
"recipekeeper": {
"title": "Recipe Keeper",
"title": "Чувар рецепата",
"description-long": "Mealie може да увезе рецепте из Recipe Keeper-а. Извезите ваше рецепте у zip формату, затим отпремите .zip датотеку испод."
}
},
@@ -627,7 +628,7 @@
"create-recipe-description": "Креирајте нови рецепт од нуле.",
"create-recipes": "Направите рецепте",
"import-with-zip": "Увези помоћу .zip архиве",
"create-recipe-from-an-image": "Направи рецепт на основи слике",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Креирајте рецепт отпремањем његове слике. Mealie ће покушати да издвоји текст из слике користећи АИ и од њега направи рецепт.",
"crop-and-rotate-the-image": "Исеците и ротирајте слику тако да је видљив само текст и да је у исправној оријентацији.",
"create-from-images": "Креирај из слика",
@@ -916,6 +917,7 @@
"quantity": "Количина: {0}",
"shopping-list": "Списак за куповину",
"shopping-lists": "Спискови за куповину",
"add-item": "Додај ставку",
"food": "Храна",
"note": "Белешка",
"label": "Натпис",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Да ли сте сигурни да желите да означите све ставке?",
"are-you-sure-you-want-to-uncheck-all-items": "Да ли сте сигурни да желите да одзначите све ставке?",
"are-you-sure-you-want-to-delete-checked-items": "Да ли сте сигурни да желите да обришете све означене ставке?",
"no-shopping-lists-found": "Нису пронађени спискови за куповину"
"no-shopping-lists-found": "Нису пронађени спискови за куповину",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Сви рецепти",
@@ -1476,9 +1479,9 @@
},
"announcements": {
"announcements": "Најава",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"all-announcements": "Најава",
"mark-all-as-read": "Означи све као прочитано",
"show-announcements-from-mealie": "Најава",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"show-announcements-setting-description": "Да ли желите да дозволите корисницима да виде обавештења од Mealie-а. Када је омогућено, корисници и даље могу да се искључе из приказивања обавештења у својим корисничким подешавањима."
}
}

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Tisdag",
"type": "Typ",
"undo": "Ångra",
"update": "Uppdatera",
"updated": "Uppdaterad",
"upload": "Ladda upp",
@@ -332,8 +333,8 @@
"any-household": "Valfritt hushåll",
"no-meal-plan-defined-yet": "Ingen måltidsplan definierad ännu",
"no-meal-planned-for-today": "Ingen måltidsplan för idag",
"numberOfDaysPast-hint": "Number of days in the past on page load",
"numberOfDaysPast-label": "Default Days in the Past",
"numberOfDaysPast-hint": "Antal förflutna dagar vid sidhämtning",
"numberOfDaysPast-label": "Förvalda förflutna dagar",
"numberOfDays-hint": "Antal dagar vid sidhämtning",
"numberOfDays-label": "Förvalda dagar",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Endast recept med dessa kategorier kommer att användas i måltidsplaner",
@@ -627,7 +628,7 @@
"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",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Skapa ett recept genom att ladda upp en bild på det. Mealie kommer att försöka extrahera texten från bilden med hjälp av AI och skapa ett recept från det.",
"crop-and-rotate-the-image": "Beskär och rotera bilden så att endast texten är synlig och den är åt rätt håll.",
"create-from-images": "Skapa från bild",
@@ -811,7 +812,7 @@
"settings-updated": "Inställningar uppdaterade",
"site-settings": "Systeminställningar",
"theme": {
"accent": "Accent",
"accent": "Accentfärg",
"dark": "Mörkt",
"default-to-system": "Standard",
"error": "Fel",
@@ -892,17 +893,17 @@
"server-side-base-url-error-text": "`BASE_URL` är fortfarande standardvärdet på API-servern. Detta kommer att orsaka problem med meddelanden som genereras på servern för e-postmeddelanden, etc.",
"server-side-base-url-success-text": "Serversidans URL matchar inte standard",
"ldap-ready": "LDAP Redo",
"ldap-not-ready": "LDAP Not Ready",
"ldap-not-ready": "LDAP ej tillgängligt",
"ldap-ready-error-text": "Alla LDAP-värden är inte konfigurerade. Detta kan ignoreras om du inte använder LDAP-autentisering.",
"ldap-ready-success-text": "Alla obligatoriska LDAP-variabler är satta.",
"build": "Bygge",
"recipe-scraper-version": "Version av Recept-scraper",
"oidc-ready": "OIDC Klar",
"oidc-not-ready": "OIDC Not Ready",
"oidc-not-ready": "OIDC ej tillgängligt",
"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 redo",
"openai-not-ready": "OpenAI Not Ready",
"openai-not-ready": "OpenAI ej tillgängligt",
"openai-ready-error-text": "Alla OpenAI-värden är inte konfigurerade. Detta kan ignoreras om du inte använder OpenAI-funktioner.",
"openai-ready-success-text": "Alla obligatoriska OpenAI-variabler är satta."
},
@@ -916,6 +917,7 @@
"quantity": "Antal {0}",
"shopping-list": "Inköpslista",
"shopping-lists": "Inköpslistor",
"add-item": "Lägg till vara",
"food": "Mat",
"note": "Anteckning",
"label": "Etikett",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Är du säker på att du vill markera alla objekt?",
"are-you-sure-you-want-to-uncheck-all-items": "Är du säker på att du vill avmarkera alla objekt?",
"are-you-sure-you-want-to-delete-checked-items": "Är du säker på att du vill ta bort alla markerade objekt?",
"no-shopping-lists-found": "Inga inköpslistor hittades"
"no-shopping-lists-found": "Inga inköpslistor hittades",
"item-checked-off": "Kryssat av {item}"
},
"sidebar": {
"all-recipes": "Recept",
@@ -1475,10 +1478,10 @@
"max-length": "Måste Vara Som Mest {max} Tecken|Måste Vara Som Mest {max} Tecken"
},
"announcements": {
"announcements": "Announcements",
"all-announcements": "All announcements",
"mark-all-as-read": "Mark All as Read",
"show-announcements-from-mealie": "Show announcements from Mealie",
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
"announcements": "Meddelanden",
"all-announcements": "Alla meddelanden",
"mark-all-as-read": "Markera alla som lästa",
"show-announcements-from-mealie": "Visa meddelanden från Mealie",
"show-announcements-setting-description": "Om du vill tillåta användare att se meddelanden från Mealie eller inte. När funktionen är aktiverad kan användarna fortfarande välja att inte se dem i sina användarinställningar"
}
}

View File

@@ -169,6 +169,7 @@
"token": "Anahtar",
"tuesday": "Salı",
"type": "Tür",
"undo": "Undo",
"update": "Güncelle",
"updated": "Güncellendi",
"upload": "Yükle",
@@ -627,7 +628,7 @@
"create-recipe-description": "Sıfırdan yeni bir tarif oluşturun.",
"create-recipes": "Tarif Oluştur",
"import-with-zip": ".zip ile içe aktar",
"create-recipe-from-an-image": "Görüntüden yemek tarifi oluştur",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Bir görsel yükleyerek yemek tarifi oluşturun. Mealie, yapay zekâ kullanarak görseldeki bilgileri çekip yemek tarifini oluşturmaya çalışacaktır.",
"crop-and-rotate-the-image": "Resmi sadece yazılar gözükecek şekilde kesin ve döndürün, ayrıca doğru yönde durduğuna emin olun.",
"create-from-images": "Resimden Oluştur",
@@ -916,6 +917,7 @@
"quantity": "Miktar: {0}",
"shopping-list": "Alışveriş Listesi",
"shopping-lists": "Alışveriş Listeleri",
"add-item": "Add item",
"food": "Gıda",
"note": "Not",
"label": "Etiket",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Tüm öğeleri işaretlemek istediğinizden emin misiniz?",
"are-you-sure-you-want-to-uncheck-all-items": "Tüm öğelerden işaretleri kaldırmak istediğinize emin misiniz?",
"are-you-sure-you-want-to-delete-checked-items": "İşaretlenmiş tüm öğeleri silmek istediğinizden emin misiniz?",
"no-shopping-lists-found": "Alışveriş Listesi Bulunamadı"
"no-shopping-lists-found": "Alışveriş Listesi Bulunamadı",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Tüm Tarifler",

View File

@@ -169,6 +169,7 @@
"token": "Токен",
"tuesday": "Вівторок",
"type": "Тип",
"undo": "Undo",
"update": "Оновити",
"updated": "Дата оновлення",
"upload": "Завантажити",
@@ -627,7 +628,7 @@
"create-recipe-description": "Створити новий рецепт з нуля.",
"create-recipes": "Створити рецепти",
"import-with-zip": "Імпорт з .zip",
"create-recipe-from-an-image": "Створити рецепт з зображення",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Створити рецепт, завантаживши його зображення. Mealie спробує витягти текст з зображення за допомогою ШІ та створити рецепт з нього.",
"crop-and-rotate-the-image": "Обріжте і поверніть зображення так, щоб було видно лише текст в правильній орієнтації.",
"create-from-images": "Створити з зображень",
@@ -910,12 +911,13 @@
"all-lists": "Всі списки",
"create-shopping-list": "Сторити список покупок",
"from-recipe": "З рецепту",
"ingredient-of-recipe": "Ingredient of {recipe}",
"ingredient-of-recipe": "Інгредієнт з {recipe}",
"list-name": "Назва списку",
"new-list": "Новий список",
"quantity": "Кількість: {0}",
"shopping-list": "Список покупок",
"shopping-lists": "Списки покупок",
"add-item": "Add item",
"food": "Продукт",
"note": "Нотатка",
"label": "Мітка",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Ви впевнені, що хочете відмітити всі елементи?",
"are-you-sure-you-want-to-uncheck-all-items": "Ви впевнені, що хочете зняти відмітку з усіх елементів?",
"are-you-sure-you-want-to-delete-checked-items": "Ви впевнені, що хочете видалити всі відмічені елементи?",
"no-shopping-lists-found": "Списків покупок не знайдено"
"no-shopping-lists-found": "Списків покупок не знайдено",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "Всі рецепти",

View File

@@ -169,6 +169,7 @@
"token": "Token",
"tuesday": "Thứ 3",
"type": "Loại",
"undo": "Undo",
"update": "Cập nhật",
"updated": "Đã cập nhật",
"upload": "Tải lên",
@@ -627,7 +628,7 @@
"create-recipe-description": "Create a new recipe from scratch.",
"create-recipes": "Create Recipes",
"import-with-zip": "Import with .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
"create-from-images": "Create from Images",
@@ -916,6 +917,7 @@
"quantity": "Quantity: {0}",
"shopping-list": "Shopping List",
"shopping-lists": "Shopping Lists",
"add-item": "Add item",
"food": "Food",
"note": "Note",
"label": "Label",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "Are you sure you want to check all items?",
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
"no-shopping-lists-found": "No Shopping Lists Found"
"no-shopping-lists-found": "No Shopping Lists Found",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "All Recipes",

View File

@@ -169,6 +169,7 @@
"token": "密钥",
"tuesday": "周二",
"type": "类型",
"undo": "Undo",
"update": "更新",
"updated": "已更新",
"upload": "上传",
@@ -627,7 +628,7 @@
"create-recipe-description": "从头创建一个新食谱。",
"create-recipes": "创建食谱",
"import-with-zip": "使用 .zip 导入",
"create-recipe-from-an-image": "用图片创建食谱",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "通过上传食谱文本的图片来创建一个食谱。Mealie将尝试使用人工智能从图像中提取文本并从中创建一个新的食谱。",
"crop-and-rotate-the-image": "裁剪并旋转图片,使仅文字可见且方向正确。",
"create-from-images": "从图片创建",
@@ -916,6 +917,7 @@
"quantity": "数量: {0} 个",
"shopping-list": "购物清单",
"shopping-lists": "购物清单",
"add-item": "Add item",
"food": "食品",
"note": "备注",
"label": "标注",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "您确定要选择所有项目吗?",
"are-you-sure-you-want-to-uncheck-all-items": "您确定要取消选中所有项目吗?",
"are-you-sure-you-want-to-delete-checked-items": "您确定要删除所有选中的项目吗?",
"no-shopping-lists-found": "没有发现购物清单"
"no-shopping-lists-found": "没有发现购物清单",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "全部食谱",

View File

@@ -169,6 +169,7 @@
"token": "金鑰",
"tuesday": "星期二",
"type": "類型",
"undo": "Undo",
"update": "更新",
"updated": "已更新",
"upload": "上傳",
@@ -627,7 +628,7 @@
"create-recipe-description": "從頭開始建立新食譜。",
"create-recipes": "建立食譜",
"import-with-zip": "以 .zip 匯入",
"create-recipe-from-an-image": "從圖片建立食譜",
"create-recipe-from-images": "Create Recipe from Images",
"create-recipe-from-an-image-description": "上傳食譜圖片來建立食譜Mealie 將嘗試使用 AI 從圖片中擷取文字並建立食譜。",
"crop-and-rotate-the-image": "請裁切並旋轉圖片,使其僅顯示文字且方向正確。",
"create-from-images": "從圖片建立",
@@ -916,6 +917,7 @@
"quantity": "數量:{0}",
"shopping-list": "購物清單",
"shopping-lists": "購物清單",
"add-item": "Add item",
"food": "食材",
"note": "備註",
"label": "標籤",
@@ -940,7 +942,8 @@
"are-you-sure-you-want-to-check-all-items": "確定要勾選所有項目嗎?",
"are-you-sure-you-want-to-uncheck-all-items": "確定要取消勾選所有項目嗎?",
"are-you-sure-you-want-to-delete-checked-items": "確定要刪除所有已勾選的項目嗎?",
"no-shopping-lists-found": "找不到購物清單"
"no-shopping-lists-found": "找不到購物清單",
"item-checked-off": "Checked off {item}"
},
"sidebar": {
"all-recipes": "所有食譜",

View File

@@ -3,8 +3,11 @@ export default defineNuxtRouteMiddleware((to) => {
const { user } = useMealieAuth();
const groupSlug = user.value?.groupSlug;
if (!groupSlug) {
return navigateTo("/login", { redirectCode: 301 });
// Preserve the full path (including recipe_import_url query param) so the
// login page can redirect back here after successful authentication.
const redirect = encodeURIComponent(to.fullPath);
return navigateTo(`/login?redirect=${redirect}`, { redirectCode: 302 });
}
return navigateTo(`/g/${groupSlug}${to.fullPath}`, { redirectCode: 301 });
return navigateTo(`/g/${groupSlug}${to.fullPath}`, { redirectCode: 302 });
}
});

View File

@@ -14,7 +14,7 @@
</v-card-title>
<BaseDivider />
<v-card-text>
<v-form @submit.prevent="requestLink()">
<v-form ref="form" @submit.prevent="requestLink()">
<v-text-field
v-model="state.email"
:prepend-inner-icon="$globals.icons.email"
@@ -24,6 +24,7 @@
name="login"
:label="$t('user.email')"
type="text"
:rules="[validators.email]"
/>
<p class="text-center">
{{ $t('user.forgot-password-text') }}
@@ -63,11 +64,15 @@
<script setup lang="ts">
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { validators } from "~/composables/use-validators";
import type { VForm } from "~/types/auto-forms";
definePageMeta({
layout: "basic",
});
const form = ref<VForm | null>(null);
const state = reactive({
email: "",
loading: false,
@@ -84,17 +89,27 @@ useSeoMeta({
const api = useUserApi();
async function requestLink() {
if (!form.value) {
return;
};
const { valid } = await form.value.validate();
if (!valid) {
return;
};
state.loading = true;
// TODO: Fix Response to send meaningful error
const { response } = await api.email.sendForgotPassword({ email: state.email });
state.loading = false;
if (response?.status === 200) {
state.loading = false;
state.error = false;
alert.success(i18n.t("profile.email-sent"));
await navigateTo("/login");
}
else {
state.loading = false;
state.error = true;
alert.error(i18n.t("profile.error-sending-email"));
}

View File

@@ -3,7 +3,7 @@
<v-form ref="domUrlForm" @submit.prevent="createRecipe">
<div>
<v-card-title class="headline">
{{ $t("recipe.create-recipe-from-an-image") }}
{{ $t("recipe.create-recipe-from-images") }}
</v-card-title>
<v-card-text>
<p>{{ $t("recipe.create-recipe-from-an-image-description") }}</p>

View File

@@ -198,15 +198,32 @@ const recipeUrl = computed({
}
},
get() {
return route.query.recipe_import_url as string | null;
// Prefer the 'url' share field (recipe_import_url, populated by Chrome when
// sharing a page URL). Fall back to the 'text' share field (recipe_import_text)
// for apps that share URLs as plain text, but only when the text value is
// actually a valid http/https URL — shared text can be arbitrary.
const urlFromField = route.query.recipe_import_url as string | null;
if (urlFromField) {
return urlFromField;
}
const textFromField = route.query.recipe_import_text as string | null;
if (textFromField) {
try {
const parsed = new URL(textFromField);
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
return textFromField;
}
}
catch { /* not a URL, ignore */ }
}
return null;
},
});
onMounted(() => {
if (recipeUrl.value && recipeUrl.value.includes("https")) {
// Check if we have a query params for using keywords as tags or staying in edit mode.
// We don't use these in the app anymore, but older automations such as Bookmarklet might still use them,
// and they're easy enough to support.
if (recipeUrl.value) {
// Apply legacy query params for older automations such as the Bookmarklet.
// These are no longer used by the app itself but are easy to keep supporting.
const importKeywordsAsTagsParam = route.query.use_keywords;
if (importKeywordsAsTagsParam === "1") {
importKeywordsAsTags.value = true;
@@ -223,8 +240,9 @@ onMounted(() => {
stayInEditMode.value = false;
}
createByUrl(recipeUrl.value, importKeywordsAsTags.value, false);
return;
// The URL is pre-filled via the recipeUrl computed property.
// Do not auto-submit: the user should review the import options and
// confirm by clicking the submit button.
}
});

View File

@@ -211,7 +211,7 @@
</template>
<script setup lang="ts">
import { useDark, whenever } from "@vueuse/core";
import { useDark, useSessionStorage, whenever } from "@vueuse/core";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { usePasswordField } from "~/composables/use-passwords";
import { alert } from "~/composables/use-toast";
@@ -225,6 +225,7 @@ definePageMeta({
const isDark = useDark();
const router = useRouter();
const route = useRoute();
const i18n = useI18n();
const auth = useMealieAuth();
const { $appInfo, $axios } = useNuxtApp();
@@ -235,6 +236,9 @@ const isFirstLogin = ref(false);
const activityPreferences = useUserActivityPreferences();
const { getDefaultActivityRoute } = useDefaultActivity();
// Survives the page reload that happens during OIDC redirect
const pendingShareRedirect = useSessionStorage<string | null>("pwa_share_redirect", null);
useSeoMeta({
title: i18n.t("user.login"),
});
@@ -259,14 +263,29 @@ useAsyncData(useAsyncKey(), async () => {
whenever(
() => loggedIn.value && groupSlug.value,
() => {
// First-login setup always takes priority
if (!isDemo.value && isFirstLogin.value && auth.user.value?.admin) {
router.push("/admin/setup");
return;
}
// After login, honour a pending PWA share redirect.
// The redirect param can arrive via the URL query string (password login)
// or via sessionStorage (OIDC login, where the OIDC provider reload clears
// the query string).
const redirectFromQuery = route.query.redirect as string | undefined;
const redirectTarget = redirectFromQuery ?? pendingShareRedirect.value;
if (redirectTarget && redirectTarget.startsWith("/")) {
pendingShareRedirect.value = null;
router.push(redirectTarget);
return;
}
const defaultActivityRoute = getDefaultActivityRoute(
activityPreferences.value.defaultActivity,
groupSlug.value,
);
if (!isDemo.value && isFirstLogin.value && auth.user.value?.admin) {
router.push("/admin/setup");
}
else if (defaultActivityRoute) {
if (defaultActivityRoute) {
router.push(defaultActivityRoute);
}
else {
@@ -316,6 +335,13 @@ async function oidcAuthenticate(callback = false) {
oidcLoggingIn.value = false;
}
else {
// Save any pending PWA share redirect before leaving for the OIDC provider.
// The OIDC callback reloads the page, which clears the query string, so we
// persist the target in sessionStorage and restore it after login.
const redirectTarget = route.query.redirect as string | undefined;
if (redirectTarget && redirectTarget.startsWith("/")) {
pendingShareRedirect.value = redirectTarget;
}
navigateTo("/api/auth/oauth", { external: true }); // start the redirect process
}
}

View File

@@ -14,7 +14,7 @@
</v-card-title>
<BaseDivider />
<v-card-text>
<v-form @submit.prevent="requestLink()">
<v-form ref="form" @submit.prevent="requestLink()">
<v-text-field
v-model="state.email"
:prepend-inner-icon="$globals.icons.email"
@@ -86,6 +86,7 @@ import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { validators } from "@/composables/use-validators";
import { useRouteQuery } from "~/composables/use-router";
import type { VForm } from "~/types/auto-forms";
definePageMeta({
layout: "basic",
@@ -99,6 +100,8 @@ const state = reactive({
error: false,
});
const form = ref<VForm | null>(null);
const i18n = useI18n();
const passwordMatch = () => state.password === state.passwordConfirm || i18n.t("user.password-must-match");
@@ -115,6 +118,15 @@ const token = useRouteQuery("token", "");
// API
const api = useUserApi();
async function requestLink() {
if (!form.value) {
return;
};
const { valid } = await form.value.validate();
if (!valid) {
return;
};
state.loading = true;
// TODO: Fix Response to send meaningful error
const { response } = await api.users.resetPassword({
@@ -127,12 +139,11 @@ async function requestLink() {
state.loading = false;
if (response?.status === 200) {
state.loading = false;
state.error = false;
alert.success(i18n.t("user.password-updated"));
await navigateTo("/login");
}
else {
state.loading = false;
state.error = true;
alert.error(i18n.t("events.something-went-wrong"));
}

View File

@@ -377,20 +377,22 @@ const { store: allUnits } = useUnitStore();
const { store: allFoods } = useFoodStore();
function itemCheckedToast(item: ShoppingListItemOut) {
alert.info(
i18n.t("shopping-list.item-checked-off", { item: item.food?.name || item.note || i18n.t("recipe.ingredient") }),
undefined,
{
timeout: 4000,
action: {
message: i18n.t("general.undo"),
onClick: () => {
item.checked = false;
shoppingListPage.saveListItem(item);
setTimeout(() => {
alert.info(
i18n.t("shopping-list.item-checked-off", { item: item.food?.name || item.note || i18n.t("recipe.ingredient") }),
undefined,
{
timeout: 4000,
action: {
message: i18n.t("general.undo"),
onClick: () => {
item.checked = false;
shoppingListPage.saveListItem(item);
},
},
},
},
);
);
}, 500);
}
const {

View File

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

View File

@@ -17,7 +17,7 @@ from sqlalchemy.orm import Session, load_only
from alembic import op
from mealie.db.models._model_utils.guid import GUID
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel
# revision identifiers, used by Alembic.

View File

@@ -0,0 +1,76 @@
"""more aggresive normalization
Revision ID: c7427796f7b6
Revises: 4395a04f7784
Create Date: 2026-05-10 18:44:53.159775
"""
from sqlalchemy import orm, text
from alembic import op
from mealie.db.models._model_base import SqlAlchemyBase
# revision identifiers, used by Alembic.
revision = "c7427796f7b6"
down_revision: str | None = "4395a04f7784"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def _update_table(session: orm.Session, table: str, columns: list[str], source_columns: list[str]) -> None:
"""Re-normalize all rows in `table`, reading raw values from `source_columns` and writing to `columns`."""
rows = session.execute(text(f"SELECT id, {', '.join(source_columns)} FROM {table}")).fetchall()
for row in rows:
id_ = row[0]
updates = {}
for col, src in zip(columns, source_columns, strict=True):
val = row[source_columns.index(src) + 1]
updates[col] = SqlAlchemyBase.normalize(val) if val is not None else None
set_clause = ", ".join(f"{col} = :{col}" for col in columns)
session.execute(text(f"UPDATE {table} SET {set_clause} WHERE id = :id"), {**updates, "id": id_})
session.commit()
def update_normalization() -> None:
bind = op.get_bind()
session = orm.Session(bind=bind)
# recipes: name_normalized, description_normalized
_update_table(session, "recipes", ["name_normalized", "description_normalized"], ["name", "description"])
# recipe ingredients: note_normalized, original_text_normalized
_update_table(
session,
"recipes_ingredients",
["note_normalized", "original_text_normalized"],
["note", "original_text"],
)
# ingredient units: name, plural_name, abbreviation, plural_abbreviation
_update_table(
session,
"ingredient_units",
["name_normalized", "plural_name_normalized", "abbreviation_normalized", "plural_abbreviation_normalized"],
["name", "plural_name", "abbreviation", "plural_abbreviation"],
)
# ingredient foods: name, plural_name
_update_table(session, "ingredient_foods", ["name_normalized", "plural_name_normalized"], ["name", "plural_name"])
# unit aliases
_update_table(session, "ingredient_units_aliases", ["name_normalized"], ["name"])
# food aliases
_update_table(session, "ingredient_foods_aliases", ["name_normalized"], ["name"])
def upgrade():
# no table changes, this is a data migration
update_normalization()
def downgrade():
pass

View File

@@ -35,7 +35,7 @@ class OpenIDProvider(AuthProvider[UserInfo]):
self._logger.debug("[OIDC] %s: %s", key, value)
if not self.required_claims.issubset(claims.keys()):
self._logger.error(
self._logger.debug(
"[OIDC] Required claims not present. Expected: %s Actual: %s",
self.required_claims,
claims.keys(),
@@ -45,7 +45,7 @@ class OpenIDProvider(AuthProvider[UserInfo]):
# Check for empty required claims
for claim in self.required_claims:
if not claims.get(claim):
self._logger.error("[OIDC] Required claim '%s' is empty", claim)
self._logger.debug("[OIDC] Required claim '%s' is empty", claim)
raise MissingClaimException()
repos = get_repositories(self.session, group_id=None, household_id=None)

View File

@@ -8,8 +8,8 @@ from mealie.core import root_logger
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models.group.group import Group
from mealie.db.models.household.shopping_list import ShoppingList, ShoppingListMultiPurposeLabel
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
from mealie.db.models.recipe.labels import MultiPurposeLabel
from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.users.users import User

View File

@@ -1,5 +1,4 @@
from .group import *
from .labels import *
from .recipe import *
from .server import *
from .users import *

View File

@@ -0,0 +1,23 @@
from typing import TYPE_CHECKING, Annotated
from sqlalchemy.orm import Mapped, mapped_column
class _FilterableColumn[T]:
"""
Drop-in replacement for `Mapped[]` that marks a column as filterable.
Filterable columns can be used in query filter expressions.
Only valid on scalar column fields. Using it on a relationship type (e.g. `list[Model]`).
"""
def __class_getitem__(cls, item: type) -> type:
return Mapped[Annotated[item, mapped_column(info={"filterable": True})]]
# SQLAlchemy doesn't play nice with mypy when overriding Mapped, so
# we use this awkward workaround to make mypy happy
if TYPE_CHECKING:
FilterableColumn = Mapped
else:
FilterableColumn = _FilterableColumn

View File

@@ -1,16 +1,26 @@
import string
from datetime import datetime
from sqlalchemy import Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column, synonym
from text_unidecode import unidecode
from ._filterable_column import FilterableColumn
from ._model_utils.datetime import NaiveDateTime, get_utc_now
# Punctuation characters replaced with spaces during text normalization.
# Mirrors SearchFilter in query_search.py: string.punctuation minus apostrophe and
# double-quote, which are reserved for quoted literal searches.
NORMALIZE_PUNCTUATION = string.punctuation.replace("'", "").replace('"', "")
_NORMALIZE_PUNCTUATION_TABLE = str.maketrans(NORMALIZE_PUNCTUATION, " " * len(NORMALIZE_PUNCTUATION))
class SqlAlchemyBase(DeclarativeBase):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
created_at: Mapped[datetime | None] = mapped_column(NaiveDateTime, default=get_utc_now, index=True)
update_at: Mapped[datetime | None] = mapped_column(NaiveDateTime, default=get_utc_now, onupdate=get_utc_now)
created_at: FilterableColumn[datetime | None] = mapped_column(NaiveDateTime, default=get_utc_now, index=True)
update_at: FilterableColumn[datetime | None] = mapped_column(
NaiveDateTime, default=get_utc_now, onupdate=get_utc_now
)
@declared_attr
def updated_at(cls) -> Mapped[datetime | None]:
@@ -20,7 +30,7 @@ class SqlAlchemyBase(DeclarativeBase):
def normalize(cls, val: str) -> str:
# We cap the length to 255 to prevent indexes from being too long; see:
# https://www.postgresql.org/docs/current/btree.html
return unidecode(val).lower().strip()[:255]
return unidecode(val).translate(_NORMALIZE_PUNCTUATION_TABLE).lower().strip()[:255]
class BaseMixins:

View File

@@ -5,9 +5,9 @@ import sqlalchemy.orm as orm
from pydantic import ConfigDict
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.labels import MultiPurposeLabel
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..household.cookbook import CookBook
@@ -31,9 +31,9 @@ if TYPE_CHECKING:
class Group(SqlAlchemyBase, BaseMixins):
__tablename__ = "groups"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: Mapped[str] = mapped_column(sa.String, index=True, nullable=False, unique=True)
slug: Mapped[str | None] = mapped_column(sa.String, index=True, unique=True)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: FilterableColumn[str] = mapped_column(sa.String, index=True, nullable=False, unique=True)
slug: FilterableColumn[str | None] = mapped_column(sa.String, index=True, unique=True)
households: Mapped[list["Household"]] = orm.relationship("Household", back_populates="group")
users: Mapped[list["User"]] = orm.relationship("User", back_populates="group")
categories: Mapped[list[Category]] = orm.relationship(Category, secondary=group_to_categories, single_parent=True)

View File

@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Optional
from sqlalchemy import Boolean, ForeignKey, Integer, String, UniqueConstraint, orm
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils import guid
from .._model_utils.auto_init import auto_init
from ..recipe.category import Category, cookbooks_to_categories
@@ -21,31 +21,31 @@ class CookBook(SqlAlchemyBase, BaseMixins):
UniqueConstraint("slug", "group_id", name="cookbook_slug_group_id_key"),
)
id: Mapped[guid.GUID] = mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
position: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
id: FilterableColumn[guid.GUID] = mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
position: FilterableColumn[int] = mapped_column(Integer, nullable=False, default=1)
group_id: Mapped[guid.GUID | None] = mapped_column(guid.GUID, ForeignKey("groups.id"), index=True)
group_id: FilterableColumn[guid.GUID | None] = mapped_column(guid.GUID, ForeignKey("groups.id"), index=True)
group: Mapped[Optional["Group"]] = orm.relationship("Group", back_populates="cookbooks")
household_id: Mapped[guid.GUID | None] = mapped_column(guid.GUID, ForeignKey("households.id"), index=True)
household_id: FilterableColumn[guid.GUID | None] = mapped_column(guid.GUID, ForeignKey("households.id"), index=True)
household: Mapped[Optional["Household"]] = orm.relationship("Household", back_populates="cookbooks")
name: Mapped[str] = mapped_column(String, nullable=False)
slug: Mapped[str] = mapped_column(String, nullable=False, index=True)
description: Mapped[str | None] = mapped_column(String, default="")
public: Mapped[str | None] = mapped_column(Boolean, default=False)
name: FilterableColumn[str] = mapped_column(String, nullable=False)
slug: FilterableColumn[str] = mapped_column(String, nullable=False, index=True)
description: FilterableColumn[str | None] = mapped_column(String, default="")
public: FilterableColumn[str | None] = mapped_column(Boolean, default=False)
query_filter_string: Mapped[str] = mapped_column(String, nullable=False, default="")
# Old filters - deprecated in favor of query filter strings
categories: Mapped[list[Category]] = orm.relationship(
Category, secondary=cookbooks_to_categories, single_parent=True
)
require_all_categories: Mapped[bool | None] = mapped_column(Boolean, default=True)
require_all_categories: FilterableColumn[bool | None] = mapped_column(Boolean, default=True)
tags: Mapped[list[Tag]] = orm.relationship(Tag, secondary=cookbooks_to_tags, single_parent=True)
require_all_tags: Mapped[bool | None] = mapped_column(Boolean, default=True)
require_all_tags: FilterableColumn[bool | None] = mapped_column(Boolean, default=True)
tools: Mapped[list[Tool]] = orm.relationship(Tool, secondary=cookbooks_to_tools, single_parent=True)
require_all_tools: Mapped[bool | None] = mapped_column(Boolean, default=True)
require_all_tools: FilterableColumn[bool | None] = mapped_column(Boolean, default=True)
@auto_init()
def __init__(self, **_) -> None:

View File

@@ -5,7 +5,7 @@ import sqlalchemy.orm as orm
from pydantic import ConfigDict
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..recipe.ingredient import households_to_ingredient_foods
@@ -33,9 +33,9 @@ class Household(SqlAlchemyBase, BaseMixins):
sa.UniqueConstraint("group_id", "slug", name="household_slug_group_id_key"),
)
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: Mapped[str] = mapped_column(sa.String, index=True, nullable=False)
slug: Mapped[str | None] = mapped_column(sa.String, index=True)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: FilterableColumn[str] = mapped_column(sa.String, index=True, nullable=False)
slug: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
invite_tokens: Mapped[list["GroupInviteToken"]] = orm.relationship(
"GroupInviteToken", back_populates="household", cascade="all, delete-orphan"
@@ -48,7 +48,7 @@ class Household(SqlAlchemyBase, BaseMixins):
cascade="all, delete-orphan",
)
group_id: Mapped[GUID] = mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="households")
users: Mapped[list["User"]] = orm.relationship("User", back_populates="household")

View File

@@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models.recipe.tag import Tag, plan_rules_to_tags
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..recipe.category import Category, plan_rules_to_categories
@@ -30,14 +30,14 @@ plan_rules_to_households = Table(
class GroupMealPlanRules(BaseMixins, SqlAlchemyBase):
__tablename__ = "group_meal_plan_rules"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
household_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("households.id"), index=True)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
household_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("households.id"), index=True)
day: Mapped[str] = mapped_column(
day: FilterableColumn[str] = mapped_column(
String, nullable=False, default="unset"
) # "MONDAY", "TUESDAY", "WEDNESDAY", etc...
entry_type: Mapped[str] = mapped_column(
entry_type: FilterableColumn[str] = mapped_column(
String, nullable=False, default=""
) # "breakfast", "lunch", "dinner", etc ...
query_filter_string: Mapped[str] = mapped_column(String, nullable=False, default="")
@@ -55,19 +55,19 @@ class GroupMealPlanRules(BaseMixins, SqlAlchemyBase):
class GroupMealPlan(SqlAlchemyBase, BaseMixins):
__tablename__ = "group_meal_plans"
date: Mapped[datetime.date] = mapped_column(Date, index=True, nullable=False)
entry_type: Mapped[str] = mapped_column(String, index=True, nullable=False)
title: Mapped[str] = mapped_column(String, index=True, nullable=False)
text: Mapped[str] = mapped_column(String, nullable=False)
date: FilterableColumn[datetime.date] = mapped_column(Date, index=True, nullable=False)
entry_type: FilterableColumn[str] = mapped_column(String, index=True, nullable=False)
title: FilterableColumn[str] = mapped_column(String, index=True, nullable=False)
text: FilterableColumn[str] = mapped_column(String, nullable=False)
group_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("groups.id"), index=True)
group_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("groups.id"), index=True)
group: Mapped[Optional["Group"]] = orm.relationship("Group", back_populates="mealplans")
household_id: AssociationProxy[GUID] = association_proxy("user", "household_id")
household: AssociationProxy["Household"] = association_proxy("user", "household")
user_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("users.id"), index=True)
user_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("users.id"), index=True)
user: Mapped[Optional["User"]] = orm.relationship("User", back_populates="mealplans")
recipe_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe: Mapped[Optional["RecipeModel"]] = orm.relationship(
"RecipeModel", back_populates="meal_entries", uselist=False
)

View File

@@ -5,7 +5,7 @@ import sqlalchemy.orm as orm
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from sqlalchemy.orm import Mapped, mapped_column
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
@@ -15,27 +15,29 @@ if TYPE_CHECKING:
class HouseholdPreferencesModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "household_preferences"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
household_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("households.id"), nullable=False, index=True)
household_id: FilterableColumn[GUID | None] = mapped_column(
GUID, sa.ForeignKey("households.id"), nullable=False, index=True
)
household: Mapped[Optional["Household"]] = orm.relationship("Household", back_populates="preferences")
group_id: AssociationProxy[GUID] = association_proxy("household", "group_id")
private_household: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
show_announcements: Mapped[bool] = mapped_column(sa.Boolean, default=True)
private_household: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=True)
show_announcements: FilterableColumn[bool] = mapped_column(sa.Boolean, default=True)
lock_recipe_edits_from_other_households: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
first_day_of_week: Mapped[int | None] = mapped_column(sa.Integer, default=0)
lock_recipe_edits_from_other_households: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=True)
first_day_of_week: FilterableColumn[int | None] = mapped_column(sa.Integer, default=0)
# Recipe Defaults
recipe_public: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
recipe_show_nutrition: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_show_assets: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_landscape_view: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_disable_comments: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_public: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=True)
recipe_show_nutrition: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_show_assets: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_landscape_view: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_disable_comments: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=False)
# Deprecated
recipe_disable_amount: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
recipe_disable_amount: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=True)
@auto_init()
def __init__(self, **_) -> None:

View File

@@ -8,10 +8,10 @@ from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.api_extras import ShoppingListExtras, ShoppingListItemExtras, api_extras
from mealie.db.models.recipe.labels import MultiPurposeLabel
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
from ..recipe.ingredient import IngredientFoodModel, IngredientUnitModel
@@ -25,18 +25,20 @@ if TYPE_CHECKING:
class ShoppingListItemRecipeReference(BaseMixins, SqlAlchemyBase):
__tablename__ = "shopping_list_item_recipe_reference"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
shopping_list_item: Mapped["ShoppingListItem"] = orm.relationship(
"ShoppingListItem", back_populates="recipe_references"
)
shopping_list_item_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("shopping_list_items.id"), primary_key=True)
shopping_list_item_id: FilterableColumn[GUID] = mapped_column(
GUID, ForeignKey("shopping_list_items.id"), primary_key=True
)
recipe_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe: Mapped[Optional["RecipeModel"]] = orm.relationship("RecipeModel", back_populates="shopping_list_item_refs")
recipe_quantity: Mapped[float] = mapped_column(Float, nullable=False)
recipe_scale: Mapped[float] = mapped_column(Float, default=1)
recipe_note: Mapped[str | None] = mapped_column(String)
recipe_quantity: FilterableColumn[float] = mapped_column(Float, nullable=False)
recipe_scale: FilterableColumn[float] = mapped_column(Float, default=1)
recipe_note: FilterableColumn[str | None] = mapped_column(String)
group_id: AssociationProxy[GUID] = association_proxy("shopping_list_item", "group_id")
household_id: AssociationProxy[GUID] = association_proxy("shopping_list_item", "household_id")
@@ -50,33 +52,33 @@ class ShoppingListItem(SqlAlchemyBase, BaseMixins):
__tablename__ = "shopping_list_items"
# Id's
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
shopping_list: Mapped["ShoppingList"] = orm.relationship("ShoppingList", back_populates="list_items")
shopping_list_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("shopping_lists.id"), index=True)
shopping_list_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("shopping_lists.id"), index=True)
group_id: AssociationProxy[GUID] = association_proxy("shopping_list", "group_id")
household_id: AssociationProxy[GUID] = association_proxy("shopping_list", "household_id")
# Meta
is_ingredient: Mapped[bool | None] = mapped_column(Boolean, default=True)
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0, index=True)
checked: Mapped[bool | None] = mapped_column(Boolean, default=False)
is_ingredient: FilterableColumn[bool | None] = mapped_column(Boolean, default=True)
position: FilterableColumn[int] = mapped_column(Integer, nullable=False, default=0, index=True)
checked: FilterableColumn[bool | None] = mapped_column(Boolean, default=False)
quantity: Mapped[float | None] = mapped_column(Float, default=1)
note: Mapped[str | None] = mapped_column(String)
quantity: FilterableColumn[float | None] = mapped_column(Float, default=1)
note: FilterableColumn[str | None] = mapped_column(String)
extras: Mapped[list[ShoppingListItemExtras]] = orm.relationship(
"ShoppingListItemExtras", cascade="all, delete-orphan"
)
# Scaling Items
unit_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_units.id"))
unit_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_units.id"))
unit: Mapped[IngredientUnitModel | None] = orm.relationship(IngredientUnitModel, uselist=False)
food_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_foods.id"))
food_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_foods.id"))
food: Mapped[IngredientFoodModel | None] = orm.relationship(IngredientFoodModel, uselist=False)
label_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"))
label_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"))
label: Mapped[MultiPurposeLabel | None] = orm.relationship(
MultiPurposeLabel, uselist=False, back_populates="shopping_list_items"
)
@@ -98,19 +100,19 @@ class ShoppingListItem(SqlAlchemyBase, BaseMixins):
class ShoppingListRecipeReference(BaseMixins, SqlAlchemyBase):
__tablename__ = "shopping_list_recipe_reference"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
shopping_list: Mapped["ShoppingList"] = orm.relationship("ShoppingList", back_populates="recipe_references")
shopping_list_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("shopping_lists.id"), primary_key=True)
shopping_list_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("shopping_lists.id"), primary_key=True)
group_id: AssociationProxy[GUID] = association_proxy("shopping_list", "group_id")
household_id: AssociationProxy[GUID] = association_proxy("shopping_list", "household_id")
recipe_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe: Mapped[Optional["RecipeModel"]] = orm.relationship(
"RecipeModel", uselist=False, back_populates="shopping_list_refs"
)
recipe_quantity: Mapped[float] = mapped_column(Float, nullable=False)
recipe_quantity: FilterableColumn[float] = mapped_column(Float, nullable=False)
model_config = ConfigDict(exclude={"id", "recipe"})
@auto_init()
@@ -121,12 +123,12 @@ class ShoppingListRecipeReference(BaseMixins, SqlAlchemyBase):
class ShoppingListMultiPurposeLabel(SqlAlchemyBase, BaseMixins):
__tablename__ = "shopping_lists_multi_purpose_labels"
__table_args__ = (UniqueConstraint("shopping_list_id", "label_id", name="shopping_list_id_label_id_key"),)
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
shopping_list_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("shopping_lists.id"), primary_key=True)
shopping_list_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("shopping_lists.id"), primary_key=True)
shopping_list: Mapped["ShoppingList"] = orm.relationship("ShoppingList", back_populates="label_settings")
label_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), primary_key=True)
label_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), primary_key=True)
label: Mapped["MultiPurposeLabel"] = orm.relationship(
"MultiPurposeLabel", back_populates="shopping_lists_label_settings"
)
@@ -134,7 +136,7 @@ class ShoppingListMultiPurposeLabel(SqlAlchemyBase, BaseMixins):
group_id: AssociationProxy[GUID] = association_proxy("shopping_list", "group_id")
household_id: AssociationProxy[GUID] = association_proxy("shopping_list", "household_id")
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
position: FilterableColumn[int] = mapped_column(Integer, nullable=False, default=0)
model_config = ConfigDict(exclude={"label"})
@auto_init()
@@ -144,16 +146,16 @@ class ShoppingListMultiPurposeLabel(SqlAlchemyBase, BaseMixins):
class ShoppingList(SqlAlchemyBase, BaseMixins):
__tablename__ = "shopping_lists"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="shopping_lists")
household_id: AssociationProxy[GUID] = association_proxy("user", "household_id")
household: AssociationProxy["Household"] = association_proxy("user", "household")
user_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("users.id"), nullable=False, index=True)
user_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("users.id"), nullable=False, index=True)
user: Mapped["User"] = orm.relationship("User", back_populates="shopping_lists")
name: Mapped[str | None] = mapped_column(String)
name: FilterableColumn[str | None] = mapped_column(String)
list_items: Mapped[list[ShoppingListItem]] = orm.relationship(
ShoppingListItem,
cascade="all, delete, delete-orphan",

View File

@@ -1,7 +1,7 @@
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import mapped_column
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models._model_base import FilterableColumn, SqlAlchemyBase
from mealie.db.models._model_utils.guid import GUID
@@ -28,9 +28,9 @@ class ExtrasGeneric:
This class is not an actual table, so it does not inherit from SqlAlchemyBase
"""
id: Mapped[int] = mapped_column(sa.Integer, primary_key=True)
key_name: Mapped[str | None] = mapped_column(sa.String)
value: Mapped[str | None] = mapped_column(sa.String)
id: FilterableColumn[int] = mapped_column(sa.Integer, primary_key=True)
key_name: FilterableColumn[str | None] = mapped_column(sa.String)
value: FilterableColumn[str | None] = mapped_column(sa.String)
def __init__(self, key, value) -> None:
self.key_name = key
@@ -40,21 +40,25 @@ class ExtrasGeneric:
# used specifically for recipe extras
class ApiExtras(ExtrasGeneric, SqlAlchemyBase):
__tablename__ = "api_extras"
recipee_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
recipee_id: FilterableColumn[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
class IngredientFoodExtras(ExtrasGeneric, SqlAlchemyBase):
__tablename__ = "ingredient_food_extras"
ingredient_food_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("ingredient_foods.id"), index=True)
ingredient_food_id: FilterableColumn[GUID | None] = mapped_column(
GUID, sa.ForeignKey("ingredient_foods.id"), index=True
)
class ShoppingListExtras(ExtrasGeneric, SqlAlchemyBase):
__tablename__ = "shopping_list_extras"
shopping_list_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("shopping_lists.id"), index=True)
shopping_list_id: FilterableColumn[GUID | None] = mapped_column(
GUID, sa.ForeignKey("shopping_lists.id"), index=True
)
class ShoppingListItemExtras(ExtrasGeneric, SqlAlchemyBase):
__tablename__ = "shopping_list_item_extras"
shopping_list_item_id: Mapped[GUID | None] = mapped_column(
shopping_list_item_id: FilterableColumn[GUID | None] = mapped_column(
GUID, sa.ForeignKey("shopping_list_items.id"), index=True
)

View File

@@ -1,17 +1,17 @@
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import mapped_column
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models._model_base import FilterableColumn, SqlAlchemyBase
from mealie.db.models._model_utils.guid import GUID
class RecipeAsset(SqlAlchemyBase):
__tablename__ = "recipe_assets"
id: Mapped[int] = mapped_column(sa.Integer, primary_key=True)
recipe_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
name: Mapped[str | None] = mapped_column(sa.String)
icon: Mapped[str | None] = mapped_column(sa.String)
file_name: Mapped[str | None] = mapped_column(sa.String)
id: FilterableColumn[int] = mapped_column(sa.Integer, primary_key=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
name: FilterableColumn[str | None] = mapped_column(sa.String)
icon: FilterableColumn[str | None] = mapped_column(sa.String)
file_name: FilterableColumn[str | None] = mapped_column(sa.String)
def __init__(self, name=None, icon=None, file_name=None) -> None:
self.name = name

View File

@@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column, validates
from mealie.core import root_logger
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.guid import GUID
if TYPE_CHECKING:
@@ -54,12 +54,12 @@ class Category(SqlAlchemyBase, BaseMixins):
__table_args__ = (sa.UniqueConstraint("slug", "group_id", name="category_slug_group_id_key"),)
# ID Relationships
group_id: Mapped[GUID] = mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="categories", foreign_keys=[group_id])
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: Mapped[str] = mapped_column(sa.String, index=True, nullable=False)
slug: Mapped[str] = mapped_column(sa.String, index=True, nullable=False)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: FilterableColumn[str] = mapped_column(sa.String, index=True, nullable=False)
slug: FilterableColumn[str] = mapped_column(sa.String, index=True, nullable=False)
recipes: Mapped[list["RecipeModel"]] = orm.relationship(
"RecipeModel", secondary=recipes_to_categories, back_populates="recipe_category"
)

View File

@@ -6,9 +6,9 @@ from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, event, orm
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.session import Session
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from mealie.db.models.recipe.api_extras import IngredientFoodExtras, api_extras
from mealie.db.models.recipe.labels import MultiPurposeLabel
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
@@ -29,19 +29,19 @@ households_to_ingredient_foods = sa.Table(
class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "ingredient_units"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
# ID Relationships
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_units", foreign_keys=[group_id])
name: Mapped[str | None] = mapped_column(String)
plural_name: Mapped[str | None] = mapped_column(String)
description: Mapped[str | None] = mapped_column(String)
abbreviation: Mapped[str | None] = mapped_column(String)
plural_abbreviation: Mapped[str | None] = mapped_column(String)
use_abbreviation: Mapped[bool | None] = mapped_column(Boolean, default=False)
fraction: Mapped[bool | None] = mapped_column(Boolean, default=True)
name: FilterableColumn[str | None] = mapped_column(String)
plural_name: FilterableColumn[str | None] = mapped_column(String)
description: FilterableColumn[str | None] = mapped_column(String)
abbreviation: FilterableColumn[str | None] = mapped_column(String)
plural_abbreviation: FilterableColumn[str | None] = mapped_column(String)
use_abbreviation: FilterableColumn[bool | None] = mapped_column(Boolean, default=False)
fraction: FilterableColumn[bool | None] = mapped_column(Boolean, default=True)
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
"RecipeIngredientModel", back_populates="unit"
@@ -53,14 +53,14 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
)
# Standardization
standard_quantity: Mapped[float | None] = mapped_column(Float)
standard_unit: Mapped[str | None] = mapped_column(String)
standard_quantity: FilterableColumn[float | None] = mapped_column(Float)
standard_unit: FilterableColumn[str | None] = mapped_column(String)
# Automatically updated by sqlalchemy event, do not write to this manually
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
plural_abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
name_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
plural_name_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
abbreviation_normalized: FilterableColumn[str | None] = mapped_column(String, index=True)
plural_abbreviation_normalized: FilterableColumn[str | None] = mapped_column(String, index=True)
@auto_init()
def __init__(
@@ -152,18 +152,18 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "ingredient_foods"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
# ID Relationships
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id])
households_with_ingredient_food: Mapped[list["Household"]] = orm.relationship(
"Household", secondary=households_to_ingredient_foods, back_populates="ingredient_foods_on_hand"
)
name: Mapped[str | None] = mapped_column(String)
plural_name: Mapped[str | None] = mapped_column(String)
description: Mapped[str | None] = mapped_column(String)
name: FilterableColumn[str | None] = mapped_column(String)
plural_name: FilterableColumn[str | None] = mapped_column(String)
description: FilterableColumn[str | None] = mapped_column(String)
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
"RecipeIngredientModel", back_populates="food"
@@ -175,12 +175,12 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
)
extras: Mapped[list[IngredientFoodExtras]] = orm.relationship("IngredientFoodExtras", cascade="all, delete-orphan")
label_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), index=True)
label_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), index=True)
label: Mapped[MultiPurposeLabel | None] = orm.relationship(MultiPurposeLabel, uselist=False, back_populates="foods")
# Automatically updated by sqlalchemy event, do not write to this manually
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
name_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
plural_name_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
model_config = ConfigDict(
exclude={
@@ -261,15 +261,15 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
class IngredientUnitAliasModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "ingredient_units_aliases"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
unit_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("ingredient_units.id"), primary_key=True)
unit_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("ingredient_units.id"), primary_key=True)
unit: Mapped["IngredientUnitModel"] = orm.relationship("IngredientUnitModel", back_populates="aliases")
name: Mapped[str] = mapped_column(String)
name: FilterableColumn[str] = mapped_column(String)
# Automatically updated by sqlalchemy event, do not write to this manually
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
name_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
@auto_init()
def __init__(self, session: Session, name: str, **_) -> None:
@@ -302,15 +302,15 @@ class IngredientUnitAliasModel(SqlAlchemyBase, BaseMixins):
class IngredientFoodAliasModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "ingredient_foods_aliases"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
food_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("ingredient_foods.id"), primary_key=True)
food_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("ingredient_foods.id"), primary_key=True)
food: Mapped["IngredientFoodModel"] = orm.relationship("IngredientFoodModel", back_populates="aliases")
name: Mapped[str] = mapped_column(String)
name: FilterableColumn[str] = mapped_column(String)
# Automatically updated by sqlalchemy event, do not write to this manually
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
name_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
@auto_init()
def __init__(self, session: Session, name: str, **_) -> None:
@@ -343,34 +343,34 @@ class IngredientFoodAliasModel(SqlAlchemyBase, BaseMixins):
class RecipeIngredientModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipes_ingredients"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
position: Mapped[int | None] = mapped_column(Integer, index=True)
recipe_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"))
id: FilterableColumn[int] = mapped_column(Integer, primary_key=True)
position: FilterableColumn[int | None] = mapped_column(Integer, index=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"))
title: Mapped[str | None] = mapped_column(String) # Section Header - Shows if Present
note: Mapped[str | None] = mapped_column(String) # Force Show Text - Overrides Concat
title: FilterableColumn[str | None] = mapped_column(String) # Section Header - Shows if Present
note: FilterableColumn[str | None] = mapped_column(String) # Force Show Text - Overrides Concat
# Scaling Items
unit_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_units.id"), index=True)
unit_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_units.id"), index=True)
unit: Mapped[IngredientUnitModel | None] = orm.relationship(IngredientUnitModel, uselist=False)
food_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_foods.id"), index=True)
food_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("ingredient_foods.id"), index=True)
food: Mapped[IngredientFoodModel | None] = orm.relationship(IngredientFoodModel, uselist=False)
quantity: Mapped[float | None] = mapped_column(Float)
quantity: FilterableColumn[float | None] = mapped_column(Float)
original_text: Mapped[str | None] = mapped_column(String)
original_text: FilterableColumn[str | None] = mapped_column(String)
reference_id: Mapped[GUID | None] = mapped_column(GUID) # Reference Links
reference_id: FilterableColumn[GUID | None] = mapped_column(GUID) # Reference Links
# Recipe Reference
referenced_recipe_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
referenced_recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
referenced_recipe: Mapped["RecipeModel"] = orm.relationship(
"RecipeModel", back_populates="referenced_ingredients", foreign_keys=[referenced_recipe_id]
)
# Automatically updated by sqlalchemy event, do not write to this manually
note_normalized: Mapped[str | None] = mapped_column(String, index=True)
original_text_normalized: Mapped[str | None] = mapped_column(String, index=True)
note_normalized: FilterableColumn[str | None] = mapped_column(String, index=True)
original_text_normalized: FilterableColumn[str | None] = mapped_column(String, index=True)
@auto_init()
def __init__(

View File

@@ -3,26 +3,26 @@ from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String, UniqueConstraint, orm
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from ._model_utils.auto_init import auto_init
from ._model_utils.guid import GUID
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
if TYPE_CHECKING:
from .group.group import Group
from .household.shopping_list import ShoppingListItem, ShoppingListMultiPurposeLabel
from .recipe import IngredientFoodModel
from ..group.group import Group
from ..household.shopping_list import ShoppingListItem, ShoppingListMultiPurposeLabel
from . import IngredientFoodModel
class MultiPurposeLabel(SqlAlchemyBase, BaseMixins):
__tablename__ = "multi_purpose_labels"
__table_args__ = (UniqueConstraint("name", "group_id", name="multi_purpose_labels_name_group_id_key"),)
id: Mapped[GUID] = mapped_column(GUID, default=GUID.generate, primary_key=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
color: Mapped[str] = mapped_column(String(10), nullable=False, default="")
id: FilterableColumn[GUID] = mapped_column(GUID, default=GUID.generate, primary_key=True)
name: FilterableColumn[str] = mapped_column(String(255), nullable=False)
color: FilterableColumn[str] = mapped_column(String(10), nullable=False, default="")
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="labels")
shopping_list_items: Mapped["ShoppingListItem"] = orm.relationship("ShoppingListItem", back_populates="label")

View File

@@ -1,22 +1,22 @@
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import mapped_column
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models._model_base import FilterableColumn, SqlAlchemyBase
from mealie.db.models._model_utils.guid import GUID
class Nutrition(SqlAlchemyBase):
__tablename__ = "recipe_nutrition"
id: Mapped[int] = mapped_column(sa.Integer, primary_key=True)
recipe_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
id: FilterableColumn[int] = mapped_column(sa.Integer, primary_key=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
calories: Mapped[str | None] = mapped_column(sa.String)
carbohydrate_content: Mapped[str | None] = mapped_column(sa.String)
cholesterol_content: Mapped[str | None] = mapped_column(sa.String)
fat_content: Mapped[str | None] = mapped_column(sa.String)
fiber_content: Mapped[str | None] = mapped_column(sa.String)
protein_content: Mapped[str | None] = mapped_column(sa.String)
saturated_fat_content: Mapped[str | None] = mapped_column(sa.String)
calories: FilterableColumn[str | None] = mapped_column(sa.String)
carbohydrate_content: FilterableColumn[str | None] = mapped_column(sa.String)
cholesterol_content: FilterableColumn[str | None] = mapped_column(sa.String)
fat_content: FilterableColumn[str | None] = mapped_column(sa.String)
fiber_content: FilterableColumn[str | None] = mapped_column(sa.String)
protein_content: FilterableColumn[str | None] = mapped_column(sa.String)
saturated_fat_content: FilterableColumn[str | None] = mapped_column(sa.String)
# `serving_size` is not a scaling factor, but a per-serving volume or mass
# according to schema.org. E.g., "2 L", "500 g", "5 cups", etc.
@@ -28,10 +28,10 @@ class Nutrition(SqlAlchemyBase):
#
# serving_size: Mapped[str | None] = mapped_column(sa.String)
sodium_content: Mapped[str | None] = mapped_column(sa.String)
sugar_content: Mapped[str | None] = mapped_column(sa.String)
trans_fat_content: Mapped[str | None] = mapped_column(sa.String)
unsaturated_fat_content: Mapped[str | None] = mapped_column(sa.String)
sodium_content: FilterableColumn[str | None] = mapped_column(sa.String)
sugar_content: FilterableColumn[str | None] = mapped_column(sa.String)
trans_fat_content: FilterableColumn[str | None] = mapped_column(sa.String)
unsaturated_fat_content: FilterableColumn[str | None] = mapped_column(sa.String)
def __init__(
self,

View File

@@ -16,7 +16,7 @@ from mealie.db.models._model_utils.datetime import NaiveDateTime, get_utc_today
from mealie.db.models._model_utils.guid import GUID
from mealie.db.models.recipe.ingredient import RecipeIngredientModel
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from ..household.household_to_recipe import HouseholdToRecipe
from ..users.user_to_recipe import UserToRecipe
from .api_extras import ApiExtras, api_extras
@@ -45,20 +45,20 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
sa.UniqueConstraint("slug", "group_id", name="recipe_slug_group_id_key"),
)
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
slug: Mapped[str | None] = mapped_column(sa.String, index=True)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
slug: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
# ID Relationships
group_id: Mapped[GUID] = mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
group_id: FilterableColumn[GUID] = mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
group: Mapped["Group"] = orm.relationship("Group", back_populates="recipes", foreign_keys=[group_id])
household_id: AssociationProxy[GUID] = association_proxy("user", "household_id")
household: AssociationProxy["Household"] = association_proxy("user", "household")
user_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("users.id", use_alter=True), index=True)
user_id: FilterableColumn[GUID | None] = mapped_column(GUID, sa.ForeignKey("users.id", use_alter=True), index=True)
user: Mapped["User"] = orm.relationship("User", uselist=False, foreign_keys=[user_id])
rating: Mapped[float | None] = mapped_column(sa.Float, index=True, nullable=True)
rating: FilterableColumn[float | None] = mapped_column(sa.Float, index=True, nullable=True)
rated_by: Mapped[list["User"]] = orm.relationship(
"User",
secondary=UserToRecipe.__tablename__,
@@ -78,20 +78,20 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
)
# General Recipe Properties
name: Mapped[str] = mapped_column(sa.String, nullable=False)
description: Mapped[str | None] = mapped_column(sa.String)
name: FilterableColumn[str] = mapped_column(sa.String, nullable=False)
description: FilterableColumn[str | None] = mapped_column(sa.String)
image: Mapped[str | None] = mapped_column(sa.String)
image: FilterableColumn[str | None] = mapped_column(sa.String)
# Time Related Properties
total_time: Mapped[str | None] = mapped_column(sa.String)
prep_time: Mapped[str | None] = mapped_column(sa.String)
perform_time: Mapped[str | None] = mapped_column(sa.String)
cook_time: Mapped[str | None] = mapped_column(sa.String)
total_time: FilterableColumn[str | None] = mapped_column(sa.String)
prep_time: FilterableColumn[str | None] = mapped_column(sa.String)
perform_time: FilterableColumn[str | None] = mapped_column(sa.String)
cook_time: FilterableColumn[str | None] = mapped_column(sa.String)
recipe_yield: Mapped[str | None] = mapped_column(sa.String)
recipe_yield_quantity: Mapped[float] = mapped_column(sa.Float, index=True, default=0)
recipe_servings: Mapped[float] = mapped_column(sa.Float, index=True, default=0)
recipe_yield: FilterableColumn[str | None] = mapped_column(sa.String)
recipe_yield_quantity: FilterableColumn[float] = mapped_column(sa.Float, index=True, default=0)
recipe_servings: FilterableColumn[float] = mapped_column(sa.Float, index=True, default=0)
assets: Mapped[list[RecipeAsset]] = orm.relationship("RecipeAsset", cascade="all, delete-orphan")
nutrition: Mapped[Nutrition] = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
@@ -137,14 +137,14 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
)
tags: Mapped[list["Tag"]] = orm.relationship("Tag", secondary=recipes_to_tags, back_populates="recipes")
notes: Mapped[list[Note]] = orm.relationship("Note", cascade="all, delete-orphan")
org_url: Mapped[str | None] = mapped_column(sa.String)
org_url: FilterableColumn[str | None] = mapped_column(sa.String)
extras: Mapped[list[ApiExtras]] = orm.relationship("ApiExtras", cascade="all, delete-orphan")
# Time Stamp Properties
date_added: Mapped[date | None] = mapped_column(sa.Date, default=get_utc_today)
date_updated: Mapped[datetime | None] = mapped_column(NaiveDateTime)
date_added: FilterableColumn[date | None] = mapped_column(sa.Date, default=get_utc_today)
date_updated: FilterableColumn[datetime | None] = mapped_column(NaiveDateTime)
last_made: Mapped[datetime | None] = mapped_column(NaiveDateTime)
last_made: FilterableColumn[datetime | None] = mapped_column(NaiveDateTime)
made_by: Mapped[list["Household"]] = orm.relationship(
"Household", secondary=HouseholdToRecipe.__tablename__, back_populates="made_recipes"
)
@@ -162,8 +162,8 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
)
# Automatically updated by sqlalchemy event, do not write to this manually
name_normalized: Mapped[str] = mapped_column(sa.String, nullable=False, index=True)
description_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
name_normalized: FilterableColumn[str] = mapped_column(sa.String, nullable=False, index=True)
description_normalized: FilterableColumn[str | None] = mapped_column(sa.String, index=True)
model_config = ConfigDict(
get_attr="slug",
exclude={

View File

@@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from mealie.db.models._model_utils.datetime import NaiveDateTime
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_base import BaseMixins, FilterableColumn, SqlAlchemyBase
from .._model_utils.auto_init import auto_init
from .._model_utils.guid import GUID
@@ -18,29 +18,29 @@ if TYPE_CHECKING:
class RecipeTimelineEvent(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipe_timeline_events"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
id: FilterableColumn[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
# Parent Recipe
recipe_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("recipes.id"), nullable=False, index=True)
recipe_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("recipes.id"), nullable=False, index=True)
recipe: Mapped["RecipeModel"] = relationship("RecipeModel", back_populates="timeline_events")
group_id: AssociationProxy[GUID] = association_proxy("recipe", "group_id")
household_id: AssociationProxy[GUID] = association_proxy("recipe", "household_id")
# Related User (Actor)
user_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("users.id"), nullable=False, index=True)
user_id: FilterableColumn[GUID] = mapped_column(GUID, ForeignKey("users.id"), nullable=False, index=True)
user: Mapped["User"] = relationship(
"User", back_populates="recipe_timeline_events", single_parent=True, foreign_keys=[user_id]
)
# General Properties
subject: Mapped[str] = mapped_column(String, nullable=False)
message: Mapped[str | None] = mapped_column(String)
event_type: Mapped[str | None] = mapped_column(String)
image: Mapped[str | None] = mapped_column(String)
subject: FilterableColumn[str] = mapped_column(String, nullable=False)
message: FilterableColumn[str | None] = mapped_column(String)
event_type: FilterableColumn[str | None] = mapped_column(String)
image: FilterableColumn[str | None] = mapped_column(String)
# Timestamps
timestamp: Mapped[datetime | None] = mapped_column(NaiveDateTime, index=True)
timestamp: FilterableColumn[datetime | None] = mapped_column(NaiveDateTime, index=True)
@auto_init()
def __init__(

View File

@@ -1,20 +1,20 @@
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models._model_base import FilterableColumn, SqlAlchemyBase
from mealie.db.models._model_utils.guid import GUID
class RecipeSettings(SqlAlchemyBase):
__tablename__ = "recipe_settings"
id: Mapped[int] = mapped_column(sa.Integer, primary_key=True)
recipe_id: Mapped[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
public: Mapped[bool | None] = mapped_column(sa.Boolean)
show_nutrition: Mapped[bool | None] = mapped_column(sa.Boolean)
show_assets: Mapped[bool | None] = mapped_column(sa.Boolean)
landscape_view: Mapped[bool | None] = mapped_column(sa.Boolean)
disable_comments: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
locked: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
id: FilterableColumn[int] = mapped_column(sa.Integer, primary_key=True)
recipe_id: FilterableColumn[GUID | None] = mapped_column(GUID, sa.ForeignKey("recipes.id"), index=True)
public: FilterableColumn[bool | None] = mapped_column(sa.Boolean)
show_nutrition: FilterableColumn[bool | None] = mapped_column(sa.Boolean)
show_assets: FilterableColumn[bool | None] = mapped_column(sa.Boolean)
landscape_view: FilterableColumn[bool | None] = mapped_column(sa.Boolean)
disable_comments: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=False)
locked: FilterableColumn[bool | None] = mapped_column(sa.Boolean, default=False)
# Deprecated
disable_amount: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)

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