Merge remote-tracking branch 'origin/mealie-next' into mealie-next

This commit is contained in:
Julian van der Horst 2024-11-04 13:22:14 +01:00
commit 449570f3bc
29 changed files with 385 additions and 277 deletions

View file

@ -15,8 +15,30 @@ jobs:
- name: Checkout 🛎 - name: Checkout 🛎
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Update pre-commit Hooks - name: Setup Python
uses: vrslev/pre-commit-autoupdate@v1.0.0 uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set PY
shell: bash
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
- name: Cache
uses: actions/cache@v4
with:
path: |
~/.cache/pre-commit
~/.cache/pip
key: pre-commit-${{ env.PY }}-${{ hashFiles('.pre-commit-config.yaml') }}
- name: Install pre-commit
shell: bash
run: pip install -U pre-commit
- name: Run `pre-commit autoupdate`
shell: bash
run: pre-commit autoupdate --color=always
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v6 uses: peter-evans/create-pull-request@v6

View file

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

View file

@ -5,6 +5,7 @@ vars:
GREETING: Hello, World! GREETING: Hello, World!
env: env:
DEFAULT_GROUP: Home DEFAULT_GROUP: Home
DEFAULT_HOUSEHOLD: Family
PRODUCTION: false PRODUCTION: false
API_PORT: 9000 API_PORT: 9000
API_DOCS: True API_DOCS: True

View file

@ -9,6 +9,7 @@
| PUID | 911 | UserID permissions between host OS and container | | PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container | | PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users | | DEFAULT_GROUP | Home | The default group for users |
| DEFAULT_HOUSEHOLD | Family | The default household for users in each group |
| BASE_URL | http://localhost:8080 | Used for Notifications | | BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid | | TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** | | API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |

View file

