mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-06-13 12:30:14 -04:00
Compare commits
37 Commits
v3.17.0
...
fix/6853-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f644ee1879 | ||
|
|
8eb00c3dc0 | ||
|
|
642c826f2b | ||
|
|
493154caa8 | ||
|
|
71e0d99a46 | ||
|
|
c52a4e10c9 | ||
|
|
8b9149a1ce | ||
|
|
c8ff75c02a | ||
|
|
3d6ff52358 | ||
|
|
f04b0c741c | ||
|
|
742b498c1d | ||
|
|
eddb0c30e0 | ||
|
|
1cebfd56ab | ||
|
|
074ec7aab2 | ||
|
|
af75c5f39d | ||
|
|
703db2931f | ||
|
|
52399547d6 | ||
|
|
be4ff86c57 | ||
|
|
8a054b1be8 | ||
|
|
2dbfc7f72b | ||
|
|
e492da67e2 | ||
|
|
811be08996 | ||
|
|
fdd17182d8 | ||
|
|
d340fdd9df | ||
|
|
551a92a031 | ||
|
|
8c06f49b02 | ||
|
|
9fd3fbca8b | ||
|
|
a242aea9f2 | ||
|
|
6e9ad5fef1 | ||
|
|
ee181a598b | ||
|
|
3a84b3f262 | ||
|
|
a616e14bf9 | ||
|
|
b902d2cd98 | ||
|
|
565736e116 | ||
|
|
7f29efc0e4 | ||
|
|
743c15a981 | ||
|
|
3be9193590 |
@@ -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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
###############################################
|
||||
# Frontend Build
|
||||
###############################################
|
||||
FROM node:24@sha256:e9891237dfbb1de60ce19e9ff9fac5d73ad9c37da303ad72ff2a425ad1057e71 \
|
||||
FROM node:24@sha256:050bf2bbe33c1d6754e060bec89378a79ed831f04a7bb1a53fe45e997df7b3bb \
|
||||
AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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="[
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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[]>(() => {
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -93,4 +93,8 @@ function emitCreate() {
|
||||
emit("create", searchInput.value);
|
||||
autocompleteRef.value?.blur();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
focus: () => autocompleteRef.value?.focus(),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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" });
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "Всички рецепти",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "Συνταγές όλες",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 d’une 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 l’IA pour tenter d’extraire le texte et de créer une recette.",
|
||||
"crop-and-rotate-the-image": "Rogner et pivoter l’image pour que seul le texte soit visible, et qu’il soit dans la bonne orientation.",
|
||||
"create-from-images": "Créer à partir d’une 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",
|
||||
|
||||
@@ -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 d’une 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 l’IA pour tenter d’extraire le texte et de créer une recette.",
|
||||
"crop-and-rotate-the-image": "Rogner et pivoter l’image pour que seul le texte soit visible et qu’il soit dans la bonne orientation.",
|
||||
"create-from-images": "Créer à partir d’images",
|
||||
@@ -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",
|
||||
|
||||
@@ -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 d’une 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 l’IA pour tenter d’extraire le texte et de créer une recette.",
|
||||
"crop-and-rotate-the-image": "Rogner et pivoter l’image pour que seul le texte soit visible, et qu’il soit dans la bonne orientation.",
|
||||
"create-from-images": "Créer à partir d’images",
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "כל המתכונים",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 URL‑jé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ó‑Url‑t 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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "すべてのレシピ",
|
||||
|
||||
@@ -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": "모든 레시피",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Все рецепты",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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-а. Када је омогућено, корисници и даље могу да се искључе из приказивања обавештења у својим корисничким подешавањима."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Всі рецепти",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "全部食谱",
|
||||
|
||||
@@ -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": "所有食譜",
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mealie",
|
||||
"version": "3.17.0",
|
||||
"version": "3.18.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxt dev",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from .group import *
|
||||
from .labels import *
|
||||
from .recipe import *
|
||||
from .server import *
|
||||
from .users import *
|
||||
|
||||
23
mealie/db/models/_filterable_column.py
Normal file
23
mealie/db/models/_filterable_column.py
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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")
|
||||
@@ -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,
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user