@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do: We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
1. Take a backup just in case! 1. Take a backup just in case!
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.0.0` 2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.1.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. 3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
4. Restart the container 4. Restart the container
@ -65,7 +65,7 @@ After you've decided setup the files it's important to set a few ENV variables t
- [x] You've configured the relevant ENV variables for your database selection in the `docker-compose.yaml` files. - [x] You've configured the relevant ENV variables for your database selection in the `docker-compose.yaml` files.
- [x] You've configured the [SMTP server settings](./backend-config.md#email) (used for invitations, password resets, etc). You can setup a [google app password](https://support.google.com/accounts/answer/185833?hl=en) if you want to send email via gmail. - [x] You've configured the [SMTP server settings](./backend-config.md#email) (used for invitations, password resets, etc). You can setup a [google app password](https://support.google.com/accounts/answer/185833?hl=en) if you want to send email via gmail.
- [x] You've set the [`BASE_URL`](./backend-config.md#general) variable. - [x] You've set the [`BASE_URL`](./backend-config.md#general) variable.
- [x] You've set the `DEFAULT_EMAIL` and `DEFAULT_GROUP` variable. - [x] You've set the `DEFAULT_EMAIL`, `DEFAULT_GROUP`, and `DEFAULT_HOUSEHOLD` variables.
## Step 4: Startup ## Step 4: Startup

View file

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

View file

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

View file

@ -205,23 +205,14 @@ export default defineComponent({
const route = useRoute(); const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const router = useRouter();
function navigateRandom() {
if (props.recipes.length > 0) {
const recipe = props.recipes[Math.floor(Math.random() * props.recipes.length)];
if (recipe.slug !== undefined) {
router.push(`/g/${groupSlug.value}/r/${recipe.slug}`);
}
}
}
const page = ref(1); const page = ref(1);
const perPage = 32; const perPage = 32;
const hasMore = ref(true); const hasMore = ref(true);
const ready = ref(false); const ready = ref(false);
const loading = ref(false); const loading = ref(false);
const { fetchMore } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value); const { fetchMore, getRandom } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
const router = useRouter();
const queryFilter = computed(() => { const queryFilter = computed(() => {
const orderBy = props.query?.orderBy || preferences.value.orderBy; const orderBy = props.query?.orderBy || preferences.value.orderBy;
@ -383,6 +374,15 @@ export default defineComponent({
}, useAsyncKey()); }, useAsyncKey());
} }
async function navigateRandom() {
const recipe = await getRandom(props.query, queryFilter.value);
if (!recipe?.slug) {
return;
}
router.push(`/g/${groupSlug.value}/r/${recipe.slug}`);
}
function toggleMobileCards() { function toggleMobileCards() {
preferences.value.useMobileCards = !preferences.value.useMobileCards; preferences.value.useMobileCards = !preferences.value.useMobileCards;
} }

View file

@ -98,7 +98,7 @@
<tr v-for="(value, key) in recipe.nutrition" :key="key"> <tr v-for="(value, key) in recipe.nutrition" :key="key">
<template v-if="value"> <template v-if="value">
<td>{{ labels[key].label }}</td> <td>{{ labels[key].label }}</td>
<td>{{ value || '-' }}</td> <td>{{ value ? (labels[key].suffix ? `${value} ${labels[key].suffix}` : value) : '-' }}</td>
</template> </template>
</tr> </tr>
</tbody> </tbody>
@ -322,10 +322,32 @@ li {
} }
.nutrition-table { .nutrition-table {
width: 25%; max-width: 80%;
border-collapse: collapse; border-collapse: collapse;
} }
.nutrition-table th,
.nutrition-table td {
padding: 6px 10px;
text-align: left;
vertical-align: top;
font-size: 14px;
}
.nutrition-table th {
font-weight: bold;
padding-bottom: 10px;
}
.nutrition-table td:first-child {
width: 70%;
font-weight: bold;
}
.nutrition-table td:last-child {
width: 30%;
text-align: right;
}
.nutrition-table td { .nutrition-table td {
padding: 2px; padding: 2px;
text-align: left; text-align: left;

View file

@ -97,8 +97,8 @@ export default defineComponent({
}, },
}); });
const numerator = ref<number>(parseFloat(props.basicYieldNum.toFixed(3)) ?? 1); const numerator = ref<number>(props.basicYieldNum != null ? parseFloat(props.basicYieldNum.toFixed(3)) : 1);
const denominator = parseFloat(props.basicYieldNum.toFixed(32)) ?? 1; const denominator = props.basicYieldNum != null ? parseFloat(props.basicYieldNum.toFixed(32)) : 1;
const numberParsed = !!props.basicYieldNum; const numberParsed = !!props.basicYieldNum;
watch(() => numerator.value, () => { watch(() => numerator.value, () => {

View file

@ -54,7 +54,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
// casting to number is required as sometimes quantity is a string // casting to number is required as sometimes quantity is a string
if (quantity && Number(quantity) !== 0) { if (quantity && Number(quantity) !== 0) {
if (unit && !unit.fraction) { if (unit && !unit.fraction) {
returnQty = (quantity * scale).toString(); returnQty = Number((quantity * scale).toPrecision(3)).toString();
} else { } else {
const fraction = frac(quantity * scale, 10, true); const fraction = frac(quantity * scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) { if (fraction[0] !== undefined && fraction[0] > 0) {

View file

@ -8,6 +8,32 @@ import { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
export const allRecipes = ref<Recipe[]>([]); export const allRecipes = ref<Recipe[]>([]);
export const recentRecipes = ref<Recipe[]>([]); export const recentRecipes = ref<Recipe[]>([]);
function getParams(
orderBy: string | null = null,
orderDirection = "desc",
query: RecipeSearchQuery | null = null,
queryFilter: string | null = null
) {
return {
orderBy,
orderDirection,
paginationSeed: query?._searchSeed, // propagate searchSeed to stabilize random order pagination
searchSeed: query?._searchSeed, // unused, but pass it along for completeness of data
search: query?.search,
cookbook: query?.cookbook,
households: query?.households,
categories: query?.categories,
requireAllCategories: query?.requireAllCategories,
tags: query?.tags,
requireAllTags: query?.requireAllTags,
tools: query?.tools,
requireAllTools: query?.requireAllTools,
foods: query?.foods,
requireAllFoods: query?.requireAllFoods,
queryFilter,
};
};
export const useLazyRecipes = function (publicGroupSlug: string | null = null) { export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
const router = useRouter(); const router = useRouter();
@ -25,24 +51,11 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
queryFilter: string | null = null, queryFilter: string | null = null,
) { ) {
const { data, error } = await api.recipes.getAll(page, perPage, { const { data, error } = await api.recipes.getAll(
orderBy, page,
orderDirection, perPage,
paginationSeed: query?._searchSeed, // propagate searchSeed to stabilize random order pagination getParams(orderBy, orderDirection, query, queryFilter),
searchSeed: query?._searchSeed, // unused, but pass it along for completeness of data );
search: query?.search,
cookbook: query?.cookbook,
households: query?.households,
categories: query?.categories,
requireAllCategories: query?.requireAllCategories,
tags: query?.tags,
requireAllTags: query?.requireAllTags,
tools: query?.tools,
requireAllTools: query?.requireAllTools,
foods: query?.foods,
requireAllFoods: query?.requireAllFoods,
queryFilter,
});
if (error?.response?.status === 404) { if (error?.response?.status === 404) {
router.push("/login"); router.push("/login");
@ -74,6 +87,13 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
recipes.value = val; recipes.value = val;
} }
async function getRandom(query: RecipeSearchQuery | null = null, queryFilter: string | null = null) {
const { data } = await api.recipes.getAll(1, 1, getParams("random", "desc", query, queryFilter));
if (data?.items.length) {
return data.items[0];
}
}
return { return {
recipes, recipes,
fetchMore, fetchMore,
@ -81,6 +101,7 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
assignSorted, assignSorted,
removeRecipe, removeRecipe,
replaceRecipes, replaceRecipes,
getRandom,
}; };
}; };

View file

@ -14,7 +14,7 @@
"development": "פיתוח", "development": "פיתוח",
"docs": "תיעוד", "docs": "תיעוד",
"download-log": "הורדת לוגים", "download-log": "הורדת לוגים",
"download-recipe-json": JSON האחרון שנקרא", "download-recipe-json": -JSON האחרון שנקרא",
"github": "גיטהאב", "github": "גיטהאב",
"log-lines": "שורות לוג", "log-lines": "שורות לוג",
"not-demo": "לא בהדגמה", "not-demo": "לא בהדגמה",
@ -54,7 +54,7 @@
"apprise-url": "קישור להודעה", "apprise-url": "קישור להודעה",
"database": "מסד נתונים", "database": "מסד נתונים",
"delete-event": "מחיקת אירוע", "delete-event": "מחיקת אירוע",
"event-delete-confirmation": "האם את/ה בטוח/ה שברצונך למחוק את האירוע?", "event-delete-confirmation": "למחוק את האירוע?",
"event-deleted": "אירוע נמחק", "event-deleted": "אירוע נמחק",
"event-updated": "האירוע עודכן", "event-updated": "האירוע עודכן",
"new-notification-form-description": "Mקשךןק עושה שימוש בספריה בשם Apprise לשליחת התראות. Apprise מציעה אפשרויות רבות עבור התראות לבעלי שירותים. פנה לאתר הWiki של Apprise להסבר מלא על יצירת לינקים לשירות שלך.", "new-notification-form-description": "Mקשךןק עושה שימוש בספריה בשם Apprise לשליחת התראות. Apprise מציעה אפשרויות רבות עבור התראות לבעלי שירותים. פנה לאתר הWiki של Apprise להסבר מלא על יצירת לינקים לשירות שלך.",
@ -88,7 +88,7 @@
"close": "סגירה", "close": "סגירה",
"confirm": "אישור", "confirm": "אישור",
"confirm-how-does-everything-look": "איך הכל נראה?", "confirm-how-does-everything-look": "איך הכל נראה?",
"confirm-delete-generic": "האם את/ה בטוח/ה שברצונך למחוק את זה?", "confirm-delete-generic": "למחוק את זה?",
"copied_message": "הועתק!", "copied_message": "הועתק!",
"create": "יצירה", "create": "יצירה",
"created": "נוצר", "created": "נוצר",
@ -189,7 +189,7 @@
"menu": "תפריט", "menu": "תפריט",
"a-name-is-required": "נדרש שם", "a-name-is-required": "נדרש שם",
"delete-with-name": "מחיקת {name}", "delete-with-name": "מחיקת {name}",
"confirm-delete-generic-with-name": "האם את/ה בטוח/ה שברצונך למחוק את {name}?", "confirm-delete-generic-with-name": "למחוק את {name}?",
"confirm-delete-own-admin-account": "אזהרה! אתה עומד למחוק את חשבון המנהל שלך. פעולה זו לא ניתנת לביטול ותמחק לחלוטין את החשבון שלך. האם להמשיך?", "confirm-delete-own-admin-account": "אזהרה! אתה עומד למחוק את חשבון המנהל שלך. פעולה זו לא ניתנת לביטול ותמחק לחלוטין את החשבון שלך. האם להמשיך?",
"organizer": "מארגן", "organizer": "מארגן",
"transfer": "העברה", "transfer": "העברה",
@ -211,7 +211,7 @@
"created-on-date": "נוצר ב-{0}", "created-on-date": "נוצר ב-{0}",
"unsaved-changes": "יש שינויים שלא נשמרו. לצאת לפני שמירה? אשר לשמירה, בטל למחיקת שינויים.", "unsaved-changes": "יש שינויים שלא נשמרו. לצאת לפני שמירה? אשר לשמירה, בטל למחיקת שינויים.",
"clipboard-copy-failure": "כשלון בהעתקה ללוח ההדבקה.", "clipboard-copy-failure": "כשלון בהעתקה ללוח ההדבקה.",
"confirm-delete-generic-items": "האם אתה בטוח שברצונך למחוק את הפריטים הנבחרים?", "confirm-delete-generic-items": "למחוק את הפריטים שנבחרו?",
"organizers": "מארגנים", "organizers": "מארגנים",
"caution": "זהירות", "caution": "זהירות",
"show-advanced": "הצג הגדרות מתקדמות", "show-advanced": "הצג הגדרות מתקדמות",
@ -220,7 +220,7 @@
"date-updated": "תאריך עדכון" "date-updated": "תאריך עדכון"
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "האם את/ה בטוח/ה שברצונך למחוק את <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "למחוק את <b>{groupName}<b/>?",
"cannot-delete-default-group": "לא ניתן למחוק את קבוצת ברירת המחדל", "cannot-delete-default-group": "לא ניתן למחוק את קבוצת ברירת המחדל",
"cannot-delete-group-with-users": "לא ניתן למחוק קבוצה עם משתמשים", "cannot-delete-group-with-users": "לא ניתן למחוק קבוצה עם משתמשים",
"confirm-group-deletion": "אשר/י מחיקת קבוצה", "confirm-group-deletion": "אשר/י מחיקת קבוצה",
@ -322,7 +322,7 @@
"mealplan-updated": "תכנית ארוחה עודכנה", "mealplan-updated": "תכנית ארוחה עודכנה",
"mealplan-households-description": "אם לא נבחר משק בית, ניתן להוסיף מתכונים מכל משק בית", "mealplan-households-description": "אם לא נבחר משק בית, ניתן להוסיף מתכונים מכל משק בית",
"any-category": "כל קטגוריה", "any-category": "כל קטגוריה",
"any-tag": "כל תג", "any-tag": "כל תגית",
"any-household": "כל משק בית", "any-household": "כל משק בית",
"no-meal-plan-defined-yet": "עדיין לא הוגדרה תכנית ארוחה", "no-meal-plan-defined-yet": "עדיין לא הוגדרה תכנית ארוחה",
"no-meal-planned-for-today": "לא מתוכננת ארוחה להיום", "no-meal-planned-for-today": "לא מתוכננת ארוחה להיום",
@ -377,7 +377,7 @@
}, },
"nextcloud": { "nextcloud": {
"description": "ייבא מידע מספר המתכונים ב- Nextcould", "description": "ייבא מידע מספר המתכונים ב- Nextcould",
"description-long": "ייבוא מתכונים מ- Nextcould יכול להתבצע בקובץ zip שמכיל את המידע מ- Nextcloud. ראה את דוגמת מבנה התיקיות למטה כדי לוודא שניתן לייבא את המתכון.", "description-long": "ייבוא מתכונים מ-Nextcould יכול להתבצע בקובץ zip שמכיל את המידע. המבנה צריך להתאים לתיקייה לדוגמא למטה כדי להבטיח שניתן לייבא את המתכון.",
"title": "ספר בישול של Nextcloud" "title": "ספר בישול של Nextcloud"
}, },
"copymethat": { "copymethat": {
@ -400,8 +400,8 @@
"recipe-data-migrations-explanation": "ניתן לייבא מתכונים מאפליקציות תומכות אחרות אל Mealie. זו דרך מעולה להתחיל במילי.", "recipe-data-migrations-explanation": "ניתן לייבא מתכונים מאפליקציות תומכות אחרות אל Mealie. זו דרך מעולה להתחיל במילי.",
"coming-from-another-application-or-an-even-older-version-of-mealie": "הגעת מתוכנה אחרת או גרסה ישנה יותר של Mealie? מומלץ לבדוק מיגרציות ולראות אם ניתן לייבא את המידע שלך.", "coming-from-another-application-or-an-even-older-version-of-mealie": "הגעת מתוכנה אחרת או גרסה ישנה יותר של Mealie? מומלץ לבדוק מיגרציות ולראות אם ניתן לייבא את המידע שלך.",
"choose-migration-type": "בחר סוג מיגרציה", "choose-migration-type": "בחר סוג מיגרציה",
"tag-all-recipes": "תייג את כל המתכונים עם תגית {tag-name}", "tag-all-recipes": "תיוג את כל המתכונים עם תגית {tag-name}",
"nextcloud-text": "ייבוא מתכונים מ- Nextcould יכול להתבצע בקובץ zip שמכיל את המידע מ- Nextcloud. ראה את דוגמת מבנה התיקיות למטה כדי לוודא שניתן לייבא את המתכון.", "nextcloud-text": "ייבוא מתכונים מ-Nextcould יכול להתבצע בקובץ zip שמכיל את המידע. המבנה צריך להתאים לתיקייה לדוגמא למטה כדי להבטיח שניתן לייבא את המתכון.",
"chowdown-text": "Mealie תומכת באופן טבעי בפורמט Chowdown. יש להוריד את רפוזיטורי הקוד כ-zip ולהעלות אותו כאן.", "chowdown-text": "Mealie תומכת באופן טבעי בפורמט Chowdown. יש להוריד את רפוזיטורי הקוד כ-zip ולהעלות אותו כאן.",
"recipe-1": "מתכון 1", "recipe-1": "מתכון 1",
"recipe-2": "מתכון 2", "recipe-2": "מתכון 2",
@ -470,7 +470,7 @@
"comment-action": "הערה", "comment-action": "הערה",
"comment": "הערה", "comment": "הערה",
"comments": "הערות", "comments": "הערות",
"delete-confirmation": "האם את/ה בטוח/ה שברצונך למחוק את המתכון הזה?", "delete-confirmation": "למחוק את המתכון הזה?",
"delete-recipe": "מחיקת מתכון", "delete-recipe": "מחיקת מתכון",
"description": "תיאור", "description": "תיאור",
"disable-amount": "ביטול כמויות מרכיבים", "disable-amount": "ביטול כמויות מרכיבים",
@ -607,7 +607,7 @@
"should-translate-description": "תרגום המתכון לשפה שלי", "should-translate-description": "תרגום המתכון לשפה שלי",
"please-wait-image-procesing": "נה להמתין, התמונה עוברת עיבוץ. זה יכול לקחת זמן.", "please-wait-image-procesing": "נה להמתין, התמונה עוברת עיבוץ. זה יכול לקחת זמן.",
"bulk-url-import": "ייבוא מספר לינקים", "bulk-url-import": "ייבוא מספר לינקים",
"debug-scraper": "סורק דיבוג", "debug-scraper": "סורק לניפוי שגיאות",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "יצירת מתכון באמצעות שם. כל שמות המתכונים צריכים להיות שונים.", "create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "יצירת מתכון באמצעות שם. כל שמות המתכונים צריכים להיות שונים.",
"new-recipe-names-must-be-unique": "שם מתכון חדש חייב להיות ייחודי", "new-recipe-names-must-be-unique": "שם מתכון חדש חייב להיות ייחודי",
"scrape-recipe": "קריאת מתכון", "scrape-recipe": "קריאת מתכון",
@ -616,7 +616,7 @@
"scrape-recipe-suggest-bulk-importer": "נסה את יכולת קריאת רשימה", "scrape-recipe-suggest-bulk-importer": "נסה את יכולת קריאת רשימה",
"scrape-recipe-have-raw-html-or-json-data": "יש לך מידע גולמי ב-HTML או JSON?", "scrape-recipe-have-raw-html-or-json-data": "יש לך מידע גולמי ב-HTML או JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "ניתן לייבא ישירות ממידע גולמי", "scrape-recipe-you-can-import-from-raw-data-directly": "ניתן לייבא ישירות ממידע גולמי",
"import-original-keywords-as-tags": "ייבא שמות מפתח מקוריות כתגיות", "import-original-keywords-as-tags": "ייבוא שמות מפתח מקוריות כתגיות",
"stay-in-edit-mode": "השאר במצב עריכה", "stay-in-edit-mode": "השאר במצב עריכה",
"import-from-zip": "ייבא מקובץ", "import-from-zip": "ייבא מקובץ",
"import-from-zip-description": "ייבוא מתכון בודד שיוצא ממילי אחרת.", "import-from-zip-description": "ייבוא מתכון בודד שיוצא ממילי אחרת.",
@ -634,11 +634,11 @@
"bulk-import-process-has-started": "ייבוא קבוצה התחיל", "bulk-import-process-has-started": "ייבוא קבוצה התחיל",
"bulk-import-process-has-failed": "יבוא קבוצתי נכשל", "bulk-import-process-has-failed": "יבוא קבוצתי נכשל",
"report-deletion-failed": "מחיקת דוח נכשלה", "report-deletion-failed": "מחיקת דוח נכשלה",
"recipe-debugger": "דיבאגר למתכון", "recipe-debugger": "מנפה שגיאות למתכון",
"recipe-debugger-description": "ניתן להדביק פה קישור למתכון שברצונך לדבג. הכתובת תיסרק ע\"י סורק המתכונים והתוצאות יוצגו. אם לא חוזרות תוצאות, האתר לא נתמך ע\"י Mealie או ספריית הסריקה.", "recipe-debugger-description": "ניתן להדביק פה קישור למתכון שברצונך לנפות שגיאות עבורו. הכתובת תיסרק ע\"י סורק המתכונים והתוצאות יוצגו. אם לא חוזרות תוצאות, האתר לא נתמך ע\"י Mealie או ספריית הסריקה.",
"use-openai": "השתמש ב-OpenAI", "use-openai": "השתמש ב-OpenAI",
"recipe-debugger-use-openai-description": "ניתן להשתמש ב-OpenAI כדי לפענח את התוצאות במקום להסתמך על ספריית הסריקה. כאשר מייצרים מתכון באמצעות כתובת, זה נעשה אוטומטית אם ספריית הסריקה נכשלת, אך ניתן לבדוק זאת ידנית כאן.", "recipe-debugger-use-openai-description": "ניתן להשתמש ב-OpenAI כדי לפענח את התוצאות במקום להסתמך על ספריית הסריקה. כאשר מייצרים מתכון באמצעות כתובת, זה נעשה אוטומטית אם ספריית הסריקה נכשלת, אך ניתן לבדוק זאת ידנית כאן.",
"debug": "דיבאג", "debug": "ניפוי שגיאות",
"tree-view": "תצוגת עץ", "tree-view": "תצוגת עץ",
"recipe-yield": "תשואת מתכון", "recipe-yield": "תשואת מתכון",
"unit": "יחידה", "unit": "יחידה",
@ -701,7 +701,7 @@
"import-summary": "ייבא תקציר", "import-summary": "ייבא תקציר",
"partial-backup": "גיבוי חלקי", "partial-backup": "גיבוי חלקי",
"unable-to-delete-backup": "לא ניתן למחוק גיבוי.", "unable-to-delete-backup": "לא ניתן למחוק גיבוי.",
"experimental-description": "Backups are total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.", "experimental-description": "גיבויים הם תמונת מצב של מסד הנתונים ותיקיית המידע של האתר. זה כולל את כל המידע ולא ניתן להוריד חלק מהמידע. ניתן להתייחס אליהם כתמונת מצב של Mealie בזמן מסוים. הגיבויים הם דרך לייבוא ולייצוא מידע ללא תלות במסד הנתונים עצמו, או לגיבוי האתר למיקום חיצוני.",
"backup-restore": "גיבוי / שחזור", "backup-restore": "גיבוי / שחזור",
"back-restore-description": "שחזור מגיבוי זה ידרוס את המידע הקיים במסד הנתונים ובספריות האתר ויחליף אותם בזה הקיים בגיבוי. {cannot-be-undone} אם השחזור יצליח, המשתמש ינותק מהמערכת.", "back-restore-description": "שחזור מגיבוי זה ידרוס את המידע הקיים במסד הנתונים ובספריות האתר ויחליף אותם בזה הקיים בגיבוי. {cannot-be-undone} אם השחזור יצליח, המשתמש ינותק מהמערכת.",
"cannot-be-undone": "פעולה זו לא בלתי הפיכה - השתמש בזהירות.", "cannot-be-undone": "פעולה זו לא בלתי הפיכה - השתמש בזהירות.",
@ -809,7 +809,7 @@
"email-configured": "דואר אלקטרוני הוגדר", "email-configured": "דואר אלקטרוני הוגדר",
"email-test-results": "תוצאות בדיקת דואר אלקטרוני", "email-test-results": "תוצאות בדיקת דואר אלקטרוני",
"ready": "מוכן", "ready": "מוכן",
"not-ready": "לא מוכן - בדוק משתני סביבה", "not-ready": "לא מוכן - נא לבדוק משתני סביבה",
"succeeded": "הצליח", "succeeded": "הצליח",
"failed": "נכשל", "failed": "נכשל",
"general-about": "מידע כללי", "general-about": "מידע כללי",
@ -823,16 +823,16 @@
"server-side-base-url-error-text": "'כתובת בסיס' היא עדיין הערך ברירת המחדל בשרת ה- API. הדבר יגרום לבעיות עם קישורי התראות בשרת עבור אימייל וכו'.", "server-side-base-url-error-text": "'כתובת בסיס' היא עדיין הערך ברירת המחדל בשרת ה- API. הדבר יגרום לבעיות עם קישורי התראות בשרת עבור אימייל וכו'.",
"server-side-base-url-success-text": "קישור צד שרת אינו תואם לברירת המחדל", "server-side-base-url-success-text": "קישור צד שרת אינו תואם לברירת המחדל",
"ldap-ready": "LDAP מוכן", "ldap-ready": "LDAP מוכן",
"ldap-ready-error-text": "לא כל ערכי ה- LDAP מוגדרים. ניתן להתעלם אם אינך משתמשת באימות LDAP.", "ldap-ready-error-text": "לא כל הערכים הנחוצים ל-LDAP מוגדרים. ניתן להתעלם אם לא נעשה שימוש באימות LDAP.",
"ldap-ready-success-text": "כל משתני ה- LDAP הנחוצים מוגדרים.", "ldap-ready-success-text": "כל המשתנים הנחוצים ל-LDAP מוגדרים.",
"build": "בניה", "build": "בניה",
"recipe-scraper-version": "גרסת סורק המתכונים", "recipe-scraper-version": "גרסת סורק המתכונים",
"oidc-ready": "OIDC Ready", "oidc-ready": "OIDC מוכן",
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.", "oidc-ready-error-text": "לא כל הערכים הנחוצים ל-OIDC מוגדרים. ניתן להתעלם אם לא נעשה שימוש באימות OIDC.",
"oidc-ready-success-text": "Required OIDC variables are all set.", "oidc-ready-success-text": "כל המשתנים הנחוצים ל-OIDC מוגדרים.",
"openai-ready": "OpenAI Ready", "openai-ready": "OpenAI מוכן",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.", "openai-ready-error-text": "לא כל הערכים הנחוצים ל-OpenAI מוגדרים. ניתן להתעלם אם לא נעשה שימוש ב-OpenAI.",
"openai-ready-success-text": "Required OpenAI variables are all set." "openai-ready-success-text": "כל המשתנים הנחוצים ל-OpenAI מוגדרים."
}, },
"shopping-list": { "shopping-list": {
"all-lists": "כל הרשימות", "all-lists": "כל הרשימות",
@ -850,7 +850,7 @@
"linked-item-warning": "האובייקט הזה מקושר לאחד או יותר מתכונים. שינוי היחידות או האוכל יוביל לתוצאות בלתי צפויות בהוספה או הסרת מתכונים מהרשימה.", "linked-item-warning": "האובייקט הזה מקושר לאחד או יותר מתכונים. שינוי היחידות או האוכל יוביל לתוצאות בלתי צפויות בהוספה או הסרת מתכונים מהרשימה.",
"toggle-food": "הצג/הסתר אוכל", "toggle-food": "הצג/הסתר אוכל",
"manage-labels": "ניהול תוויות", "manage-labels": "ניהול תוויות",
"are-you-sure-you-want-to-delete-this-item": "האם אתה בטוח שברצונך למחוק פריט זה?", "are-you-sure-you-want-to-delete-this-item": "למחוק פריט זה?",
"copy-as-text": "העתק כטקסט", "copy-as-text": "העתק כטקסט",
"copy-as-markdown": "העתק כ-Markdown", "copy-as-markdown": "העתק כ-Markdown",
"delete-checked": "מחק מסומנים", "delete-checked": "מחק מסומנים",
@ -862,11 +862,11 @@
"items-checked-count": "לא נבחרו פריטים|פריט אחד נבחר|{count} פריטים נבחרו", "items-checked-count": "לא נבחרו פריטים|פריט אחד נבחר|{count} פריטים נבחרו",
"no-label": "ללא תווית", "no-label": "ללא תווית",
"completed-on": "הושלם ב- {date}", "completed-on": "הושלם ב- {date}",
"you-are-offline": "You are offline", "you-are-offline": "במצב לא מקוון",
"you-are-offline-description": "Not all features are available while offline. You can still add, modify, and remove items, but you will not be able to sync your changes to the server until you are back online.", "you-are-offline-description": "חלק מהיכולות אינן זמינות במצב לא מקוון. עדיין ניתן להוסיף, לשנות, ולהוריד פריטים, אך לא ניתן לסנכרן את השינויים שלך לשרת עד לחידוש החיבור.",
"are-you-sure-you-want-to-check-all-items": "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-uncheck-all-items": "לבטל את סימון כל הפריטים?",
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?" "are-you-sure-you-want-to-delete-checked-items": "למחוק את כל הפריטים המסומנים?"
}, },
"sidebar": { "sidebar": {
"all-recipes": "כל המתכונים", "all-recipes": "כל המתכונים",
@ -909,7 +909,7 @@
"tag-updated": "תגית עודכנה", "tag-updated": "תגית עודכנה",
"tags": "תגיות", "tags": "תגיות",
"untagged-count": "לא מתוייג {count}", "untagged-count": "לא מתוייג {count}",
"create-a-tag": "צור תגית", "create-a-tag": "יצירת תגית",
"tag-name": "שם תגית", "tag-name": "שם תגית",
"tag": "תגית" "tag": "תגית"
}, },
@ -925,8 +925,8 @@
}, },
"user": { "user": {
"admin": "אדמין", "admin": "אדמין",
"are-you-sure-you-want-to-delete-the-link": "האם את/ה בטוח/ה שברצונך למחוק את הלינק <b>{link}<b/>?", "are-you-sure-you-want-to-delete-the-link": "למחוק את הלינק <b>{link}<b/>?",
"are-you-sure-you-want-to-delete-the-user": "האם את/ה בטוח/ה שברצונך למחוק את המשתמש <b>{activeName} ID: {activeId}<b/>?", "are-you-sure-you-want-to-delete-the-user": "למחוק את המשתמש <b>{activeName} מזהה: {activeId}<b/>?",
"auth-method": "שיטת אימות", "auth-method": "שיטת אימות",
"confirm-link-deletion": "אשר מחיקת לינק", "confirm-link-deletion": "אשר מחיקת לינק",
"confirm-password": "אימות סיסמה", "confirm-password": "אימות סיסמה",
@ -1016,7 +1016,7 @@
"user-can-organize-group-data": "משתמש יכול לשנות מידע של קבוצה", "user-can-organize-group-data": "משתמש יכול לשנות מידע של קבוצה",
"enable-advanced-features": "אפשר אפשרויות מתקדמות", "enable-advanced-features": "אפשר אפשרויות מתקדמות",
"it-looks-like-this-is-your-first-time-logging-in": "נראה שזו ההתחברות הראשונה שלך.", "it-looks-like-this-is-your-first-time-logging-in": "נראה שזו ההתחברות הראשונה שלך.",
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "לא רוצה לראות את זה יותר? דאג לשנות את המייל של בהגדרות המשתמש!", "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "לא רוצה לראות את זה יותר? ניתן לשנות את המייל שלך בהגדרות המשתמש!",
"forgot-password": "שכחתי סיסמא", "forgot-password": "שכחתי סיסמא",
"forgot-password-text": "נא לספק כתובת דוא\"ל. אנו נשלח לך הודעת דוא\"ל לצורך איפוס הסיסמה שלך.", "forgot-password-text": "נא לספק כתובת דוא\"ל. אנו נשלח לך הודעת דוא\"ל לצורך איפוס הסיסמה שלך.",
"changes-reflected-immediately": "שינויים למשתמש זה ישתקפו מיידית." "changes-reflected-immediately": "שינויים למשתמש זה ישתקפו מיידית."
@ -1077,8 +1077,8 @@
}, },
"recipes": { "recipes": {
"purge-exports": "נקה ייצואים", "purge-exports": "נקה ייצואים",
"are-you-sure-you-want-to-delete-all-export-data": "האם אתה בטוח שאתה רוצה למחוק נתוני הייצוא?", "are-you-sure-you-want-to-delete-all-export-data": "למחוק את כל נתוני הייצוא?",
"confirm-delete-recipes": "האם אתה בטוח שאתה רוצה למחוק את המתכונים הבאים? פעולה זו בלתי הפיכה.", "confirm-delete-recipes": "למחוק את המתכונים שנבחרו? פעולה זו בלתי הפיכה.",
"the-following-recipes-selected-length-will-be-exported": "המתכונים הבאים ({0}) ייוצאו.", "the-following-recipes-selected-length-will-be-exported": "המתכונים הבאים ({0}) ייוצאו.",
"settings-chosen-explanation": "הגדרות שנבחרו מלבד האפשרויות הנעולות, יופעלו על כל המתכונים שנבחרו.", "settings-chosen-explanation": "הגדרות שנבחרו מלבד האפשרויות הנעולות, יופעלו על כל המתכונים שנבחרו.",
"selected-length-recipe-s-settings-will-be-updated": "הגדרות של {count} מתכונים יעודכנו.", "selected-length-recipe-s-settings-will-be-updated": "הגדרות של {count} מתכונים יעודכנו.",
@ -1097,10 +1097,10 @@
"source-unit-will-be-deleted": "יחידת המקור תמחק" "source-unit-will-be-deleted": "יחידת המקור תמחק"
}, },
"recipe-actions": { "recipe-actions": {
"recipe-actions-data": "Recipe Actions Data", "recipe-actions-data": "מידע על הפעולות במתכון",
"new-recipe-action": "New Recipe Action", "new-recipe-action": "פעולת-מתכון חדשה",
"edit-recipe-action": "Edit Recipe Action", "edit-recipe-action": "עריכת פעולת-מתכון",
"action-type": "Action Type" "action-type": "סוג פעולה"
}, },
"create-alias": "יצירת שם נרדף", "create-alias": "יצירת שם נרדף",
"manage-aliases": "נהל שמות נרדפים", "manage-aliases": "נהל שמות נרדפים",
@ -1118,9 +1118,9 @@
"category-data": "נתוני קטגוריה" "category-data": "נתוני קטגוריה"
}, },
"tags": { "tags": {
"new-tag": "טאג חדש", "new-tag": "תגית חדשה",
"edit-tag": "ערוך טאג", "edit-tag": "עריכת תגית",
"tag-data": "נתוני טאגים" "tag-data": "נתוני תגיות"
}, },
"tools": { "tools": {
"new-tool": "כלי חדש", "new-tool": "כלי חדש",
@ -1261,54 +1261,54 @@
"setup": { "setup": {
"first-time-setup": "הגדרה ראשונית", "first-time-setup": "הגדרה ראשונית",
"welcome-to-mealie-get-started": "ברוכים הבאים ל-Mealie! בואו נתחיל", "welcome-to-mealie-get-started": "ברוכים הבאים ל-Mealie! בואו נתחיל",
"already-set-up-bring-to-homepage": "I'm already set up, just bring me to the homepage", "already-set-up-bring-to-homepage": "כבר הגדרתי הכל, תעבירו אותי לעמוד הבית",
"common-settings-for-new-sites": "Here are some common settings for new sites", "common-settings-for-new-sites": "הגדרות נפוצות לאתרים חדשים יופיעו כאן",
"setup-complete": "Setup Complete!", "setup-complete": "ההגדרה הושלמה!",
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie", "here-are-a-few-things-to-help-you-get-started": "כמה דברים שיעזרו לך להתחיל להשתמש ב-Mealie",
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.", "restore-from-v1-backup": "יש לך גיבוי משרת Mealie v1? ניתן לשחזר אותו כאן.",
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others." "manage-profile-or-get-invite-link": "ניתן לנהל את הפרופיל שלך, או לשתף את לינק ההזמנה לאחרים."
}, },
"debug-openai-services": "Debug OpenAI Services", "debug-openai-services": "ניפוי שגיאות לשירותי OpenAI",
"debug-openai-services-description": "Use this page to debug OpenAI services. You can test your OpenAI connection and see the results here. If you have image services enabled, you can also provide an image.", "debug-openai-services-description": "עמוד זה ישמש לניפוי שגיאות בשירותי OpenAI. ניתן לבדוק חיבור ל-OpenAI ולראות את התוצאות כאן. אם איפשרת את שירותי התמונה, ניתן גם לספק תמונה.",
"run-test": "Run Test", "run-test": "הרצת בדיקה",
"test-results": "Test Results", "test-results": "תוצאות הבדיקה",
"group-delete-note": "Groups with users or households cannot be deleted", "group-delete-note": "לא ניתן למחוק קבוצות עם משתמשים או משקי בית",
"household-delete-note": "Households with users cannot be deleted" "household-delete-note": "לא ניתן למחוק משקי בית עם משתמשים"
}, },
"profile": { "profile": {
"welcome-user": "👋 Welcome, {0}!", "welcome-user": "👋 שלום, {0}!",
"description": "ניהול פרופיל, מתכונים והגדרות קבוצה.", "description": "ניהול פרופיל, מתכונים והגדרות קבוצה.",
"get-invite-link": "קבלת קישור להזמנה", "get-invite-link": "קבלת קישור להזמנה",
"get-public-link": "כתובת פומבית", "get-public-link": "כתובת פומבית",
"account-summary": "פירוט משתמש", "account-summary": "פירוט משתמש",
"account-summary-description": "Here's a summary of your group's information.", "account-summary-description": "הנה סיכום המידע של הקבוצה שלך.",
"group-statistics": "נתונים סטטיסטיים של קבוצה", "group-statistics": "נתונים סטטיסטיים של קבוצה",
"group-statistics-description": "סטטיסטיקות על הקבוצה שלך.", "group-statistics-description": "סטטיסטיקות על הקבוצה שלך.",
"household-statistics": "Household Statistics", "household-statistics": "סטטיסטיקות משק הבית",
"household-statistics-description": "Your Household Statistics provide some insight how you're using Mealie.", "household-statistics-description": "סטטיסטיקות משק הבית שלך מספקות תובנות לאיך אתם משתמשים ב-Mealie.",
"storage-capacity": "מקום אחסון", "storage-capacity": "מקום אחסון",
"storage-capacity-description": "מקום האחסון מחושב ע\"י סיכום התמונות והקבצים שהעלת.", "storage-capacity-description": "מקום האחסון מחושב ע\"י סיכום התמונות והקבצים שהעלת.",
"personal": "אישי", "personal": "אישי",
"personal-description": "These are settings that are personal to you. Changes here won't affect other users.", "personal-description": "הגדרות אלה אישיות עבורך. שינויים לא ישפיעו על משתמשים אחרים.",
"user-settings": "הגדרות משתמש", "user-settings": "הגדרות משתמש",
"user-settings-description": "Manage your preferences, change your password, and update your email.", "user-settings-description": "ניהול ההעדפות שלך, שינוי סיסמא, ועדכון כתובת מייל.",
"api-tokens-description": "Manage your API Tokens for access from external applications.", "api-tokens-description": "ניהול אסימוני API יאפשר גישה ליישומים חיצוניים.",
"group-description": "פריטים אלו שותפו עם קבוצתך. עריכתם תבצע שינוי עבור כל הקבוצה!", "group-description": "פריטים אלו שותפו עם קבוצתך. עריכתם תבצע שינוי עבור כל הקבוצה!",
"group-settings": "הגדרות קבוצה", "group-settings": "הגדרות קבוצה",
"group-settings-description": "Manage your common group settings, like privacy settings.", "group-settings-description": "ניהול הגדרות הקבוצות שלך, כגון הגדרות פרטיות.",
"household-description": "These items are shared within your household. Editing one of them will change it for the whole household!", "household-description": "פריטים אלה משותפים עם משק הבית שלך. עריכה תשנה אותם עבור משק הבית כולו!",
"household-settings": "Household Settings", "household-settings": "הגדרות משק הבית",
"household-settings-description": "Manage your household settings, like mealplan and privacy settings.", "household-settings-description": "ניהול הגדרות משק הבית שלך, כגון תכנון ארוחות והגדרות פרטיות.",
"cookbooks-description": "נהל אוסף של מתכונים וצור עבורם עמודים.", "cookbooks-description": "נהל אוסף של מתכונים וצור עבורם עמודים.",
"members": "חברים", "members": "חברים",
"members-description": "See who's in your household and manage their permissions.", "members-description": "צפייה בחברי משק הביתה שלך ובהרשאות שלהם.",
"webhooks-description": "הגדר webhooks אשר יופעלו בימים בהם יש לך תוכניות ארוחות.", "webhooks-description": "הגדר webhooks אשר יופעלו בימים בהם יש לך תוכניות ארוחות.",
"notifiers": "מתריעים", "notifiers": "מתריעים",
"notifiers-description": "הגדרת הודעות דואל והודעות בדחיפה אשר יופעלו עם אירועים ספציפיים.", "notifiers-description": "הגדרת הודעות דואל והודעות בדחיפה אשר יופעלו עם אירועים ספציפיים.",
"manage-data": "נהל נתונים", "manage-data": "נהל נתונים",
"manage-data-description": "Manage your Mealie data; Foods, Units, Categories, Tags and more.", "manage-data-description": "ניהול המידע שלך ב-Mealie; אוכל, יחידות מידה, קטגוריות, תגיות ועוד.",
"data-migrations": "ניוד נתונים", "data-migrations": "ניוד נתונים",
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown.", "data-migrations-description": "העברת המידע שלך מאפליקציות אחרות כגון Nextcloud Recipes ו-Chowdown.",
"email-sent": "דוא\"ל נשלח", "email-sent": "דוא\"ל נשלח",
"error-sending-email": "שגיאה בשליחת דוא\"ל", "error-sending-email": "שגיאה בשליחת דוא\"ל",
"personal-information": "פרטים אישיים", "personal-information": "פרטים אישיים",
@ -1326,7 +1326,7 @@
}, },
"cookbook": { "cookbook": {
"cookbooks": "ספרי בישול", "cookbooks": "ספרי בישול",
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.", "description": "ספרי בישול הם דרך נוספת לסידור מתכונים על ידי חיתוך של מתכונים, מארגנים, ומסננים נוספים. יצירת ספר בישול תוסיף רשומה לתפריט הצדדי וכל המתכונים שעונים לסינון יוצגו באופן אוטומטי.",
"public-cookbook": "ספר בישול פומבי", "public-cookbook": "ספר בישול פומבי",
"public-cookbook-description": "ספרי בישול ניתנים לשיתוף עם משתמשים מחוץ ל-Mealie ויופיעו בתוך עמוד הקבוצות.", "public-cookbook-description": "ספרי בישול ניתנים לשיתוף עם משתמשים מחוץ ל-Mealie ויופיעו בתוך עמוד הקבוצות.",
"filter-options": "אפשרויות סינון", "filter-options": "אפשרויות סינון",
@ -1336,31 +1336,31 @@
"require-all-tools": "זקוק לכל הכלים", "require-all-tools": "זקוק לכל הכלים",
"cookbook-name": "שם ספר בישול", "cookbook-name": "שם ספר בישול",
"cookbook-with-name": "ספר בישול {0}", "cookbook-with-name": "ספר בישול {0}",
"household-cookbook-name": "{0} Cookbook {1}", "household-cookbook-name": "{0} ספר בישול {1}",
"create-a-cookbook": "צור ספר בישול חדש", "create-a-cookbook": "צור ספר בישול חדש",
"cookbook": "ספר בישול" "cookbook": "ספר בישול"
}, },
"query-filter": { "query-filter": {
"logical-operators": { "logical-operators": {
"and": "AND", "and": "וגם",
"or": "OR" "or": "או"
}, },
"relational-operators": { "relational-operators": {
"equals": "equals", "equals": "שווה",
"does-not-equal": "does not equal", "does-not-equal": "לא שווה",
"is-greater-than": "is greater than", "is-greater-than": "גדול מ-",
"is-greater-than-or-equal-to": "is greater than or equal to", "is-greater-than-or-equal-to": "גדול או שווה ל-",
"is-less-than": "is less than", "is-less-than": "קטן מ-",
"is-less-than-or-equal-to": "is less than or equal to" "is-less-than-or-equal-to": "קטן או שווה ל-"
}, },
"relational-keywords": { "relational-keywords": {
"is": "is", "is": "זהה ל-",
"is-not": "is not", "is-not": "לא זהה ל-",
"is-one-of": "is one of", "is-one-of": "אחד מ-",
"is-not-one-of": "is not one of", "is-not-one-of": "לא אחד מ-",
"contains-all-of": "contains all of", "contains-all-of": "מכיל הכל מתוך",
"is-like": "is like", "is-like": "דומה ל-",
"is-not-like": "is not like" "is-not-like": "לא דומה לא-"
} }
} }
} }

View file

@ -182,7 +182,7 @@
"date": "日付", "date": "日付",
"id": "Id", "id": "Id",
"owner": "所有者", "owner": "所有者",
"change-owner": "Change Owner", "change-owner": "所有者の変更",
"date-added": "追加日", "date-added": "追加日",
"none": "なし", "none": "なし",
"run": "実行", "run": "実行",
@ -214,10 +214,10 @@
"confirm-delete-generic-items": "次のアイテムを本当に削除しますか?", "confirm-delete-generic-items": "次のアイテムを本当に削除しますか?",
"organizers": "収納", "organizers": "収納",
"caution": "注意", "caution": "注意",
"show-advanced": "Show Advanced", "show-advanced": "詳細を表示",
"add-field": "Add Field", "add-field": "フィールドを追加",
"date-created": "Date Created", "date-created": "作成日",
"date-updated": "Date Updated" "date-updated": "更新日"
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/> を削除しますか?", "are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/> を削除しますか?",
@ -356,7 +356,7 @@
"for-type-meal-types": "{0} の食事の種類", "for-type-meal-types": "{0} の食事の種類",
"meal-plan-rules": "食事プランのルール", "meal-plan-rules": "食事プランのルール",
"new-rule": "新しいルール", "new-rule": "新しいルール",
"meal-plan-rules-description": "You can create rules for auto selecting recipes for your meal plans. These rules are used by the server to determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same day/type constraints then the rule filters will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.", "meal-plan-rules-description": "食事プランのレシピを自動選択するためのルールを作成できます。これらのルールは、食事プランを作成するときに選択するレシピのランダムプールを決定するためにサーバーによって使用されます。ルールに同じ日/タイプの制約がある場合は、ルールフィルターが結合されることに注意してください。実際には、重複したルールを作成する必要はありませんが、重複して作成することは可能です。",
"new-rule-description": "食事プランの新しいルールを作成する場合は、特定の曜日および/または特定の種類の食事に適用されるルールを制限できます。 すべての日またはすべての食事タイプにルールを適用するには、その日または食事タイプのすべての可能な値に適用されるルールを \"すべて\" に設定できます。", "new-rule-description": "食事プランの新しいルールを作成する場合は、特定の曜日および/または特定の種類の食事に適用されるルールを制限できます。 すべての日またはすべての食事タイプにルールを適用するには、その日または食事タイプのすべての可能な値に適用されるルールを \"すべて\" に設定できます。",
"recipe-rules": "レシピのルール", "recipe-rules": "レシピのルール",
"applies-to-all-days": "すべての日に適用", "applies-to-all-days": "すべての日に適用",
@ -1272,8 +1272,8 @@
"debug-openai-services-description": "このページを使用して、OpenAIサービスをデバッグします。ここでOpenAI接続をテストし、結果を確認できます。画像サービスが有効になっている場合は、画像を提供することもできます。", "debug-openai-services-description": "このページを使用して、OpenAIサービスをデバッグします。ここでOpenAI接続をテストし、結果を確認できます。画像サービスが有効になっている場合は、画像を提供することもできます。",
"run-test": "テスト実行", "run-test": "テスト実行",
"test-results": "テスト結果", "test-results": "テスト結果",
"group-delete-note": "Groups with users or households cannot be deleted", "group-delete-note": "ユーザーまたは世帯を含むグループは削除できません",
"household-delete-note": "Households with users cannot be deleted" "household-delete-note": "ユーザーがいる世帯は削除できません"
}, },
"profile": { "profile": {
"welcome-user": "👋 ようこそ, {0}!", "welcome-user": "👋 ようこそ, {0}!",
@ -1326,7 +1326,7 @@
}, },
"cookbook": { "cookbook": {
"cookbooks": "料理本", "cookbooks": "料理本",
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.", "description": "料理本は、レシピのクロスセクション、オーガナイザー、その他のフィルターを作成してレシピを整理するもう 1 つの方法です。料理本を作成するとサイドバーにエントリが追加され、選択したフィルターが適用されたすべてのレシピが料理本に表示されます。",
"public-cookbook": "公開料理本", "public-cookbook": "公開料理本",
"public-cookbook-description": "公開料理本は非Mealieユーザーと共有でき、グループページに表示されます。", "public-cookbook-description": "公開料理本は非Mealieユーザーと共有でき、グループページに表示されます。",
"filter-options": "フィルタオプション", "filter-options": "フィルタオプション",
@ -1346,21 +1346,21 @@
"or": "OR" "or": "OR"
}, },
"relational-operators": { "relational-operators": {
"equals": "equals", "equals": "等しい",
"does-not-equal": "does not equal", "does-not-equal": "等しくない",
"is-greater-than": "is greater than", "is-greater-than": "より大きい",
"is-greater-than-or-equal-to": "is greater than or equal to", "is-greater-than-or-equal-to": "以上",
"is-less-than": "is less than", "is-less-than": "より小さい",
"is-less-than-or-equal-to": "is less than or equal to" "is-less-than-or-equal-to": "以下"
}, },
"relational-keywords": { "relational-keywords": {
"is": "is", "is": "",
"is-not": "is not", "is-not": "ではない",
"is-one-of": "is one of", "is-one-of": "のうちの1つです",
"is-not-one-of": "is not one of", "is-not-one-of": "のひとつではない",
"contains-all-of": "contains all of", "contains-all-of": "すべてを含む",
"is-like": "is like", "is-like": "次のようなものです",
"is-not-like": "is not like" "is-not-like": "というわけではありません"
} }
} }
} }

View file

@ -61,7 +61,7 @@
"new-version": "새로운 버전 사용 가능", "new-version": "새로운 버전 사용 가능",
"notification": "알림", "notification": "알림",
"refresh": "새로고침", "refresh": "새로고침",
"scheduled": "Scheduled", "scheduled": "예약됨",
"something-went-wrong": "문제가 발생했습니다!", "something-went-wrong": "문제가 발생했습니다!",
"subscribed-events": "구독한 이벤트", "subscribed-events": "구독한 이벤트",
"test-message-sent": "테스트 메시지가 전송됐습니다.", "test-message-sent": "테스트 메시지가 전송됐습니다.",
@ -76,7 +76,7 @@
"when-a-user-in-your-group-creates-a-new-mealplan": "When a user in your group creates a new mealplan", "when-a-user-in-your-group-creates-a-new-mealplan": "When a user in your group creates a new mealplan",
"shopping-list-events": "Shopping List Events", "shopping-list-events": "Shopping List Events",
"cookbook-events": "Cookbook Events", "cookbook-events": "Cookbook Events",
"tag-events": "Tag Events", "tag-events": "Tag 이벤트",
"category-events": "Category Events", "category-events": "Category Events",
"when-a-new-user-joins-your-group": "When a new user joins your group", "when-a-new-user-joins-your-group": "When a new user joins your group",
"recipe-events": "Recipe Events" "recipe-events": "Recipe Events"
@ -118,7 +118,7 @@
"json": "JSON", "json": "JSON",
"keyword": "키워드", "keyword": "키워드",
"link-copied": "링크 복사됨", "link-copied": "링크 복사됨",
"loading": "Loading", "loading": "불러오는 중",
"loading-events": "이벤트를 불러오는 중", "loading-events": "이벤트를 불러오는 중",
"loading-recipe": "레시피 로딩 중...", "loading-recipe": "레시피 로딩 중...",
"loading-ocr-data": "Loading OCR data...", "loading-ocr-data": "Loading OCR data...",
@ -146,49 +146,49 @@
"save": "저장", "save": "저장",
"settings": "설정", "settings": "설정",
"share": "공유", "share": "공유",
"show-all": "Show All", "show-all": "모두 보기",
"shuffle": "섞기", "shuffle": "섞기",
"sort": "정렬", "sort": "정렬",
"sort-ascending": "Sort Ascending", "sort-ascending": "오름차순 정렬",
"sort-descending": "Sort Descending", "sort-descending": "내림차순으로 정렬",
"sort-alphabetically": "알파벳순", "sort-alphabetically": "알파벳순",
"status": "상태", "status": "상태",
"subject": "제목", "subject": "제목",
"submit": "제출", "submit": "제출",
"success-count": "성공: {count}", "success-count": "성공: {count}",
"sunday": "일요일", "sunday": "일요일",
"system": "System", "system": "시스템",
"templates": "Templates:", "templates": "템플릿:",
"test": "Test", "test": "테스트",
"themes": "Themes", "themes": "테마",
"thursday": "목요일", "thursday": "목요일",
"title": "Title", "title": "제목",
"token": "Token", "token": "토큰",
"tuesday": "화요일", "tuesday": "화요일",
"type": "Type", "type": "Type",
"update": "Update", "update": "업데이트",
"updated": "Updated", "updated": "업데이트됨",
"upload": "Upload", "upload": "업로드",
"url": "URL", "url": "URL",
"view": "View", "view": "View",
"wednesday": "수요일", "wednesday": "수요일",
"yes": "Yes", "yes": "",
"foods": "Foods", "foods": "음식",
"units": "Units", "units": "Units",
"back": "Back", "back": "뒤로",
"next": "Next", "next": "다음",
"start": "Start", "start": "시작",
"toggle-view": "Toggle View", "toggle-view": "Toggle View",
"date": "Date", "date": "날짜",
"id": "Id", "id": "Id",
"owner": "작성자", "owner": "작성자",
"change-owner": "Change Owner", "change-owner": "Change Owner",
"date-added": "Date Added", "date-added": "추가된 날짜",
"none": "없음", "none": "없음",
"run": "실행", "run": "실행",
"menu": "메뉴", "menu": "메뉴",
"a-name-is-required": "이름은 필수 항목 입니다.", "a-name-is-required": "이름은 필수 항목 입니다.",
"delete-with-name": "Delete {name}", "delete-with-name": "{name} 삭제",
"confirm-delete-generic-with-name": "Are you sure you want to delete this {name}?", "confirm-delete-generic-with-name": "Are you sure you want to delete this {name}?",
"confirm-delete-own-admin-account": "Please note that you are trying to delete your own admin account! This action cannot be undone and will permanently delete your account?", "confirm-delete-own-admin-account": "Please note that you are trying to delete your own admin account! This action cannot be undone and will permanently delete your account?",
"organizer": "Organizer", "organizer": "Organizer",

View file

@ -1278,7 +1278,7 @@
"profile": { "profile": {
"welcome-user": "👋 Välkommen, {0}!", "welcome-user": "👋 Välkommen, {0}!",
"description": "Hantera dina profil, recept och gruppinställningar.", "description": "Hantera dina profil, recept och gruppinställningar.",
"get-invite-link": " inbjudningslänk", "get-invite-link": "Skapa inbjudningslänk",
"get-public-link": "Få offentlig länk", "get-public-link": "Få offentlig länk",
"account-summary": "Kontosammanfattning", "account-summary": "Kontosammanfattning",
"account-summary-description": "Här är en sammanfattning av din grupps information.", "account-summary-description": "Här är en sammanfattning av din grupps information.",

View file

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

View file

@ -323,7 +323,7 @@ export default defineComponent({
// we explicitly set booleans to false since forms don't POST unchecked boxes // we explicitly set booleans to false since forms don't POST unchecked boxes
const createTarget = ref<CreateIngredientUnit>({ const createTarget = ref<CreateIngredientUnit>({
name: "", name: "",
fraction: false, fraction: true,
useAbbreviation: false, useAbbreviation: false,
}); });

View file

@ -13,7 +13,7 @@
"no-recipes-match-your-rules": "규칙과 일치하는 Recipe가 없습니다" "no-recipes-match-your-rules": "규칙과 일치하는 Recipe가 없습니다"
}, },
"user": { "user": {
"user-updated": "User updated", "user-updated": "사용자 업데이트됨",
"password-updated": "비밀번호가 변경되었습니다", "password-updated": "비밀번호가 변경되었습니다",
"invalid-current-password": "현재 비밀번호가 잘못되었습니다", "invalid-current-password": "현재 비밀번호가 잘못되었습니다",
"ldap-update-password-unavailable": "사용자가 LDAP으로 제어되기 때문에 비밀번호를 변경할 수 없습니다" "ldap-update-password-unavailable": "사용자가 LDAP으로 제어되기 때문에 비밀번호를 변경할 수 없습니다"
@ -22,11 +22,11 @@
"report-deleted": "Report가 삭제됐습니다" "report-deleted": "Report가 삭제됐습니다"
}, },
"exceptions": { "exceptions": {
"permission_denied": "You do not have permission to perform this action", "permission_denied": "이 작업을 수행할 권한이 없습니다.",
"no-entry-found": "The requested resource was not found", "no-entry-found": "요청한 페이지를 찾을 수 없습니다. 주소를 정확히 입력했는지 확인해보세요.",
"integrity-error": "Database integrity error", "integrity-error": "Database integrity error",
"username-conflict-error": "This username is already taken", "username-conflict-error": "이미 사용 중인 사용자 이름입니다.",
"email-conflict-error": "This email is already in use" "email-conflict-error": "이 이메일은 이미 사용중입니다"
}, },
"notifications": { "notifications": {
"generic-created": "{name} was created", "generic-created": "{name} was created",
@ -48,23 +48,23 @@
"emails": { "emails": {
"password": { "password": {
"subject": "Mealie Forgot Password", "subject": "Mealie Forgot Password",
"header_text": "Forgot Password", "header_text": "비밀번호를 분실",
"message_top": "You have requested to reset your password.", "message_top": "비밀번호 재설정을 요청하셨습니다",
"message_bottom": "Please click the button above to reset your password.", "message_bottom": "비밀번호를 변경하려면 아래 버튼을 클릭하십시오.",
"button_text": "Reset Password" "button_text": "비밀번호 재설정"
}, },
"invitation": { "invitation": {
"subject": "Invitation to join Mealie", "subject": "Invitation to join Mealie",
"header_text": "You're Invited!", "header_text": "귀하는 초대되었습니다!",
"message_top": "You have been invited to join Mealie.", "message_top": "You have been invited to join Mealie.",
"message_bottom": "Please click the button above to accept the invitation.", "message_bottom": "Please click the button above to accept the invitation.",
"button_text": "Accept Invitation" "button_text": "초대 수락"
}, },
"test": { "test": {
"subject": "Mealie Test Email", "subject": "Mealie Test Email",
"header_text": "Test Email", "header_text": "테스트 이메일",
"message_top": "This is a test email.", "message_top": "이것은 테스트용 이메일입니다.",
"message_bottom": "Please click the button above to test the email.", "message_bottom": "비밀번호를 변경하려면 아래 버튼을 클릭하십시오.",
"button_text": "Open Mealie" "button_text": "Open Mealie"
} }
} }

View file

@ -543,7 +543,7 @@
}, },
"scallion": { "scallion": {
"name": "salladslök", "name": "salladslök",
"plural_name": "scallions" "plural_name": "salladslökar"
}, },
"seafood": { "seafood": {
"name": "fisk och skaldjur" "name": "fisk och skaldjur"
@ -556,7 +556,7 @@
}, },
"shallot": { "shallot": {
"name": "schalottenlök", "name": "schalottenlök",
"plural_name": "shallots" "plural_name": "schalottenlökar"
}, },
"skate": { "skate": {
"name": "skate" "name": "skate"
@ -585,11 +585,11 @@
}, },
"spring-onion": { "spring-onion": {
"name": "vårlök", "name": "vårlök",
"plural_name": "spring onions" "plural_name": "vårlökar"
}, },
"squash": { "squash": {
"name": "squash", "name": "squash",
"plural_name": "squashes" "plural_name": "squash"
}, },
"squash-family": { "squash-family": {
"name": "squash-familj" "name": "squash-familj"
@ -612,7 +612,7 @@
}, },
"sweet-potato": { "sweet-potato": {
"name": "sötpotatis", "name": "sötpotatis",
"plural_name": "sweet potatoes" "plural_name": "sötpotatisar"
}, },
"sweetcorn": { "sweetcorn": {
"name": "sockermajs", "name": "sockermajs",
@ -633,7 +633,7 @@
}, },
"tomato": { "tomato": {
"name": "tomat", "name": "tomat",
"plural_name": "tomatoes" "plural_name": "tomater"
}, },
"trout": { "trout": {
"name": "öring" "name": "öring"
@ -650,7 +650,7 @@
}, },
"turnip": { "turnip": {
"name": "majrova", "name": "majrova",
"plural_name": "turnips" "plural_name": "majrovor"
}, },
"unrefined-sugar": { "unrefined-sugar": {
"name": "råsocker" "name": "råsocker"
@ -666,11 +666,11 @@
}, },
"watermelon": { "watermelon": {
"name": "vattenmelon", "name": "vattenmelon",
"plural_name": "watermelons" "plural_name": "vattenmeloner"
}, },
"white-mushroom": { "white-mushroom": {
"name": "schampinjon", "name": "schampinjon",
"plural_name": "white mushrooms" "plural_name": "champinjoner"
}, },
"white-sugar": { "white-sugar": {
"name": "strösocker" "name": "strösocker"
@ -687,6 +687,6 @@
}, },
"zucchini": { "zucchini": {
"name": "zucchini", "name": "zucchini",
"plural_name": "zucchinis" "plural_name": "zucchinins"
} }
} }

View file

@ -14,7 +14,7 @@ def main():
logger = root_logger.get_logger() logger = root_logger.get_logger()
with session_context() as session: with session_context() as session:
repos = AllRepositories(session) repos = AllRepositories(session, group_id=None, household_id=None)
user = repos.users.get_one(confirmed, "email") user = repos.users.get_one(confirmed, "email")

View file

@ -14,7 +14,7 @@ def main():
logger = root_logger.get_logger() logger = root_logger.get_logger()
with session_context() as session: with session_context() as session:
repos = AllRepositories(session) repos = AllRepositories(session, group_id=None, household_id=None)
user_service = UserService(repos) user_service = UserService(repos)
locked_users = user_service.get_locked_users() locked_users = user_service.get_locked_users()

View file

@ -1,13 +1,15 @@
import datetime import datetime
import os import os
import uuid import uuid
from logging import Logger
from os import path from os import path
from pathlib import Path from pathlib import Path
from textwrap import dedent
from typing import Any from typing import Any
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import ForeignKey, ForeignKeyConstraint, MetaData, Table, create_engine, insert, text from sqlalchemy import Connection, ForeignKey, ForeignKeyConstraint, MetaData, Table, create_engine, insert, text
from sqlalchemy.engine import base from sqlalchemy.engine import base
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -21,6 +23,36 @@ from mealie.services._base_service import BaseService
PROJECT_DIR = Path(__file__).parent.parent.parent.parent PROJECT_DIR = Path(__file__).parent.parent.parent.parent
class ForeignKeyDisabler:
def __init__(self, connection: Connection, dialect_name: str, *, logger: Logger | None = None):
self.connection = connection
self.is_postgres = dialect_name == "postgresql"
self.logger = logger
self._initial_fk_state: str | None = None
def __enter__(self):
if self.is_postgres:
self._initial_fk_state = self.connection.execute(text("SHOW session_replication_role;")).scalar()
self.connection.execute(text("SET session_replication_role = 'replica';"))
else:
self._initial_fk_state = self.connection.execute(text("PRAGMA foreign_keys;")).scalar()
self.connection.execute(text("PRAGMA foreign_keys = OFF;"))
def __exit__(self, exc_type, exc_val, exc_tb):
try:
if self.is_postgres:
initial_state = self._initial_fk_state or "origin"
self.connection.execute(text(f"SET session_replication_role = '{initial_state}';"))
else:
initial_state = self._initial_fk_state or "ON"
self.connection.execute(text(f"PRAGMA foreign_keys = {initial_state};"))
except Exception:
if self.logger:
self.logger.exception("Error when re-enabling foreign keys")
raise
class AlchemyExporter(BaseService): class AlchemyExporter(BaseService):
connection_str: str connection_str: str
engine: base.Engine engine: base.Engine
@ -175,6 +207,7 @@ class AlchemyExporter(BaseService):
del db_dump["alembic_version"] del db_dump["alembic_version"]
"""Restores all data from dictionary into the database""" """Restores all data from dictionary into the database"""
with self.engine.begin() as connection: with self.engine.begin() as connection:
with ForeignKeyDisabler(connection, self.engine.dialect.name, logger=self.logger):
data = self.convert_types(db_dump) data = self.convert_types(db_dump)
self.meta.reflect(bind=self.engine) self.meta.reflect(bind=self.engine)
@ -188,27 +221,28 @@ class AlchemyExporter(BaseService):
connection.execute(insert(table), rows) connection.execute(insert(table), rows)
if self.engine.dialect.name == "postgresql": if self.engine.dialect.name == "postgresql":
# Restore postgres sequence numbers # Restore postgres sequence numbers
connection.execute( sequences = [
text( ("api_extras_id_seq", "api_extras"),
""" ("group_meal_plans_id_seq", "group_meal_plans"),
SELECT SETVAL('api_extras_id_seq', (SELECT MAX(id) FROM api_extras)); ("ingredient_food_extras_id_seq", "ingredient_food_extras"),
SELECT SETVAL('group_meal_plans_id_seq', (SELECT MAX(id) FROM group_meal_plans)); ("invite_tokens_id_seq", "invite_tokens"),
SELECT SETVAL('ingredient_food_extras_id_seq', (SELECT MAX(id) FROM ingredient_food_extras)); ("long_live_tokens_id_seq", "long_live_tokens"),
SELECT SETVAL('invite_tokens_id_seq', (SELECT MAX(id) FROM invite_tokens)); ("notes_id_seq", "notes"),
SELECT SETVAL('long_live_tokens_id_seq', (SELECT MAX(id) FROM long_live_tokens)); ("password_reset_tokens_id_seq", "password_reset_tokens"),
SELECT SETVAL('notes_id_seq', (SELECT MAX(id) FROM notes)); ("recipe_assets_id_seq", "recipe_assets"),
SELECT SETVAL('password_reset_tokens_id_seq', (SELECT MAX(id) FROM password_reset_tokens)); ("recipe_ingredient_ref_link_id_seq", "recipe_ingredient_ref_link"),
SELECT SETVAL('recipe_assets_id_seq', (SELECT MAX(id) FROM recipe_assets)); ("recipe_nutrition_id_seq", "recipe_nutrition"),
SELECT SETVAL('recipe_ingredient_ref_link_id_seq', (SELECT MAX(id) FROM recipe_ingredient_ref_link)); ("recipe_settings_id_seq", "recipe_settings"),
SELECT SETVAL('recipe_nutrition_id_seq', (SELECT MAX(id) FROM recipe_nutrition)); ("recipes_ingredients_id_seq", "recipes_ingredients"),
SELECT SETVAL('recipe_settings_id_seq', (SELECT MAX(id) FROM recipe_settings)); ("server_tasks_id_seq", "server_tasks"),
SELECT SETVAL('recipes_ingredients_id_seq', (SELECT MAX(id) FROM recipes_ingredients)); ("shopping_list_extras_id_seq", "shopping_list_extras"),
SELECT SETVAL('server_tasks_id_seq', (SELECT MAX(id) FROM server_tasks)); ("shopping_list_item_extras_id_seq", "shopping_list_item_extras"),
SELECT SETVAL('shopping_list_extras_id_seq', (SELECT MAX(id) FROM shopping_list_extras)); ]
SELECT SETVAL('shopping_list_item_extras_id_seq', (SELECT MAX(id) FROM shopping_list_item_extras));
""" sql = "\n".join(
) [f"SELECT SETVAL('{seq}', (SELECT MAX(id) FROM {table}));" for seq, table in sequences]
) )
connection.execute(text(dedent(sql)))
# Re-init database to finish migrations # Re-init database to finish migrations
init_db.main() init_db.main()

70
poetry.lock generated
View file

@ -627,13 +627,13 @@ cli = ["requests"]
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.115.3" version = "0.115.4"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "fastapi-0.115.3-py3-none-any.whl", hash = "sha256:8035e8f9a2b0aa89cea03b6c77721178ed5358e1aea4cd8570d9466895c0638c"}, {file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"},
{file = "fastapi-0.115.3.tar.gz", hash = "sha256:c091c6a35599c036d676fa24bd4a6e19fa30058d93d950216cdc672881f6f7db"}, {file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"},
] ]
[package.dependencies] [package.dependencies]
@ -1464,13 +1464,13 @@ pyyaml = ">=5.1"
[[package]] [[package]]
name = "mkdocs-material" name = "mkdocs-material"
version = "9.5.42" version = "9.5.43"
description = "Documentation that simply works" description = "Documentation that simply works"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "mkdocs_material-9.5.42-py3-none-any.whl", hash = "sha256:452a7c5d21284b373f36b981a2cbebfff59263feebeede1bc28652e9c5bbe316"}, {file = "mkdocs_material-9.5.43-py3-none-any.whl", hash = "sha256:4aae0664c456fd12837a3192e0225c17960ba8bf55d7f0a7daef7e4b0b914a34"},
{file = "mkdocs_material-9.5.42.tar.gz", hash = "sha256:92779b5e9b5934540c574c11647131d217dc540dce72b05feeda088c8eb1b8f2"}, {file = "mkdocs_material-9.5.43.tar.gz", hash = "sha256:83be7ff30b65a1e4930dfa4ab911e75780a3afc9583d162692e434581cb46979"},
] ]
[package.dependencies] [package.dependencies]
@ -1598,13 +1598,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]] [[package]]
name = "openai" name = "openai"
version = "1.52.2" version = "1.53.0"
description = "The official Python library for the openai API" description = "The official Python library for the openai API"
optional = false optional = false
python-versions = ">=3.7.1" python-versions = ">=3.7.1"
files = [ files = [
{file = "openai-1.52.2-py3-none-any.whl", hash = "sha256:57e9e37bc407f39bb6ec3a27d7e8fb9728b2779936daa1fcf95df17d3edfaccc"}, {file = "openai-1.53.0-py3-none-any.whl", hash = "sha256:20f408c32fc5cb66e60c6882c994cdca580a5648e10045cd840734194f033418"},
{file = "openai-1.52.2.tar.gz", hash = "sha256:87b7d0f69d85f5641678d414b7ee3082363647a5c66a462ed7f3ccb59582da0d"}, {file = "openai-1.53.0.tar.gz", hash = "sha256:be2c4e77721b166cce8130e544178b7d579f751b4b074ffbaade3854b6f85ec5"},
] ]
[package.dependencies] [package.dependencies]
@ -2174,13 +2174,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]] [[package]]
name = "pydantic-settings" name = "pydantic-settings"
version = "2.6.0" version = "2.6.1"
description = "Settings management using Pydantic" description = "Settings management using Pydantic"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic_settings-2.6.0-py3-none-any.whl", hash = "sha256:4a819166f119b74d7f8c765196b165f95cc7487ce58ea27dec8a5a26be0970e0"}, {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"},
{file = "pydantic_settings-2.6.0.tar.gz", hash = "sha256:44a1804abffac9e6a30372bb45f6cafab945ef5af25e66b1c634c01dd39e0188"}, {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"},
] ]
[package.dependencies] [package.dependencies]
@ -2413,13 +2413,13 @@ pyasn1_modules = ">=0.1.5"
[[package]] [[package]]
name = "python-multipart" name = "python-multipart"
version = "0.0.12" version = "0.0.17"
description = "A streaming multipart parser for Python" description = "A streaming multipart parser for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "python_multipart-0.0.12-py3-none-any.whl", hash = "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf"}, {file = "python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d"},
{file = "python_multipart-0.0.12.tar.gz", hash = "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb"}, {file = "python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538"},
] ]
[[package]] [[package]]
@ -2815,29 +2815,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.7.1" version = "0.7.2"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, {file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"},
{file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, {file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"},
{file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, {file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"},
{file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, {file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
{file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, {file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"},
{file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, {file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"},
{file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, {file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"},
{file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, {file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"},
{file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, {file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"},
{file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, {file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"},
{file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, {file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"},
{file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, {file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
] ]
[[package]] [[package]]
@ -3416,4 +3416,4 @@ pgsql = ["psycopg2-binary"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "455f4f29104e6614f7ff7e899cf2d63302cd84dd693b59ee17d48ca05fd39543" content-hash = "956580fac81cd8b933caf4e05d9578a1c5f50c815c5e57b6e8a8b7c8bd4761d3"

View file

@ -3,7 +3,7 @@ authors = ["Hayden <hay-kot@pm.me>"]
description = "A Recipe Manager" description = "A Recipe Manager"
license = "AGPL" license = "AGPL"
name = "mealie" name = "mealie"
version = "2.0.0" version = "2.1.0"
[tool.poetry.scripts] [tool.poetry.scripts]
start = "mealie.app:main" start = "mealie.app:main"
@ -31,7 +31,7 @@ python = "^3.10"
python-dateutil = "^2.8.2" python-dateutil = "^2.8.2"
python-dotenv = "^1.0.0" python-dotenv = "^1.0.0"
python-ldap = "^3.3.1" python-ldap = "^3.3.1"
python-multipart = "^0.0.12" python-multipart = "^0.0.17"
python-slugify = "^8.0.0" python-slugify = "^8.0.0"
recipe-scrapers = "^15.0.0" recipe-scrapers = "^15.0.0"
requests = "^2.31.0" requests = "^2.31.0"

View file

@ -16,15 +16,18 @@ backup_version_44e8d670719d_3 = CWD / "backups/backup-version-44e8d670719d-3.zip
backup_version_44e8d670719d_4 = CWD / "backups/backup-version-44e8d670719d-4.zip" backup_version_44e8d670719d_4 = CWD / "backups/backup-version-44e8d670719d-4.zip"
"""44e8d670719d: add extras to shopping lists, list items, and ingredient foods""" """44e8d670719d: add extras to shopping lists, list items, and ingredient foods"""
backup_version_ba1e4a6cfe99_1 = CWD / "backups/backup-version-ba1e4a6cfe99-1.zip"
"""ba1e4a6cfe99: added plural names and alias tables for foods and units"""
backup_version_bcfdad6b7355_1 = CWD / "backups/backup-version-bcfdad6b7355-1.zip" backup_version_bcfdad6b7355_1 = CWD / "backups/backup-version-bcfdad6b7355-1.zip"
"""bcfdad6b7355: remove tool name and slug unique contraints""" """bcfdad6b7355: remove tool name and slug unique contraints"""
backup_version_ba1e4a6cfe99_1 = CWD / "backups/backup-version-ba1e4a6cfe99-1.zip"
"""ba1e4a6cfe99: added plural names and alias tables for foods and units"""
backup_version_09aba125b57a_1 = CWD / "backups/backup-version-09aba125b57a-1.zip" backup_version_09aba125b57a_1 = CWD / "backups/backup-version-09aba125b57a-1.zip"
"""09aba125b57a: add OIDC auth method (Safari-mangled ZIP structure)""" """09aba125b57a: add OIDC auth method (Safari-mangled ZIP structure)"""
backup_version_86054b40fd06_1 = CWD / "backups/backup-version-86054b40fd06-1.zip"
"""86054b40fd06: added query_filter_string to cookbook and mealplan"""
migrations_paprika = CWD / "migrations/paprika.zip" migrations_paprika = CWD / "migrations/paprika.zip"
migrations_chowdown = CWD / "migrations/chowdown.zip" migrations_chowdown = CWD / "migrations/chowdown.zip"

Binary file not shown.

View file

@ -84,15 +84,17 @@ def test_database_restore():
test_data.backup_version_ba1e4a6cfe99_1, test_data.backup_version_ba1e4a6cfe99_1,
test_data.backup_version_bcfdad6b7355_1, test_data.backup_version_bcfdad6b7355_1,
test_data.backup_version_09aba125b57a_1, test_data.backup_version_09aba125b57a_1,
test_data.backup_version_86054b40fd06_1,
], ],
ids=[ ids=[
"44e8d670719d_1: add extras to shopping lists, list items, and ingredient foods", "44e8d670719d_1: add extras to shopping lists, list items, and ingredient foods",
"44e8d670719d_2: add extras to shopping lists, list items, and ingredient foods", "44e8d670719d_2: add extras to shopping lists, list items, and ingredient foods",
"44e8d670719d_3: add extras to shopping lists, list items, and ingredient foods", "44e8d670719d_3: add extras to shopping lists, list items, and ingredient foods",
"44e8d670719d_4: add extras to shopping lists, list items, and ingredient foods", "44e8d670719d_4: add extras to shopping lists, list items, and ingredient foods",
"ba1e4a6cfe99_1: added plural names and alias tables for foods and units",
"bcfdad6b7355_1: remove tool name and slug unique contraints", "bcfdad6b7355_1: remove tool name and slug unique contraints",
"09aba125b57a: add OIDC auth method (Safari-mangled ZIP structure)", "ba1e4a6cfe99_1: added plural names and alias tables for foods and units",
"09aba125b57a_1: add OIDC auth method (Safari-mangled ZIP structure)",
"86054b40fd06_1: added query_filter_string to cookbook and mealplan",
], ],
) )
def test_database_restore_data(backup_path: Path): def test_database_restore_data(backup_path: Path):

View file

@ -9,6 +9,7 @@ from mealie.core.settings.settings import AppSettings
def test_non_default_settings(monkeypatch): def test_non_default_settings(monkeypatch):
monkeypatch.setenv("DEFAULT_GROUP", "Test Group") monkeypatch.setenv("DEFAULT_GROUP", "Test Group")
monkeypatch.setenv("DEFAULT_HOUSEHOLD", "Test Household")
monkeypatch.setenv("API_PORT", "8000") monkeypatch.setenv("API_PORT", "8000")
monkeypatch.setenv("API_DOCS", "False") monkeypatch.setenv("API_DOCS", "False")
@ -16,6 +17,7 @@ def test_non_default_settings(monkeypatch):
app_settings = get_app_settings() app_settings = get_app_settings()
assert app_settings.DEFAULT_GROUP == "Test Group" assert app_settings.DEFAULT_GROUP == "Test Group"
assert app_settings.DEFAULT_HOUSEHOLD == "Test Household"
assert app_settings.API_PORT == 8000 assert app_settings.API_PORT == 8000
assert app_settings.API_DOCS is False assert app_settings.API_DOCS is False