Merge branch 'mealie-next' into fix/add-recipe-to-list-cross-household

This commit is contained in:
Michael Genson 2025-08-14 08:55:08 -05:00 committed by GitHub
commit 152a1f31a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
111 changed files with 3751 additions and 3242 deletions

View file

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 rev: v6.0.0
hooks: hooks:
- id: check-yaml - id: check-yaml
exclude: "mkdocs.yml" exclude: "mkdocs.yml"
@ -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.12.7 rev: v0.12.8
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format

View file

@ -1,7 +1,8 @@
############################################### ###############################################
# Frontend Build # Frontend Build
############################################### ###############################################
FROM node:20 AS frontend-builder FROM node:20@sha256:452293f0e5c9b7075829f2cd0dbd5fcae44d89cf5508b84a17bbe0857dc1a654 \
AS frontend-builder
WORKDIR /frontend WORKDIR /frontend
@ -20,7 +21,8 @@ RUN yarn generate
############################################### ###############################################
# Base Image - Python # Base Image - Python
############################################### ###############################################
FROM python:3.12-slim AS python-base FROM python:3.12-slim@sha256:2267adc248a477c1f1a852a07a5a224d42abe54c28aafa572efa157dfb001bba \
AS python-base
ENV MEALIE_HOME="/app" ENV MEALIE_HOME="/app"
@ -132,7 +134,7 @@ RUN apt-get update \
gosu \ gosu \
iproute2 \ iproute2 \
libldap-common \ libldap-common \
libldap-2.5 \ libldap2 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# create directory used for Docker Secrets # create directory used for Docker Secrets

View file

@ -1,5 +1,5 @@
<template> <template>
<v-list :class="tile ? 'd-flex flex-wrap background' : 'background'"> <v-list :class="tile ? 'd-flex flex-wrap background' : 'background'" style="background-color: transparent;">
<v-sheet <v-sheet
v-for="recipe, index in recipes" v-for="recipe, index in recipes"
:key="recipe.id" :key="recipe.id"

View file

@ -20,6 +20,7 @@
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
size="small" size="small"
variant="text"
class="ml-2 handle" class="ml-2 handle"
icon icon
v-bind="props" v-bind="props"

View file

@ -13,7 +13,7 @@
v-model="listItem.checked" v-model="listItem.checked"
hide-details hide-details
density="compact" density="compact"
class="mt-0" class="mt-0 flex-shrink-0"
color="null" color="null"
@change="$emit('checked', listItem)" @change="$emit('checked', listItem)"
/> />
@ -27,16 +27,6 @@
</div> </div>
</v-col> </v-col>
<v-spacer /> <v-spacer />
<v-col
v-if="label && showLabel"
cols="3"
class="text-right"
>
<MultiPurposeLabel
:label="label"
size="small"
/>
</v-col>
<v-col <v-col
cols="auto" cols="auto"
class="text-right" class="text-right"
@ -75,27 +65,6 @@
</template> </template>
<span>Toggle Recipes</span> <span>Toggle Recipes</span>
</v-tooltip> </v-tooltip>
<!-- Dummy button so the spacing is consistent when labels are enabled -->
<v-btn
v-else
size="small"
variant="text"
class="ml-2"
icon
disabled
/>
<v-btn
size="small"
variant="text"
class="ml-2 handle"
icon
v-bind="props"
>
<v-icon>
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-btn>
<v-btn <v-btn
size="small" size="small"
variant="text" variant="text"
@ -107,6 +76,17 @@
{{ $globals.icons.edit }} {{ $globals.icons.edit }}
</v-icon> </v-icon>
</v-btn> </v-btn>
<v-btn
size="small"
variant="text"
class="handle"
icon
v-bind="props"
>
<v-icon>
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-btn>
</template> </template>
<v-list density="compact"> <v-list density="compact">
<v-list-item <v-list-item
@ -177,7 +157,6 @@
import { useOnline } from "@vueuse/core"; import { useOnline } from "@vueuse/core";
import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue"; import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue";
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue"; import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
import MultiPurposeLabel from "./MultiPurposeLabel.vue";
import type { ShoppingListItemOut } from "~/lib/api/types/household"; import type { ShoppingListItemOut } from "~/lib/api/types/household";
import type { MultiPurposeLabelOut, MultiPurposeLabelSummary } from "~/lib/api/types/labels"; import type { MultiPurposeLabelOut, MultiPurposeLabelSummary } from "~/lib/api/types/labels";
import type { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe"; import type { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe";
@ -189,16 +168,12 @@ interface actions {
} }
export default defineNuxtComponent({ export default defineNuxtComponent({
components: { ShoppingListItemEditor, MultiPurposeLabel, RecipeList, RecipeIngredientListItem }, components: { ShoppingListItemEditor, RecipeList, RecipeIngredientListItem },
props: { props: {
modelValue: { modelValue: {
type: Object as () => ShoppingListItemOut, type: Object as () => ShoppingListItemOut,
required: true, required: true,
}, },
showLabel: {
type: Boolean,
default: false,
},
labels: { labels: {
type: Array as () => MultiPurposeLabelOut[], type: Array as () => MultiPurposeLabelOut[],
required: true, required: true,
@ -220,7 +195,7 @@ export default defineNuxtComponent({
setup(props, context) { setup(props, context) {
const i18n = useI18n(); const i18n = useI18n();
const displayRecipeRefs = ref(false); const displayRecipeRefs = ref(false);
const itemLabelCols = ref<string>(props.modelValue.checked ? "auto" : props.showLabel ? "4" : "6"); const itemLabelCols = ref<string>(props.modelValue.checked ? "auto" : "6");
const isOffline = computed(() => useOnline().value === false); const isOffline = computed(() => useOnline().value === false);
const contextMenu: actions[] = [ const contextMenu: actions[] = [
@ -305,7 +280,7 @@ export default defineNuxtComponent({
} }
listItem.value.recipeReferences.forEach((ref) => { listItem.value.recipeReferences.forEach((ref) => {
const recipe = props.recipes.get(ref.recipeId); const recipe = props.recipes?.get(ref.recipeId);
if (recipe) { if (recipe) {
recipeList.push(recipe); recipeList.push(recipe);
} }

View file

@ -37,7 +37,8 @@
:name="inputField.varName" :name="inputField.varName"
:disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))" :disabled="(inputField.disableUpdate && updateMode) || (!updateMode && inputField.disableCreate) || (disabledFields && disabledFields.includes(inputField.varName))"
:hint="inputField.hint" :hint="inputField.hint"
hide-details="auto" :hide-details="!inputField.hint"
:persistent-hint="!!inputField.hint"
density="comfortable" density="comfortable"
@change="emitBlur"> @change="emitBlur">
<template #label> <template #label>
@ -45,7 +46,7 @@
{{ inputField.label }} {{ inputField.label }}
</span> </span>
</template> </template>
</v-checkbox> </v-checkbox>
<!-- Text Field --> <!-- Text Field -->
<v-text-field <v-text-field
@ -97,8 +98,8 @@
:label="inputField.label" :label="inputField.label"
:name="inputField.varName" :name="inputField.varName"
:items="inputField.options" :items="inputField.options"
:item-title="inputField.itemText" item-title="text"
:item-value="inputField.itemValue" item-value="text"
:return-object="false" :return-object="false"
:hint="inputField.hint" :hint="inputField.hint"
density="comfortable" density="comfortable"
@ -107,10 +108,11 @@
@blur="emitBlur" @blur="emitBlur"
> >
<template #item="{ item }"> <template #item="{ item }">
<div> <v-list-item
<v-list-item-title>{{ item.raw.text }}</v-list-item-title> v-bind="props"
<v-list-item-subtitle>{{ item.raw.description }}</v-list-item-subtitle> :title="item.raw.text"
</div> :subtitle="item.raw.description"
/>
</template> </template>
</v-select> </v-select>

View file

@ -0,0 +1,50 @@
import type { ShoppingListItemOut } from "~/lib/api/types/household";
import { useCopyList } from "~/composables/use-copy";
type CopyTypes = "plain" | "markdown";
/**
* Composable for managing shopping list copy functionality
*/
export function useShoppingListCopy() {
const copy = useCopyList();
function copyListItems(itemsByLabel: { [key: string]: ShoppingListItemOut[] }, copyType: CopyTypes) {
const text: string[] = [];
Object.entries(itemsByLabel).forEach(([label, items], idx) => {
if (idx) {
text.push("");
}
text.push(formatCopiedLabelHeading(copyType, label));
items.forEach(item => text.push(formatCopiedListItem(copyType, item)));
});
copy.copyPlain(text);
}
function formatCopiedListItem(copyType: CopyTypes, item: ShoppingListItemOut): string {
const display = item.display || "";
switch (copyType) {
case "markdown":
return `- [ ] ${display}`;
default:
return display;
}
}
function formatCopiedLabelHeading(copyType: CopyTypes, label: string): string {
switch (copyType) {
case "markdown":
return `# ${label}`;
default:
return `[${label}]`;
}
}
return {
copyListItems,
formatCopiedListItem,
formatCopiedLabelHeading,
};
}

View file

@ -0,0 +1,263 @@
import type { ShoppingListOut, ShoppingListItemOut, ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/household";
import { useUserApi } from "~/composables/api";
import { uuid4 } from "~/composables/use-utils";
/**
* Composable for managing shopping list item CRUD operations
*/
export function useShoppingListCrud(
shoppingList: Ref<ShoppingListOut | null>,
loadingCounter: Ref<number>,
listItems: { unchecked: ShoppingListItemOut[]; checked: ShoppingListItemOut[] },
shoppingListItemActions: any,
refresh: () => void,
sortCheckedItems: (a: ShoppingListItemOut, b: ShoppingListItemOut) => number,
updateListItemOrder: () => void,
) {
const { t } = useI18n();
const userApi = useUserApi();
const createListItemData = ref<ShoppingListItemOut>(listItemFactory());
const localLabels = ref<ShoppingListMultiPurposeLabelOut[]>();
function listItemFactory(): ShoppingListItemOut {
return {
id: uuid4(),
shoppingListId: shoppingList.value?.id || "",
checked: false,
position: shoppingList.value?.listItems?.length || 1,
quantity: 0,
note: "",
labelId: undefined,
unitId: undefined,
foodId: undefined,
} as ShoppingListItemOut;
}
// Check/Uncheck All operations
function checkAllItems() {
let hasChanged = false;
shoppingList.value?.listItems?.forEach((item) => {
if (!item.checked) {
hasChanged = true;
item.checked = true;
}
});
if (hasChanged) {
updateUncheckedListItems();
}
}
function uncheckAllItems() {
let hasChanged = false;
shoppingList.value?.listItems?.forEach((item) => {
if (item.checked) {
hasChanged = true;
item.checked = false;
}
});
if (hasChanged) {
listItems.unchecked = [...listItems.unchecked, ...listItems.checked];
listItems.checked = [];
updateUncheckedListItems();
}
}
function deleteCheckedItems() {
const checked = shoppingList.value?.listItems?.filter(item => item.checked);
if (!checked || checked?.length === 0) {
return;
}
loadingCounter.value += 1;
deleteListItems(checked);
loadingCounter.value -= 1;
refresh();
}
function saveListItem(item: ShoppingListItemOut) {
if (!shoppingList.value) {
return;
}
// set a temporary updatedAt timestamp prior to refresh so it appears at the top of the checked items
item.updatedAt = new Date().toISOString();
// make updates reflect immediately
if (shoppingList.value.listItems) {
shoppingList.value.listItems.forEach((oldListItem: ShoppingListItemOut, idx: number) => {
if (oldListItem.id === item.id && shoppingList.value?.listItems) {
shoppingList.value.listItems[idx] = item;
}
});
// Immediately update checked/unchecked arrays for UI
listItems.unchecked = shoppingList.value.listItems.filter(i => !i.checked);
listItems.checked = shoppingList.value.listItems.filter(i => i.checked)
.sort(sortCheckedItems);
}
// Update the item if it's checked, otherwise updateUncheckedListItems will handle it
if (item.checked) {
shoppingListItemActions.updateItem(item);
}
updateListItemOrder();
updateUncheckedListItems();
}
function deleteListItem(item: ShoppingListItemOut) {
if (!shoppingList.value) {
return;
}
shoppingListItemActions.deleteItem(item);
// remove the item from the list immediately so the user sees the change
if (shoppingList.value.listItems) {
shoppingList.value.listItems = shoppingList.value.listItems.filter(itm => itm.id !== item.id);
}
refresh();
}
function deleteListItems(items: ShoppingListItemOut[]) {
if (!shoppingList.value) {
return;
}
items.forEach((item) => {
shoppingListItemActions.deleteItem(item);
});
// remove the items from the list immediately so the user sees the change
if (shoppingList.value?.listItems) {
const deletedItems = new Set(items.map(item => item.id));
shoppingList.value.listItems = shoppingList.value.listItems.filter(itm => !deletedItems.has(itm.id));
}
refresh();
}
function createListItem() {
if (!shoppingList.value) {
return;
}
if (!createListItemData.value.foodId && !createListItemData.value.note) {
// don't create an empty item
return;
}
loadingCounter.value += 1;
// make sure it's inserted into the end of the list, which may have been updated
createListItemData.value.position = shoppingList.value?.listItems?.length
? (shoppingList.value.listItems.reduce((a, b) => (a.position || 0) > (b.position || 0) ? a : b).position || 0) + 1
: 0;
createListItemData.value.createdAt = new Date().toISOString();
createListItemData.value.updatedAt = createListItemData.value.createdAt;
updateListItemOrder();
shoppingListItemActions.createItem(createListItemData.value);
loadingCounter.value -= 1;
if (shoppingList.value.listItems) {
// add the item to the list immediately so the user sees the change
shoppingList.value.listItems.push(createListItemData.value);
updateListItemOrder();
}
createListItemData.value = listItemFactory();
refresh();
}
function updateUncheckedListItems() {
if (!shoppingList.value?.listItems) {
return;
}
// Set position for unchecked items
listItems.unchecked.forEach((item: ShoppingListItemOut, idx: number) => {
item.position = idx;
shoppingListItemActions.updateItem(item);
});
refresh();
}
// Label management
function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) {
if (!shoppingList.value) {
return;
}
labelSettings.forEach((labelSetting, index) => {
labelSetting.position = index;
return labelSetting;
});
localLabels.value = labelSettings;
}
function cancelLabelOrder() {
loadingCounter.value -= 1;
if (!shoppingList.value) {
return;
}
// restore original state
localLabels.value = shoppingList.value.labelSettings;
}
async function saveLabelOrder(updateItemsByLabel: () => void) {
if (!shoppingList.value || !localLabels.value || (localLabels.value === shoppingList.value.labelSettings)) {
return;
}
loadingCounter.value += 1;
const { data } = await userApi.shopping.lists.updateLabelSettings(shoppingList.value.id, localLabels.value);
loadingCounter.value -= 1;
if (data) {
// update shoppingList labels using the API response
shoppingList.value.labelSettings = (data as ShoppingListOut).labelSettings;
updateItemsByLabel();
}
}
function toggleReorderLabelsDialog(reorderLabelsDialog: Ref<boolean>) {
// stop polling and populate localLabels
loadingCounter.value += 1;
reorderLabelsDialog.value = !reorderLabelsDialog.value;
localLabels.value = shoppingList.value?.labelSettings;
}
// Context menu actions
const contextActions = {
delete: "delete",
};
const contextMenu = [
{ title: t("general.delete"), action: contextActions.delete },
];
return {
createListItemData,
localLabels,
listItemFactory,
checkAllItems,
uncheckAllItems,
deleteCheckedItems,
saveListItem,
deleteListItem,
deleteListItems,
createListItem,
updateUncheckedListItems,
updateLabelOrder,
cancelLabelOrder,
saveLabelOrder,
toggleReorderLabelsDialog,
contextActions,
contextMenu,
};
}

View file

@ -0,0 +1,117 @@
import { useOnline, useIdle } from "@vueuse/core";
import type { ShoppingListOut } from "~/lib/api/types/household";
import { useShoppingListItemActions } from "~/composables/use-shopping-list-item-actions";
/**
* Composable for managing shopping list data fetching and polling
*/
export function useShoppingListData(listId: string, shoppingList: Ref<ShoppingListOut | null>, loadingCounter: Ref<number>) {
const isOffline = computed(() => useOnline().value === false);
const { idle } = useIdle(5 * 60 * 1000); // 5 minutes
const shoppingListItemActions = useShoppingListItemActions(listId);
async function fetchShoppingList() {
const data = await shoppingListItemActions.getList();
return data;
}
async function refresh(updateListItemOrder: () => void) {
loadingCounter.value += 1;
try {
await shoppingListItemActions.process();
}
catch (error) {
console.error(error);
}
let newListValue: typeof shoppingList.value = null;
try {
newListValue = await fetchShoppingList();
}
catch (error) {
console.error(error);
}
loadingCounter.value -= 1;
// only update the list with the new value if we're not loading, to prevent UI jitter
if (loadingCounter.value) {
return;
}
// Prevent overwriting local changes with stale backend data when offline
if (isOffline.value) {
// Do not update shoppingList.value from backend when offline
updateListItemOrder();
return;
}
// if we're not connected to the network, this will be null, so we don't want to clear the list
if (newListValue) {
shoppingList.value = newListValue;
}
updateListItemOrder();
}
// constantly polls for changes
async function pollForChanges(updateListItemOrder: () => void) {
// pause polling if the user isn't active or we're busy
if (idle.value || loadingCounter.value) {
return;
}
try {
await refresh(updateListItemOrder);
if (shoppingList.value) {
attempts = 0;
return;
}
// if the refresh was unsuccessful, the shopping list will be null, so we increment the attempt counter
attempts++;
}
catch {
attempts++;
}
// if we hit too many errors, stop polling
if (attempts >= maxAttempts) {
clearInterval(pollTimer);
}
}
// start polling
loadingCounter.value -= 1;
// max poll time = pollFrequency * maxAttempts = 24 hours
// we use a long max poll time since polling stops when the user is idle anyway
const pollFrequency = 5000;
const maxAttempts = 17280;
let attempts = 0;
let pollTimer: ReturnType<typeof setInterval>;
function startPolling(updateListItemOrder: () => void) {
pollForChanges(updateListItemOrder); // populate initial list
pollTimer = setInterval(() => {
pollForChanges(updateListItemOrder);
}, pollFrequency);
}
function stopPolling() {
if (pollTimer) {
clearInterval(pollTimer);
}
}
return {
isOffline,
fetchShoppingList,
refresh,
startPolling,
stopPolling,
shoppingListItemActions,
};
}

View file

@ -0,0 +1,73 @@
import { useToggle } from "@vueuse/core";
import type { ShoppingListOut, ShoppingListItemOut } from "~/lib/api/types/household";
/**
* Composable for managing shopping list label state and operations
*/
export function useShoppingListLabels(shoppingList: Ref<ShoppingListOut | null>) {
const { t } = useI18n();
const labelOpenState = ref<{ [key: string]: boolean }>({});
const [showChecked, toggleShowChecked] = useToggle(false);
const initializeLabelOpenStates = () => {
if (!shoppingList.value?.listItems) return;
const existingLabels = new Set(Object.keys(labelOpenState.value));
let hasChanges = false;
for (const item of shoppingList.value.listItems) {
const labelName = item.label?.name || t("shopping-list.no-label");
if (!existingLabels.has(labelName) && !(labelName in labelOpenState.value)) {
labelOpenState.value[labelName] = true;
hasChanges = true;
}
}
if (hasChanges) {
labelOpenState.value = { ...labelOpenState.value };
}
};
const labelNames = computed(() => {
return new Set(
shoppingList.value?.listItems
?.map(item => item.label?.name || t("shopping-list.no-label"))
.filter(Boolean) ?? [],
);
});
watch(labelNames, initializeLabelOpenStates, { immediate: true });
function toggleShowLabel(key: string) {
labelOpenState.value[key] = !labelOpenState.value[key];
}
function getLabelColor(item: ShoppingListItemOut | null) {
return item?.label?.color;
}
const presentLabels = computed(() => {
const labels: Array<{ id: string; name: string }> = [];
shoppingList.value?.listItems?.forEach((item) => {
if (item.labelId && item.label) {
labels.push({
name: item.label.name,
id: item.labelId,
});
}
});
return labels;
});
return {
labelOpenState,
showChecked,
toggleShowChecked,
toggleShowLabel,
getLabelColor,
presentLabels,
initializeLabelOpenStates,
};
}

View file

@ -0,0 +1,51 @@
import type { ShoppingListOut } from "~/lib/api/types/household";
import { useUserApi } from "~/composables/api";
/**
* Composable for managing shopping list recipe references
*/
export function useShoppingListRecipes(
shoppingList: Ref<ShoppingListOut | null>,
loadingCounter: Ref<number>,
recipeReferenceLoading: Ref<boolean>,
refresh: () => void,
) {
const userApi = useUserApi();
async function addRecipeReferenceToList(recipeId: string) {
if (!shoppingList.value || recipeReferenceLoading.value) {
return;
}
loadingCounter.value += 1;
recipeReferenceLoading.value = true;
const { data } = await userApi.shopping.lists.addRecipes(shoppingList.value.id, [{ recipeId }]);
recipeReferenceLoading.value = false;
loadingCounter.value -= 1;
if (data) {
refresh();
}
}
async function removeRecipeReferenceToList(recipeId: string) {
if (!shoppingList.value || recipeReferenceLoading.value) {
return;
}
loadingCounter.value += 1;
recipeReferenceLoading.value = true;
const { data } = await userApi.shopping.lists.removeRecipe(shoppingList.value.id, recipeId);
recipeReferenceLoading.value = false;
loadingCounter.value -= 1;
if (data) {
refresh();
}
}
return {
addRecipeReferenceToList,
removeRecipeReferenceToList,
};
}

View file

@ -0,0 +1,135 @@
import type { ShoppingListOut, ShoppingListItemOut } from "~/lib/api/types/household";
interface ListItemGroup {
position: number;
createdAt: string;
items: ShoppingListItemOut[];
}
/**
* Composable for managing shopping list item sorting and organization
*/
export function useShoppingListSorting() {
const { t } = useI18n();
function sortItems(a: ShoppingListItemOut | ListItemGroup, b: ShoppingListItemOut | ListItemGroup) {
// Sort by position ASC, then by createdAt ASC
const posA = a.position ?? 0;
const posB = b.position ?? 0;
if (posA !== posB) {
return posA - posB;
}
const createdA = a.createdAt ?? "";
const createdB = b.createdAt ?? "";
if (createdA !== createdB) {
return createdA < createdB ? -1 : 1;
}
return 0;
}
function groupAndSortListItemsByFood(shoppingList: ShoppingListOut) {
if (!shoppingList?.listItems?.length) {
return;
}
const checkedItemKey = "__checkedItem";
const listItemGroupsMap = new Map<string, ListItemGroup>();
listItemGroupsMap.set(checkedItemKey, { position: Number.MAX_SAFE_INTEGER, createdAt: "", items: [] });
// group items by checked status, food, or note
shoppingList.listItems.forEach((item) => {
const key = item.checked
? checkedItemKey
: item.food?.name
? item.food.name
: item.note || "";
const group = listItemGroupsMap.get(key);
if (!group) {
listItemGroupsMap.set(key, { position: item.position || 0, createdAt: item.createdAt || "", items: [item] });
}
else {
group.items.push(item);
}
});
const listItemGroups = Array.from(listItemGroupsMap.values());
listItemGroups.sort(sortItems);
// sort group items, then aggregate them
const sortedItems: ShoppingListItemOut[] = [];
let nextPosition = 0;
listItemGroups.forEach((listItemGroup) => {
listItemGroup.items.sort(sortItems);
listItemGroup.items.forEach((item) => {
item.position = nextPosition;
nextPosition += 1;
sortedItems.push(item);
});
});
shoppingList.listItems = sortedItems;
}
function sortListItems(shoppingList: ShoppingListOut) {
if (!shoppingList?.listItems?.length) {
return;
}
shoppingList.listItems.sort(sortItems);
}
function updateItemsByLabel(shoppingList: ShoppingListOut) {
const items: { [prop: string]: ShoppingListItemOut[] } = {};
const noLabelText = t("shopping-list.no-label");
const noLabel = [] as ShoppingListItemOut[];
shoppingList?.listItems?.forEach((item) => {
if (item.checked) {
return;
}
if (item.labelId) {
if (item.label && item.label.name in items) {
items[item.label.name].push(item);
}
else if (item.label) {
items[item.label.name] = [item];
}
}
else {
noLabel.push(item);
}
});
if (noLabel.length > 0) {
items[noLabelText] = noLabel;
}
// sort the map by label order
const orderedLabelNames = shoppingList?.labelSettings?.map(labelSetting => labelSetting.label.name);
if (!orderedLabelNames) {
return items;
}
const itemsSorted: { [prop: string]: ShoppingListItemOut[] } = {};
if (noLabelText in items) {
itemsSorted[noLabelText] = items[noLabelText];
}
orderedLabelNames.forEach((labelName) => {
if (labelName in items) {
itemsSorted[labelName] = items[labelName];
}
});
return itemsSorted;
}
return {
sortItems,
groupAndSortListItemsByFood,
sortListItems,
updateItemsByLabel,
};
}

View file

@ -0,0 +1,70 @@
import type { ShoppingListOut, ShoppingListItemOut } from "~/lib/api/types/household";
/**
* Composable for managing shopping list state and reactive data
*/
export function useShoppingListState() {
const shoppingList = ref<ShoppingListOut | null>(null);
const loadingCounter = ref(1);
const recipeReferenceLoading = ref(false);
const preserveItemOrder = ref(false);
// UI state
const edit = ref(false);
const threeDot = ref(false);
const reorderLabelsDialog = ref(false);
const createEditorOpen = ref(false);
// Dialog states
const state = reactive({
checkAllDialog: false,
uncheckAllDialog: false,
deleteCheckedDialog: false,
});
// Hydrate listItems from shoppingList.value?.listItems
const listItems = reactive({
unchecked: [] as ShoppingListItemOut[],
checked: [] as ShoppingListItemOut[],
});
function sortCheckedItems(a: ShoppingListItemOut, b: ShoppingListItemOut) {
if (a.updatedAt! === b.updatedAt!) {
return ((a.position || 0) > (b.position || 0)) ? -1 : 1;
}
return a.updatedAt! < b.updatedAt! ? 1 : -1;
}
watch(
() => shoppingList.value?.listItems,
(items) => {
listItems.unchecked = (items?.filter(item => !item.checked) ?? []);
listItems.checked = (items?.filter(item => item.checked)
.sort(sortCheckedItems) ?? []);
},
{ immediate: true },
);
const recipeMap = computed(() => new Map(
(shoppingList.value?.recipeReferences?.map(ref => ref.recipe) ?? [])
.map(recipe => [recipe.id || "", recipe])),
);
const recipeList = computed(() => Array.from(recipeMap.value.values()));
return {
shoppingList,
loadingCounter,
recipeReferenceLoading,
preserveItemOrder,
edit,
threeDot,
reorderLabelsDialog,
createEditorOpen,
state,
listItems,
recipeMap,
recipeList,
sortCheckedItems,
};
}

View file

@ -0,0 +1,194 @@
import type { ShoppingListItemOut } from "~/lib/api/types/household";
import { useShoppingListState } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-state";
import { useShoppingListData } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-data";
import { useShoppingListSorting } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-sorting";
import { useShoppingListLabels } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-labels";
import { useShoppingListCopy } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-copy";
import { useShoppingListCrud } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-crud";
import { useShoppingListRecipes } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-recipes";
/**
* Main composable that orchestrates all shopping list page functionality
*/
export function useShoppingListPage(listId: string) {
// Initialize state
const state = useShoppingListState();
const {
shoppingList,
loadingCounter,
recipeReferenceLoading,
preserveItemOrder,
listItems,
sortCheckedItems,
} = state;
// Initialize sorting functionality
const sorting = useShoppingListSorting();
const { groupAndSortListItemsByFood, sortListItems, updateItemsByLabel } = sorting;
// Track items organized by label
const itemsByLabel = ref<{ [key: string]: ShoppingListItemOut[] }>({});
function updateListItemOrder() {
if (!shoppingList.value) return;
if (!preserveItemOrder.value) {
groupAndSortListItemsByFood(shoppingList.value);
}
else {
sortListItems(shoppingList.value);
}
const labeledItems = updateItemsByLabel(shoppingList.value);
if (labeledItems) {
itemsByLabel.value = labeledItems;
}
}
// Initialize data management
const dataManager = useShoppingListData(listId, shoppingList, loadingCounter);
const { isOffline, refresh: baseRefresh, startPolling, stopPolling, shoppingListItemActions } = dataManager;
const refresh = () => baseRefresh(updateListItemOrder);
// Initialize shopping list labels
const labels = useShoppingListLabels(shoppingList);
// Initialize copy functionality
const copyManager = useShoppingListCopy();
// Initialize CRUD operations
const crud = useShoppingListCrud(
shoppingList,
loadingCounter,
listItems,
shoppingListItemActions,
refresh,
sortCheckedItems,
updateListItemOrder,
);
// Initialize recipe management
const recipes = useShoppingListRecipes(
shoppingList,
loadingCounter,
recipeReferenceLoading,
refresh,
);
// Handle item reordering by label
function updateIndexUncheckedByLabel(labelName: string, labeledUncheckedItems: ShoppingListItemOut[]) {
if (!itemsByLabel.value[labelName]) {
return;
}
// update this label's item order
itemsByLabel.value[labelName] = labeledUncheckedItems;
// reset list order of all items
const allUncheckedItems: ShoppingListItemOut[] = [];
for (const labelKey in itemsByLabel.value) {
allUncheckedItems.push(...itemsByLabel.value[labelKey]);
}
// since the user has manually reordered the list, we should preserve this order
preserveItemOrder.value = true;
// save changes
listItems.unchecked = allUncheckedItems;
listItems.checked = shoppingList.value?.listItems?.filter(item => item.checked) || [];
crud.updateUncheckedListItems();
}
// Dialog helpers
function openCheckAll() {
if (shoppingList.value?.listItems?.some(item => !item.checked)) {
state.state.checkAllDialog = true;
}
}
function openUncheckAll() {
if (shoppingList.value?.listItems?.some(item => item.checked)) {
state.state.uncheckAllDialog = true;
}
}
function openDeleteChecked() {
if (shoppingList.value?.listItems?.some(item => item.checked)) {
state.state.deleteCheckedDialog = true;
}
}
function checkAll() {
state.state.checkAllDialog = false;
crud.checkAllItems();
}
function uncheckAll() {
state.state.uncheckAllDialog = false;
crud.uncheckAllItems();
}
function deleteChecked() {
state.state.deleteCheckedDialog = false;
crud.deleteCheckedItems();
}
// Copy functionality wrapper
function copyListItems(copyType: "plain" | "markdown") {
copyManager.copyListItems(itemsByLabel.value, copyType);
}
// Label reordering helpers
function toggleReorderLabelsDialog() {
crud.toggleReorderLabelsDialog(state.reorderLabelsDialog);
}
async function saveLabelOrder() {
await crud.saveLabelOrder(() => {
const labeledItems = updateItemsByLabel(shoppingList.value!);
if (labeledItems) {
itemsByLabel.value = labeledItems;
}
});
}
// Lifecycle management
onMounted(() => {
startPolling(updateListItemOrder);
});
onUnmounted(() => {
stopPolling();
});
return {
itemsByLabel,
isOffline,
// Sub-composables
...state,
...labels,
...crud,
...recipes,
// Specialized functions
updateIndexUncheckedByLabel,
copyListItems,
// Dialog actions
openCheckAll,
openUncheckAll,
openDeleteChecked,
checkAll,
uncheckAll,
deleteChecked,
// Label management
toggleReorderLabelsDialog,
saveLabelOrder,
// Data refresh
refresh,
};
}

View file

@ -33,7 +33,7 @@ export const LOCALES = [
{ {
name: "Svenska (Swedish)", name: "Svenska (Swedish)",
value: "sv-SE", value: "sv-SE",
progress: 50, progress: 52,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -57,7 +57,7 @@ export const LOCALES = [
{ {
name: "Pусский (Russian)", name: "Pусский (Russian)",
value: "ru-RU", value: "ru-RU",
progress: 37, progress: 38,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -75,7 +75,7 @@ export const LOCALES = [
{ {
name: "Português do Brasil (Brazilian Portuguese)", name: "Português do Brasil (Brazilian Portuguese)",
value: "pt-BR", value: "pt-BR",
progress: 36, progress: 40,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -105,7 +105,7 @@ export const LOCALES = [
{ {
name: "Lietuvių (Lithuanian)", name: "Lietuvių (Lithuanian)",
value: "lt-LT", value: "lt-LT",
progress: 27, progress: 26,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -153,13 +153,13 @@ export const LOCALES = [
{ {
name: "Galego (Galician)", name: "Galego (Galician)",
value: "gl-ES", value: "gl-ES",
progress: 38, progress: 39,
dir: "ltr", dir: "ltr",
}, },
{ {
name: "Français (French)", name: "Français (French)",
value: "fr-FR", value: "fr-FR",
progress: 50, progress: 52,
dir: "ltr", dir: "ltr",
}, },
{ {
@ -219,7 +219,7 @@ export const LOCALES = [
{ {
name: "Dansk (Danish)", name: "Dansk (Danish)",
value: "da-DK", value: "da-DK",
progress: 39, progress: 40,
dir: "ltr", dir: "ltr",
}, },
{ {

View file

@ -4,7 +4,7 @@ import type { AutoFormItems } from "~/types/auto-forms";
export const useCommonSettingsForm = () => { export const useCommonSettingsForm = () => {
const i18n = useI18n(); const i18n = useI18n();
const commonSettingsForm: AutoFormItems = [ const commonSettingsForm = computed<AutoFormItems>(() => [
{ {
section: i18n.t("profile.group-settings"), section: i18n.t("profile.group-settings"),
label: i18n.t("group.enable-public-access"), label: i18n.t("group.enable-public-access"),
@ -21,7 +21,7 @@ export const useCommonSettingsForm = () => {
type: fieldTypes.BOOLEAN, type: fieldTypes.BOOLEAN,
rules: ["required"], rules: ["required"],
}, },
]; ]);
return { return {
commonSettingsForm, commonSettingsForm,

View file

@ -33,7 +33,6 @@ export interface UserRecipePreferences {
export interface UserShoppingListPreferences { export interface UserShoppingListPreferences {
viewAllLists: boolean; viewAllLists: boolean;
viewByLabel: boolean;
} }
export interface UserTimelinePreferences { export interface UserTimelinePreferences {
@ -129,7 +128,6 @@ export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
"shopping-list-preferences", "shopping-list-preferences",
{ {
viewAllLists: false, viewAllLists: false,
viewByLabel: true,
}, },
{ mergeDefaults: true }, { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref // we cast to a Ref because by default it will return an optional type ref

View file

@ -1,5 +1,5 @@
import { useAsyncValidator } from "~/composables/use-validators"; import { useAsyncValidator } from "~/composables/use-validators";
import type { VForm } from "~/types/vuetify"; import type { VForm } from "~/types/auto-forms";
import { usePublicApi } from "~/composables/api/api-client"; import { usePublicApi } from "~/composables/api/api-client";
const domAccountForm = ref<VForm | null>(null); const domAccountForm = ref<VForm | null>(null);
@ -13,12 +13,14 @@ const advancedOptions = ref(false);
export const useUserRegistrationForm = () => { export const useUserRegistrationForm = () => {
const i18n = useI18n(); const i18n = useI18n();
function safeValidate(form: Ref<VForm | null>) { async function safeValidate(form: Ref<VForm | null>) {
if (form.value && form.value.validate) { if (!form.value) {
return form.value.validate();
}
return false; return false;
} }
const result = await form.value.validate();
return result.valid;
}
// ================================================================ // ================================================================
// Provide Group Details // Provide Group Details
const publicApi = usePublicApi(); const publicApi = usePublicApi();
@ -45,11 +47,15 @@ export const useUserRegistrationForm = () => {
email, email,
advancedOptions, advancedOptions,
validate: async () => { validate: async () => {
if (!(validUsername.value && validEmail.value)) { if (!validUsername.value || !validEmail.value) {
await Promise.all([validateUsername(), validateEmail()]); await Promise.all([validateUsername(), validateEmail()]);
} }
return (safeValidate(domAccountForm as Ref<VForm>) && validUsername.value && validEmail.value); if (!validUsername.value || !validEmail.value) {
return false;
}
return await safeValidate(domAccountForm as Ref<VForm>);
}, },
reset: () => { reset: () => {
accountDetails.username.value = ""; accountDetails.username.value = "";

View file

@ -79,7 +79,8 @@
"tag-events": "Merker gebeurtenisse", "tag-events": "Merker gebeurtenisse",
"category-events": "Kategorie Gebeurtenisse", "category-events": "Kategorie Gebeurtenisse",
"when-a-new-user-joins-your-group": "Wanneer 'n nuwe gebruiker by jou groep aansluit", "when-a-new-user-joins-your-group": "Wanneer 'n nuwe gebruiker by jou groep aansluit",
"recipe-events": "Recipe Events" "recipe-events": "Recipe Events",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Add", "add": "Add",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Groep besonderhede", "group-details": "Groep besonderhede",
"group-details-description": "Voordat jy 'n rekening skep, moet jy eers 'n groep skep. Jy sal die enigste lid van die groep wees, maar jy kan later ander nooi. Lede van jou groep kan maaltydplanne, inkopielyste, resepte en meer deel!", "group-details-description": "Voordat jy 'n rekening skep, moet jy eers 'n groep skep. Jy sal die enigste lid van die groep wees, maar jy kan later ander nooi. Lede van jou groep kan maaltydplanne, inkopielyste, resepte en meer deel!",
"use-seed-data": "Gebruik voorbeelddata", "use-seed-data": "Gebruik voorbeelddata",
"use-seed-data-description": "Mealie bevat 'n versameling bestanddele, eenhede en etikette wat gebruik kan word om jou groep met nuttige data te vul om jou resepte te organiseer.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Rekening besonderhede" "account-details": "Rekening besonderhede"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "أحداث الوسم", "tag-events": "أحداث الوسم",
"category-events": "أحداث الفئة", "category-events": "أحداث الفئة",
"when-a-new-user-joins-your-group": "عندما ينضم مستخدم جديد إلى مجموعتك", "when-a-new-user-joins-your-group": "عندما ينضم مستخدم جديد إلى مجموعتك",
"recipe-events": "وصفات المناسبات" "recipe-events": "وصفات المناسبات",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "أضف", "add": "أضف",
@ -672,7 +673,9 @@
"not-linked-ingredients": "مكونات إضافية", "not-linked-ingredients": "مكونات إضافية",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "البحث عن الوصفات", "recipe-finder": "البحث عن الوصفات",
@ -1165,7 +1168,7 @@
"group-details": "تفاصيل المجموعة", "group-details": "تفاصيل المجموعة",
"group-details-description": "قبل إنشاء حساب ستحتاج إلى إنشاء مجموعة. المجموعة الخاصة بك سوف تحتوي عليك فقط، ولكن ستتمكن من دعوة الآخرين لاحقاً. يمكن لأعضاء مجموعتك مشاركة خطط الوجبات وقوائم التسوق والوصفات، والمزيد!", "group-details-description": "قبل إنشاء حساب ستحتاج إلى إنشاء مجموعة. المجموعة الخاصة بك سوف تحتوي عليك فقط، ولكن ستتمكن من دعوة الآخرين لاحقاً. يمكن لأعضاء مجموعتك مشاركة خطط الوجبات وقوائم التسوق والوصفات، والمزيد!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie يأتي بمجموعة من الأطعمة والوحدات والعلامات التي يمكن استخدامها لتزويد مجموعتك ببيانات مفيدة لتنظيم وصفاتك.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "تفاصيل الحساب" "account-details": "تفاصيل الحساب"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "История на етикетите", "tag-events": "История на етикетите",
"category-events": "Събития за категория", "category-events": "Събития за категория",
"when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група", "when-a-new-user-joins-your-group": "Когато потребител се присъедини към твоята потребителска група",
"recipe-events": "Събития на рецептата" "recipe-events": "Събития на рецептата",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Добави", "add": "Добави",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Подробности за групата", "group-details": "Подробности за групата",
"group-details-description": "Преди да създадете акаунт, ще трябва да създадете група. Вашата група ще съдържа само Вас, но ще можете да поканите други по-късно. Членовете във вашата група могат да споделят планове за хранене, списъци за пазаруване, рецепти и други!", "group-details-description": "Преди да създадете акаунт, ще трябва да създадете група. Вашата група ще съдържа само Вас, но ще можете да поканите други по-късно. Членовете във вашата група могат да споделят планове за хранене, списъци за пазаруване, рецепти и други!",
"use-seed-data": "Използвай предварителни данни", "use-seed-data": "Използвай предварителни данни",
"use-seed-data-description": "Mealie се доставя с колекция от продукти, мерни единици и етикети за попълване на Вашата група с полезни данни за организиране на рецептите.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Подробни данни за акаунта" "account-details": "Подробни данни за акаунта"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Esdeveniments de les etiquetes", "tag-events": "Esdeveniments de les etiquetes",
"category-events": "Esdeveniments de les categories", "category-events": "Esdeveniments de les categories",
"when-a-new-user-joins-your-group": "Quan un nou usuari s'afegeix al grup", "when-a-new-user-joins-your-group": "Quan un nou usuari s'afegeix al grup",
"recipe-events": "Esdeveniments de receptes" "recipe-events": "Esdeveniments de receptes",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Afegeix", "add": "Afegeix",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ingredients addicionals", "not-linked-ingredients": "Ingredients addicionals",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Cercador de receptes", "recipe-finder": "Cercador de receptes",
@ -1165,7 +1168,7 @@
"group-details": "Detalls del grup", "group-details": "Detalls del grup",
"group-details-description": "Abans de crear un compte heu de crear un grup. Al grup només hi serà vostè, però després podeu convidar d'altres. Els membres d'un grup poden compartir menús, llistes de la compra, receptes i molt més!", "group-details-description": "Abans de crear un compte heu de crear un grup. Al grup només hi serà vostè, però després podeu convidar d'altres. Els membres d'un grup poden compartir menús, llistes de la compra, receptes i molt més!",
"use-seed-data": "Afegiu dades predeterminades", "use-seed-data": "Afegiu dades predeterminades",
"use-seed-data-description": "Mealie ve configurat amb una col·lecció d'aliments, unitats i etiquetes que poden ser emprades pel vostre grup per a ajudar-vos a organitzar les vostres receptes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detalls del compte" "account-details": "Detalls del compte"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Události tagu", "tag-events": "Události tagu",
"category-events": "Události kategorie", "category-events": "Události kategorie",
"when-a-new-user-joins-your-group": "Když se nový uživatel připojí do vaší skupiny", "when-a-new-user-joins-your-group": "Když se nový uživatel připojí do vaší skupiny",
"recipe-events": "Události receptu" "recipe-events": "Události receptu",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Přidat", "add": "Přidat",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Další ingredience", "not-linked-ingredients": "Další ingredience",
"upload-another-image": "Nahrát další obrázek", "upload-another-image": "Nahrát další obrázek",
"upload-images": "Nahrát obrázky", "upload-images": "Nahrát obrázky",
"upload-more-images": "Nahrát více obrázků" "upload-more-images": "Nahrát více obrázků",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Vyhledávač receptů", "recipe-finder": "Vyhledávač receptů",
@ -1165,7 +1168,7 @@
"group-details": "Podrobnosti o skupině", "group-details": "Podrobnosti o skupině",
"group-details-description": "Než vytvoříte svůj účet, musíte vytvořit skupinu. Vaše skupina bude obsahovat pouze vás, ale později budete moct přizvat jiné uživatele. Členové vaší skupiny mohou sdílet jídelníčky, nákupní seznamy, recepty a další!", "group-details-description": "Než vytvoříte svůj účet, musíte vytvořit skupinu. Vaše skupina bude obsahovat pouze vás, ale později budete moct přizvat jiné uživatele. Členové vaší skupiny mohou sdílet jídelníčky, nákupní seznamy, recepty a další!",
"use-seed-data": "Použít Seed Data", "use-seed-data": "Použít Seed Data",
"use-seed-data-description": "Mealie obsahuje kolekci potravin, jednotek a popisků, které můžete použít ve své skupině pro organizování svých receptů.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Podrobnosti účtu" "account-details": "Podrobnosti účtu"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Tagbegivenheder", "tag-events": "Tagbegivenheder",
"category-events": "Kategoribegivenheder", "category-events": "Kategoribegivenheder",
"when-a-new-user-joins-your-group": "Når en ny bruger slutter sig til din gruppe", "when-a-new-user-joins-your-group": "Når en ny bruger slutter sig til din gruppe",
"recipe-events": "Hændelser for opskrifter" "recipe-events": "Hændelser for opskrifter",
"label-events": "Navn på begivenheder"
}, },
"general": { "general": {
"add": "Tilføj", "add": "Tilføj",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Yderligere ingredienser", "not-linked-ingredients": "Yderligere ingredienser",
"upload-another-image": "Upload et andet billede", "upload-another-image": "Upload et andet billede",
"upload-images": "Upload billeder", "upload-images": "Upload billeder",
"upload-more-images": "Upload flere billeder" "upload-more-images": "Upload flere billeder",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Opskriftssøger", "recipe-finder": "Opskriftssøger",
@ -1165,7 +1168,7 @@
"group-details": "Gruppeoplysninger", "group-details": "Gruppeoplysninger",
"group-details-description": "Før du opretter en konto, skal du oprette en gruppe. Din gruppe vil kun indeholde dig, men du vil kunne invitere andre senere. Medlemmer i din gruppe kan dele madplaner, indkøbslister, opskrifter og meget mere!", "group-details-description": "Før du opretter en konto, skal du oprette en gruppe. Din gruppe vil kun indeholde dig, men du vil kunne invitere andre senere. Medlemmer i din gruppe kan dele madplaner, indkøbslister, opskrifter og meget mere!",
"use-seed-data": "Anved standard data", "use-seed-data": "Anved standard data",
"use-seed-data-description": "Mealie indeholder som standard en samling af fødevarer, enheder og etiketter, som du kan bruge til at oprette og organisere dine opskrifter.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Kontodetaljer" "account-details": "Kontodetaljer"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Schlagwort-Ereignisse", "tag-events": "Schlagwort-Ereignisse",
"category-events": "Kategorie-Ereignisse", "category-events": "Kategorie-Ereignisse",
"when-a-new-user-joins-your-group": "Wenn ein neuer Benutzer deiner Gruppe beitritt", "when-a-new-user-joins-your-group": "Wenn ein neuer Benutzer deiner Gruppe beitritt",
"recipe-events": "Rezept-Ereignisse" "recipe-events": "Rezept-Ereignisse",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Hinzufügen", "add": "Hinzufügen",
@ -672,11 +673,13 @@
"not-linked-ingredients": "Zusätzliche Zutaten", "not-linked-ingredients": "Zusätzliche Zutaten",
"upload-another-image": "Weiteres Bild hochladen", "upload-another-image": "Weiteres Bild hochladen",
"upload-images": "Bilder hochladen", "upload-images": "Bilder hochladen",
"upload-more-images": "Weitere Bilder hochladen" "upload-more-images": "Weitere Bilder hochladen",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Rezept-Suche", "recipe-finder": "Rezept-Suche",
"recipe-finder-description": "Suche nach Rezepten basierend auf den Zutaten, die du zur Hand hast. Sie können auch nach verfügbaren Werkzeugen filtern und eine maximale Anzahl an fehlenden Zutaten oder Werkzeugen festlegen.", "recipe-finder-description": "Suche nach Rezepten basierend auf den Zutaten, die du zur Hand hast. Du kannst auch nach verfügbaren Werkzeugen filtern und eine maximale Anzahl an fehlenden Zutaten oder Werkzeugen festlegen.",
"selected-ingredients": "Ausgewählte Zutaten", "selected-ingredients": "Ausgewählte Zutaten",
"no-ingredients-selected": "Keine Zutaten ausgewählt", "no-ingredients-selected": "Keine Zutaten ausgewählt",
"missing": "Fehlend", "missing": "Fehlend",
@ -1165,7 +1168,7 @@
"group-details": "Gruppendetails", "group-details": "Gruppendetails",
"group-details-description": "Bevor du ein Konto erstellst, musst du eine Gruppe erstellen. Deine Gruppe wird nur dich enthalten, aber du kannst andere später einladen. Mitglieder in deiner Gruppe können Essenspläne, Einkaufslisten, Rezepte und vieles mehr teilen!", "group-details-description": "Bevor du ein Konto erstellst, musst du eine Gruppe erstellen. Deine Gruppe wird nur dich enthalten, aber du kannst andere später einladen. Mitglieder in deiner Gruppe können Essenspläne, Einkaufslisten, Rezepte und vieles mehr teilen!",
"use-seed-data": "Musterdaten", "use-seed-data": "Musterdaten",
"use-seed-data-description": "Mealie enthält eine Sammlung von Lebensmitteln, Maßeinheiten und Kategorien, die verwendet werden können, um deine Gruppe mit hilfreichen Daten für die Organisation deiner Rezepte zu füllen.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Kontoinformationen" "account-details": "Kontoinformationen"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Συμβάντα ετικέτας", "tag-events": "Συμβάντα ετικέτας",
"category-events": "Συμβάντα κατηγορίας", "category-events": "Συμβάντα κατηγορίας",
"when-a-new-user-joins-your-group": "Οταν ένας νέος χρήστης ενταχθεί στην ομάδα σας", "when-a-new-user-joins-your-group": "Οταν ένας νέος χρήστης ενταχθεί στην ομάδα σας",
"recipe-events": "Συμβάντα συνταγών" "recipe-events": "Συμβάντα συνταγών",
"label-events": "Ετικέτα συμβάντων"
}, },
"general": { "general": {
"add": "Προσθήκη", "add": "Προσθήκη",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Πρόσθετα συστατικά", "not-linked-ingredients": "Πρόσθετα συστατικά",
"upload-another-image": "Ανέβασμα άλλης εικόνας", "upload-another-image": "Ανέβασμα άλλης εικόνας",
"upload-images": "Ανέβασμα εικόνων", "upload-images": "Ανέβασμα εικόνων",
"upload-more-images": "Ανέβασμα περισσότερων εικόνων" "upload-more-images": "Ανέβασμα περισσότερων εικόνων",
"set-as-cover-image": "Ορισμός ως εικόνα εξώφυλλου συνταγής",
"cover-image": "Εικόνα εξώφυλλου"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Εύρεση συνταγών", "recipe-finder": "Εύρεση συνταγών",
@ -1165,7 +1168,7 @@
"group-details": "Λεπτομέρειες ομάδας", "group-details": "Λεπτομέρειες ομάδας",
"group-details-description": "Πριν δημιουργήσετε ένα λογαριασμό θα πρέπει να δημιουργήσετε μια ομάδα. Η ομάδα σας θα περιέχει μόνο εσάς, αλλά θα μπορείτε να προσκαλέσετε άλλους αργότερα. Μέλη της ομάδας σας μπορούν να μοιραστούν προγράμματα γευμάτων, λίστες για ψώνια, συνταγές και πολλά άλλα!", "group-details-description": "Πριν δημιουργήσετε ένα λογαριασμό θα πρέπει να δημιουργήσετε μια ομάδα. Η ομάδα σας θα περιέχει μόνο εσάς, αλλά θα μπορείτε να προσκαλέσετε άλλους αργότερα. Μέλη της ομάδας σας μπορούν να μοιραστούν προγράμματα γευμάτων, λίστες για ψώνια, συνταγές και πολλά άλλα!",
"use-seed-data": "Χρήση δεδομένων από τροφοδοσία", "use-seed-data": "Χρήση δεδομένων από τροφοδοσία",
"use-seed-data-description": "Το Mealie έρχεται με μια συλλογή Τροφίμων, Μονάδων και Ετικετών που μπορούν να χρησιμοποιηθούν για τη συμπλήρωση της ομάδας σας με χρήσιμα δεδομένα για την οργάνωση των συνταγών σας.", "use-seed-data-description": "Το Mealie έρχεται με μια συλλογή Τροφίμων, Μονάδων και Ετικετών που μπορούν να χρησιμοποιηθούν για τη συμπλήρωση της ομάδας σας με χρήσιμα δεδομένα για την οργάνωση των συνταγών σας. Αυτά είναι μεταφρασμένα στη γλώσσα που έχετε επιλέξει. Μπορείτε πάντα να προσθέσετε ή να τροποποιήσετε αυτά τα δεδομένα αργότερα.",
"account-details": "Λεπτομέρειες λογαριασμού" "account-details": "Λεπτομέρειες λογαριασμού"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Tag Events", "tag-events": "Tag Events",
"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",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Add", "add": "Add",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!", "group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -69,6 +69,7 @@
"new-notification": "New Notification", "new-notification": "New Notification",
"event-notifiers": "Event Notifiers", "event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)", "apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Enable Notifier", "enable-notifier": "Enable Notifier",
"what-events": "What events should this notifier subscribe to?", "what-events": "What events should this notifier subscribe to?",
"user-events": "User Events", "user-events": "User Events",
@ -79,7 +80,8 @@
"tag-events": "Tag Events", "tag-events": "Tag Events",
"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",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Add", "add": "Add",
@ -672,7 +674,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1169,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!", "group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Eventos de etiqueta", "tag-events": "Eventos de etiqueta",
"category-events": "Eventos de Categoría", "category-events": "Eventos de Categoría",
"when-a-new-user-joins-your-group": "Cuando un nuevo usuario se une a tu grupo", "when-a-new-user-joins-your-group": "Cuando un nuevo usuario se une a tu grupo",
"recipe-events": "Eventos de receta" "recipe-events": "Eventos de receta",
"label-events": "Eventos de etiqueta"
}, },
"general": { "general": {
"add": "Agregar", "add": "Agregar",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ingredientes adicionales", "not-linked-ingredients": "Ingredientes adicionales",
"upload-another-image": "Subir otra imagen", "upload-another-image": "Subir otra imagen",
"upload-images": "Subir imágenes", "upload-images": "Subir imágenes",
"upload-more-images": "Subir más imágenes" "upload-more-images": "Subir más imágenes",
"set-as-cover-image": "Establecer como imagen de portada de receta",
"cover-image": "Imagen de portada"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Buscador de recetas", "recipe-finder": "Buscador de recetas",
@ -1165,7 +1168,7 @@
"group-details": "Detalles del grupo", "group-details": "Detalles del grupo",
"group-details-description": "Antes de crear una cuenta, debe crear un grupo. En el grupo sólo estará usted, pero puede invitar a otros más tarde. Los miembros de un grupo pueden compartir menús, listas de la compra, recetas y más...", "group-details-description": "Antes de crear una cuenta, debe crear un grupo. En el grupo sólo estará usted, pero puede invitar a otros más tarde. Los miembros de un grupo pueden compartir menús, listas de la compra, recetas y más...",
"use-seed-data": "Utilizar datos de ejemplo", "use-seed-data": "Utilizar datos de ejemplo",
"use-seed-data-description": "Mealie incluye una colección de alimentos, unidades y etiquetas, que puede utilizar como ejemplo en su grupo, para ayudarle a organizar sus recetas.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Información de la cuenta" "account-details": "Información de la cuenta"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Märksõna sündmused", "tag-events": "Märksõna sündmused",
"category-events": "Kategooria sündmused", "category-events": "Kategooria sündmused",
"when-a-new-user-joins-your-group": "Kui uus kasutaja liitub sinu grupiga", "when-a-new-user-joins-your-group": "Kui uus kasutaja liitub sinu grupiga",
"recipe-events": "Retsepti sündmused" "recipe-events": "Retsepti sündmused",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Lisa", "add": "Lisa",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Lisa-koostisosad", "not-linked-ingredients": "Lisa-koostisosad",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Retsepti otsing", "recipe-finder": "Retsepti otsing",
@ -1165,7 +1168,7 @@
"group-details": "Grupi detailid", "group-details": "Grupi detailid",
"group-details-description": "Sa pead looma grupi enne konto loomist. Sinu grupis oled vaid sina, kuid sa saad kutsuda teisi sinna hiljem. Su grupi liikmed saavad jagada toitumisplaane, ostunimekirju, retsepte ja muud!", "group-details-description": "Sa pead looma grupi enne konto loomist. Sinu grupis oled vaid sina, kuid sa saad kutsuda teisi sinna hiljem. Su grupi liikmed saavad jagada toitumisplaane, ostunimekirju, retsepte ja muud!",
"use-seed-data": "Kasuta baasandmete infot.", "use-seed-data": "Kasuta baasandmete infot.",
"use-seed-data-description": "Mealsiga on kaasas toiduainete, ühikute ja siltide kogu, mida saate kasutada oma rühma täitmiseks kasuliku teabega retseptide korraldamiseks.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Konto üksikasjad" "account-details": "Konto üksikasjad"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Valitse tapahtumat", "tag-events": "Valitse tapahtumat",
"category-events": "Luokkatapahtumat", "category-events": "Luokkatapahtumat",
"when-a-new-user-joins-your-group": "Kun ryhmääsi liittyy uusi jäsen", "when-a-new-user-joins-your-group": "Kun ryhmääsi liittyy uusi jäsen",
"recipe-events": "Reseptitapahtumat" "recipe-events": "Reseptitapahtumat",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Lisää", "add": "Lisää",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Muut ainesosat", "not-linked-ingredients": "Muut ainesosat",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Reseptin etsijä", "recipe-finder": "Reseptin etsijä",
@ -1165,7 +1168,7 @@
"group-details": "Ryhmän tiedot", "group-details": "Ryhmän tiedot",
"group-details-description": "Ennen kuin luot tilin, sinun on luotava ryhmä. Ryhmässäsi on vain sinä, mutta voit kutsua muita myöhemmin. Ryhmäsi jäsenet voivat jakaa ateriasuunnitelmia, ostoslistoja, reseptejä ja paljon muuta!", "group-details-description": "Ennen kuin luot tilin, sinun on luotava ryhmä. Ryhmässäsi on vain sinä, mutta voit kutsua muita myöhemmin. Ryhmäsi jäsenet voivat jakaa ateriasuunnitelmia, ostoslistoja, reseptejä ja paljon muuta!",
"use-seed-data": "Käytä pohjatietoja", "use-seed-data": "Käytä pohjatietoja",
"use-seed-data-description": "Mealien mukana toimitetaan kokoelma elintarvikkeita, yksiköitä ja tarroja, joiden avulla voit täyttää ryhmäsi hyödyllisillä tiedoilla reseptien järjestämiseen.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Tilitiedot" "account-details": "Tilitiedot"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Événements des mots-clés", "tag-events": "Événements des mots-clés",
"category-events": "Événements de catégories", "category-events": "Événements de catégories",
"when-a-new-user-joins-your-group": "Lorsqu'un nouvel utilisateur rejoint votre groupe", "when-a-new-user-joins-your-group": "Lorsqu'un nouvel utilisateur rejoint votre groupe",
"recipe-events": "Événements de recette" "recipe-events": "Événements de recette",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Ajouter", "add": "Ajouter",
@ -472,7 +473,7 @@
"comment": "Commentaire", "comment": "Commentaire",
"comments": "Commentaires", "comments": "Commentaires",
"delete-confirmation": "Voulez-vous vraiment supprimer cette recette?", "delete-confirmation": "Voulez-vous vraiment supprimer cette recette?",
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?", "admin-delete-confirmation": "Vous êtes sur le point de supprimer une recette qui n'est pas la vôtre en utilisant les permissions d'administrateur. Êtes-vous sûr(e) ?",
"delete-recipe": "Supprimer la recette", "delete-recipe": "Supprimer la recette",
"description": "Description", "description": "Description",
"disable-amount": "Désactiver les quantités des ingrédients", "disable-amount": "Désactiver les quantités des ingrédients",
@ -587,7 +588,7 @@
"api-extras-description": "Les suppléments des recettes sont une fonctionnalité clé de lAPI Mealie. Ils permettent de créer des paires JSON clé/valeur personnalisées dans une recette, qui peuvent être référencées depuis des applications tierces. Ces clés peuvent être utilisées par exemple pour déclencher des tâches automatisées ou des messages personnalisés à transmettre à lappareil souhaité.", "api-extras-description": "Les suppléments des recettes sont une fonctionnalité clé de lAPI Mealie. Ils permettent de créer des paires JSON clé/valeur personnalisées dans une recette, qui peuvent être référencées depuis des applications tierces. Ces clés peuvent être utilisées par exemple pour déclencher des tâches automatisées ou des messages personnalisés à transmettre à lappareil souhaité.",
"message-key": "Clé de message", "message-key": "Clé de message",
"parse": "Analyser", "parse": "Analyser",
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.", "ingredients-not-parsed-description": "Il semble que vos ingrédients ne soient pas encore analysés. Cliquez sur le bouton \"{parse}\" ci-dessous pour analyser vos ingrédients en aliments structurés.",
"attach-images-hint": "Ajouter des images en les glissant-déposant dans l'éditeur", "attach-images-hint": "Ajouter des images en les glissant-déposant dans l'éditeur",
"drop-image": "Déposer l'image", "drop-image": "Déposer l'image",
"enable-ingredient-amounts-to-use-this-feature": "Activez les quantités d'ingrédients pour utiliser cette fonctionnalité", "enable-ingredient-amounts-to-use-this-feature": "Activez les quantités d'ingrédients pour utiliser cette fonctionnalité",
@ -664,15 +665,17 @@
"no-unit": "Pas d'unité", "no-unit": "Pas d'unité",
"missing-unit": "Créer une unité manquante : {unit}", "missing-unit": "Créer une unité manquante : {unit}",
"missing-food": "Créer un aliment manquant : {food}", "missing-food": "Créer un aliment manquant : {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically", "this-unit-could-not-be-parsed-automatically": "Cette unité n'a pas pu être analysée automatiquement",
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically", "this-food-could-not-be-parsed-automatically": "Cet aliment n'a pas pu être analysé automatiquement",
"no-food": "Aucun aliment" "no-food": "Aucun aliment"
}, },
"reset-servings-count": "Réinitialiser le nombre de portions", "reset-servings-count": "Réinitialiser le nombre de portions",
"not-linked-ingredients": "Ingrédients supplémentaires", "not-linked-ingredients": "Ingrédients supplémentaires",
"upload-another-image": "Télécharger une autre image", "upload-another-image": "Télécharger une autre image",
"upload-images": "Télécharger des images", "upload-images": "Télécharger des images",
"upload-more-images": "Télécharger d'autres images" "upload-more-images": "Télécharger d'autres images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recherche de recette", "recipe-finder": "Recherche de recette",
@ -1165,7 +1168,7 @@
"group-details": "Détails du groupe", "group-details": "Détails du groupe",
"group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter dautres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes dachat, leurs recettes et plus encore!", "group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter dautres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes dachat, leurs recettes et plus encore!",
"use-seed-data": "Utiliser l'initialisation de données", "use-seed-data": "Utiliser l'initialisation de données",
"use-seed-data-description": "Mealie inclut avec une liste daliments, dunités et détiquettes qui peut être utilisée pour initialiser votre groupe avec des données utiles pour organiser vos recettes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Détails du compte" "account-details": "Détails du compte"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Événements des mots-clés", "tag-events": "Événements des mots-clés",
"category-events": "Événements de catégories", "category-events": "Événements de catégories",
"when-a-new-user-joins-your-group": "Lorsqu'un nouvel utilisateur rejoint votre groupe", "when-a-new-user-joins-your-group": "Lorsqu'un nouvel utilisateur rejoint votre groupe",
"recipe-events": "Événements de recette" "recipe-events": "Événements de recette",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Ajouter", "add": "Ajouter",
@ -472,7 +473,7 @@
"comment": "Commentaire", "comment": "Commentaire",
"comments": "Commentaires", "comments": "Commentaires",
"delete-confirmation": "Êtes-vous sûr(e) de vouloir supprimer cette recette?", "delete-confirmation": "Êtes-vous sûr(e) de vouloir supprimer cette recette?",
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?", "admin-delete-confirmation": "Vous êtes sur le point de supprimer une recette qui n'est pas la vôtre en utilisant les permissions d'administrateur. Êtes-vous sûr(e) ?",
"delete-recipe": "Supprimer la recette", "delete-recipe": "Supprimer la recette",
"description": "Description", "description": "Description",
"disable-amount": "Désactiver les quantités d'ingrédients", "disable-amount": "Désactiver les quantités d'ingrédients",
@ -587,7 +588,7 @@
"api-extras-description": "Les suppléments des recettes sont une fonctionnalité clé de lAPI Mealie. Ils permettent de créer des paires JSON clé/valeur personnalisées dans une recette, qui peuvent être référencées depuis des applications tierces. Ces clés peuvent être utilisées par exemple pour déclencher des tâches automatisées ou des messages personnalisés à transmettre à lappareil souhaité.", "api-extras-description": "Les suppléments des recettes sont une fonctionnalité clé de lAPI Mealie. Ils permettent de créer des paires JSON clé/valeur personnalisées dans une recette, qui peuvent être référencées depuis des applications tierces. Ces clés peuvent être utilisées par exemple pour déclencher des tâches automatisées ou des messages personnalisés à transmettre à lappareil souhaité.",
"message-key": "Clé de message", "message-key": "Clé de message",
"parse": "Analyser", "parse": "Analyser",
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.", "ingredients-not-parsed-description": "Il semble que vos ingrédients ne soient pas encore analysés. Cliquez sur le bouton \"{parse}\" ci-dessous pour analyser vos ingrédients en aliments structurés.",
"attach-images-hint": "Ajouter des images en les glissant-déposant dans l'éditeur", "attach-images-hint": "Ajouter des images en les glissant-déposant dans l'éditeur",
"drop-image": "Déposer l'image", "drop-image": "Déposer l'image",
"enable-ingredient-amounts-to-use-this-feature": "Activez les quantités d'ingrédients pour utiliser cette fonctionnalité", "enable-ingredient-amounts-to-use-this-feature": "Activez les quantités d'ingrédients pour utiliser cette fonctionnalité",
@ -664,15 +665,17 @@
"no-unit": "Pas d'unité", "no-unit": "Pas d'unité",
"missing-unit": "Créer une unité manquante : {unit}", "missing-unit": "Créer une unité manquante : {unit}",
"missing-food": "Créer un aliment manquant : {food}", "missing-food": "Créer un aliment manquant : {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically", "this-unit-could-not-be-parsed-automatically": "Cette unité n'a pas pu être analysée automatiquement",
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically", "this-food-could-not-be-parsed-automatically": "Cet aliment n'a pas pu être analysé automatiquement",
"no-food": "Aucun aliment" "no-food": "Aucun aliment"
}, },
"reset-servings-count": "Réinitialiser le nombre de portions", "reset-servings-count": "Réinitialiser le nombre de portions",
"not-linked-ingredients": "Ingrédients supplémentaires", "not-linked-ingredients": "Ingrédients supplémentaires",
"upload-another-image": "Télécharger une autre image", "upload-another-image": "Télécharger une autre image",
"upload-images": "Télécharger des images", "upload-images": "Télécharger des images",
"upload-more-images": "Télécharger d'autres images" "upload-more-images": "Télécharger d'autres images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recherche de recette", "recipe-finder": "Recherche de recette",
@ -1165,7 +1168,7 @@
"group-details": "Détails du groupe", "group-details": "Détails du groupe",
"group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter dautres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes dachat, leurs recettes et plus encore!", "group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter dautres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes dachat, leurs recettes et plus encore!",
"use-seed-data": "Utiliser l'initialisation de données", "use-seed-data": "Utiliser l'initialisation de données",
"use-seed-data-description": "Mealie inclut avec une liste daliments, dunités et détiquettes qui peut être utilisée pour initialiser votre groupe avec des données utiles pour organiser vos recettes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Détails du compte" "account-details": "Détails du compte"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Événements des mots-clés", "tag-events": "Événements des mots-clés",
"category-events": "Événements de catégories", "category-events": "Événements de catégories",
"when-a-new-user-joins-your-group": "Lorsqu'un nouvel utilisateur rejoint votre groupe", "when-a-new-user-joins-your-group": "Lorsqu'un nouvel utilisateur rejoint votre groupe",
"recipe-events": "Événements de recette" "recipe-events": "Événements de recette",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Ajouter", "add": "Ajouter",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ingrédients supplémentaires", "not-linked-ingredients": "Ingrédients supplémentaires",
"upload-another-image": "Télécharger une autre image", "upload-another-image": "Télécharger une autre image",
"upload-images": "Télécharger des images", "upload-images": "Télécharger des images",
"upload-more-images": "Télécharger d'autres images" "upload-more-images": "Télécharger d'autres images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recherche de recette", "recipe-finder": "Recherche de recette",
@ -1165,7 +1168,7 @@
"group-details": "Détails du groupe", "group-details": "Détails du groupe",
"group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter dautres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes dachat, leurs recettes et plus encore!", "group-details-description": "Avant de créer un compte, vous devrez créer un groupe. Votre groupe ne contiendra que vous, mais vous pourrez inviter dautres personnes plus tard. Les membres de votre groupe peuvent partager leur menu de la semaine, leurs listes dachat, leurs recettes et plus encore!",
"use-seed-data": "Utiliser l'initialisation de données", "use-seed-data": "Utiliser l'initialisation de données",
"use-seed-data-description": "Mealie inclut avec une liste daliments, dunités et détiquettes qui peut être utilisée pour initialiser votre groupe avec des données utiles pour organiser vos recettes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Détails du compte" "account-details": "Détails du compte"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Eventos de Etiquetas", "tag-events": "Eventos de Etiquetas",
"category-events": "Eventos de Categorías", "category-events": "Eventos de Categorías",
"when-a-new-user-joins-your-group": "Cando un novo usuario se une ao teu grupo", "when-a-new-user-joins-your-group": "Cando un novo usuario se une ao teu grupo",
"recipe-events": "Eventos de Receitas" "recipe-events": "Eventos de Receitas",
"label-events": "Rotular Eventos"
}, },
"general": { "general": {
"add": "Engadir", "add": "Engadir",
@ -638,7 +639,7 @@
"bulk-import-process-has-failed": "Erro no proceso de importación en masa", "bulk-import-process-has-failed": "Erro no proceso de importación en masa",
"report-deletion-failed": "Erro ao eliminar relatorio", "report-deletion-failed": "Erro ao eliminar relatorio",
"recipe-debugger": "Depurador de Receitas", "recipe-debugger": "Depurador de Receitas",
"recipe-debugger-description": "Copie o URL da receita que quer depurar e pégueo aqui. O URL será lido polo lector de receitas e os resultados serán mostrados. Se non ves negún dato devolto, a páxina que está a tentar ler non é suportada polo Mealie ou pola sua biblioteca de 'scrapping'.", "recipe-debugger-description": "Copie o URL da receita que quer depurar e pégueo aqui. O URL será lido polo lector de receitas e os resultados serán mostrados. Se non ve nengún dato devolto, a páxina que está a tentar ler non é suportada polo Mealie ou pola sua biblioteca de 'scrapping'.",
"use-openai": "Utilizar OpenAI", "use-openai": "Utilizar OpenAI",
"recipe-debugger-use-openai-description": "Utilize o OpenAI para analisar os resultados en vez de depender da biblioteca de scrapers. Ao crear unha receita através dun URL, isto é feito automaticamente se a biblioteca de scrapers falla, mas pode provala manualmente aqui.", "recipe-debugger-use-openai-description": "Utilize o OpenAI para analisar os resultados en vez de depender da biblioteca de scrapers. Ao crear unha receita através dun URL, isto é feito automaticamente se a biblioteca de scrapers falla, mas pode provala manualmente aqui.",
"debug": "Depurar", "debug": "Depurar",
@ -664,7 +665,7 @@
"no-unit": "Sen unidades", "no-unit": "Sen unidades",
"missing-unit": "Crear a unidade que falta: {unit}", "missing-unit": "Crear a unidade que falta: {unit}",
"missing-food": "Crear a comida que falta: {food}", "missing-food": "Crear a comida que falta: {food}",
"this-unit-could-not-be-parsed-automatically": "Non foi posível procesar automaticamente esta unidade", "this-unit-could-not-be-parsed-automatically": "Non foi posíbel procesar automaticamente esta unidade",
"this-food-could-not-be-parsed-automatically": "Non foi posíbel procesar automaticamente este alimento", "this-food-could-not-be-parsed-automatically": "Non foi posíbel procesar automaticamente este alimento",
"no-food": "Sen Comida" "no-food": "Sen Comida"
}, },
@ -672,11 +673,13 @@
"not-linked-ingredients": "Ingredientes Adicionais", "not-linked-ingredients": "Ingredientes Adicionais",
"upload-another-image": "Cargar outra imaxen", "upload-another-image": "Cargar outra imaxen",
"upload-images": "Cargar imaxens", "upload-images": "Cargar imaxens",
"upload-more-images": "Cargar mais imaxens" "upload-more-images": "Cargar mais imaxens",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Localizador de Receitas", "recipe-finder": "Localizador de Receitas",
"recipe-finder-description": "Procure receitas con base nos ingredientes que teñas a man. Pode tamén filtrar polas ferramentas disponíveis e definir un número máximo de ingredientes ou ferramentas que faltan.", "recipe-finder-description": "Procure receitas con base nos ingredientes que teña a man. Pode tamén filtrar polas ferramentas disponíbeis e definir un número máximo de ingredientes ou ferramentas que faltan.",
"selected-ingredients": "Ingredientes Selecionados", "selected-ingredients": "Ingredientes Selecionados",
"no-ingredients-selected": "Nengun ingrediente selecionado", "no-ingredients-selected": "Nengun ingrediente selecionado",
"missing": "En falta", "missing": "En falta",
@ -1165,7 +1168,7 @@
"group-details": "Detalles do Grupo", "group-details": "Detalles do Grupo",
"group-details-description": "Antes de crear unha conta é necesario crear un grupo. Será o único membro do seu grupo, mas poderá convidar outros mais tarde. Os membros do seu grupo poden compartir menús, listas de compras, receitas e moito mais!", "group-details-description": "Antes de crear unha conta é necesario crear un grupo. Será o único membro do seu grupo, mas poderá convidar outros mais tarde. Os membros do seu grupo poden compartir menús, listas de compras, receitas e moito mais!",
"use-seed-data": "Utilizar datos xerados", "use-seed-data": "Utilizar datos xerados",
"use-seed-data-description": "O Mealie ven cunha coleción de Alimentos, Unidades e Rótulos que poden ser usados para preencher o seu grupo con datos úteis para organizar as suas receitas.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detalles da Conta" "account-details": "Detalles da Conta"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "אירועי תגיות", "tag-events": "אירועי תגיות",
"category-events": "אירועי קטגוריות", "category-events": "אירועי קטגוריות",
"when-a-new-user-joins-your-group": "כאשר משתמש חדש מצטרף לקבוצה", "when-a-new-user-joins-your-group": "כאשר משתמש חדש מצטרף לקבוצה",
"recipe-events": "אירועי מתכון" "recipe-events": "אירועי מתכון",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "הוספה", "add": "הוספה",
@ -672,7 +673,9 @@
"not-linked-ingredients": "מרכיבים נוספים", "not-linked-ingredients": "מרכיבים נוספים",
"upload-another-image": "העלאת תמונה נוספת", "upload-another-image": "העלאת תמונה נוספת",
"upload-images": "העלאת תמונות", "upload-images": "העלאת תמונות",
"upload-more-images": "העלאת תמונות נוספות" "upload-more-images": "העלאת תמונות נוספות",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "מצא מתכון", "recipe-finder": "מצא מתכון",
@ -1165,7 +1168,7 @@
"group-details": "פרטי הקבוצה", "group-details": "פרטי הקבוצה",
"group-details-description": "לפני יצירת חשבון יש צורך ליצור קבוצה. הקבוצה תכיל רק אותך אבל תוכל להזמין אחרים בשלב מאוחר יותר. חברים בקבוצה יכולים לשתף תוכנית ארוחות, רשימות קניות, מתכונים ועוד!", "group-details-description": "לפני יצירת חשבון יש צורך ליצור קבוצה. הקבוצה תכיל רק אותך אבל תוכל להזמין אחרים בשלב מאוחר יותר. חברים בקבוצה יכולים לשתף תוכנית ארוחות, רשימות קניות, מתכונים ועוד!",
"use-seed-data": "השתמש בנתוני האכלוס", "use-seed-data": "השתמש בנתוני האכלוס",
"use-seed-data-description": "Mealie מגיעה עם אוסף של מאכלים, יחידות מדידה ותוויות שניתן להשתמש לאכלוס הקבוצות עם מידע שימושי לארגון המתכונים.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "פרטי חשבון" "account-details": "פרטי חשבון"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Događaji Oznaka", "tag-events": "Događaji Oznaka",
"category-events": "Događaji Kategorija", "category-events": "Događaji Kategorija",
"when-a-new-user-joins-your-group": "Kada se novi korisnik pridruži vašoj grupi", "when-a-new-user-joins-your-group": "Kada se novi korisnik pridruži vašoj grupi",
"recipe-events": "Događaji recepta" "recipe-events": "Događaji recepta",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Dodaj", "add": "Dodaj",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Detalji o Grupi", "group-details": "Detalji o Grupi",
"group-details-description": "Prije nego što kreirate korisnički račun, morat ćete stvoriti grupu. Vaša grupa će sadržavati samo vas, ali kasnije ćete moći pozvati druge članove. Članovi vaše grupe mogu dijeliti planove obroka, popise za kupovinu, recepte i još mnogo toga!", "group-details-description": "Prije nego što kreirate korisnički račun, morat ćete stvoriti grupu. Vaša grupa će sadržavati samo vas, ali kasnije ćete moći pozvati druge članove. Članovi vaše grupe mogu dijeliti planove obroka, popise za kupovinu, recepte i još mnogo toga!",
"use-seed-data": "Koristi Pridržane Podatke", "use-seed-data": "Koristi Pridržane Podatke",
"use-seed-data-description": "Mealie dolazi s kolekcijom hrane, jedinica i oznaka koje se mogu koristiti za popunjavanje vaše grupe korisnim podacima za organiziranje vaših recepata.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detalji Računa" "account-details": "Detalji Računa"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Címke események", "tag-events": "Címke események",
"category-events": "Kategória események", "category-events": "Kategória események",
"when-a-new-user-joins-your-group": "Amikor egy új felhasználó csatlakozik a csoportodba", "when-a-new-user-joins-your-group": "Amikor egy új felhasználó csatlakozik a csoportodba",
"recipe-events": "Recept esemény" "recipe-events": "Recept esemény",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Hozzáadás", "add": "Hozzáadás",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Kiegészítő hozzávalók", "not-linked-ingredients": "Kiegészítő hozzávalók",
"upload-another-image": "Másik kép feltöltése", "upload-another-image": "Másik kép feltöltése",
"upload-images": "Képek feltöltése", "upload-images": "Képek feltöltése",
"upload-more-images": "További képek feltöltése" "upload-more-images": "További képek feltöltése",
"set-as-cover-image": "Beállítás a recept borítóképének",
"cover-image": "Borítókép"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Receptkereső", "recipe-finder": "Receptkereső",
@ -1165,7 +1168,7 @@
"group-details": "Csoport részletek", "group-details": "Csoport részletek",
"group-details-description": "Mielőtt létrehozna egy fiókot, létre kell hoznia egy csoportot. A csoportban csak ön lesz, de később másokat is meghívhat. A csoport tagjai menüterveket, bevásárlólistákat, recepteket és még sok mást is megoszthatnak egymással!", "group-details-description": "Mielőtt létrehozna egy fiókot, létre kell hoznia egy csoportot. A csoportban csak ön lesz, de később másokat is meghívhat. A csoport tagjai menüterveket, bevásárlólistákat, recepteket és még sok mást is megoszthatnak egymással!",
"use-seed-data": "Mintaadatok használata", "use-seed-data": "Mintaadatok használata",
"use-seed-data-description": "Mealie az alapanyagok, a mennyiségi egységek és a címkék gyűjteményét tartalmazza, amelyek megoszthatók a csoporttal és hasznos adataival segítségül szolgálhat a receptek szervezéséhez.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "A fiók részletei" "account-details": "A fiók részletei"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Merki viðburðir", "tag-events": "Merki viðburðir",
"category-events": "Flokka viðburðir", "category-events": "Flokka viðburðir",
"when-a-new-user-joins-your-group": "Þegar nýr notandi bætist við í þinn hóp", "when-a-new-user-joins-your-group": "Þegar nýr notandi bætist við í þinn hóp",
"recipe-events": "Uppskriftar viðburðir" "recipe-events": "Uppskriftar viðburðir",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Bæta við", "add": "Bæta við",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!", "group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Tag Eventi", "tag-events": "Tag Eventi",
"category-events": "Categoria Eventi", "category-events": "Categoria Eventi",
"when-a-new-user-joins-your-group": "Quando un nuovo utente entra nel tuo gruppo", "when-a-new-user-joins-your-group": "Quando un nuovo utente entra nel tuo gruppo",
"recipe-events": "Eventi di ricette" "recipe-events": "Eventi di ricette",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Aggiungi", "add": "Aggiungi",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ingredienti Aggiuntivi", "not-linked-ingredients": "Ingredienti Aggiuntivi",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Trova ricette", "recipe-finder": "Trova ricette",
@ -1165,7 +1168,7 @@
"group-details": "Dettagli Gruppo", "group-details": "Dettagli Gruppo",
"group-details-description": "Prima di creare un account, è necessario creare un gruppo. Il gruppo conterrà solo voi, ma potrete invitare altre persone in seguito. I membri del gruppo possono condividere piani alimentari, liste della spesa, ricette e molto altro!", "group-details-description": "Prima di creare un account, è necessario creare un gruppo. Il gruppo conterrà solo voi, ma potrete invitare altre persone in seguito. I membri del gruppo possono condividere piani alimentari, liste della spesa, ricette e molto altro!",
"use-seed-data": "Utilizzo Dati Generati", "use-seed-data": "Utilizzo Dati Generati",
"use-seed-data-description": "Mealie viene fornito con una raccolta di alimenti, unità ed etichette che possono essere utilizzate per popolare il tuo gruppo con dati utili per organizzare le tue ricette.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Dettagli dell'Account" "account-details": "Dettagli dell'Account"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "タグイベント", "tag-events": "タグイベント",
"category-events": "カテゴリイベント", "category-events": "カテゴリイベント",
"when-a-new-user-joins-your-group": "新しいユーザーがグループに参加したとき", "when-a-new-user-joins-your-group": "新しいユーザーがグループに参加したとき",
"recipe-events": "レシピイベント" "recipe-events": "レシピイベント",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "追加", "add": "追加",
@ -672,7 +673,9 @@
"not-linked-ingredients": "追加の材料", "not-linked-ingredients": "追加の材料",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "レシピ検索", "recipe-finder": "レシピ検索",
@ -1165,7 +1168,7 @@
"group-details": "グループの詳細", "group-details": "グループの詳細",
"group-details-description": "アカウントを作成する前に、グループを作成する必要があります。グループにはあなたしか含まれませんが、後で他の人を招待できます。グループのメンバーは、食事計画、買い物リスト、レシピなどを共有できます!", "group-details-description": "アカウントを作成する前に、グループを作成する必要があります。グループにはあなたしか含まれませんが、後で他の人を招待できます。グループのメンバーは、食事計画、買い物リスト、レシピなどを共有できます!",
"use-seed-data": "シードデータを使用", "use-seed-data": "シードデータを使用",
"use-seed-data-description": "Mealieには、レシピを整理するために役立つデータをグループに追加するために使用できる、食品、単位、ラベルのコレクションが付属しています。", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "アカウントの詳細" "account-details": "アカウントの詳細"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Tag 이벤트", "tag-events": "Tag 이벤트",
"category-events": "카테고리 이벤트", "category-events": "카테고리 이벤트",
"when-a-new-user-joins-your-group": "새로운 사용자가 그룹에 가입하면", "when-a-new-user-joins-your-group": "새로운 사용자가 그룹에 가입하면",
"recipe-events": "레시피 이벤트" "recipe-events": "레시피 이벤트",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "추가", "add": "추가",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!", "group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Žymų įvykiai", "tag-events": "Žymų įvykiai",
"category-events": "Kategorijų įvykiai", "category-events": "Kategorijų įvykiai",
"when-a-new-user-joins-your-group": "Kai prie jūsų grupės prisijungia naujas naudotojas", "when-a-new-user-joins-your-group": "Kai prie jūsų grupės prisijungia naujas naudotojas",
"recipe-events": "Recipe Events" "recipe-events": "Recipe Events",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Pridėti", "add": "Pridėti",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Grupės informacija", "group-details": "Grupės informacija",
"group-details-description": "Prieš kurdami paskyrą turite sukurti grupę. Jūsų grupėje būsite tik jūs, tačiau vėliau galėsite pakviesti ir kitus. Jūsų grupės nariai galės dalintis maitinimo planais, pirkinių sąrašais, receptais ir kita!", "group-details-description": "Prieš kurdami paskyrą turite sukurti grupę. Jūsų grupėje būsite tik jūs, tačiau vėliau galėsite pakviesti ir kitus. Jūsų grupės nariai galės dalintis maitinimo planais, pirkinių sąrašais, receptais ir kita!",
"use-seed-data": "Naudoti pradinius duomenis", "use-seed-data": "Naudoti pradinius duomenis",
"use-seed-data-description": "\"Mealie\" sistemoje jau yra pradinis duomenų rinkinys su produktais, vienetais ir etiketėmis. Jį galite panaudoti savo grupės užpildymui naudinga informacija, kuri padės organizuoti jūsų receptus.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Paskyros informacija" "account-details": "Paskyros informacija"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Atzīmēt notikumus", "tag-events": "Atzīmēt notikumus",
"category-events": "Kategorija Notikumi", "category-events": "Kategorija Notikumi",
"when-a-new-user-joins-your-group": "Kad jūsu grupai pievienojas jauns lietotājs", "when-a-new-user-joins-your-group": "Kad jūsu grupai pievienojas jauns lietotājs",
"recipe-events": "Recepšu notikumi" "recipe-events": "Recepšu notikumi",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Pievienot", "add": "Pievienot",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Grupas informācija", "group-details": "Grupas informācija",
"group-details-description": "Pirms konta izveides jums būs jāizveido grupa. Jūsu grupā būs tikai jūs, bet vēlāk varēsiet uzaicināt citus. Jūsu grupas dalībnieki var dalīties maltīšu plānos, iepirkumu sarakstos, receptēs un daudz ko citu!", "group-details-description": "Pirms konta izveides jums būs jāizveido grupa. Jūsu grupā būs tikai jūs, bet vēlāk varēsiet uzaicināt citus. Jūsu grupas dalībnieki var dalīties maltīšu plānos, iepirkumu sarakstos, receptēs un daudz ko citu!",
"use-seed-data": "Izmantojiet sēklu datus", "use-seed-data": "Izmantojiet sēklu datus",
"use-seed-data-description": "Mealie piegādā kopā ar pārtikas produktu, vienību un etiķešu kolekciju, ko var izmantot, lai papildinātu grupu ar noderīgiem datiem recepšu sakārtošanai.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Konta informācija" "account-details": "Konta informācija"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Label-gebeurtenissen", "tag-events": "Label-gebeurtenissen",
"category-events": "Categorie-gebeurtenissen", "category-events": "Categorie-gebeurtenissen",
"when-a-new-user-joins-your-group": "Als een nieuwe gebruiker zich bij je groep aansluit", "when-a-new-user-joins-your-group": "Als een nieuwe gebruiker zich bij je groep aansluit",
"recipe-events": "Recept gebeurtenissen" "recipe-events": "Recept gebeurtenissen",
"label-events": "Label gebeurtenissen"
}, },
"general": { "general": {
"add": "Voeg toe", "add": "Voeg toe",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Extra ingrediënten", "not-linked-ingredients": "Extra ingrediënten",
"upload-another-image": "Een andere afbeelding uploaden", "upload-another-image": "Een andere afbeelding uploaden",
"upload-images": "Afbeelding uploaden", "upload-images": "Afbeelding uploaden",
"upload-more-images": "Meer afbeeldingen uploaden" "upload-more-images": "Meer afbeeldingen uploaden",
"set-as-cover-image": "Als recept omslagfoto instellen",
"cover-image": "Omslagfoto"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recept zoeker", "recipe-finder": "Recept zoeker",
@ -1165,7 +1168,7 @@
"group-details": "Groepsdetails", "group-details": "Groepsdetails",
"group-details-description": "Voordat je een account aanmaakt moet je eerst een groep aanmaken. Jij bent het enige lid van de groep, maar je kunt later anderen uitnodigen. Leden van je groep kunnen maaltijdplannen, boodschappenlijstjes, recepten en nog veel meer delen!", "group-details-description": "Voordat je een account aanmaakt moet je eerst een groep aanmaken. Jij bent het enige lid van de groep, maar je kunt later anderen uitnodigen. Leden van je groep kunnen maaltijdplannen, boodschappenlijstjes, recepten en nog veel meer delen!",
"use-seed-data": "Gebruik voorbeeldgegevens", "use-seed-data": "Gebruik voorbeeldgegevens",
"use-seed-data-description": "Mealie bevat een verzameling ingrediënten, eenheden en labels die gebruikt kunnen worden om je groep te vullen met handige gegevens voor het organiseren van je recepten.", "use-seed-data-description": "Mealie komt standaard met lijsten voor Voedsel, Eenheden en Labels. Die gebruik je om je recepten handig in te delen. Of om je groep handige informatie te geven. Ze zijn vertaald in de taal die je voor Mealie hebt ingesteld. Je kunt deze lijsten altijd aanvullen of aanpassen.",
"account-details": "Accountgegevens" "account-details": "Accountgegevens"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Emneordhendelser", "tag-events": "Emneordhendelser",
"category-events": "Kategorihendelser", "category-events": "Kategorihendelser",
"when-a-new-user-joins-your-group": "Når en ny bruker blir med i gruppen din", "when-a-new-user-joins-your-group": "Når en ny bruker blir med i gruppen din",
"recipe-events": "Oppskriftshendelser" "recipe-events": "Oppskriftshendelser",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Legg til", "add": "Legg til",
@ -472,7 +473,7 @@
"comment": "Kommentar", "comment": "Kommentar",
"comments": "Kommentarer", "comments": "Kommentarer",
"delete-confirmation": "Er du sikker på at du vil slette denne oppskriften?", "delete-confirmation": "Er du sikker på at du vil slette denne oppskriften?",
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?", "admin-delete-confirmation": "Du er i ferd med å slette en oppskrift som ikke er din ved å bruke administratortillatelser. Er du sikker?",
"delete-recipe": "Slett oppskrift", "delete-recipe": "Slett oppskrift",
"description": "Beskrivelse", "description": "Beskrivelse",
"disable-amount": "Deaktiver ingrediensmengde", "disable-amount": "Deaktiver ingrediensmengde",
@ -581,7 +582,7 @@
"how-did-it-turn-out": "Hvordan ble det?", "how-did-it-turn-out": "Hvordan ble det?",
"user-made-this": "{user} har laget dette", "user-made-this": "{user} har laget dette",
"added-to-timeline": "Legg til tidslinje", "added-to-timeline": "Legg til tidslinje",
"failed-to-add-to-timeline": "Failed to add to timeline", "failed-to-add-to-timeline": "Kunne ikke legge til på tidslinjen",
"failed-to-update-recipe": "Kunne ikke oppdatere oppskriften", "failed-to-update-recipe": "Kunne ikke oppdatere oppskriften",
"added-to-timeline-but-failed-to-add-image": "Lagt til i tidslinjen, men klarte ikke å legge til bilde", "added-to-timeline-but-failed-to-add-image": "Lagt til i tidslinjen, men klarte ikke å legge til bilde",
"api-extras-description": "Ekstramaterialer til oppskrifter er en viktig funksjon i Mealie API-en. De lar deg opprette egendefinerte JSON-nøkkel/verdi-par innenfor en oppskrift for å referere fra tredjepartsapplikasjoner. Du kan bruke disse nøklene til å gi informasjon for eksempel for å utløse automatiseringer eller egendefinerte meldinger som skal videreformidles til ønsket enhet.", "api-extras-description": "Ekstramaterialer til oppskrifter er en viktig funksjon i Mealie API-en. De lar deg opprette egendefinerte JSON-nøkkel/verdi-par innenfor en oppskrift for å referere fra tredjepartsapplikasjoner. Du kan bruke disse nøklene til å gi informasjon for eksempel for å utløse automatiseringer eller egendefinerte meldinger som skal videreformidles til ønsket enhet.",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Tilleggsingredienser", "not-linked-ingredients": "Tilleggsingredienser",
"upload-another-image": "Last opp nytt bilde", "upload-another-image": "Last opp nytt bilde",
"upload-images": "Last opp bilder", "upload-images": "Last opp bilder",
"upload-more-images": "Last opp flere bilder" "upload-more-images": "Last opp flere bilder",
"set-as-cover-image": "Bruk som forsidebilde for oppskriften",
"cover-image": "Forsidebilde"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Oppskriftsfinner", "recipe-finder": "Oppskriftsfinner",
@ -1165,7 +1168,7 @@
"group-details": "Gruppedetaljer", "group-details": "Gruppedetaljer",
"group-details-description": "Før du oppretter en konto må du opprette en gruppe. Gruppen din vil bare inneholde deg, men du vil kunne invitere andre senere. Medlemmer i gruppen din kan dele måltider, handlelister, oppskrifter med mer!", "group-details-description": "Før du oppretter en konto må du opprette en gruppe. Gruppen din vil bare inneholde deg, men du vil kunne invitere andre senere. Medlemmer i gruppen din kan dele måltider, handlelister, oppskrifter med mer!",
"use-seed-data": "Bruk tilføringsdata", "use-seed-data": "Bruk tilføringsdata",
"use-seed-data-description": "Mealie kommer med en samling av matvarer, enheter og etiketter som kan brukes til å fylle gruppen din med nyttige data for å organisere oppskriftene dine.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Kontodetaljer" "account-details": "Kontodetaljer"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Zdarzenia tagów", "tag-events": "Zdarzenia tagów",
"category-events": "Wydarzenia kategorii", "category-events": "Wydarzenia kategorii",
"when-a-new-user-joins-your-group": "Kiedy nowy użytkownik dołączy do Twojej grupy", "when-a-new-user-joins-your-group": "Kiedy nowy użytkownik dołączy do Twojej grupy",
"recipe-events": "Zdarzenia Przepisów" "recipe-events": "Zdarzenia Przepisów",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Dodaj", "add": "Dodaj",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Dodatkowe składniki", "not-linked-ingredients": "Dodatkowe składniki",
"upload-another-image": "Prześlij kolejny obraz", "upload-another-image": "Prześlij kolejny obraz",
"upload-images": "Prześlij obraz", "upload-images": "Prześlij obraz",
"upload-more-images": "Prześlij więcej obrazów" "upload-more-images": "Prześlij więcej obrazów",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Wyszukiwarka przepisów", "recipe-finder": "Wyszukiwarka przepisów",
@ -1165,7 +1168,7 @@
"group-details": "Szczegóły grupy", "group-details": "Szczegóły grupy",
"group-details-description": "Zanim utworzysz konto musisz stworzyć grupę. Twoja grupa zawierać będzie tylko Ciebie, ale będziesz istniała możlwiość zaproszenia do niej innych. Użytkownicy Twojej grupy mogą współdzielić plany posiłków, listy zakupów, przepisy i więcej!", "group-details-description": "Zanim utworzysz konto musisz stworzyć grupę. Twoja grupa zawierać będzie tylko Ciebie, ale będziesz istniała możlwiość zaproszenia do niej innych. Użytkownicy Twojej grupy mogą współdzielić plany posiłków, listy zakupów, przepisy i więcej!",
"use-seed-data": "Użyj przykładowych danych", "use-seed-data": "Użyj przykładowych danych",
"use-seed-data-description": "Mealie dostarcza zestaw posiłków, jednostek i opisów które mogą zostać użyte do zapełnienia Twojej grupy przydatnymi danymi do ogranizacji Twoich przepisów.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Szczegóły konta" "account-details": "Szczegóły konta"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Eventos de Etiqueta", "tag-events": "Eventos de Etiqueta",
"category-events": "Eventos de Categoria", "category-events": "Eventos de Categoria",
"when-a-new-user-joins-your-group": "Quando um novo usuário entrar no seu grupo", "when-a-new-user-joins-your-group": "Quando um novo usuário entrar no seu grupo",
"recipe-events": "Eventos da Receita" "recipe-events": "Eventos da Receita",
"label-events": "Rotular Eventos"
}, },
"general": { "general": {
"add": "Adicionar", "add": "Adicionar",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ingredientes adicionais", "not-linked-ingredients": "Ingredientes adicionais",
"upload-another-image": "Carregar outra imagem", "upload-another-image": "Carregar outra imagem",
"upload-images": "Carregar imagens", "upload-images": "Carregar imagens",
"upload-more-images": "Carregar mais imagens" "upload-more-images": "Carregar mais imagens",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Localizador de Receitas", "recipe-finder": "Localizador de Receitas",
@ -683,9 +686,9 @@
"no-recipes-found": "Nenhuma receita encontrada", "no-recipes-found": "Nenhuma receita encontrada",
"no-recipes-found-description": "Tente adicionar mais ingredientes à sua busca ou ajuste seus filtros", "no-recipes-found-description": "Tente adicionar mais ingredientes à sua busca ou ajuste seus filtros",
"include-ingredients-on-hand": "Incluir Ingredientes Manualmente", "include-ingredients-on-hand": "Incluir Ingredientes Manualmente",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Incluir Utensílios Manualmente",
"max-missing-ingredients": "Max Missing Ingredients", "max-missing-ingredients": "Máximo de Ingredientes Faltando",
"max-missing-tools": "Max Missing Tools", "max-missing-tools": "Máximo de Utensílios Faltando",
"selected-tools": "Ferramentas Selecionadas", "selected-tools": "Ferramentas Selecionadas",
"other-filters": "Outros Filtros", "other-filters": "Outros Filtros",
"ready-to-make": "Pronto Para Fazer", "ready-to-make": "Pronto Para Fazer",
@ -1165,7 +1168,7 @@
"group-details": "Detalhes do Grupo", "group-details": "Detalhes do Grupo",
"group-details-description": "Antes de criar uma conta é necessário criar um grupo. O seu grupo só conterá você, mas você poderá convidar os outros mais tarde. Os membros do seu grupo podem compartilhar planos de refeição, listas de compras, receitas e muito mais!", "group-details-description": "Antes de criar uma conta é necessário criar um grupo. O seu grupo só conterá você, mas você poderá convidar os outros mais tarde. Os membros do seu grupo podem compartilhar planos de refeição, listas de compras, receitas e muito mais!",
"use-seed-data": "Usar dados semeados", "use-seed-data": "Usar dados semeados",
"use-seed-data-description": "O Mealie é fornecido com uma coleção de alimentos, unidades e rótulos que podem ser usados para preencher seu grupo com dados úteis para organizar suas receitas.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detalhes da Conta" "account-details": "Detalhes da Conta"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Eventos de Etiquetagem", "tag-events": "Eventos de Etiquetagem",
"category-events": "Eventos de Categoria", "category-events": "Eventos de Categoria",
"when-a-new-user-joins-your-group": "Quando um novo utilizador entra no seu grupo", "when-a-new-user-joins-your-group": "Quando um novo utilizador entra no seu grupo",
"recipe-events": "Eventos de receita" "recipe-events": "Eventos de receita",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Adicionar", "add": "Adicionar",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ingredientes Adicionais", "not-linked-ingredients": "Ingredientes Adicionais",
"upload-another-image": "Carregar outra imagem", "upload-another-image": "Carregar outra imagem",
"upload-images": "Carregar imagens", "upload-images": "Carregar imagens",
"upload-more-images": "Carregar mais imagens" "upload-more-images": "Carregar mais imagens",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Localizador de Receitas", "recipe-finder": "Localizador de Receitas",
@ -1165,7 +1168,7 @@
"group-details": "Detalhes do Grupo", "group-details": "Detalhes do Grupo",
"group-details-description": "Antes de criar uma conta é necessário criar um grupo. Será o único membro do seu grupo, mas poderá convidar outros mais tarde. Os membros do seu grupo podem partilhar planos de refeição, listas de compras, receitas e muito mais!", "group-details-description": "Antes de criar uma conta é necessário criar um grupo. Será o único membro do seu grupo, mas poderá convidar outros mais tarde. Os membros do seu grupo podem partilhar planos de refeição, listas de compras, receitas e muito mais!",
"use-seed-data": "Utilizar dados gerados", "use-seed-data": "Utilizar dados gerados",
"use-seed-data-description": "O Mealie vem com uma coleção de Alimentos, Unidades e Rótulos que podem ser usados para popular o seu grupo com dados úteis para organizar as suas receitas.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detalhes da Conta" "account-details": "Detalhes da Conta"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Etichetele de Evenimente", "tag-events": "Etichetele de Evenimente",
"category-events": "Categorie de Evenimente", "category-events": "Categorie de Evenimente",
"when-a-new-user-joins-your-group": "Când un utilizator nou se alătură grupului tău", "when-a-new-user-joins-your-group": "Când un utilizator nou se alătură grupului tău",
"recipe-events": "Evenimente rețetă" "recipe-events": "Evenimente rețetă",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Adaugă", "add": "Adaugă",
@ -587,6 +588,7 @@
"api-extras-description": "Recipes extras sunt o caracteristică cheie a API-ului Mealie. Îți permit să creezi perechi personalizate de cheie/valoare JSON într-o rețetă, ca să faci referire la aplicații terțe. Puteți utiliza aceste chei pentru a furniza informații, de exemplu pentru a declanșa automatizări sau mesaje personalizate pentru a transmite dispozitivul dorit.", "api-extras-description": "Recipes extras sunt o caracteristică cheie a API-ului Mealie. Îți permit să creezi perechi personalizate de cheie/valoare JSON într-o rețetă, ca să faci referire la aplicații terțe. Puteți utiliza aceste chei pentru a furniza informații, de exemplu pentru a declanșa automatizări sau mesaje personalizate pentru a transmite dispozitivul dorit.",
"message-key": "Cheie mesaj", "message-key": "Cheie mesaj",
"parse": "Parsează", "parse": "Parsează",
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.",
"attach-images-hint": "Atașează imagini trăgându-le cu mouse-ul și plasându-le în editor", "attach-images-hint": "Atașează imagini trăgându-le cu mouse-ul și plasându-le în editor",
"drop-image": "Trage imaginea", "drop-image": "Trage imaginea",
"enable-ingredient-amounts-to-use-this-feature": "Activaţi cantităţile de ingrediente pentru a utiliza această funcționalitate", "enable-ingredient-amounts-to-use-this-feature": "Activaţi cantităţile de ingrediente pentru a utiliza această funcționalitate",
@ -671,7 +673,9 @@
"not-linked-ingredients": "Ingrediente suplimentare", "not-linked-ingredients": "Ingrediente suplimentare",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Căutător de rețete", "recipe-finder": "Căutător de rețete",
@ -1164,7 +1168,7 @@
"group-details": "Detalii grup", "group-details": "Detalii grup",
"group-details-description": "Înainte de a crea un cont, va trebui să creezi un grup. Grupul tău va conține inițial doar pe tine, dar vei putea invita și alte persoane ulterior. Membrii din grupul tău vor putea să partajeze planuri de mese, liste de cumpărături, rețete și multe altele!", "group-details-description": "Înainte de a crea un cont, va trebui să creezi un grup. Grupul tău va conține inițial doar pe tine, dar vei putea invita și alte persoane ulterior. Membrii din grupul tău vor putea să partajeze planuri de mese, liste de cumpărături, rețete și multe altele!",
"use-seed-data": "Utilizează setul de date a populării", "use-seed-data": "Utilizează setul de date a populării",
"use-seed-data-description": "Mealie vine cu o colecție de Alimente, Unități, și Etichete care pot fi utilizate pentru a popula grupul tău cu date utile pentru organizarea rețetelor.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detalii Cont" "account-details": "Detalii Cont"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "События тегов", "tag-events": "События тегов",
"category-events": "События категорий", "category-events": "События категорий",
"when-a-new-user-joins-your-group": "Когда новый пользователь присоединяется к вашей группе", "when-a-new-user-joins-your-group": "Когда новый пользователь присоединяется к вашей группе",
"recipe-events": "События Рецепта" "recipe-events": "События Рецепта",
"label-events": "Ярлыки событий"
}, },
"general": { "general": {
"add": "Добавить", "add": "Добавить",
@ -645,7 +646,7 @@
"tree-view": "В виде дерева", "tree-view": "В виде дерева",
"recipe-servings": "Порции", "recipe-servings": "Порции",
"recipe-yield": "Количество порций", "recipe-yield": "Количество порций",
"recipe-yield-text": "Recipe Yield Text", "recipe-yield-text": "Количество порций рецепта",
"unit": "Единица измерения", "unit": "Единица измерения",
"upload-image": "Загрузить изображение", "upload-image": "Загрузить изображение",
"screen-awake": "Держать экран включенным", "screen-awake": "Держать экран включенным",
@ -654,25 +655,27 @@
"recipe-actions": "Действия с рецептом", "recipe-actions": "Действия с рецептом",
"parser": { "parser": {
"ingredient-parser": "Разделитель ингредиентов", "ingredient-parser": "Разделитель ингредиентов",
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.", "explanation": "Чтобы использовать парсер ингредиентов, нажмите кнопку «Обработать все». Как только обработанные ингредиенты будут доступны, Вы можете просмотреть элементы и убедиться, что они были обработаны правильно. Показатель достоверности модели отображается справа от названия элемента. Этот показатель рассчитывается как среднее всех индивидуальных результатов и не всегда полностью точен.",
"alerts-explainer": "Оповещение появится если подходящие продукты или единица измерения найдены, но не занесены в базу данных.", "alerts-explainer": "Оповещение появится если подходящие продукты или единица измерения найдены, но не занесены в базу данных.",
"select-parser": "Выбрать Разделитель", "select-parser": "Выбрать Разделитель",
"natural-language-processor": "Обработчик естественного языка", "natural-language-processor": "Обработчик естественного языка",
"brute-parser": "Brute Parser", "brute-parser": "Грубый Парсер",
"openai-parser": "OpenAI Parser", "openai-parser": "OpenAI Parser",
"parse-all": "Обработать все", "parse-all": "Обработать все",
"no-unit": "Без единиц", "no-unit": "Без единиц",
"missing-unit": "Создать недостающую единицу: {unit}", "missing-unit": "Создать недостающую единицу: {unit}",
"missing-food": "Создать недостающую еду: {food}", "missing-food": "Создать недостающую еду: {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically", "this-unit-could-not-be-parsed-automatically": "Эта единица не может быть обработана автоматически",
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically", "this-food-could-not-be-parsed-automatically": "Этот продукт не может быть обработан автоматически",
"no-food": "Нет еды" "no-food": "Нет еды"
}, },
"reset-servings-count": "Сбросить количество порций", "reset-servings-count": "Сбросить количество порций",
"not-linked-ingredients": "Дополнительные ингредиенты", "not-linked-ingredients": "Дополнительные ингредиенты",
"upload-another-image": "Upload another image", "upload-another-image": "Загрузите другое изображение",
"upload-images": "Загрузить изображения", "upload-images": "Загрузить изображения",
"upload-more-images": "Загрузить больше изображений" "upload-more-images": "Загрузить больше изображений",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Поиск рецептов", "recipe-finder": "Поиск рецептов",
@ -682,8 +685,8 @@
"missing": "Отсутствует", "missing": "Отсутствует",
"no-recipes-found": "Рецепты не найдены", "no-recipes-found": "Рецепты не найдены",
"no-recipes-found-description": "Попробуйте добавить больше ингредиентов для поиска или измените настройки фильтров", "no-recipes-found-description": "Попробуйте добавить больше ингредиентов для поиска или измените настройки фильтров",
"include-ingredients-on-hand": "Include Ingredients On Hand", "include-ingredients-on-hand": "Включать ингредиенты в наличии, под рукой",
"include-tools-on-hand": "Include Tools On Hand", "include-tools-on-hand": "Включать инструменты в наличии, под рукой",
"max-missing-ingredients": "Максимальное количество отсутствующих ингредиентов", "max-missing-ingredients": "Максимальное количество отсутствующих ингредиентов",
"max-missing-tools": "Максимальное количество отсутствующих инструментов", "max-missing-tools": "Максимальное количество отсутствующих инструментов",
"selected-tools": "Выбранные инструменты", "selected-tools": "Выбранные инструменты",
@ -1072,7 +1075,7 @@
"example-food-singular": "пр. Луковица", "example-food-singular": "пр. Луковица",
"example-food-plural": "пр. Луковиц", "example-food-plural": "пр. Луковиц",
"label-overwrite-warning": "Это назначит выбранную метку всем выбранным продуктам и потенциально перезапишет существующие метки.", "label-overwrite-warning": "Это назначит выбранную метку всем выбранным продуктам и потенциально перезапишет существующие метки.",
"on-hand-checkbox-label": "Setting this flag will make this food unchecked by default when adding a recipe to a shopping list." "on-hand-checkbox-label": "Установка этого флага сделает этот продукт неотмеченным по умолчанию при добавлении рецепта в список покупок."
}, },
"units": { "units": {
"seed-dialog-text": "Заполняет базу данных рядовыми единицами измерений на основе выбранного языка.", "seed-dialog-text": "Заполняет базу данных рядовыми единицами измерений на основе выбранного языка.",
@ -1125,9 +1128,9 @@
"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": "Тип Действия"
}, },
"create-alias": "Создать псевдоним", "create-alias": "Создать псевдоним",
@ -1165,7 +1168,7 @@
"group-details": "Сведения о группе", "group-details": "Сведения о группе",
"group-details-description": "Прежде чем создать учетную запись, вам нужно создать группу. В вашей группе будете только вы, но вы сможете пригласить других позже. Участники группы могут обмениваться планами питания, списками покупок, рецептами и многим другим!", "group-details-description": "Прежде чем создать учетную запись, вам нужно создать группу. В вашей группе будете только вы, но вы сможете пригласить других позже. Участники группы могут обмениваться планами питания, списками покупок, рецептами и многим другим!",
"use-seed-data": "Использовать дефолтные значения", "use-seed-data": "Использовать дефолтные значения",
"use-seed-data-description": "Mealie идёт с коллекцией продуктов, единиц измерения и меток, которые могут быть использованы для заполнения вашей группы полезными данными для организации ваших рецептов.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Параметры учетной записи" "account-details": "Параметры учетной записи"
}, },
"validation": { "validation": {
@ -1290,18 +1293,18 @@
"first-time-setup": "Первоначальная Настройка", "first-time-setup": "Первоначальная Настройка",
"welcome-to-mealie-get-started": "Добро пожаловать в Mealie! Давайте начнем", "welcome-to-mealie-get-started": "Добро пожаловать в Mealie! Давайте начнем",
"already-set-up-bring-to-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": "Настройка завершена!",
"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": "Отладка OpenAI сервисов", "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": "Запустить тест",
"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}!",
@ -1314,7 +1317,7 @@
"group-statistics": "Статистика группы", "group-statistics": "Статистика группы",
"group-statistics-description": "Статистика вашей группы дает некоторую информацию о том, как вы используете Mealie.", "group-statistics-description": "Статистика вашей группы дает некоторую информацию о том, как вы используете Mealie.",
"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": "Личное",
@ -1324,8 +1327,8 @@
"api-tokens-description": "Управляйте вашими API-токенами для доступа сторонних приложений.", "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-description": "Управляйте настройками вашего домохозяйства, такими как настройки плана питания и конфиденциальности.", "household-settings-description": "Управляйте настройками вашего домохозяйства, такими как настройки плана питания и конфиденциальности.",
"cookbooks-description": "Управление коллекцией категорий рецептов и созданием страниц для них.", "cookbooks-description": "Управление коллекцией категорий рецептов и созданием страниц для них.",
@ -1355,9 +1358,9 @@
}, },
"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": "Книги рецептов это еще один способ организовать рецепты путем создания разделов рецептов, органайзеров и других фильтров. Создание книги рецептов добавит запись в боковую панель, и все рецепты с выбранными фильтрами будут отображаться в книге рецептов.",
"hide-cookbooks-from-other-households": "Скрыть книги рецептов от других домохозяйств", "hide-cookbooks-from-other-households": "Скрыть книги рецептов от других домохозяйств",
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar", "hide-cookbooks-from-other-households-description": "Если эта функция включена, на боковой панели будут отображаться только книги рецептов из Вашего домохозяйства",
"public-cookbook": "Публичные книги рецептов", "public-cookbook": "Публичные книги рецептов",
"public-cookbook-description": "Публичными книгами рецептов можно делиться с людьми без аккаунта в Mealie, и они будут отображаться на странице вашей группы.", "public-cookbook-description": "Публичными книгами рецептов можно делиться с людьми без аккаунта в Mealie, и они будут отображаться на странице вашей группы.",
"filter-options": "Параметры фильтрации", "filter-options": "Параметры фильтрации",
@ -1385,13 +1388,13 @@
"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-not-one-of": "не один из", "is-not-one-of": "не один из",
"contains-all-of": "содержит все", "contains-all-of": "содержит все",
"is-like": "is like", "is-like": "содержит",
"is-not-like": "is not like" "is-not-like": "не содержит"
} }
} }
} }

View file

@ -79,7 +79,8 @@
"tag-events": "Udalosti štítkov", "tag-events": "Udalosti štítkov",
"category-events": "Udalosti kategórií", "category-events": "Udalosti kategórií",
"when-a-new-user-joins-your-group": "Keď sa k vašej skupine pripojí nový používateľ", "when-a-new-user-joins-your-group": "Keď sa k vašej skupine pripojí nový používateľ",
"recipe-events": "Udalosti receptov" "recipe-events": "Udalosti receptov",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Pridať", "add": "Pridať",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ďalšie suroviny", "not-linked-ingredients": "Ďalšie suroviny",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Nahrať obrázky", "upload-images": "Nahrať obrázky",
"upload-more-images": "Nahrať ďalšie obrázky" "upload-more-images": "Nahrať ďalšie obrázky",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Hľadač receptov", "recipe-finder": "Hľadač receptov",
@ -1165,7 +1168,7 @@
"group-details": "Podrobnosti o skupine", "group-details": "Podrobnosti o skupine",
"group-details-description": "Pred vytvorením účtu musíte vytvoriť skupinu. Vaša skupina bude obsahovať iba vás, ale neskôr budete môcť pozvať ostatných. Členovia vašej skupiny môžu zdieľať stravovacie plány, nákupné zoznamy, recepty a ďalšie!", "group-details-description": "Pred vytvorením účtu musíte vytvoriť skupinu. Vaša skupina bude obsahovať iba vás, ale neskôr budete môcť pozvať ostatných. Členovia vašej skupiny môžu zdieľať stravovacie plány, nákupné zoznamy, recepty a ďalšie!",
"use-seed-data": "Použiť predvolené dáta", "use-seed-data": "Použiť predvolené dáta",
"use-seed-data-description": "Mealie prichádza so zbierkou potravín, jednotiek a štítkov, ktoré možno použiť na naplnenie vašej skupiny užitočnými údajmi na organizáciu vašich receptov.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Detaily účtu" "account-details": "Detaily účtu"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Dogodki značk", "tag-events": "Dogodki značk",
"category-events": "Dogodki kategorij", "category-events": "Dogodki kategorij",
"when-a-new-user-joins-your-group": "Ko se novi uporabnik pridruži tvoji skupini", "when-a-new-user-joins-your-group": "Ko se novi uporabnik pridruži tvoji skupini",
"recipe-events": "Dogodki receptov" "recipe-events": "Dogodki receptov",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Dodaj", "add": "Dodaj",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Dodatne sestavine", "not-linked-ingredients": "Dodatne sestavine",
"upload-another-image": "Naloži drugo sliko", "upload-another-image": "Naloži drugo sliko",
"upload-images": "Naloži slike", "upload-images": "Naloži slike",
"upload-more-images": "Naloži več slik" "upload-more-images": "Naloži več slik",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Iskalnik receptov", "recipe-finder": "Iskalnik receptov",
@ -1165,7 +1168,7 @@
"group-details": "Detajli skupine", "group-details": "Detajli skupine",
"group-details-description": "Preden kreirate račun, morate kreirati skupino. V skupini boste sprva samo vi, vendar imate možnost povabiti še ostale člane. Člani v vaši skupini lahko delijo načrte obrokov, nakupovalne sezname, recepte in še več!", "group-details-description": "Preden kreirate račun, morate kreirati skupino. V skupini boste sprva samo vi, vendar imate možnost povabiti še ostale člane. Člani v vaši skupini lahko delijo načrte obrokov, nakupovalne sezname, recepte in še več!",
"use-seed-data": "Uporabi privzete podatke", "use-seed-data": "Uporabi privzete podatke",
"use-seed-data-description": "Meali vključuje zbirko jedi, enot in oznak, ki se lahko uporabno uporabijo v vaši skupini za organizacijo receptov.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Podatki o računu" "account-details": "Podatki o računu"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Догађаји ознаке", "tag-events": "Догађаји ознаке",
"category-events": "Догађаји категорије", "category-events": "Догађаји категорије",
"when-a-new-user-joins-your-group": "Када се нови корисник придружи вашој групи", "when-a-new-user-joins-your-group": "Када се нови корисник придружи вашој групи",
"recipe-events": "Recipe Events" "recipe-events": "Recipe Events",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Add", "add": "Add",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Пре него што креирате налог, морате креирати групу. Ваша група ће садржавати само вас, али касније ћете моћи позвати друге. Чланови ваше групе могу делити јеловнике, спискове за куповину, рецепте и још много тога!", "group-details-description": "Пре него што креирате налог, морате креирати групу. Ваша група ће садржавати само вас, али касније ћете моћи позвати друге. Чланови ваше групе могу делити јеловнике, спискове за куповину, рецепте и још много тога!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Tagga händelser", "tag-events": "Tagga händelser",
"category-events": "Kategorihändelser", "category-events": "Kategorihändelser",
"when-a-new-user-joins-your-group": "När en ny användare går med i din grupp", "when-a-new-user-joins-your-group": "När en ny användare går med i din grupp",
"recipe-events": "Recepthändelser" "recipe-events": "Recepthändelser",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Lägg till", "add": "Lägg till",
@ -472,7 +473,7 @@
"comment": "Kommentar", "comment": "Kommentar",
"comments": "Kommentarer", "comments": "Kommentarer",
"delete-confirmation": "Är du säker på att du vill ta bort detta recept?", "delete-confirmation": "Är du säker på att du vill ta bort detta recept?",
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?", "admin-delete-confirmation": "Du är på väg att ta bort ett recept som inte är ditt med administratörsbehörigheter. Är du säker?",
"delete-recipe": "Radera recept", "delete-recipe": "Radera recept",
"description": "Beskrivning", "description": "Beskrivning",
"disable-amount": "Inaktivera ingredienser mängder", "disable-amount": "Inaktivera ingredienser mängder",
@ -587,7 +588,7 @@
"api-extras-description": "Recept API-tillägg är en viktig funktion i Mealie's API. Med hjälp av dem kan du skapa anpassade JSON-nyckel/värdepar i ett recept, som du kan referera till från tredjepartsapplikationer. Du kan använda dessa nycklar för att tillhandahålla information, till exempel för att trigga automatiseringar eller anpassade meddelanden som ska vidarebefordras till önskad enhet.", "api-extras-description": "Recept API-tillägg är en viktig funktion i Mealie's API. Med hjälp av dem kan du skapa anpassade JSON-nyckel/värdepar i ett recept, som du kan referera till från tredjepartsapplikationer. Du kan använda dessa nycklar för att tillhandahålla information, till exempel för att trigga automatiseringar eller anpassade meddelanden som ska vidarebefordras till önskad enhet.",
"message-key": "Meddelandenyckel", "message-key": "Meddelandenyckel",
"parse": "Läs in", "parse": "Läs in",
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.", "ingredients-not-parsed-description": "Det verkar som om dina ingredienser inte är tolkade ännu. Klicka på knappen \"{parse}\" nedan för att tolka dina ingredienser till strukturerade livsmedel.",
"attach-images-hint": "Bifoga bilder genom att dra och släppa dem i redigeraren", "attach-images-hint": "Bifoga bilder genom att dra och släppa dem i redigeraren",
"drop-image": "Släpp bild", "drop-image": "Släpp bild",
"enable-ingredient-amounts-to-use-this-feature": "Aktivera ingrediensmängd för att använda denna funktion", "enable-ingredient-amounts-to-use-this-feature": "Aktivera ingrediensmängd för att använda denna funktion",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ytterligare ingredienser", "not-linked-ingredients": "Ytterligare ingredienser",
"upload-another-image": "Ladda upp en annan bild", "upload-another-image": "Ladda upp en annan bild",
"upload-images": "Ladda upp bilder", "upload-images": "Ladda upp bilder",
"upload-more-images": "Ladda upp fler bilder" "upload-more-images": "Ladda upp fler bilder",
"set-as-cover-image": "Använd som receptbild",
"cover-image": "Receptbild"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Sök recept", "recipe-finder": "Sök recept",
@ -1165,7 +1168,7 @@
"group-details": "Gruppuppgifter", "group-details": "Gruppuppgifter",
"group-details-description": "Innan du skapar ett konto måste du skapa en grupp. Din grupp kommer bara att innehålla dig, men du kommer att kunna bjuda in andra senare. Medlemmarna i din grupp kan dela måltidsplaner, inköpslistor, recept och mycket mer!", "group-details-description": "Innan du skapar ett konto måste du skapa en grupp. Din grupp kommer bara att innehålla dig, men du kommer att kunna bjuda in andra senare. Medlemmarna i din grupp kan dela måltidsplaner, inköpslistor, recept och mycket mer!",
"use-seed-data": "Använd exempeldata", "use-seed-data": "Använd exempeldata",
"use-seed-data-description": "Mealie innehåller en samling av livsmedel, enheter och etiketter som kan användas för att fylla din grupp med användbara data för att organisera dina recept.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Kontouppgifter" "account-details": "Kontouppgifter"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Etiket Etkinlikleri", "tag-events": "Etiket Etkinlikleri",
"category-events": "Kategori Etkinlikleri", "category-events": "Kategori Etkinlikleri",
"when-a-new-user-joins-your-group": "Grubunuza yeni bir kullanıcı katıldığında", "when-a-new-user-joins-your-group": "Grubunuza yeni bir kullanıcı katıldığında",
"recipe-events": "Tarif Etkinlikleri" "recipe-events": "Tarif Etkinlikleri",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Ekle", "add": "Ekle",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Ek Malzemeler", "not-linked-ingredients": "Ek Malzemeler",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Tarif Bulucu", "recipe-finder": "Tarif Bulucu",
@ -1165,7 +1168,7 @@
"group-details": "Grup Detayları", "group-details": "Grup Detayları",
"group-details-description": "Hesap oluşturmadan önce bir grup oluşturmanız gerekir. Grubunuzda yalnızca siz yer alacaksınız ancak daha sonra başkalarını da davet edebileceksiniz. Grubunuzdaki üyeler yemek planlarını, alışveriş listelerini, tarifleri ve daha fazlasını paylaşabilir!", "group-details-description": "Hesap oluşturmadan önce bir grup oluşturmanız gerekir. Grubunuzda yalnızca siz yer alacaksınız ancak daha sonra başkalarını da davet edebileceksiniz. Grubunuzdaki üyeler yemek planlarını, alışveriş listelerini, tarifleri ve daha fazlasını paylaşabilir!",
"use-seed-data": "Tohum Verisi Kullan", "use-seed-data": "Tohum Verisi Kullan",
"use-seed-data-description": "Mealie, grubunuzu tariflerinizi düzenlemenize yardımcı olacak yararlı verilerle doldurmak için kullanılabilecek bir Yiyecek, Birim ve Etiket koleksiyonuyla birlikte gelir.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Hesap Detayları" "account-details": "Hesap Detayları"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Події міток", "tag-events": "Події міток",
"category-events": "Події категорій", "category-events": "Події категорій",
"when-a-new-user-joins-your-group": "Коли новий користувач приєднується до групи", "when-a-new-user-joins-your-group": "Коли новий користувач приєднується до групи",
"recipe-events": "Події рецепту" "recipe-events": "Події рецепту",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Додати", "add": "Додати",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Додаткові продукти", "not-linked-ingredients": "Додаткові продукти",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Шукач рецептів", "recipe-finder": "Шукач рецептів",
@ -1165,7 +1168,7 @@
"group-details": "Деталі групи", "group-details": "Деталі групи",
"group-details-description": "Перед створенням облікового запису вам потрібно створити групу. Спочатку ваша група буде містити тільки вас, але ви зможете запрошувати інших пізніше. Учасники вашої групи можуть обмінюватися планами харчування, списками покупок, рецептами і багато чим іншим!", "group-details-description": "Перед створенням облікового запису вам потрібно створити групу. Спочатку ваша група буде містити тільки вас, але ви зможете запрошувати інших пізніше. Учасники вашої групи можуть обмінюватися планами харчування, списками покупок, рецептами і багато чим іншим!",
"use-seed-data": "Використати початкові дані", "use-seed-data": "Використати початкові дані",
"use-seed-data-description": "Mealie має вбудований набір продуктів, одиниць виміру, та етикеток що можуть бути додані до вашої групи для допомоги в організації рецептів.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Деталі акаунта" "account-details": "Деталі акаунта"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "Tag Events", "tag-events": "Tag Events",
"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",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Thêm", "add": "Thêm",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!", "group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "标签事件", "tag-events": "标签事件",
"category-events": "分类事件", "category-events": "分类事件",
"when-a-new-user-joins-your-group": "当新用户加入您的群组时", "when-a-new-user-joins-your-group": "当新用户加入您的群组时",
"recipe-events": "食谱事件" "recipe-events": "食谱事件",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "添加", "add": "添加",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "食谱搜索", "recipe-finder": "食谱搜索",
@ -1165,7 +1168,7 @@
"group-details": "群组详情", "group-details": "群组详情",
"group-details-description": "在你创建账户之前,需要先创建一个群组。此时群组将只包含你自己,但稍后你便可邀请其他人。 你的群组成员可以分享食谱、饮食计划、购物清单等!", "group-details-description": "在你创建账户之前,需要先创建一个群组。此时群组将只包含你自己,但稍后你便可邀请其他人。 你的群组成员可以分享食谱、饮食计划、购物清单等!",
"use-seed-data": "使用初始数据", "use-seed-data": "使用初始数据",
"use-seed-data-description": "Mealie附带一套现成的“食品”、“单位”、“标签”数据,可以帮助你的群组管理食谱。", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "账户详情" "account-details": "账户详情"
}, },
"validation": { "validation": {

View file

@ -79,7 +79,8 @@
"tag-events": "標籤事件", "tag-events": "標籤事件",
"category-events": "類別事件", "category-events": "類別事件",
"when-a-new-user-joins-your-group": "當新用戶加入您的群組時", "when-a-new-user-joins-your-group": "當新用戶加入您的群組時",
"recipe-events": "食譜事件" "recipe-events": "食譜事件",
"label-events": "Label Events"
}, },
"general": { "general": {
"add": "Add", "add": "Add",
@ -672,7 +673,9 @@
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image", "upload-another-image": "Upload another image",
"upload-images": "Upload images", "upload-images": "Upload images",
"upload-more-images": "Upload more images" "upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",
@ -1165,7 +1168,7 @@
"group-details": "Group Details", "group-details": "Group Details",
"group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!", "group-details-description": "Before you create an account you'll need to create a group. Your group will only contain you, but you'll be able to invite others later. Members in your group can share meal plans, shopping lists, recipes, and more!",
"use-seed-data": "Use Seed Data", "use-seed-data": "Use Seed Data",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes.", "use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"account-details": "Account Details" "account-details": "Account Details"
}, },
"validation": { "validation": {

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script
@ -117,6 +116,7 @@ export interface CustomPageBase {
export interface RecipeCategoryResponse { export interface RecipeCategoryResponse {
name: string; name: string;
id: string; id: string;
groupId?: string | null;
slug: string; slug: string;
recipes?: RecipeSummary[]; recipes?: RecipeSummary[];
} }
@ -149,18 +149,21 @@ export interface RecipeSummary {
} }
export interface RecipeCategory { export interface RecipeCategory {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTag { export interface RecipeTag {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTool { export interface RecipeTool {
id: string; id: string;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
householdsWithTool?: string[]; householdsWithTool?: string[];

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script
@ -39,7 +38,6 @@ export interface QueryFilterJSONPart {
attributeName?: string | null; attributeName?: string | null;
relationalOperator?: RelationalKeyword | RelationalOperator | null; relationalOperator?: RelationalKeyword | RelationalOperator | null;
value?: string | string[] | null; value?: string | string[] | null;
[k: string]: unknown;
} }
export interface RecipeCookBook { export interface RecipeCookBook {
name: string; name: string;
@ -83,18 +81,21 @@ export interface RecipeSummary {
} }
export interface RecipeCategory { export interface RecipeCategory {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTag { export interface RecipeTag {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTool { export interface RecipeTool {
id: string; id: string;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
householdsWithTool?: string[]; householdsWithTool?: string[];

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script
@ -70,6 +69,9 @@ export interface GroupEventNotifierOptions {
categoryCreated?: boolean; categoryCreated?: boolean;
categoryUpdated?: boolean; categoryUpdated?: boolean;
categoryDeleted?: boolean; categoryDeleted?: boolean;
labelCreated?: boolean;
labelUpdated?: boolean;
labelDeleted?: boolean;
} }
export interface GroupEventNotifierOptionsOut { export interface GroupEventNotifierOptionsOut {
testMessage?: boolean; testMessage?: boolean;
@ -94,6 +96,9 @@ export interface GroupEventNotifierOptionsOut {
categoryCreated?: boolean; categoryCreated?: boolean;
categoryUpdated?: boolean; categoryUpdated?: boolean;
categoryDeleted?: boolean; categoryDeleted?: boolean;
labelCreated?: boolean;
labelUpdated?: boolean;
labelDeleted?: boolean;
id: string; id: string;
} }
export interface GroupEventNotifierOptionsSave { export interface GroupEventNotifierOptionsSave {
@ -119,6 +124,9 @@ export interface GroupEventNotifierOptionsSave {
categoryCreated?: boolean; categoryCreated?: boolean;
categoryUpdated?: boolean; categoryUpdated?: boolean;
categoryDeleted?: boolean; categoryDeleted?: boolean;
labelCreated?: boolean;
labelUpdated?: boolean;
labelDeleted?: boolean;
notifierId: string; notifierId: string;
} }
export interface GroupEventNotifierOut { export interface GroupEventNotifierOut {
@ -166,6 +174,7 @@ export interface GroupRecipeActionOut {
export interface GroupRecipeActionPayload { export interface GroupRecipeActionPayload {
action: GroupRecipeActionOut; action: GroupRecipeActionOut;
content: unknown; content: unknown;
recipeScale: number;
} }
export interface HouseholdCreate { export interface HouseholdCreate {
groupId?: string | null; groupId?: string | null;
@ -587,18 +596,21 @@ export interface RecipeSummary {
} }
export interface RecipeCategory { export interface RecipeCategory {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTag { export interface RecipeTag {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTool { export interface RecipeTool {
id: string; id: string;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
householdsWithTool?: string[]; householdsWithTool?: string[];

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,12 +1,9 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script
*/ */
import type { HouseholdSummary } from "./household";
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side"; export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side";
export type PlanRulesDay = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | "unset"; export type PlanRulesDay = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | "unset";
export type PlanRulesType = "breakfast" | "lunch" | "dinner" | "side" | "unset"; export type PlanRulesType = "breakfast" | "lunch" | "dinner" | "side" | "unset";
@ -44,9 +41,6 @@ export interface PlanRulesOut {
householdId: string; householdId: string;
id: string; id: string;
queryFilter?: QueryFilterJSON; queryFilter?: QueryFilterJSON;
categories?: RecipeCategory[];
tags?: RecipeTag[];
households?: HouseholdSummary[];
} }
export interface QueryFilterJSON { export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[]; parts?: QueryFilterJSONPart[];
@ -108,18 +102,21 @@ export interface RecipeSummary {
} }
export interface RecipeCategory { export interface RecipeCategory {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTag { export interface RecipeTag {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface RecipeTool { export interface RecipeTool {
id: string; id: string;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
householdsWithTool?: string[]; householdsWithTool?: string[];

View file

@ -19,6 +19,7 @@ export interface AssignCategories {
export interface CategoryBase { export interface CategoryBase {
name: string; name: string;
id: string; id: string;
groupId?: string | null;
slug: string; slug: string;
} }
export interface AssignSettings { export interface AssignSettings {
@ -40,6 +41,7 @@ export interface AssignTags {
export interface TagBase { export interface TagBase {
name: string; name: string;
id: string; id: string;
groupId?: string | null;
slug: string; slug: string;
} }
export interface CategoryIn { export interface CategoryIn {
@ -48,8 +50,8 @@ export interface CategoryIn {
export interface CategoryOut { export interface CategoryOut {
name: string; name: string;
id: string; id: string;
slug: string;
groupId: string; groupId: string;
slug: string;
} }
export interface CategorySave { export interface CategorySave {
name: string; name: string;
@ -97,11 +99,13 @@ export interface CreateRecipeBulk {
} }
export interface RecipeCategory { export interface RecipeCategory {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
} }
export interface RecipeTag { export interface RecipeTag {
id?: string | null; id?: string | null;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
} }
@ -223,7 +227,7 @@ export interface Recipe {
groupId?: string; groupId?: string;
name?: string | null; name?: string | null;
slug?: string; slug?: string;
image?: string; image?: unknown;
recipeServings?: number; recipeServings?: number;
recipeYieldQuantity?: number; recipeYieldQuantity?: number;
recipeYield?: string | null; recipeYield?: string | null;
@ -255,6 +259,7 @@ export interface Recipe {
} }
export interface RecipeTool { export interface RecipeTool {
id: string; id: string;
groupId?: string | null;
name: string; name: string;
slug: string; slug: string;
householdsWithTool?: string[]; householdsWithTool?: string[];
@ -293,6 +298,7 @@ export interface UserBase {
export interface RecipeCategoryResponse { export interface RecipeCategoryResponse {
name: string; name: string;
id: string; id: string;
groupId?: string | null;
slug: string; slug: string;
recipes?: RecipeSummary[]; recipes?: RecipeSummary[];
} }
@ -399,6 +405,7 @@ export interface RecipeSuggestionResponseItem {
export interface RecipeTagResponse { export interface RecipeTagResponse {
name: string; name: string;
id: string; id: string;
groupId?: string | null;
slug: string; slug: string;
recipes?: RecipeSummary[]; recipes?: RecipeSummary[];
} }
@ -447,12 +454,14 @@ export interface RecipeToolOut {
name: string; name: string;
householdsWithTool?: string[]; householdsWithTool?: string[];
id: string; id: string;
groupId: string;
slug: string; slug: string;
} }
export interface RecipeToolResponse { export interface RecipeToolResponse {
name: string; name: string;
householdsWithTool?: string[]; householdsWithTool?: string[];
id: string; id: string;
groupId: string;
slug: string; slug: string;
recipes?: RecipeSummary[]; recipes?: RecipeSummary[];
} }
@ -507,7 +516,7 @@ export interface ScrapeRecipeTest {
url: string; url: string;
useOpenAI?: boolean; useOpenAI?: boolean;
} }
export interface SlugResponse { } export interface SlugResponse {}
export interface TagIn { export interface TagIn {
name: string; name: string;
} }

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,5 +1,4 @@
/* tslint:disable */ /* tslint:disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script /* Do not modify it by hand - just update the pydantic models and then re-run the script
@ -63,6 +62,7 @@ export interface GroupInDB {
export interface CategoryBase { export interface CategoryBase {
name: string; name: string;
id: string; id: string;
groupId?: string | null;
slug: string; slug: string;
} }
export interface ReadWebhook { export interface ReadWebhook {
@ -197,7 +197,6 @@ export interface UserBase {
canManage?: boolean; canManage?: boolean;
canManageHousehold?: boolean; canManageHousehold?: boolean;
canOrganize?: boolean; canOrganize?: boolean;
advancedOptions?: boolean;
} }
export interface UserIn { export interface UserIn {
id?: string | null; id?: string | null;

View file

@ -1,5 +1,6 @@
<template> <template>
<v-container fill-height <v-container
fill-height
fluid fluid
class="d-flex justify-center align-center" class="d-flex justify-center align-center"
width="1200px" width="1200px"
@ -8,7 +9,8 @@
'bg-off-white': !$vuetify.theme.current.dark, 'bg-off-white': !$vuetify.theme.current.dark,
}" }"
> >
<BaseWizard v-model="currentPage" <BaseWizard
v-model="currentPage"
:max-page-number="totalPages" :max-page-number="totalPages"
:title="$t('admin.setup.first-time-setup')" :title="$t('admin.setup.first-time-setup')"
:prev-button-show="activeConfig.showPrevButton" :prev-button-show="activeConfig.showPrevButton"
@ -20,13 +22,15 @@
:is-submitting="isSubmitting" :is-submitting="isSubmitting"
@submit="handleSubmit" @submit="handleSubmit"
> >
<v-container v-if="currentPage === Pages.LANDING" <v-container
v-if="currentPage === Pages.LANDING"
class="mb-12" class="mb-12"
> >
<v-card-title class="text-h4 justify-center text-center"> <v-card-title class="text-h4 justify-center text-center">
{{ $t('admin.setup.welcome-to-mealie-get-started') }} {{ $t('admin.setup.welcome-to-mealie-get-started') }}
</v-card-title> </v-card-title>
<v-btn :to="groupSlug ? `/g/${groupSlug}` : '/login'" <v-btn
:to="groupSlug ? `/g/${groupSlug}` : '/login'"
rounded rounded
variant="outlined" variant="outlined"
color="grey-lighten-1" color="grey-lighten-1"
@ -36,35 +40,42 @@
{{ $t('admin.setup.already-set-up-bring-to-homepage') }} {{ $t('admin.setup.already-set-up-bring-to-homepage') }}
</v-btn> </v-btn>
</v-container> </v-container>
<v-container v-if="currentPage === Pages.USER_INFO"> <v-container v-if="currentPage === Pages.USER_INFO">
<UserRegistrationForm /> <UserRegistrationForm />
</v-container> </v-container>
<v-container v-if="currentPage === Pages.PAGE_2"> <v-container v-if="currentPage === Pages.PAGE_2">
<v-card-title class="headline justify-center"> <v-card-title class="headline justify-center pa-0">
{{ $t('admin.setup.common-settings-for-new-sites') }} {{ $t('admin.setup.common-settings-for-new-sites') }}
</v-card-title> </v-card-title>
<AutoForm v-model="commonSettings" <AutoForm
v-model="commonSettings"
:items="commonSettingsForm" :items="commonSettingsForm"
/> />
</v-container> </v-container>
<v-container v-if="currentPage === Pages.CONFIRM"> <v-container v-if="currentPage === Pages.CONFIRM">
<v-card-title class="headline justify-center"> <v-card-title class="headline justify-center">
{{ $t("general.confirm-how-does-everything-look") }} {{ $t("general.confirm-how-does-everything-look") }}
</v-card-title> </v-card-title>
<v-list> <v-list>
<template v-for="(item, idx) in confirmationData"> <template v-for="(item, idx) in confirmationData">
<v-list-item v-if="item.display" <v-list-item
v-if="item.display"
:key="idx" :key="idx"
> >
<v-list-item-title> {{ item.text }} </v-list-item-title> <v-list-item-title>{{ item.text }}</v-list-item-title>
<v-list-item-subtitle> {{ item.value }} </v-list-item-subtitle> <v-list-item-subtitle>{{ item.value }}</v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-divider v-if="idx !== confirmationData.length - 1" <v-divider
v-if="idx !== confirmationData.length - 1"
:key="`divider-${idx}`" :key="`divider-${idx}`"
/> />
</template> </template>
</v-list> </v-list>
</v-container> </v-container>
<v-container v-if="currentPage === Pages.END"> <v-container v-if="currentPage === Pages.END">
<v-card-title class="text-h4 justify-center"> <v-card-title class="text-h4 justify-center">
{{ $t('admin.setup.setup-complete') }} {{ $t('admin.setup.setup-complete') }}
@ -72,7 +83,8 @@
<v-card-title class="text-h6 justify-center"> <v-card-title class="text-h6 justify-center">
{{ $t('admin.setup.here-are-a-few-things-to-help-you-get-started') }} {{ $t('admin.setup.here-are-a-few-things-to-help-you-get-started') }}
</v-card-title> </v-card-title>
<div v-for="link, idx in setupCompleteLinks" <div
v-for="link, idx in setupCompleteLinks"
:key="idx" :key="idx"
class="px-4 pt-4" class="px-4 pt-4"
> >
@ -82,7 +94,8 @@
{{ link.section }} {{ link.section }}
</v-card-text> </v-card-text>
</div> </div>
<v-btn :to="link.to" <v-btn
:to="link.to"
color="info" color="info"
> >
{{ link.text }} {{ link.text }}

View file

@ -32,15 +32,30 @@
lg="4" lg="4"
xl="3" xl="3"
> >
<v-col>
<ImageCropper <ImageCropper
:img="imageUrl" :img="imageUrl"
cropper-height="100%" cropper-height="100%"
cropper-width="100%" cropper-width="100%"
:submitted="loading" :submitted="loading"
class="mt-4" class="mt-4 mb-2"
@save="(croppedImage) => updateUploadedImage(index, croppedImage)" @save="(croppedImage) => updateUploadedImage(index, croppedImage)"
@delete="clearImage(index)" @delete="clearImage(index)"
/> />
<v-btn
v-if="uploadedImages.length > 1"
:disabled="loading || index === 0"
color="primary"
@click="() => setCoverImage(index)"
>
<v-icon start>
{{ index === 0 ? $globals.icons.check : $globals.icons.fileImage }}
</v-icon>
{{ index === 0 ? $t("recipe.cover-image") : $t("recipe.set-as-cover-image") }}
</v-btn>
</v-col>
</v-col> </v-col>
</v-row> </v-row>
</div> </div>
@ -106,11 +121,12 @@ export default defineNuxtComponent({
} }
function clearImage(index: number) { function clearImage(index: number) {
// Revoke _before_ splicing
URL.revokeObjectURL(uploadedImagesPreviewUrls.value[index]); URL.revokeObjectURL(uploadedImagesPreviewUrls.value[index]);
uploadedImages.value = uploadedImages.value.filter((_, i) => i !== index); uploadedImages.value.splice(index, 1);
uploadedImageNames.value = uploadedImageNames.value.filter((_, i) => i !== index); uploadedImageNames.value.splice(index, 1);
uploadedImagesPreviewUrls.value = uploadedImagesPreviewUrls.value.filter((_, i) => i !== index); uploadedImagesPreviewUrls.value.splice(index, 1);
} }
async function createRecipe() { async function createRecipe() {
@ -119,6 +135,7 @@ export default defineNuxtComponent({
} }
state.loading = true; state.loading = true;
const translateLanguage = shouldTranslate.value ? i18n.locale : undefined; const translateLanguage = shouldTranslate.value ? i18n.locale : undefined;
const { data, error } = await api.recipes.createOneFromImages(uploadedImages.value, translateLanguage?.value); const { data, error } = await api.recipes.createOneFromImages(uploadedImages.value, translateLanguage?.value);
if (error || !data) { if (error || !data) {
@ -135,6 +152,32 @@ export default defineNuxtComponent({
uploadedImagesPreviewUrls.value[index] = URL.createObjectURL(croppedImage); uploadedImagesPreviewUrls.value[index] = URL.createObjectURL(croppedImage);
} }
function swapItem(array: any[], i: number, j: number) {
if (i < 0 || j < 0 || i >= array.length || j >= array.length) {
return;
}
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
function swapImages(i: number, j: number) {
swapItem(uploadedImages.value, i, j);
swapItem(uploadedImageNames.value, i, j);
swapItem(uploadedImagesPreviewUrls.value, i, j);
}
// Put the intended cover image at the start of the array
// The backend currently sets the first image as the cover image
function setCoverImage(index: number) {
if (index < 0 || index >= uploadedImages.value.length || index === 0) {
return;
}
swapImages(0, index);
}
return { return {
...toRefs(state), ...toRefs(state),
domUrlForm, domUrlForm,
@ -145,6 +188,7 @@ export default defineNuxtComponent({
clearImage, clearImage,
createRecipe, createRecipe,
updateUploadedImage, updateUploadedImage,
setCoverImage,
}; };
}, },
}); });

View file

@ -121,6 +121,7 @@
<v-text-field <v-text-field
v-model="notifiers[index].appriseUrl" v-model="notifiers[index].appriseUrl"
:label="$t('events.apprise-url-skipped-if-blank')" :label="$t('events.apprise-url-skipped-if-blank')"
:hint="$t('events.apprise-url-is-left-intentionally-blank')"
/> />
<v-checkbox <v-checkbox
v-model="notifiers[index].enabled" v-model="notifiers[index].enabled"
@ -368,6 +369,24 @@ export default defineNuxtComponent({
}, },
], ],
}, },
{
id: 8,
text: i18n.t("events.label-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "labelCreated",
},
{
text: i18n.t("general.update") as string,
key: "labelUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "labelDeleted",
},
],
},
]; ];
return { return {

View file

@ -18,10 +18,20 @@
{{ $t('user.it-looks-like-this-is-your-first-time-logging-in') }} {{ $t('user.it-looks-like-this-is-your-first-time-logging-in') }}
</p> </p>
<p class="mb-1"> <p class="mb-1">
<strong>{{ $t('user.username') }}:</strong> changeme@example.com <strong>{{ $t('user.username') }}: </strong>changeme@example.com
<AppButtonCopy
copy-text="changeme@example.com"
color="info"
btn-class="h-auto"
/>
</p> </p>
<p class="mb-3"> <p class="mb-3">
<strong>{{ $t('user.password') }}:</strong> MyPassword <strong>{{ $t('user.password') }}: </strong>MyPassword
<AppButtonCopy
copy-text="MyPassword"
color="info"
btn-class="h-auto"
/>
</p> </p>
<p> <p>
{{ $t('user.dont-want-to-see-this-anymore-be-sure-to-change-your-email') }} {{ $t('user.dont-want-to-see-this-anymore-be-sure-to-change-your-email') }}

View file

@ -4,7 +4,7 @@
class="md-container" class="md-container"
> >
<BaseDialog <BaseDialog
v-model="checkAllDialog" v-model="state.checkAllDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
can-confirm can-confirm
@confirm="checkAll" @confirm="checkAll"
@ -15,7 +15,7 @@
</BaseDialog> </BaseDialog>
<BaseDialog <BaseDialog
v-model="uncheckAllDialog" v-model="state.uncheckAllDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
can-confirm can-confirm
@confirm="uncheckAll" @confirm="uncheckAll"
@ -26,7 +26,7 @@
</BaseDialog> </BaseDialog>
<BaseDialog <BaseDialog
v-model="deleteCheckedDialog" v-model="state.deleteCheckedDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
can-confirm can-confirm
@confirm="deleteChecked" @confirm="deleteChecked"
@ -90,11 +90,6 @@
text: '', text: '',
event: 'three-dot', event: 'three-dot',
children: [ children: [
{
icon: $globals.icons.tags,
text: $t('shopping-list.toggle-label-sort'),
event: 'sort-by-labels',
},
{ {
icon: $globals.icons.tags, icon: $globals.icons.tags,
text: $t('shopping-list.reorder-labels'), text: $t('shopping-list.reorder-labels'),
@ -111,7 +106,6 @@
@edit="edit = true" @edit="edit = true"
@three-dot="threeDot = true" @three-dot="threeDot = true"
@check="openCheckAll" @check="openCheckAll"
@sort-by-labels="sortByLabels"
@copy-plain="copyListItems('plain')" @copy-plain="copyListItems('plain')"
@copy-markdown="copyListItems('markdown')" @copy-markdown="copyListItems('markdown')"
@reorder-labels="toggleReorderLabelsDialog()" @reorder-labels="toggleReorderLabelsDialog()"
@ -159,40 +153,6 @@
</BaseButton> </BaseButton>
</div> </div>
<!-- View without Label grouping -->
<div v-if="!preferences.viewByLabel">
<VueDraggable
v-model="listItems.unchecked"
handle=".handle"
:delay="250"
:delay-on-touch-only="true"
@start="loadingCounter += 1"
@end="loadingCounter -= 1"
@update:model-value="updateIndexUnchecked"
>
<v-lazy
v-for="(item, index) in listItems.unchecked"
:key="item.id"
class="my-2"
>
<ShoppingListItem
v-model="listItems.unchecked[index]"
class="my-2 my-sm-0"
:show-label="true"
:labels="allLabels || []"
:units="allUnits || []"
:foods="allFoods || []"
:recipes="recipeMap"
@checked="saveListItem"
@save="saveListItem"
@delete="deleteListItem(item)"
/>
</v-lazy>
</VueDraggable>
</div>
<!-- View By Label -->
<div v-else>
<div <div
v-for="(value, key) in itemsByLabel" v-for="(value, key) in itemsByLabel"
:key="key" :key="key"
@ -230,7 +190,6 @@
> >
<ShoppingListItem <ShoppingListItem
v-model="value[index]" v-model="value[index]"
:show-label="false"
:labels="allLabels || []" :labels="allLabels || []"
:units="allUnits || []" :units="allUnits || []"
:foods="allFoods || []" :foods="allFoods || []"
@ -244,7 +203,6 @@
</div> </div>
</v-expand-transition> </v-expand-transition>
</div> </div>
</div>
<!-- Reorder Labels --> <!-- Reorder Labels -->
<BaseDialog <BaseDialog
@ -359,12 +317,12 @@
</div> </div>
<v-divider class="my-4" /> <v-divider class="my-4" />
<RecipeList <RecipeList
:recipes="Array.from(recipeMap.values())" :recipes="recipeList"
show-description show-description
:disabled="isOffline" :disabled="isOffline"
> >
<template <template
v-for="(recipe, index) in recipeMap.values()" v-for="(recipe, index) in recipeList"
#[`actions-${recipe.id}`] #[`actions-${recipe.id}`]
:key="'item-actions-decrease' + recipe.id" :key="'item-actions-decrease' + recipe.id"
> >
@ -408,26 +366,14 @@
<script lang="ts"> <script lang="ts">
import { VueDraggable } from "vue-draggable-plus"; import { VueDraggable } from "vue-draggable-plus";
import { useIdle, useOnline, useToggle } from "@vueuse/core";
import { useCopyList } from "~/composables/use-copy";
import { useUserApi } from "~/composables/api";
import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue"; import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue";
import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue";
import type { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/household";
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
import { useShoppingListItemActions } from "~/composables/use-shopping-list-item-actions";
import { useShoppingListPreferences } from "~/composables/use-users/preferences"; import { useShoppingListPreferences } from "~/composables/use-users/preferences";
import { getTextColor } from "~/composables/use-text-color"; import { getTextColor } from "~/composables/use-text-color";
import { uuid4 } from "~/composables/use-utils"; import { useShoppingListPage } from "~/composables/shopping-list-page/use-shopping-list-page";
type CopyTypes = "plain" | "markdown";
interface PresentLabel {
id: string;
name: string;
}
export default defineNuxtComponent({ export default defineNuxtComponent({
components: { components: {
@ -437,809 +383,34 @@ export default defineNuxtComponent({
RecipeList, RecipeList,
ShoppingListItemEditor, ShoppingListItemEditor,
}, },
// middleware: "sidebase-auth",
setup() { setup() {
const { mdAndUp } = useDisplay(); const { mdAndUp } = useDisplay();
const i18n = useI18n(); const i18n = useI18n();
const $auth = useMealieAuth(); const $auth = useMealieAuth();
const preferences = useShoppingListPreferences(); const preferences = useShoppingListPreferences();
const isOffline = computed(() => useOnline().value === false);
useSeoMeta({ useSeoMeta({
title: i18n.t("shopping-list.shopping-list"), title: i18n.t("shopping-list.shopping-list"),
}); });
const { idle } = useIdle(5 * 60 * 1000); // 5 minutes
const loadingCounter = ref(1);
const recipeReferenceLoading = ref(false);
const userApi = useUserApi();
const edit = ref(false);
const threeDot = ref(false);
const reorderLabelsDialog = ref(false);
const preserveItemOrder = ref(false);
const route = useRoute(); const route = useRoute();
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || ""); const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
const id = route.params.id as string; const id = route.params.id as string;
const shoppingListItemActions = useShoppingListItemActions(id);
const state = reactive({
checkAllDialog: false,
uncheckAllDialog: false,
deleteCheckedDialog: false,
});
// ===============================================================
// Shopping List Actions
const shoppingList = ref<ShoppingListOut | null>(null);
async function fetchShoppingList() {
const data = await shoppingListItemActions.getList();
return data;
}
async function refresh() {
loadingCounter.value += 1;
try {
await shoppingListItemActions.process();
}
catch (error) {
console.error(error);
}
let newListValue: typeof shoppingList.value = null;
try {
newListValue = await fetchShoppingList();
}
catch (error) {
console.error(error);
}
loadingCounter.value -= 1;
// only update the list with the new value if we're not loading, to prevent UI jitter
if (loadingCounter.value) {
return;
}
// Prevent overwriting local changes with stale backend data when offline
if (isOffline.value) {
// Do not update shoppingList.value from backend when offline
updateListItemOrder();
return;
}
// if we're not connected to the network, this will be null, so we don't want to clear the list
if (newListValue) {
shoppingList.value = newListValue;
}
updateListItemOrder();
}
function updateListItemOrder() {
if (!preserveItemOrder.value) {
groupAndSortListItemsByFood();
}
else {
sortListItems();
}
updateItemsByLabel();
}
// constantly polls for changes
async function pollForChanges() {
// pause polling if the user isn't active or we're busy
if (idle.value || loadingCounter.value) {
return;
}
try {
await refresh();
if (shoppingList.value) {
attempts = 0;
return;
}
// if the refresh was unsuccessful, the shopping list will be null, so we increment the attempt counter
attempts++;
}
catch {
attempts++;
}
// if we hit too many errors, stop polling
if (attempts >= maxAttempts) {
clearInterval(pollTimer);
}
}
// start polling
loadingCounter.value -= 1;
pollForChanges(); // populate initial list
// max poll time = pollFrequency * maxAttempts = 24 hours
// we use a long max poll time since polling stops when the user is idle anyway
const pollFrequency = 5000;
const maxAttempts = 17280;
let attempts = 0;
const pollTimer: ReturnType<typeof setInterval> = setInterval(() => {
pollForChanges();
}, pollFrequency);
onUnmounted(() => {
clearInterval(pollTimer);
});
// =====================================
// List Item CRUD
// Hydrate listItems from shoppingList.value?.listItems
const listItems = reactive({
unchecked: [] as ShoppingListItemOut[],
checked: [] as ShoppingListItemOut[],
});
function sortCheckedItems(a: ShoppingListItemOut, b: ShoppingListItemOut) {
if (a.updatedAt! === b.updatedAt!) {
return ((a.position || 0) > (b.position || 0)) ? -1 : 1;
}
return a.updatedAt! < b.updatedAt! ? 1 : -1;
}
watch(
() => shoppingList.value?.listItems,
(items) => {
listItems.unchecked = (items?.filter(item => !item.checked) ?? []);
listItems.checked = (items?.filter(item => item.checked)
.sort(sortCheckedItems) ?? []);
},
{ immediate: true },
);
// =====================================
// Collapsable Labels
const labelOpenState = ref<{ [key: string]: boolean }>({});
const initializeLabelOpenStates = () => {
if (!shoppingList.value?.listItems) return;
const existingLabels = new Set(Object.keys(labelOpenState.value));
let hasChanges = false;
for (const item of shoppingList.value.listItems) {
const labelName = item.label?.name || i18n.t("shopping-list.no-label");
if (!existingLabels.has(labelName) && !(labelName in labelOpenState.value)) {
labelOpenState.value[labelName] = true;
hasChanges = true;
}
}
if (hasChanges) {
labelOpenState.value = { ...labelOpenState.value };
}
};
const labelNames = computed(() => {
return new Set(
shoppingList.value?.listItems
?.map(item => item.label?.name || i18n.t("shopping-list.no-label"))
.filter(Boolean) ?? [],
);
});
watch(labelNames, initializeLabelOpenStates, { immediate: true });
function toggleShowLabel(key: string) {
labelOpenState.value[key] = !labelOpenState.value[key];
}
const [showChecked, toggleShowChecked] = useToggle(false);
// =====================================
// Copy List Items
const copy = useCopyList();
function copyListItems(copyType: CopyTypes) {
const text: string[] = [];
if (preferences.value.viewByLabel) {
// if we're sorting by label, we want the copied text in subsections
Object.entries(itemsByLabel.value).forEach(([label, items], idx) => {
// for every group except the first, add a blank line
if (idx) {
text.push("");
}
// add an appropriate heading for the label depending on the copy format
text.push(formatCopiedLabelHeading(copyType, label));
// now add the appropriately formatted list items with the given label
items.forEach(item => text.push(formatCopiedListItem(copyType, item)));
});
}
else {
// labels are toggled off, so just copy in the order they come in
const items = shoppingList.value?.listItems?.filter(item => !item.checked);
items?.forEach((item) => {
text.push(formatCopiedListItem(copyType, item));
});
}
copy.copyPlain(text);
}
function formatCopiedListItem(copyType: CopyTypes, item: ShoppingListItemOut): string {
const display = item.display || "";
switch (copyType) {
case "markdown":
return `- [ ] ${display}`;
default:
return display;
}
}
function formatCopiedLabelHeading(copyType: CopyTypes, label: string): string {
switch (copyType) {
case "markdown":
return `# ${label}`;
default:
return `[${label}]`;
}
}
// =====================================
// Check / Uncheck All
function openCheckAll() {
if (shoppingList.value?.listItems?.some(item => !item.checked)) {
state.checkAllDialog = true;
}
}
function checkAll() {
state.checkAllDialog = false;
let hasChanged = false;
shoppingList.value?.listItems?.forEach((item) => {
if (!item.checked) {
hasChanged = true;
item.checked = true;
}
});
if (hasChanged) {
updateUncheckedListItems();
}
}
function openUncheckAll() {
if (shoppingList.value?.listItems?.some(item => item.checked)) {
state.uncheckAllDialog = true;
}
}
function uncheckAll() {
state.uncheckAllDialog = false;
let hasChanged = false;
shoppingList.value?.listItems?.forEach((item) => {
if (item.checked) {
hasChanged = true;
item.checked = false;
}
});
if (hasChanged) {
listItems.unchecked = [...listItems.unchecked, ...listItems.checked];
listItems.checked = [];
updateUncheckedListItems();
}
}
function openDeleteChecked() {
if (shoppingList.value?.listItems?.some(item => item.checked)) {
state.deleteCheckedDialog = true;
}
}
function deleteChecked() {
const checked = shoppingList.value?.listItems?.filter(item => item.checked);
if (!checked || checked?.length === 0) {
return;
}
loadingCounter.value += 1;
deleteListItems(checked);
loadingCounter.value -= 1;
refresh();
}
// =====================================
// List Item Context Menu
const contextActions = {
delete: "delete",
};
const contextMenu = [
{ title: i18n.t("general.delete"), action: contextActions.delete },
];
// =====================================
// Labels, Units, Foods
// TODO: Extract to Composable
const localLabels = ref<ShoppingListMultiPurposeLabelOut[]>();
const shoppingListPage = useShoppingListPage(id);
const { store: allLabels } = useLabelStore(); const { store: allLabels } = useLabelStore();
const { store: allUnits } = useUnitStore(); const { store: allUnits } = useUnitStore();
const { store: allFoods } = useFoodStore(); const { store: allFoods } = useFoodStore();
function getLabelColor(item: ShoppingListItemOut | null) {
return item?.label?.color;
}
function sortByLabels() {
preferences.value.viewByLabel = !preferences.value.viewByLabel;
}
function toggleReorderLabelsDialog() {
// stop polling and populate localLabels
loadingCounter.value += 1;
reorderLabelsDialog.value = !reorderLabelsDialog.value;
localLabels.value = shoppingList.value?.labelSettings;
}
function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) {
if (!shoppingList.value) {
return;
}
labelSettings.forEach((labelSetting, index) => {
labelSetting.position = index;
return labelSetting;
});
localLabels.value = labelSettings;
}
function cancelLabelOrder() {
loadingCounter.value -= 1;
if (!shoppingList.value) {
return;
}
// restore original state
localLabels.value = shoppingList.value.labelSettings;
}
async function saveLabelOrder() {
if (!shoppingList.value || !localLabels.value || (localLabels.value === shoppingList.value.labelSettings)) {
return;
}
loadingCounter.value += 1;
const { data } = await userApi.shopping.lists.updateLabelSettings(shoppingList.value.id, localLabels.value);
loadingCounter.value -= 1;
if (data) {
// update shoppingList labels using the API response
shoppingList.value.labelSettings = (data as ShoppingListOut).labelSettings;
updateItemsByLabel();
}
}
const presentLabels = computed(() => {
const labels: PresentLabel[] = [];
shoppingList.value?.listItems?.forEach((item) => {
if (item.labelId && item.label) {
labels.push({
name: item.label.name,
id: item.labelId,
});
}
});
return labels;
});
const itemsByLabel = ref<{ [key: string]: ShoppingListItemOut[] }>({});
interface ListItemGroup {
position: number;
createdAt: string;
items: ShoppingListItemOut[];
}
function sortItems(a: ShoppingListItemOut | ListItemGroup, b: ShoppingListItemOut | ListItemGroup) {
// Sort by position ASC, then by createdAt ASC
const posA = a.position ?? 0;
const posB = b.position ?? 0;
if (posA !== posB) {
return posA - posB;
}
const createdA = a.createdAt ?? "";
const createdB = b.createdAt ?? "";
if (createdA !== createdB) {
return createdA < createdB ? -1 : 1;
}
return 0;
}
function groupAndSortListItemsByFood() {
if (!shoppingList.value?.listItems?.length) {
return;
}
const checkedItemKey = "__checkedItem";
const listItemGroupsMap = new Map<string, ListItemGroup>();
listItemGroupsMap.set(checkedItemKey, { position: Number.MAX_SAFE_INTEGER, createdAt: "", items: [] });
// group items by checked status, food, or note
shoppingList.value.listItems.forEach((item) => {
const key = item.checked
? checkedItemKey
: item.food?.name
? item.food.name
: item.note || "";
const group = listItemGroupsMap.get(key);
if (!group) {
listItemGroupsMap.set(key, { position: item.position || 0, createdAt: item.createdAt || "", items: [item] });
}
else {
group.items.push(item);
}
});
const listItemGroups = Array.from(listItemGroupsMap.values());
listItemGroups.sort(sortItems);
// sort group items, then aggregate them
const sortedItems: ShoppingListItemOut[] = [];
let nextPosition = 0;
listItemGroups.forEach((listItemGroup) => {
listItemGroup.items.sort(sortItems);
listItemGroup.items.forEach((item) => {
item.position = nextPosition;
nextPosition += 1;
sortedItems.push(item);
});
});
shoppingList.value.listItems = sortedItems;
}
function sortListItems() {
if (!shoppingList.value?.listItems?.length) {
return;
}
shoppingList.value.listItems.sort(sortItems);
}
function updateItemsByLabel() {
const items: { [prop: string]: ShoppingListItemOut[] } = {};
const noLabelText = i18n.t("shopping-list.no-label");
const noLabel = [] as ShoppingListItemOut[];
shoppingList.value?.listItems?.forEach((item) => {
if (item.checked) {
return;
}
if (item.labelId) {
if (item.label && item.label.name in items) {
items[item.label.name].push(item);
}
else if (item.label) {
items[item.label.name] = [item];
}
}
else {
noLabel.push(item);
}
});
if (noLabel.length > 0) {
items[noLabelText] = noLabel;
}
// sort the map by label order
const orderedLabelNames = shoppingList.value?.labelSettings?.map(labelSetting => labelSetting.label.name);
if (!orderedLabelNames) {
itemsByLabel.value = items;
return;
}
const itemsSorted: { [prop: string]: ShoppingListItemOut[] } = {};
if (noLabelText in items) {
itemsSorted[noLabelText] = items[noLabelText];
}
orderedLabelNames.forEach((labelName) => {
if (labelName in items) {
itemsSorted[labelName] = items[labelName];
}
});
itemsByLabel.value = itemsSorted;
}
// =====================================
// Add/Remove Recipe References
const recipeMap = computed(() => new Map(
(shoppingList.value?.recipeReferences?.map(ref => ref.recipe) ?? [])
.map(recipe => [recipe.id || "", recipe])),
);
async function addRecipeReferenceToList(recipeId: string) {
if (!shoppingList.value || recipeReferenceLoading.value) {
return;
}
loadingCounter.value += 1;
recipeReferenceLoading.value = true;
const { data } = await userApi.shopping.lists.addRecipes(shoppingList.value.id, [{ recipeId }]);
recipeReferenceLoading.value = false;
loadingCounter.value -= 1;
if (data) {
refresh();
}
}
async function removeRecipeReferenceToList(recipeId: string) {
if (!shoppingList.value || recipeReferenceLoading.value) {
return;
}
loadingCounter.value += 1;
recipeReferenceLoading.value = true;
const { data } = await userApi.shopping.lists.removeRecipe(shoppingList.value.id, recipeId);
recipeReferenceLoading.value = false;
loadingCounter.value -= 1;
if (data) {
refresh();
}
}
// =====================================
// List Item CRUD
/*
* saveListItem updates and update on the backend server. Additionally, if the item is
* checked it will also append that item to the end of the list so that the unchecked items
* are at the top of the list.
*/
function saveListItem(item: ShoppingListItemOut) {
if (!shoppingList.value) {
return;
}
// set a temporary updatedAt timestamp prior to refresh so it appears at the top of the checked items
item.updatedAt = new Date().toISOString();
// make updates reflect immediately
if (shoppingList.value.listItems) {
shoppingList.value.listItems.forEach((oldListItem: ShoppingListItemOut, idx: number) => {
if (oldListItem.id === item.id && shoppingList.value?.listItems) {
shoppingList.value.listItems[idx] = item;
}
});
// Immediately update checked/unchecked arrays for UI
listItems.unchecked = shoppingList.value.listItems.filter(i => !i.checked);
listItems.checked = shoppingList.value.listItems.filter(i => i.checked)
.sort(sortCheckedItems);
}
// Update the item if it's checked, otherwise updateUncheckedListItems will handle it
if (item.checked) {
shoppingListItemActions.updateItem(item);
}
updateListItemOrder();
updateUncheckedListItems();
}
function deleteListItem(item: ShoppingListItemOut) {
if (!shoppingList.value) {
return;
}
shoppingListItemActions.deleteItem(item);
// remove the item from the list immediately so the user sees the change
if (shoppingList.value.listItems) {
shoppingList.value.listItems = shoppingList.value.listItems.filter(itm => itm.id !== item.id);
}
refresh();
}
// =====================================
// Create New Item
const createEditorOpen = ref(false);
const createListItemData = ref<ShoppingListItemOut>(listItemFactory());
function listItemFactory(): ShoppingListItemOut {
return { return {
id: uuid4(),
shoppingListId: id,
checked: false,
position: shoppingList.value?.listItems?.length || 1,
quantity: 0,
note: "",
labelId: undefined,
unitId: undefined,
foodId: undefined,
} as ShoppingListItemOut;
}
/* const newMeal = reactive({
date: "",
title: "",
text: "",
recipeId: undefined as string | undefined,
entryType: "dinner" as PlanEntryType,
existing: false,
id: 0,
groupId: "",
userId: $auth.user.value?.id || "",
}); */
function createListItem() {
if (!shoppingList.value) {
return;
}
if (!createListItemData.value.foodId && !createListItemData.value.note) {
// don't create an empty item
return;
}
loadingCounter.value += 1;
// make sure it's inserted into the end of the list, which may have been updated
createListItemData.value.position = shoppingList.value?.listItems?.length
? (shoppingList.value.listItems.reduce((a, b) => (a.position || 0) > (b.position || 0) ? a : b).position || 0) + 1
: 0;
createListItemData.value.createdAt = new Date().toISOString();
createListItemData.value.updatedAt = createListItemData.value.createdAt;
updateListItemOrder();
shoppingListItemActions.createItem(createListItemData.value);
loadingCounter.value -= 1;
if (shoppingList.value.listItems) {
// add the item to the list immediately so the user sees the change
shoppingList.value.listItems.push(createListItemData.value);
updateListItemOrder();
}
createListItemData.value = listItemFactory();
refresh();
}
function updateIndexUnchecked(uncheckedItems: ShoppingListItemOut[]) {
listItems.unchecked = uncheckedItems;
listItems.checked = shoppingList.value?.listItems?.filter(item => item.checked) || [];
// since the user has manually reordered the list, we should preserve this order
preserveItemOrder.value = true;
updateUncheckedListItems();
}
function updateIndexUncheckedByLabel(labelName: string, labeledUncheckedItems: ShoppingListItemOut[]) {
if (!itemsByLabel.value[labelName]) {
return;
}
// update this label's item order
itemsByLabel.value[labelName] = labeledUncheckedItems;
// reset list order of all items
const allUncheckedItems: ShoppingListItemOut[] = [];
for (labelName in itemsByLabel.value) {
allUncheckedItems.push(...itemsByLabel.value[labelName]);
}
// since the user has manually reordered the list, we should preserve this order
preserveItemOrder.value = true;
// save changes
listItems.unchecked = allUncheckedItems;
listItems.checked = shoppingList.value?.listItems?.filter(item => item.checked) || [];
updateUncheckedListItems();
}
function deleteListItems(items: ShoppingListItemOut[]) {
if (!shoppingList.value) {
return;
}
items.forEach((item) => {
shoppingListItemActions.deleteItem(item);
});
// remove the items from the list immediately so the user sees the change
if (shoppingList.value?.listItems) {
const deletedItems = new Set(items.map(item => item.id));
shoppingList.value.listItems = shoppingList.value.listItems.filter(itm => !deletedItems.has(itm.id));
}
refresh();
}
function updateUncheckedListItems() {
if (!shoppingList.value?.listItems) {
return;
}
// Set position for unchecked items
listItems.unchecked.forEach((item: ShoppingListItemOut, idx: number) => {
item.position = idx;
shoppingListItemActions.updateItem(item);
});
refresh();
}
return {
...toRefs(state),
addRecipeReferenceToList,
allLabels,
contextMenu,
copyListItems,
createEditorOpen,
createListItem,
createListItemData,
deleteChecked,
openDeleteChecked,
deleteListItem,
edit,
threeDot,
getLabelColor,
groupSlug, groupSlug,
itemsByLabel,
listItems,
loadingCounter,
preferences, preferences,
presentLabels, allLabels,
recipeMap,
removeRecipeReferenceToList,
reorderLabelsDialog,
toggleReorderLabelsDialog,
localLabels,
updateLabelOrder,
cancelLabelOrder,
saveLabelOrder,
saveListItem,
shoppingList,
showChecked,
sortByLabels,
labelOpenState,
toggleShowLabel,
toggleShowChecked,
uncheckAll,
openUncheckAll,
checkAll,
openCheckAll,
updateIndexUnchecked,
updateIndexUncheckedByLabel,
allUnits, allUnits,
allFoods, allFoods,
getTextColor, getTextColor,
isOffline,
mdAndUp, mdAndUp,
...shoppingListPage,
}; };
}, },
}); });

View file

@ -81,4 +81,4 @@ declare module "vue" {
} }
} }
export {}; export { };

View file

@ -0,0 +1,48 @@
"""'Add label notifier CRUD bools'
Revision ID: e6bb583aac2d
Revises: d7b3ce6fa31a
Create Date: 2025-08-09 19:32:37.285172
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "e6bb583aac2d"
down_revision: str | None = "d7b3ce6fa31a"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("group_events_notifier_options", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"label_created", sa.Boolean(), nullable=False, default=False, server_default=sa.sql.expression.false()
)
)
batch_op.add_column(
sa.Column(
"label_updated", sa.Boolean(), nullable=False, default=False, server_default=sa.sql.expression.false()
)
)
batch_op.add_column(
sa.Column(
"label_deleted", sa.Boolean(), nullable=False, default=False, server_default=sa.sql.expression.false()
)
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("group_events_notifier_options", schema=None) as batch_op:
batch_op.drop_column("label_deleted")
batch_op.drop_column("label_updated")
batch_op.drop_column("label_created")
# ### end Alembic commands ###

View file

@ -46,6 +46,10 @@ class GroupEventNotifierOptionsModel(SqlAlchemyBase, BaseMixins):
category_updated: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) category_updated: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
category_deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) category_deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
label_created: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
label_updated: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
label_deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
@auto_init() @auto_init()
def __init__(self, **_) -> None: def __init__(self, **_) -> None:
pass pass

View file

@ -14,7 +14,7 @@
"serves": "Порций", "serves": "Порций",
"serving": "Порция", "serving": "Порция",
"servings": "Порции", "servings": "Порции",
"yield": "Выход", "yield": "Количество порций",
"yields": "Можно получить" "yields": "Можно получить"
} }
}, },

View file

@ -132,8 +132,8 @@
"baby green": { "baby green": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "baby green", "name": "babysalat",
"plural_name": "baby greens" "plural_name": "babysalater"
}, },
"pumpkin": { "pumpkin": {
"aliases": [], "aliases": [],
@ -199,7 +199,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "romaine", "name": "romaine",
"plural_name": "romaines" "plural_name": "romaine"
}, },
"beetroot": { "beetroot": {
"aliases": [], "aliases": [],
@ -339,46 +339,46 @@
"chard": { "chard": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chard", "name": "bladbede",
"plural_name": "chards" "plural_name": "bladbeder"
}, },
"pimiento": { "pimiento": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pimiento", "name": "tapas peber",
"plural_name": "pimientoes" "plural_name": "tapas peber"
}, },
"spaghetti squash": { "spaghetti squash": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "spaghetti squash", "name": "spaghetti squash",
"plural_name": "spaghetti squashes" "plural_name": "spaghetti squash"
}, },
"butter lettuce": { "butter lettuce": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "butter lettuce", "name": "havesalat",
"plural_name": "butter lettuces" "plural_name": "havesalater"
}, },
"hash brown": { "hash brown": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "hash brown", "name": "rösti",
"plural_name": "hash browns" "plural_name": "rösti"
}, },
"napa cabbage": { "napa cabbage": {
"aliases": [ "aliases": [
"chinese leaves" "kinakål"
], ],
"description": "", "description": "",
"name": "napa cabbage", "name": "kinakål",
"plural_name": "napa cabbages" "plural_name": "kinakål"
}, },
"celeriac": { "celeriac": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "celeriac", "name": "knoldselleri",
"plural_name": "celeriacs" "plural_name": "knoldselleri"
}, },
"water chestnut": { "water chestnut": {
"aliases": [], "aliases": [],
@ -395,8 +395,8 @@
"thai chile pepper": { "thai chile pepper": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "thai chile pepper", "name": "thai chili",
"plural_name": "thai chile peppers" "plural_name": "thai chilier"
}, },
"bok choy": { "bok choy": {
"aliases": [], "aliases": [],
@ -413,8 +413,8 @@
"acorn squash": { "acorn squash": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "acorn squash", "name": "vintergræskar",
"plural_name": "acorn squashes" "plural_name": "vintergræskar"
}, },
"corn cob": { "corn cob": {
"aliases": [], "aliases": [],
@ -431,32 +431,32 @@
"pearl onion": { "pearl onion": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pearl onion", "name": "perleløg",
"plural_name": "pearl onions" "plural_name": "perleløg"
}, },
"tenderstem broccoli": { "tenderstem broccoli": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "tenderstem broccoli", "name": "broccoli",
"plural_name": "tenderstem broccolis" "plural_name": "broccoli"
}, },
"plantain": { "plantain": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "plantain", "name": "madbanan",
"plural_name": "plantains" "plural_name": "madbananer"
}, },
"leaf lettuce": { "leaf lettuce": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "leaf lettuce", "name": "pluksalat",
"plural_name": "leaf lettuces" "plural_name": "pluksalat"
}, },
"pepperoncini": { "pepperoncini": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pepperoncini", "name": "peberfrugt",
"plural_name": "pepperoncinis" "plural_name": "peberfrugt"
}, },
"baby bok choy": { "baby bok choy": {
"aliases": [], "aliases": [],
@ -468,39 +468,39 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "jicama", "name": "jicama",
"plural_name": "jicamas" "plural_name": "jicama"
}, },
"endive": { "endive": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "endive", "name": "julesalat",
"plural_name": "endives" "plural_name": "julesalat"
}, },
"habanero pepper": { "habanero pepper": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "habanero pepper", "name": "habanero peber",
"plural_name": "habanero peppers" "plural_name": "habanero peber"
}, },
"corn husk": { "corn husk": {
"aliases": [ "aliases": [
"maize" "majs"
], ],
"description": "", "description": "",
"name": "corn husk", "name": "majsblad",
"plural_name": "corn husks" "plural_name": "majsblade"
}, },
"collard green": { "collard green": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "collard green", "name": "fodermarvkål",
"plural_name": "collard greens" "plural_name": "fodermarvkål"
}, },
"french-fried onion": { "french-fried onion": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "french-fried onion", "name": "ristet løg",
"plural_name": "french-fried onions" "plural_name": "ristet løg"
}, },
"daikon": { "daikon": {
"aliases": [], "aliases": [],
@ -660,7 +660,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "orange", "name": "orange",
"plural_name": "oranges" "plural_name": "appelsiner"
}, },
"raisin": { "raisin": {
"aliases": [], "aliases": [],
@ -720,7 +720,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "granatæble", "name": "granatæble",
"plural_name": "pomegranates" "plural_name": "granatæble"
}, },
"watermelon": { "watermelon": {
"aliases": [], "aliases": [],
@ -785,116 +785,116 @@
"prune": { "prune": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "prune", "name": "sveske",
"plural_name": "prunes" "plural_name": "sveske"
}, },
"cantaloupe": { "cantaloupe": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cantaloupe", "name": "cantaloupe-melon",
"plural_name": "cantaloupes" "plural_name": "cantaloupe-meloner"
}, },
"sultana": { "sultana": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sultana", "name": "vindrue",
"plural_name": "sultanas" "plural_name": "vindruer"
}, },
"passion fruit": { "passion fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "passion fruit", "name": "passionsfrugt",
"plural_name": "passion fruits" "plural_name": "passionsfrugt"
}, },
"papaya": { "papaya": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "papaya", "name": "papaya",
"plural_name": "papayas" "plural_name": "papaya"
}, },
"tamarind": { "tamarind": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "tamarind", "name": "tamarind",
"plural_name": "tamarinds" "plural_name": "tamarind"
}, },
"nectarine": { "nectarine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "nectarine", "name": "nektarin",
"plural_name": "nectarines" "plural_name": "nektariner"
}, },
"dried fig": { "dried fig": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried fig", "name": "tørret figen",
"plural_name": "dried figs" "plural_name": "tørrede figner"
}, },
"chestnut": { "chestnut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chestnut", "name": "kastanje",
"plural_name": "chestnuts" "plural_name": "kastanier"
}, },
"meyer lemon": { "meyer lemon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "meyer lemon", "name": "citron",
"plural_name": "meyer lemons" "plural_name": "citroner"
}, },
"honeydew melon": { "honeydew melon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "honeydew melon", "name": "honningmelon",
"plural_name": "honeydew melons" "plural_name": "honningmeloner"
}, },
"dried fruit": { "dried fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried fruit", "name": "tørret frugt",
"plural_name": "dried fruits" "plural_name": "tørrede frugter"
}, },
"clementine": { "clementine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "clementine", "name": "klementin",
"plural_name": "clementines" "plural_name": "klementiner"
}, },
"persimmon": { "persimmon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "persimmon", "name": "daddelblomme",
"plural_name": "persimmons" "plural_name": "daddelblommer"
}, },
"melon": { "melon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "melon", "name": "melon",
"plural_name": "melons" "plural_name": "meloner"
}, },
"tangerine": { "tangerine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "tangerine", "name": "madarin",
"plural_name": "tangerines" "plural_name": "madariner"
}, },
"dried mango": { "dried mango": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried mango", "name": "tørret mango",
"plural_name": "dried mangoes" "plural_name": "tørret mango"
}, },
"dried apple": { "dried apple": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried apple", "name": "tørret æble",
"plural_name": "dried apples" "plural_name": "tørrede æbler"
}, },
"quince": { "quince": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "quince", "name": "kvæde",
"plural_name": "quinces" "plural_name": "kvæder"
}, },
"guava": { "guava": {
"aliases": [], "aliases": [],
@ -905,31 +905,31 @@
"banana chip": { "banana chip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "banana chip", "name": "banan chips",
"plural_name": "banana chips" "plural_name": "banan chips"
}, },
"kumquat": { "kumquat": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "kumquat", "name": "kumquat",
"plural_name": "kumquats" "plural_name": "kumquat"
}, },
"jackfruit": { "jackfruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "jackfruit", "name": "jackfrugt",
"plural_name": "jackfruits" "plural_name": "jackfrugter"
}, },
"dragon fruit": { "dragon fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dragon fruit", "name": "drage frugt",
"plural_name": "dragon fruits" "plural_name": "drage frugter"
}, },
"mixed fruit": { "mixed fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mixed fruit", "name": "blandet frugt",
"plural_name": "mixed fruits" "plural_name": "mixed fruits"
}, },
"asian pear": { "asian pear": {
@ -1055,7 +1055,7 @@
"dried lemon": { "dried lemon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried lemon", "name": "tørret citron",
"plural_name": "dried lemons" "plural_name": "dried lemons"
}, },
"young jackfruit": { "young jackfruit": {

View file

@ -4493,8 +4493,8 @@
"ground turkey sausage": { "ground turkey sausage": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "ground turkey sausage", "name": "Geräucherte Putenwurst",
"plural_name": "ground turkey sausages" "plural_name": "Geräucherte Putenwurst"
}, },
"quail": { "quail": {
"aliases": [], "aliases": [],
@ -4607,8 +4607,8 @@
"chicken andouille": { "chicken andouille": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken andouille", "name": "Hühner-Andouille",
"plural_name": "chicken andouilles" "plural_name": "Hühner-Andouille"
}, },
"chicken gizzard": { "chicken gizzard": {
"aliases": [], "aliases": [],
@ -4625,8 +4625,8 @@
"chicken italian sausage": { "chicken italian sausage": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken italian sausage", "name": "Hähnchen-Salsiccia",
"plural_name": "chicken italian sausages" "plural_name": "Hähnchen-Salsiccia"
}, },
"crispy chicken strip": { "crispy chicken strip": {
"aliases": [], "aliases": [],
@ -4643,20 +4643,20 @@
"popcorn chicken": { "popcorn chicken": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "popcorn chicken", "name": "Popcorn-Hähnchen",
"plural_name": "popcorn chickens" "plural_name": "Popcorn-Hähnchen"
}, },
"turkey kielbasa": { "turkey kielbasa": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "turkey kielbasa", "name": "Truthahn-Kielbasa",
"plural_name": "turkey kielbasas" "plural_name": "Truthahn-Kielbasa"
}, },
"chicken-apple sausage": { "chicken-apple sausage": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken-apple sausage", "name": "Hähnchen-Apfel-Wurst",
"plural_name": "chicken-apple sausages" "plural_name": "Hähnchen-Apfel-Würsten"
}, },
"chicken foot": { "chicken foot": {
"aliases": [], "aliases": [],
@ -4673,8 +4673,8 @@
"deli chicken": { "deli chicken": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "deli chicken", "name": "Delikatessen-Hähnchen",
"plural_name": "deli chickens" "plural_name": "Delikatessen-Hähnchen"
}, },
"smoked duck breast": { "smoked duck breast": {
"aliases": [], "aliases": [],
@ -4709,8 +4709,8 @@
"duck confit": { "duck confit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "duck confit", "name": "Entenconfit",
"plural_name": "duck confits" "plural_name": "Entenconfite"
}, },
"roast duck": { "roast duck": {
"aliases": [], "aliases": [],
@ -4740,13 +4740,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "geräucherte Putenflügel", "name": "geräucherte Putenflügel",
"plural_name": "smoked turkey wings" "plural_name": "Geräucherte Truthahnflügel"
}, },
"chicken curry-cut": { "chicken curry-cut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken curry-cut", "name": "Curry-geschnittenes Hähnchen",
"plural_name": "chicken curry-cuts" "plural_name": "Curry-geschnittene Hähnchenteile"
}, },
"chicken schnitzel": { "chicken schnitzel": {
"aliases": [], "aliases": [],
@ -6095,8 +6095,8 @@
"chipotle": { "chipotle": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chipotle", "name": "Chipotle",
"plural_name": "chipotles" "plural_name": "Chipotles"
}, },
"fenugreek": { "fenugreek": {
"aliases": [], "aliases": [],
@ -6131,8 +6131,8 @@
"dried parsley flake": { "dried parsley flake": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried parsley flake", "name": "Petersilie, gerebelt",
"plural_name": "dried parsley flakes" "plural_name": "Petersilie, gerebelt"
}, },
"fenugreek seed": { "fenugreek seed": {
"aliases": [], "aliases": [],
@ -6143,8 +6143,8 @@
"kashmiri red chilli": { "kashmiri red chilli": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "kashmiri red chilli", "name": "Kashmiri Mirch",
"plural_name": "kashmiri red chillis" "plural_name": "Kashmiri Mirch"
}, },
"thai basil": { "thai basil": {
"aliases": [], "aliases": [],
@ -6167,8 +6167,8 @@
"kaffir lime leaf": { "kaffir lime leaf": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "kaffir lime leaf", "name": "Kaffir-Limettenblatt",
"plural_name": "kaffir lime leaves" "plural_name": "Kaffir-Limettenblätter"
}, },
"chervil": { "chervil": {
"aliases": [], "aliases": [],
@ -6192,7 +6192,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mexikanischer Oregano", "name": "mexikanischer Oregano",
"plural_name": "mexican oreganos" "plural_name": "mexikanischer Oregano"
}, },
"mace": { "mace": {
"aliases": [], "aliases": [],
@ -6233,8 +6233,8 @@
"guajillo pepper": { "guajillo pepper": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "guajillo pepper", "name": "Guajillo-Chili",
"plural_name": "guajillo peppers" "plural_name": "Guajillo-Chilis"
}, },
"pink peppercorn": { "pink peppercorn": {
"aliases": [], "aliases": [],
@ -6305,38 +6305,38 @@
"achiote seed": { "achiote seed": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "achiote seed", "name": "Annatto-Samen",
"plural_name": "achiote seeds" "plural_name": "Annatto-Samen"
}, },
"savory herb": { "savory herb": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "savory herb", "name": "Bohnenkraut",
"plural_name": "savory herbs" "plural_name": "Bohnenkraut"
}, },
"pandan leaf": { "pandan leaf": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pandan leaf", "name": "Pandanblatt",
"plural_name": "pandan leaves" "plural_name": "Pandanblätter"
}, },
"sorrel": { "sorrel": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sorrel", "name": "Sauerampfer",
"plural_name": "sorrels" "plural_name": "Sauerampfer"
}, },
"gochugaru": { "gochugaru": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "gochugaru", "name": "Gochugaru",
"plural_name": "gochugarus" "plural_name": "Gochugaru"
}, },
"saigon cinnamon": { "saigon cinnamon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "saigon cinnamon", "name": "vietnamesischer Zimt",
"plural_name": "saigon cinnamons" "plural_name": "vietnamesischer Zimt"
}, },
"lemongrass paste": { "lemongrass paste": {
"aliases": [], "aliases": [],
@ -6347,14 +6347,14 @@
"shiso": { "shiso": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "shiso", "name": "Shiso",
"plural_name": "shisoes" "plural_name": "Shiso"
}, },
"celery powder": { "celery powder": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "celery powder", "name": "Sellerieknollenpulver",
"plural_name": "celery powders" "plural_name": "Sellerieknollenpulver"
}, },
"black cumin": { "black cumin": {
"aliases": [], "aliases": [],
@ -6365,8 +6365,8 @@
"anardana": { "anardana": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "anardana", "name": "Anardana",
"plural_name": "anardanas" "plural_name": "Anardana"
}, },
"vietnamese mint": { "vietnamese mint": {
"aliases": [], "aliases": [],
@ -6383,14 +6383,14 @@
"espelette pepper": { "espelette pepper": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "espelette pepper", "name": "Piment dEspelette",
"plural_name": "espelette peppers" "plural_name": "Piment dEspelette"
}, },
"lemon verbena": { "lemon verbena": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Zitronenverbe", "name": "Zitronenverbene",
"plural_name": "lemon verbenas" "plural_name": "Zitronenverbene"
}, },
"raw stevia": { "raw stevia": {
"aliases": [], "aliases": [],
@ -6407,8 +6407,8 @@
"summer savory": { "summer savory": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "summer savory", "name": "Sommer-Bohnenkraut",
"plural_name": "summer savories" "plural_name": "Sommer-Bohnenkräuter"
}, },
"fennel pollen": { "fennel pollen": {
"aliases": [], "aliases": [],
@ -6440,12 +6440,12 @@
"Puderzucker" "Puderzucker"
], ],
"description": "", "description": "",
"name": "confectioners sugar", "name": "Puderzucker",
"plural_name": "confectioners sugars" "plural_name": "Puderzucker"
}, },
"bar sugar": { "bar sugar": {
"aliases": [ "aliases": [
"castor sugar" "Feinzucker"
], ],
"description": "", "description": "",
"name": "Rohrzucker", "name": "Rohrzucker",
@ -6880,8 +6880,8 @@
"hot honey": { "hot honey": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "hot honey", "name": "heißer Honig",
"plural_name": "hot honeys" "plural_name": "heiße Honige"
}, },
"gula melaka": { "gula melaka": {
"aliases": [], "aliases": [],
@ -6892,14 +6892,14 @@
"elderberry syrup": { "elderberry syrup": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "elderberry syrup", "name": "Holunderbeerensirup",
"plural_name": "elderberry syrups" "plural_name": "Holunderbeerensirupe"
}, },
"rosemary syrup": { "rosemary syrup": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "rosemary syrup", "name": "Rosmarinsirup",
"plural_name": "rosemary syrups" "plural_name": "Rosmarinsirupe"
}, },
"dark chocolate syrup": { "dark chocolate syrup": {
"aliases": [], "aliases": [],
@ -7030,7 +7030,7 @@
"truffle honey": { "truffle honey": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "truffle honey", "name": "Trüffelhonig",
"plural_name": "truffle honeys" "plural_name": "truffle honeys"
} }
} }
@ -7651,7 +7651,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Mehl", "name": "Mehl",
"plural_name": "Mehle" "plural_name": "Mehl"
}, },
"vanilla extract": { "vanilla extract": {
"aliases": [ "aliases": [
@ -7704,7 +7704,7 @@
], ],
"description": "", "description": "",
"name": "Weizenvollkornmehl", "name": "Weizenvollkornmehl",
"plural_name": "Weizenvollkornmehle" "plural_name": "Weizenvollkornmehl"
}, },
"almond flour": { "almond flour": {
"aliases": [], "aliases": [],
@ -7770,7 +7770,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Maismehl", "name": "Maismehl",
"plural_name": "Maismehle" "plural_name": "Maismehl"
}, },
"cream of tartar": { "cream of tartar": {
"aliases": [], "aliases": [],
@ -7816,7 +7816,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Reismehl", "name": "Reismehl",
"plural_name": "Reismehle" "plural_name": "Reismehl"
}, },
"desiccated coconut": { "desiccated coconut": {
"aliases": [], "aliases": [],
@ -7840,7 +7840,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Kichererbsenmehl", "name": "Kichererbsenmehl",
"plural_name": "Kichererbsenmehle" "plural_name": "Kichererbsenmehl"
}, },
"xanthan gum": { "xanthan gum": {
"aliases": [], "aliases": [],
@ -7957,7 +7957,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Roggenmehl", "name": "Roggenmehl",
"plural_name": "Roggenmehle" "plural_name": "Roggenmehl"
}, },
"psyllium husk": { "psyllium husk": {
"aliases": [], "aliases": [],
@ -8071,7 +8071,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Matzemehl", "name": "Matzemehl",
"plural_name": "Matzemehle" "plural_name": "Matzemehl"
}, },
"sago": { "sago": {
"aliases": [], "aliases": [],
@ -8089,7 +8089,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Maniokmehl", "name": "Maniokmehl",
"plural_name": "Maniokmehle" "plural_name": "Maniokmehl"
}, },
"whipped cream stabilizer": { "whipped cream stabilizer": {
"aliases": [], "aliases": [],
@ -8143,13 +8143,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Quinoamehl", "name": "Quinoamehl",
"plural_name": "Quinoamehle" "plural_name": "Quinoamehl"
}, },
"finger millet flour": { "finger millet flour": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Fingerhirsemehl", "name": "Fingerhirsemehl",
"plural_name": "Fingerhirsemehle" "plural_name": "Fingerhirsemehl"
}, },
"fondant": { "fondant": {
"aliases": [], "aliases": [],
@ -8173,13 +8173,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "weißes Maismehl", "name": "weißes Maismehl",
"plural_name": "weiße Maismehle" "plural_name": "weißes Maismehl"
}, },
"millet flour": { "millet flour": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Hirsemehl", "name": "Hirsemehl",
"plural_name": "Hirsemehle" "plural_name": "Hirsemehl"
}, },
"mincemeat": { "mincemeat": {
"aliases": [], "aliases": [],

File diff suppressed because it is too large Load diff

View file

@ -280,7 +280,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mélange de légumes", "name": "mélange de légumes",
"plural_name": "mélange de légumes" "plural_name": "mélanges de légumes"
}, },
"poblano pepper": { "poblano pepper": {
"aliases": [], "aliases": [],
@ -378,7 +378,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "céleri-rave", "name": "céleri-rave",
"plural_name": "céleric-raves" "plural_name": "céleri-raves"
}, },
"water chestnut": { "water chestnut": {
"aliases": [], "aliases": [],
@ -1151,8 +1151,8 @@
"dried orange slice": { "dried orange slice": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried orange slice", "name": "tranche d'orange séchée",
"plural_name": "dried orange slices" "plural_name": "tranches d'orange séchées"
}, },
"loquat": { "loquat": {
"aliases": [], "aliases": [],
@ -1335,8 +1335,8 @@
"dried chinese mushroom": { "dried chinese mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried chinese mushroom", "name": "shiitake",
"plural_name": "dried chinese mushrooms" "plural_name": "shiitakes"
}, },
"maitake": { "maitake": {
"aliases": [], "aliases": [],
@ -1347,8 +1347,8 @@
"trumpet mushroom": { "trumpet mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "trumpet mushroom", "name": "pleurote de panicaut",
"plural_name": "trumpet mushrooms" "plural_name": "pleurotes de panicaut"
}, },
"white truffle": { "white truffle": {
"aliases": [], "aliases": [],
@ -1437,14 +1437,14 @@
"candy cap mushroom": { "candy cap mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "candy cap mushroom", "name": "Champignon Candy Cap",
"plural_name": "candy cap mushrooms" "plural_name": "Champignons Candy Cap"
}, },
"lions mane mushroom": { "lions mane mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "lions mane mushroom", "name": "Champignon lion's mane",
"plural_name": "lions mane mushrooms" "plural_name": "Champignons lion's mane"
} }
} }
}, },
@ -1745,7 +1745,7 @@
"pine nut": { "pine nut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pine nut", "name": "pignon de pin",
"plural_name": "pine nuts" "plural_name": "pine nuts"
}, },
"pistachio": { "pistachio": {
@ -2015,8 +2015,8 @@
"cream cheese": { "cream cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cream cheese", "name": "mascarpone",
"plural_name": "cream cheeses" "plural_name": "mascarpones"
}, },
"sharp cheddar": { "sharp cheddar": {
"aliases": [], "aliases": [],
@ -2382,7 +2382,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "babybel", "name": "babybel",
"plural_name": "babybels" "plural_name": "babybel"
}, },
"panela cheese": { "panela cheese": {
"aliases": [], "aliases": [],
@ -2556,7 +2556,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "port-salut", "name": "port-salut",
"plural_name": "port saluts" "plural_name": "port-salut"
}, },
"derby cheese": { "derby cheese": {
"aliases": [], "aliases": [],
@ -2637,8 +2637,8 @@
"sour cream": { "sour cream": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "crème fraiche", "name": "crème aigre",
"plural_name": "crèmes fraiches" "plural_name": "crèmes aigres"
}, },
"buttermilk": { "buttermilk": {
"aliases": [], "aliases": [],
@ -3035,14 +3035,14 @@
"sheeps milk yoghurt": { "sheeps milk yoghurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sheeps milk yoghurt", "name": "yaourt au lait de brebis",
"plural_name": "sheeps milk yoghurts" "plural_name": "yaourts au lait de brebis"
}, },
"strawberry milk": { "strawberry milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "strawberry milk", "name": "lait à la fraise",
"plural_name": "strawberry milks" "plural_name": "laits à la fraise"
}, },
"ayran": { "ayran": {
"aliases": [], "aliases": [],
@ -3078,7 +3078,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "lait de brebis", "name": "lait de brebis",
"plural_name": "sheep milks" "plural_name": "lait de brebis"
}, },
"starter culture": { "starter culture": {
"aliases": [], "aliases": [],
@ -3102,7 +3102,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "lait vanillé", "name": "lait vanillé",
"plural_name": "vanilla milks" "plural_name": "laits à la vanille"
}, },
"yoplait whip": { "yoplait whip": {
"aliases": [], "aliases": [],
@ -3303,8 +3303,8 @@
"soy yogurt": { "soy yogurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "soy yogurt", "name": "yaourt au lait de soja",
"plural_name": "soy yogurts" "plural_name": "yaourts au lait de soja"
}, },
"vegan mozzarella": { "vegan mozzarella": {
"aliases": [], "aliases": [],
@ -6665,7 +6665,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "vanilla syrup", "name": "vanilla syrup",
"plural_name": "vanilla syrups" "plural_name": "sirop de vanille"
}, },
"ginger syrup": { "ginger syrup": {
"aliases": [], "aliases": [],
@ -7655,12 +7655,12 @@
}, },
"vanilla extract": { "vanilla extract": {
"aliases": [ "aliases": [
"vanilla", "vanille",
"vanillas" "vanilles"
], ],
"description": "", "description": "",
"name": "vanilla extract", "name": "extrait de vanille",
"plural_name": "vanilla extracts" "plural_name": "extraits de vanille"
}, },
"baking powder": { "baking powder": {
"aliases": [], "aliases": [],
@ -15025,8 +15025,8 @@
"vanilla liqueur": { "vanilla liqueur": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "vanilla liqueur", "name": "liqueur de vanille",
"plural_name": "vanilla liqueurs" "plural_name": "liqueurs de vanille"
}, },
"sangria": { "sangria": {
"aliases": [], "aliases": [],

View file

@ -76,8 +76,8 @@
"shallot": { "shallot": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "shallot", "name": "chalota",
"plural_name": "shallots" "plural_name": "chalotas"
}, },
"cherry tomato": { "cherry tomato": {
"aliases": [], "aliases": [],
@ -100,8 +100,8 @@
"sweet corn": { "sweet corn": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sweet corn", "name": "millo doce",
"plural_name": "sweet corns" "plural_name": "millos doces"
}, },
"chile pepper": { "chile pepper": {
"aliases": [ "aliases": [
@ -234,8 +234,8 @@
"red cabbage": { "red cabbage": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "red cabbage", "name": "repolo roxo",
"plural_name": "red cabbages" "plural_name": "repolos roxos"
}, },
"artichoke": { "artichoke": {
"aliases": [], "aliases": [],
@ -389,8 +389,8 @@
"turnip": { "turnip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "turnip", "name": "nabo",
"plural_name": "turnips" "plural_name": "nabos"
}, },
"thai chile pepper": { "thai chile pepper": {
"aliases": [], "aliases": [],
@ -881,8 +881,8 @@
"dried mango": { "dried mango": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried mango", "name": "manga seca",
"plural_name": "dried mangoes" "plural_name": "mangas secas"
}, },
"dried apple": { "dried apple": {
"aliases": [], "aliases": [],
@ -905,8 +905,8 @@
"banana chip": { "banana chip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "banana chip", "name": "chip de banana",
"plural_name": "banana chips" "plural_name": "chips de banana"
}, },
"kumquat": { "kumquat": {
"aliases": [], "aliases": [],
@ -935,8 +935,8 @@
"asian pear": { "asian pear": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "asian pear", "name": "pera asiática",
"plural_name": "asian pears" "plural_name": "peras asiáticas"
}, },
"lychee": { "lychee": {
"aliases": [], "aliases": [],
@ -972,7 +972,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pomelo", "name": "pomelo",
"plural_name": "pomeloes" "plural_name": "pomelos"
}, },
"chestnut puree": { "chestnut puree": {
"aliases": [], "aliases": [],
@ -1007,8 +1007,8 @@
"apple chip": { "apple chip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "apple chip", "name": "chip de mazá",
"plural_name": "apple chips" "plural_name": "chips de mazá"
}, },
"mixed peel": { "mixed peel": {
"aliases": [], "aliases": [],
@ -1055,8 +1055,8 @@
"dried lemon": { "dried lemon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried lemon", "name": "limón seco",
"plural_name": "dried lemons" "plural_name": "limóns secos"
}, },
"young jackfruit": { "young jackfruit": {
"aliases": [], "aliases": [],
@ -1465,8 +1465,8 @@
"raspberry": { "raspberry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "raspberry", "name": "framboesa",
"plural_name": "raspberries" "plural_name": "framboesas"
}, },
"cranberry": { "cranberry": {
"aliases": [], "aliases": [],
@ -1483,8 +1483,8 @@
"blackberry": { "blackberry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "blackberry", "name": "amora",
"plural_name": "blackberries" "plural_name": "amoras"
}, },
"berry mix": { "berry mix": {
"aliases": [], "aliases": [],

View file

@ -1890,7 +1890,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "trail mix", "name": "trail mix",
"plural_name": "trail mixes" "plural_name": "studentenhavers"
}, },
"basil seed": { "basil seed": {
"aliases": [], "aliases": [],
@ -2105,8 +2105,8 @@
"cottage cheese": { "cottage cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cottage cheese", "name": "huttenkaas",
"plural_name": "cottage cheeses" "plural_name": "huttenkazen"
}, },
"american cheese": { "american cheese": {
"aliases": [], "aliases": [],
@ -2813,14 +2813,14 @@
"buttermilk powder": { "buttermilk powder": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "buttermilk powder", "name": "karnemelkpoeder",
"plural_name": "buttermilk powders" "plural_name": "karnemelkpoeders"
}, },
"frozen yogurt": { "frozen yogurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "frozen yogurt", "name": "bevroren yoghurt",
"plural_name": "frozen yogurts" "plural_name": "bevroren yoghurts"
}, },
"khoya": { "khoya": {
"aliases": [], "aliases": [],
@ -2951,8 +2951,8 @@
"honey greek yogurt": { "honey greek yogurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "honey greek yogurt", "name": "Griekse honing yoghurt",
"plural_name": "honey greek yogurts" "plural_name": "Griekse honing yoghurt"
}, },
"amul butter": { "amul butter": {
"aliases": [], "aliases": [],
@ -2963,8 +2963,8 @@
"honey butter": { "honey butter": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "honey butter", "name": "honing boter",
"plural_name": "honey butter" "plural_name": "honing boter"
}, },
"strawberry cream cheese": { "strawberry cream cheese": {
"aliases": [], "aliases": [],
@ -2993,8 +2993,8 @@
"goat yogurt": { "goat yogurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "goat yogurt", "name": "geiten yoghurt",
"plural_name": "goat yogurts" "plural_name": "geiten yoghurt"
}, },
"dahi": { "dahi": {
"aliases": [], "aliases": [],
@ -3041,7 +3041,7 @@
"strawberry milk": { "strawberry milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "strawberry milk", "name": "aardbeien melk",
"plural_name": "strawberry milks" "plural_name": "strawberry milks"
}, },
"ayran": { "ayran": {
@ -3165,8 +3165,8 @@
"vegan butter": { "vegan butter": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "vegan butter", "name": "veganistische boter",
"plural_name": "vegan butter" "plural_name": "veganistische boter"
}, },
"non-dairy milk": { "non-dairy milk": {
"aliases": [], "aliases": [],
@ -3243,8 +3243,8 @@
"coconut yogurt": { "coconut yogurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut yogurt", "name": "kokokosnoot yoghurt",
"plural_name": "coconut yogurts" "plural_name": "kokokosnoot yoghurt"
}, },
"non-dairy yogurt": { "non-dairy yogurt": {
"aliases": [], "aliases": [],
@ -3279,8 +3279,8 @@
"rice milk": { "rice milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "rice milk", "name": "rijstmelk",
"plural_name": "rice milks" "plural_name": "rijstmelk"
}, },
"vegan sour cream": { "vegan sour cream": {
"aliases": [], "aliases": [],
@ -3429,8 +3429,8 @@
"vegan feta": { "vegan feta": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "vegan feta", "name": "veganistische feta",
"plural_name": "vegan fetas" "plural_name": "veganistische feta"
}, },
"soy chorizo": { "soy chorizo": {
"aliases": [], "aliases": [],
@ -3693,8 +3693,8 @@
"banana milk": { "banana milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "banana milk", "name": "bananenmelk",
"plural_name": "banana milks" "plural_name": "bananenmelk"
}, },
"soy quark": { "soy quark": {
"aliases": [], "aliases": [],
@ -3739,7 +3739,7 @@
"bacon": { "bacon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "bacon", "name": "spek",
"plural_name": "bacons" "plural_name": "bacons"
}, },
"chopped bacon": { "chopped bacon": {
@ -3884,7 +3884,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "salami", "name": "salami",
"plural_name": "salamis" "plural_name": "salami"
}, },
"brisket": { "brisket": {
"aliases": [], "aliases": [],
@ -4081,8 +4081,8 @@
"rabbit": { "rabbit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "rabbit", "name": "konijn",
"plural_name": "rabbits" "plural_name": "konijnen"
}, },
"pork cutlet": { "pork cutlet": {
"aliases": [], "aliases": [],
@ -4171,8 +4171,8 @@
"beef liver": { "beef liver": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "beef liver", "name": "runderlever",
"plural_name": "beef livers" "plural_name": "runderlevers"
}, },
"pastrami": { "pastrami": {
"aliases": [], "aliases": [],
@ -4237,7 +4237,7 @@
"dried beef": { "dried beef": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried beef", "name": "gedroogd rundvlees",
"plural_name": "dried beefs" "plural_name": "dried beefs"
}, },
"gammon joint": { "gammon joint": {
@ -4439,14 +4439,14 @@
"duck": { "duck": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "duck", "name": "eend",
"plural_name": "ducks" "plural_name": "eenden"
}, },
"duck breast": { "duck breast": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "duck breast", "name": "eendenborst",
"plural_name": "duck breasts" "plural_name": "eendenborsten"
}, },
"boneless chicken": { "boneless chicken": {
"aliases": [], "aliases": [],
@ -4457,8 +4457,8 @@
"chicken liver": { "chicken liver": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken liver", "name": "kippenlever",
"plural_name": "chicken livers" "plural_name": "kippenlevers"
}, },
"cornish hen": { "cornish hen": {
"aliases": [], "aliases": [],
@ -4499,7 +4499,7 @@
"quail": { "quail": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "quail", "name": "kwartel",
"plural_name": "quails" "plural_name": "quails"
}, },
"smoked turkey sausage": { "smoked turkey sausage": {
@ -4595,7 +4595,7 @@
"chicken nugget": { "chicken nugget": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken nugget", "name": "kipnugget",
"plural_name": "chicken nuggets" "plural_name": "chicken nuggets"
}, },
"turkey burger": { "turkey burger": {

View file

@ -251,7 +251,7 @@
}, },
"summer squash": { "summer squash": {
"aliases": [ "aliases": [
"courgette", "squash",
"gem squash" "gem squash"
], ],
"description": "", "description": "",
@ -339,7 +339,7 @@
"chard": { "chard": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chard", "name": "bladbete",
"plural_name": "chards" "plural_name": "chards"
}, },
"pimiento": { "pimiento": {
@ -402,7 +402,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "bok choy", "name": "bok choy",
"plural_name": "bok choy" "plural_name": "pak choi"
}, },
"okra": { "okra": {
"aliases": [], "aliases": [],
@ -511,8 +511,8 @@
"baby corn": { "baby corn": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "baby corn", "name": "baby mais",
"plural_name": "baby corns" "plural_name": "baby mais"
}, },
"broccoli rabe": { "broccoli rabe": {
"aliases": [], "aliases": [],
@ -642,7 +642,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "lime", "name": "lime",
"plural_name": "limes" "plural_name": "lime"
}, },
"apple": { "apple": {
"aliases": [], "aliases": [],
@ -719,7 +719,7 @@
"pomegranate": { "pomegranate": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pomegranate", "name": "granateple",
"plural_name": "pomegranates" "plural_name": "pomegranates"
}, },
"watermelon": { "watermelon": {
@ -749,7 +749,7 @@
"grapefruit": { "grapefruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "grapefruit", "name": "grapefrukt",
"plural_name": "grapefrukt" "plural_name": "grapefrukt"
}, },
"plum": { "plum": {
@ -762,13 +762,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "fig", "name": "fig",
"plural_name": "figs" "plural_name": "fikener"
}, },
"apricot": { "apricot": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "aprikos", "name": "aprikos",
"plural_name": "apricots" "plural_name": "aprikos"
}, },
"currant": { "currant": {
"aliases": [], "aliases": [],
@ -803,8 +803,8 @@
"passion fruit": { "passion fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "passion fruit", "name": "pasjonsfrukt",
"plural_name": "passion fruits" "plural_name": "pasjons­frukter"
}, },
"papaya": { "papaya": {
"aliases": [], "aliases": [],
@ -821,20 +821,20 @@
"nectarine": { "nectarine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "nectarine", "name": "nektarin",
"plural_name": "nectarines" "plural_name": "nektariner"
}, },
"dried fig": { "dried fig": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried fig", "name": "dried fig",
"plural_name": "dried figs" "plural_name": "tørkede fikener"
}, },
"chestnut": { "chestnut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chestnut", "name": "kastanje",
"plural_name": "chestnuts" "plural_name": "kastanjer"
}, },
"meyer lemon": { "meyer lemon": {
"aliases": [], "aliases": [],
@ -845,14 +845,14 @@
"honeydew melon": { "honeydew melon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "honeydew melon", "name": "honning melon",
"plural_name": "honeydew melons" "plural_name": "honeydew melons"
}, },
"dried fruit": { "dried fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried fruit", "name": "tørket frukt",
"plural_name": "dried fruits" "plural_name": "tørket frukt"
}, },
"clementine": { "clementine": {
"aliases": [], "aliases": [],
@ -1056,7 +1056,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried lemon", "name": "dried lemon",
"plural_name": "dried lemons" "plural_name": "tørkede sitroner"
}, },
"young jackfruit": { "young jackfruit": {
"aliases": [], "aliases": [],

View file

@ -389,14 +389,14 @@
"turnip": { "turnip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "turnip", "name": "nabo",
"plural_name": "turnips" "plural_name": "nabos"
}, },
"thai chile pepper": { "thai chile pepper": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "thai chile pepper", "name": "pimenta tailandesa",
"plural_name": "thai chile peppers" "plural_name": "pimentas tailandesas"
}, },
"bok choy": { "bok choy": {
"aliases": [], "aliases": [],
@ -425,14 +425,14 @@
"radicchio": { "radicchio": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "radicchio", "name": "chicória",
"plural_name": "radicchio" "plural_name": "chicória"
}, },
"pearl onion": { "pearl onion": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pearl onion", "name": "cebola pérola",
"plural_name": "pearl onions" "plural_name": "cebolas pérola"
}, },
"tenderstem broccoli": { "tenderstem broccoli": {
"aliases": [], "aliases": [],
@ -443,14 +443,14 @@
"plantain": { "plantain": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "plantain", "name": "banana-da-terra",
"plural_name": "plantains" "plural_name": "bananas-da-terra"
}, },
"leaf lettuce": { "leaf lettuce": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "leaf lettuce", "name": "alface de folha",
"plural_name": "leaf lettuces" "plural_name": "alfaces de folha"
}, },
"pepperoncini": { "pepperoncini": {
"aliases": [], "aliases": [],
@ -511,8 +511,8 @@
"baby corn": { "baby corn": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "baby corn", "name": "mini milho",
"plural_name": "baby corns" "plural_name": "mini milhos"
}, },
"broccoli rabe": { "broccoli rabe": {
"aliases": [], "aliases": [],
@ -595,8 +595,8 @@
"broccoli slaw": { "broccoli slaw": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "broccoli slaw", "name": "salada de brócolis",
"plural_name": "broccoli slaws" "plural_name": "saladas de brócolis"
}, },
"arbol chile pepper": { "arbol chile pepper": {
"aliases": [], "aliases": [],
@ -713,14 +713,14 @@
"grape": { "grape": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "grape", "name": "uva",
"plural_name": "uvas" "plural_name": "uvas"
}, },
"pomegranate": { "pomegranate": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pomegranate", "name": "romã",
"plural_name": "pomegranates" "plural_name": "romãs"
}, },
"watermelon": { "watermelon": {
"aliases": [], "aliases": [],
@ -731,14 +731,14 @@
"rhubarb": { "rhubarb": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "rhubarb", "name": "ruibarbo",
"plural_name": "rhubarbs" "plural_name": "ruibarbos"
}, },
"dried apricot": { "dried apricot": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried apricot", "name": "damasco seco",
"plural_name": "dried apricots" "plural_name": "damascos secos"
}, },
"kiwi": { "kiwi": {
"aliases": [], "aliases": [],
@ -749,14 +749,14 @@
"grapefruit": { "grapefruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "grapefruit", "name": "toranja",
"plural_name": "grapefruits" "plural_name": "toranjas"
}, },
"plum": { "plum": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "plum", "name": "ameixa",
"plural_name": "plums" "plural_name": "ameixas"
}, },
"fig": { "fig": {
"aliases": [], "aliases": [],
@ -767,8 +767,8 @@
"apricot": { "apricot": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "apricot", "name": "damasco",
"plural_name": "apricots" "plural_name": "damascos"
}, },
"currant": { "currant": {
"aliases": [], "aliases": [],
@ -827,14 +827,14 @@
"dried fig": { "dried fig": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried fig", "name": "figo seco",
"plural_name": "dried figs" "plural_name": "figos secos"
}, },
"chestnut": { "chestnut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chestnut", "name": "castanha",
"plural_name": "chestnuts" "plural_name": "castanhas"
}, },
"meyer lemon": { "meyer lemon": {
"aliases": [], "aliases": [],
@ -851,8 +851,8 @@
"dried fruit": { "dried fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried fruit", "name": "fruta seca",
"plural_name": "dried fruits" "plural_name": "frutas secas"
}, },
"clementine": { "clementine": {
"aliases": [], "aliases": [],
@ -875,20 +875,20 @@
"tangerine": { "tangerine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "tangerine", "name": "tangerina",
"plural_name": "tangerines" "plural_name": "tangerinas"
}, },
"dried mango": { "dried mango": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried mango", "name": "manga seca",
"plural_name": "dried mangoes" "plural_name": "mangas secas"
}, },
"dried apple": { "dried apple": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried apple", "name": "maçã seca",
"plural_name": "dried apples" "plural_name": "maçãs secas"
}, },
"quince": { "quince": {
"aliases": [], "aliases": [],
@ -905,8 +905,8 @@
"banana chip": { "banana chip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "banana chip", "name": "chip de banana",
"plural_name": "banana chips" "plural_name": "chips de banana"
}, },
"kumquat": { "kumquat": {
"aliases": [], "aliases": [],
@ -929,14 +929,14 @@
"mixed fruit": { "mixed fruit": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mixed fruit", "name": "fruta mista",
"plural_name": "mixed fruits" "plural_name": "frutas mistas"
}, },
"asian pear": { "asian pear": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "asian pear", "name": "pera asiática",
"plural_name": "asian pears" "plural_name": "pera asiáticas"
}, },
"lychee": { "lychee": {
"aliases": [], "aliases": [],
@ -947,8 +947,8 @@
"young coconut": { "young coconut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "young coconut", "name": "coco verde",
"plural_name": "young coconuts" "plural_name": "cocos verdes"
}, },
"kaffir lime": { "kaffir lime": {
"aliases": [], "aliases": [],
@ -972,7 +972,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pomelo", "name": "pomelo",
"plural_name": "pomeloes" "plural_name": "pomelos"
}, },
"chestnut puree": { "chestnut puree": {
"aliases": [], "aliases": [],
@ -1007,8 +1007,8 @@
"apple chip": { "apple chip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "apple chip", "name": "chip de maçã",
"plural_name": "apple chips" "plural_name": "chips de maçã"
}, },
"mixed peel": { "mixed peel": {
"aliases": [], "aliases": [],
@ -1037,8 +1037,8 @@
"jujube": { "jujube": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "jujube", "name": "jujuba",
"plural_name": "jujubes" "plural_name": "jujubas"
}, },
"sweet lime": { "sweet lime": {
"aliases": [], "aliases": [],
@ -1055,8 +1055,8 @@
"dried lemon": { "dried lemon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried lemon", "name": "limão seco",
"plural_name": "dried lemons" "plural_name": "limões secos"
}, },
"young jackfruit": { "young jackfruit": {
"aliases": [], "aliases": [],
@ -1251,14 +1251,14 @@
"portobello mushroom": { "portobello mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "portobello mushroom", "name": "cogumelo portobello",
"plural_name": "portobello mushrooms" "plural_name": "cogumelos portobello"
}, },
"wild mushroom": { "wild mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "wild mushroom", "name": "cogumelo silvestre",
"plural_name": "wild mushrooms" "plural_name": "cogumelos silvestres"
}, },
"porcini": { "porcini": {
"aliases": [], "aliases": [],
@ -1269,8 +1269,8 @@
"mixed mushroom": { "mixed mushroom": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mixed mushroom", "name": "cogumelo misto",
"plural_name": "mixed mushrooms" "plural_name": "cogumelos mistos"
}, },
"oyster mushroom": { "oyster mushroom": {
"aliases": [], "aliases": [],
@ -1460,25 +1460,25 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "blueberry", "name": "blueberry",
"plural_name": "blueberries" "plural_name": "mirtilos"
}, },
"raspberry": { "raspberry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "raspberry", "name": "framboesa",
"plural_name": "raspberries" "plural_name": "framboesas"
}, },
"cranberry": { "cranberry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cranberry", "name": "arando",
"plural_name": "cranberries" "plural_name": "arandos"
}, },
"cherry": { "cherry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cereja", "name": "cereja",
"plural_name": "cherries" "plural_name": "cerejas"
}, },
"blackberry": { "blackberry": {
"aliases": [], "aliases": [],
@ -1501,8 +1501,8 @@
"dried cherry": { "dried cherry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dried cherry", "name": "cereja seca",
"plural_name": "dried cherries" "plural_name": "cerejas secas"
}, },
"juniper berry": { "juniper berry": {
"aliases": [], "aliases": [],
@ -1842,7 +1842,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "castanha do Pará", "name": "castanha do Pará",
"plural_name": "brazil nuts" "plural_name": "castanha do Pará"
}, },
"mixed seed": { "mixed seed": {
"aliases": [], "aliases": [],
@ -1853,14 +1853,14 @@
"onion seed": { "onion seed": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "onion seed", "name": "semente de cebola",
"plural_name": "onion seeds" "plural_name": "sementes de cebola"
}, },
"watermelon seed": { "watermelon seed": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "watermelon seed", "name": "semente de melancia",
"plural_name": "watermelon seeds" "plural_name": "sementes de melancia"
}, },
"honey-roasted peanut": { "honey-roasted peanut": {
"aliases": [], "aliases": [],
@ -1871,8 +1871,8 @@
"melon seed": { "melon seed": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "melon seed", "name": "semente de melão",
"plural_name": "melon seeds" "plural_name": "sementes de melão"
}, },
"lotus seed": { "lotus seed": {
"aliases": [], "aliases": [],
@ -1883,8 +1883,8 @@
"white chia": { "white chia": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "white chia", "name": "chia branca",
"plural_name": "white chias" "plural_name": "chias brancas"
}, },
"trail mix": { "trail mix": {
"aliases": [], "aliases": [],
@ -2034,7 +2034,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "muçarela", "name": "muçarela",
"plural_name": "mozzarellas" "plural_name": "muçarelas"
}, },
"feta": { "feta": {
"aliases": [], "aliases": [],
@ -2069,20 +2069,20 @@
"goat cheese": { "goat cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "goat cheese", "name": "queijo de cabra",
"plural_name": "goat cheeses" "plural_name": "queijos de cabra"
}, },
"fresh mozzarella": { "fresh mozzarella": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "fresh mozzarella", "name": "muçarela fresca",
"plural_name": "fresh mozzarellas" "plural_name": "muçarelas frescas"
}, },
"swiss cheese": { "swiss cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "queijo suíço", "name": "queijo suíço",
"plural_name": "swiss cheeses" "plural_name": "queijos suíços"
}, },
"pecorino": { "pecorino": {
"aliases": [], "aliases": [],
@ -2699,20 +2699,20 @@
"sweetened condensed milk": { "sweetened condensed milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sweetened condensed milk", "name": "leite condensado",
"plural_name": "sweetened condensed milks" "plural_name": "leites condensados"
}, },
"ice cream": { "ice cream": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sorvete", "name": "sorvete",
"plural_name": "ice creams" "plural_name": "sorvetes"
}, },
"margarine": { "margarine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "margarina", "name": "margarina",
"plural_name": "margarines" "plural_name": "margarinas"
}, },
"creme fraiche": { "creme fraiche": {
"aliases": [], "aliases": [],
@ -2729,8 +2729,8 @@
"milk powder": { "milk powder": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "milk powder", "name": "leite em pó",
"plural_name": "milk powders" "plural_name": "leites em pó"
}, },
"curd": { "curd": {
"aliases": [], "aliases": [],
@ -2903,8 +2903,8 @@
"raw milk": { "raw milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "raw milk", "name": "leite puro",
"plural_name": "raw milks" "plural_name": "leites puros"
}, },
"lime curd": { "lime curd": {
"aliases": [], "aliases": [],
@ -2922,7 +2922,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chantilly", "name": "chantilly",
"plural_name": "chantillies" "plural_name": "chantillys"
}, },
"milkfat": { "milkfat": {
"aliases": [], "aliases": [],
@ -3023,8 +3023,8 @@
"chocolate milk powder": { "chocolate milk powder": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chocolate milk powder", "name": "achocolatado em pó",
"plural_name": "chocolate milk powders" "plural_name": "achocolatados em pó"
}, },
"liquid rennet": { "liquid rennet": {
"aliases": [], "aliases": [],
@ -3135,8 +3135,8 @@
"coconut milk": { "coconut milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut milk", "name": "leite de coco",
"plural_name": "coconut milks" "plural_name": "leites de coco"
}, },
"almond milk": { "almond milk": {
"aliases": [], "aliases": [],
@ -3159,8 +3159,8 @@
"coconut cream": { "coconut cream": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut cream", "name": "creme de coco",
"plural_name": "coconut creams" "plural_name": "cremes de coco"
}, },
"vegan butter": { "vegan butter": {
"aliases": [], "aliases": [],
@ -3201,8 +3201,8 @@
"coconut butter": { "coconut butter": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut butter", "name": "manteiga de coco",
"plural_name": "coconut butter" "plural_name": "manteigas de coco"
}, },
"egg replacer": { "egg replacer": {
"aliases": [], "aliases": [],
@ -3243,8 +3243,8 @@
"coconut yogurt": { "coconut yogurt": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut yogurt", "name": "iogurte de coco",
"plural_name": "coconut yogurts" "plural_name": "iogurtes de coco"
}, },
"non-dairy yogurt": { "non-dairy yogurt": {
"aliases": [], "aliases": [],
@ -3339,8 +3339,8 @@
"coconut powder": { "coconut powder": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut powder", "name": "pó de coco",
"plural_name": "coconut powders" "plural_name": "pós de cocos"
}, },
"soy cream": { "soy cream": {
"aliases": [], "aliases": [],
@ -3369,8 +3369,8 @@
"nut milk": { "nut milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "nut milk", "name": "leite de nozes",
"plural_name": "nut milks" "plural_name": "leites de nozes"
}, },
"non-dairy cream": { "non-dairy cream": {
"aliases": [], "aliases": [],
@ -3525,8 +3525,8 @@
"vegan meatball": { "vegan meatball": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "vegan meatball", "name": "almôndega vegana",
"plural_name": "vegan meatballs" "plural_name": "almôndegas veganas"
}, },
"almond-milk yogurt": { "almond-milk yogurt": {
"aliases": [], "aliases": [],
@ -3585,8 +3585,8 @@
"coconut fat": { "coconut fat": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut fat", "name": "gordura de coco",
"plural_name": "coconut fats" "plural_name": "gorduras de coco"
}, },
"flax milk": { "flax milk": {
"aliases": [], "aliases": [],
@ -4069,8 +4069,8 @@
"frozen meatball": { "frozen meatball": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "frozen meatball", "name": "almôndega congelada",
"plural_name": "frozen meatballs" "plural_name": "almôndegas congeladas"
}, },
"mixed ground meat": { "mixed ground meat": {
"aliases": [], "aliases": [],
@ -4559,8 +4559,8 @@
"turkey meatball": { "turkey meatball": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "turkey meatball", "name": "almôndega de peru",
"plural_name": "turkey meatballs" "plural_name": "almôndegas de peru"
}, },
"foie gra": { "foie gra": {
"aliases": [], "aliases": [],
@ -4721,8 +4721,8 @@
"chicken meatball": { "chicken meatball": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chicken meatball", "name": "almôndega de frango",
"plural_name": "chicken meatballs" "plural_name": "almôndegas de frango"
}, },
"duck liver": { "duck liver": {
"aliases": [], "aliases": [],
@ -6466,8 +6466,8 @@
"coconut sugar": { "coconut sugar": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut sugar", "name": "açúcar de coco",
"plural_name": "coconut sugars" "plural_name": "açúcares de coco"
}, },
"molass": { "molass": {
"aliases": [], "aliases": [],
@ -6718,8 +6718,8 @@
"coconut syrup": { "coconut syrup": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut syrup", "name": "xarope de coco",
"plural_name": "coconut syrups" "plural_name": "xaropes de coco"
}, },
"mint syrup": { "mint syrup": {
"aliases": [], "aliases": [],
@ -7721,8 +7721,8 @@
"shredded coconut": { "shredded coconut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "shredded coconut", "name": "coco ralado",
"plural_name": "shredded coconuts" "plural_name": "cocos ralados"
}, },
"cornmeal": { "cornmeal": {
"aliases": [], "aliases": [],
@ -7751,8 +7751,8 @@
"coconut flour": { "coconut flour": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut flour", "name": "farinha de coco",
"plural_name": "coconut flours" "plural_name": "farinhas de coco"
}, },
"baking mix": { "baking mix": {
"aliases": [], "aliases": [],
@ -7821,8 +7821,8 @@
"desiccated coconut": { "desiccated coconut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "desiccated coconut", "name": "coco ralado seco",
"plural_name": "desiccated coconuts" "plural_name": "cocos ralados secos"
}, },
"tapioca starch": { "tapioca starch": {
"aliases": [], "aliases": [],
@ -8136,8 +8136,8 @@
"coconut chip": { "coconut chip": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut chip", "name": "chip de coco",
"plural_name": "coconut chips" "plural_name": "chips de coco"
}, },
"quinoa flour": { "quinoa flour": {
"aliases": [], "aliases": [],
@ -8998,8 +8998,8 @@
"coconut rice": { "coconut rice": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut rice", "name": "arroz de coco",
"plural_name": "coconut rices" "plural_name": "arrozes de coco"
}, },
"amaranth flake": { "amaranth flake": {
"aliases": [], "aliases": [],
@ -10658,8 +10658,8 @@
"coconut oil": { "coconut oil": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut oil", "name": "óleo de coco",
"plural_name": "coconut oils" "plural_name": "óleos de coco"
}, },
"cooking spray": { "cooking spray": {
"aliases": [], "aliases": [],
@ -10726,8 +10726,8 @@
"virgin coconut oil": { "virgin coconut oil": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "virgin coconut oil", "name": "óleo de coco virgem",
"plural_name": "virgin coconut oils" "plural_name": "óleos de coco virgem"
}, },
"chili oil": { "chili oil": {
"aliases": [], "aliases": [],
@ -10840,8 +10840,8 @@
"coconut oil spray": { "coconut oil spray": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut oil spray", "name": "spray de óleo de coco",
"plural_name": "coconut oil sprays" "plural_name": "sprays de óleo de coco"
}, },
"almond oil": { "almond oil": {
"aliases": [], "aliases": [],
@ -11342,8 +11342,8 @@
"coconut vinegar": { "coconut vinegar": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut vinegar", "name": "vinagre de coco",
"plural_name": "coconut vinegars" "plural_name": "vinagres de coco"
}, },
"ume plum vinegar": { "ume plum vinegar": {
"aliases": [], "aliases": [],
@ -15155,8 +15155,8 @@
"coconut water": { "coconut water": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut water", "name": "água de coco",
"plural_name": "coconut waters" "plural_name": "águas de coco"
}, },
"pomegranate juice": { "pomegranate juice": {
"aliases": [], "aliases": [],
@ -15741,8 +15741,8 @@
"coconut extract": { "coconut extract": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "coconut extract", "name": "extrato de coco",
"plural_name": "coconut extracts" "plural_name": "extratos de coco"
}, },
"rose water": { "rose water": {
"aliases": [], "aliases": [],

View file

@ -395,44 +395,44 @@
"thai chile pepper": { "thai chile pepper": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "thai chile pepper", "name": "тайский перец чили",
"plural_name": "thai chile peppers" "plural_name": "тайские перцы чили"
}, },
"bok choy": { "bok choy": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "bok choy", "name": "бок-чой, черешковая капуста",
"plural_name": "bok choy" "plural_name": "бок-чой, черешковых капуст"
}, },
"okra": { "okra": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "okra", "name": "бамия",
"plural_name": "okra" "plural_name": "бамия"
}, },
"acorn squash": { "acorn squash": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "acorn squash", "name": "желудевая тыква",
"plural_name": "acorn squashes" "plural_name": "желудевых тыкв"
}, },
"corn cob": { "corn cob": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "corn cob", "name": "кукурузный початок",
"plural_name": "corn cobs" "plural_name": "кукурузных початков"
}, },
"radicchio": { "radicchio": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "radicchio", "name": "радиччио, итальянский цикорий",
"plural_name": "radicchio" "plural_name": "радиччио, итальянских цикориев"
}, },
"pearl onion": { "pearl onion": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pearl onion", "name": "жемчужный лук",
"plural_name": "pearl onions" "plural_name": "жемчужных луковиц"
}, },
"tenderstem broccoli": { "tenderstem broccoli": {
"aliases": [], "aliases": [],

View file

@ -30,7 +30,7 @@
"name": "Gewürze" "name": "Gewürze"
}, },
{ {
"name": "Konditorwaren" "name": "Süßwaren"
}, },
{ {
"name": "Milchprodukte" "name": "Milchprodukte"
@ -54,7 +54,7 @@
"name": "Gewürze" "name": "Gewürze"
}, },
{ {
"name": "Süßwaren" "name": "Süßigkeiten"
}, },
{ {
"name": "Alkohol" "name": "Alkohol"

View file

@ -1,6 +1,6 @@
[ [
{ {
"name": "Свежие Овощи&фрукты" "name": "Свежие Овощи и фрукты"
}, },
{ {
"name": "Крупы" "name": "Крупы"

View file

@ -31,7 +31,7 @@
}, },
"quart": { "quart": {
"name": "litro", "name": "litro",
"plural_name": "quarts", "plural_name": "litros",
"description": "", "description": "",
"abbreviation": "l" "abbreviation": "l"
}, },
@ -85,14 +85,14 @@
"abbreviation": "mg" "abbreviation": "mg"
}, },
"splash": { "splash": {
"name": "punhado", "name": "salpico",
"plural_name": "splashes", "plural_name": "salpicadas",
"description": "", "description": "",
"abbreviation": "" "abbreviation": ""
}, },
"dash": { "dash": {
"name": "pitada", "name": "pitada",
"plural_name": "dashes", "plural_name": "pitadas",
"description": "", "description": "",
"abbreviation": "" "abbreviation": ""
}, },
@ -121,8 +121,8 @@
"abbreviation": "" "abbreviation": ""
}, },
"bunch": { "bunch": {
"name": "bunch", "name": "punhado",
"plural_name": "bunches", "plural_name": "punhados",
"description": "", "description": "",
"abbreviation": "" "abbreviation": ""
}, },

View file

@ -3,7 +3,7 @@ from functools import cached_property
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from pydantic import UUID4 from pydantic import UUID4
from mealie.routes._base.base_controllers import BaseUserController from mealie.routes._base.base_controllers import BaseCrudController
from mealie.routes._base.controller import controller from mealie.routes._base.controller import controller
from mealie.routes._base.mixins import HttpRepo from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import MealieCrudRoute from mealie.routes._base.routers import MealieCrudRoute
@ -15,13 +15,14 @@ from mealie.schema.labels import (
) )
from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelPagination from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelPagination
from mealie.schema.response.pagination import PaginationQuery from mealie.schema.response.pagination import PaginationQuery
from mealie.services.event_bus_service.event_types import EventLabelData, EventOperation, EventTypes
from mealie.services.group_services.labels_service import MultiPurposeLabelService from mealie.services.group_services.labels_service import MultiPurposeLabelService
router = APIRouter(prefix="/groups/labels", tags=["Groups: Multi Purpose Labels"], route_class=MealieCrudRoute) router = APIRouter(prefix="/groups/labels", tags=["Groups: Multi Purpose Labels"], route_class=MealieCrudRoute)
@controller(router) @controller(router)
class MultiPurposeLabelsController(BaseUserController): class MultiPurposeLabelsController(BaseCrudController):
@cached_property @cached_property
def service(self): def service(self):
return MultiPurposeLabelService(self.repos) return MultiPurposeLabelService(self.repos)
@ -53,7 +54,15 @@ class MultiPurposeLabelsController(BaseUserController):
@router.post("", response_model=MultiPurposeLabelOut) @router.post("", response_model=MultiPurposeLabelOut)
def create_one(self, data: MultiPurposeLabelCreate): def create_one(self, data: MultiPurposeLabelCreate):
return self.service.create_one(data) new_label = self.service.create_one(data)
self.publish_event(
event_type=EventTypes.label_created,
document_data=EventLabelData(operation=EventOperation.create, label_id=new_label.id),
group_id=new_label.group_id,
household_id=None,
message=self.t("notifications.generic-created", name=new_label.name),
)
return new_label
@router.get("/{item_id}", response_model=MultiPurposeLabelOut) @router.get("/{item_id}", response_model=MultiPurposeLabelOut)
def get_one(self, item_id: UUID4): def get_one(self, item_id: UUID4):
@ -61,8 +70,25 @@ class MultiPurposeLabelsController(BaseUserController):
@router.put("/{item_id}", response_model=MultiPurposeLabelOut) @router.put("/{item_id}", response_model=MultiPurposeLabelOut)
def update_one(self, item_id: UUID4, data: MultiPurposeLabelUpdate): def update_one(self, item_id: UUID4, data: MultiPurposeLabelUpdate):
return self.mixins.update_one(data, item_id) label = self.mixins.update_one(data, item_id)
self.publish_event(
event_type=EventTypes.label_updated,
document_data=EventLabelData(operation=EventOperation.update, label_id=label.id),
group_id=label.group_id,
household_id=None,
message=self.t("notifications.generic-updated", name=label.name),
)
return label
@router.delete("/{item_id}", response_model=MultiPurposeLabelOut) @router.delete("/{item_id}", response_model=MultiPurposeLabelOut)
def delete_one(self, item_id: UUID4): def delete_one(self, item_id: UUID4):
return self.mixins.delete_one(item_id) # type: ignore label = self.mixins.delete_one(item_id)
if label:
self.publish_event(
event_type=EventTypes.label_deleted,
document_data=EventLabelData(operation=EventOperation.delete, label_id=label.id),
group_id=label.group_id,
household_id=None,
message=self.t("notifications.generic-deleted", name=label.name),
)
return label

View file

@ -3,11 +3,11 @@ from .datetime_parse import DateError, DateTimeError, DurationError, TimeError
from .mealie_model import HasUUID, MealieModel, SearchType from .mealie_model import HasUUID, MealieModel, SearchType
__all__ = [ __all__ = [
"HasUUID",
"MealieModel",
"SearchType",
"DateError", "DateError",
"DateTimeError", "DateTimeError",
"DurationError", "DurationError",
"TimeError", "TimeError",
"HasUUID",
"MealieModel",
"SearchType",
] ]

View file

@ -18,28 +18,10 @@ from .restore import (
from .settings import CustomPageBase, CustomPageOut from .settings import CustomPageBase, CustomPageOut
__all__ = [ __all__ = [
"MaintenanceLogs",
"MaintenanceStorageDetails",
"MaintenanceSummary",
"ChowdownURL", "ChowdownURL",
"MigrationFile", "MigrationFile",
"MigrationImport", "MigrationImport",
"Migrations", "Migrations",
"CustomPageBase",
"CustomPageOut",
"CommentImport",
"CustomPageImport",
"GroupImport",
"ImportBase",
"NotificationImport",
"RecipeImport",
"SettingsImport",
"UserImport",
"AllBackups",
"BackupFile",
"BackupOptions",
"CreateBackup",
"ImportJob",
"AdminAboutInfo", "AdminAboutInfo",
"AppInfo", "AppInfo",
"AppStartupInfo", "AppStartupInfo",
@ -49,5 +31,23 @@ __all__ = [
"EmailReady", "EmailReady",
"EmailSuccess", "EmailSuccess",
"EmailTest", "EmailTest",
"CustomPageBase",
"CustomPageOut",
"AllBackups",
"BackupFile",
"BackupOptions",
"CreateBackup",
"ImportJob",
"MaintenanceLogs",
"MaintenanceStorageDetails",
"MaintenanceSummary",
"DebugResponse", "DebugResponse",
"CommentImport",
"CustomPageImport",
"GroupImport",
"ImportBase",
"NotificationImport",
"RecipeImport",
"SettingsImport",
"UserImport",
] ]

View file

@ -7,13 +7,13 @@ from .group_seeder import SeederConfig
from .group_statistics import GroupStorage from .group_statistics import GroupStorage
__all__ = [ __all__ = [
"GroupAdminUpdate",
"GroupStorage",
"GroupDataExport", "GroupDataExport",
"SeederConfig",
"CreateGroupPreferences", "CreateGroupPreferences",
"ReadGroupPreferences", "ReadGroupPreferences",
"UpdateGroupPreferences", "UpdateGroupPreferences",
"GroupStorage",
"DataMigrationCreate", "DataMigrationCreate",
"SupportedMigrations", "SupportedMigrations",
"SeederConfig",
"GroupAdminUpdate",
] ]

View file

@ -70,49 +70,6 @@ from .invite_token import CreateInviteToken, EmailInitationResponse, EmailInvita
from .webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination, WebhookType from .webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination, WebhookType
__all__ = [ __all__ = [
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
"CreateGroupRecipeAction",
"GroupRecipeActionOut",
"GroupRecipeActionPagination",
"GroupRecipeActionPayload",
"GroupRecipeActionType",
"SaveGroupRecipeAction",
"CreateWebhook",
"ReadWebhook",
"SaveWebhook",
"WebhookPagination",
"WebhookType",
"CreateHouseholdPreferences",
"ReadHouseholdPreferences",
"SaveHouseholdPreferences",
"UpdateHouseholdPreferences",
"HouseholdCreate",
"HouseholdInDB",
"HouseholdPagination",
"HouseholdRecipeBase",
"HouseholdRecipeCreate",
"HouseholdRecipeOut",
"HouseholdRecipeSummary",
"HouseholdRecipeUpdate",
"HouseholdSave",
"HouseholdSummary",
"HouseholdUserSummary",
"UpdateHousehold",
"UpdateHouseholdAdmin",
"HouseholdStatistics",
"CreateInviteToken",
"EmailInitationResponse",
"EmailInvitation",
"ReadInviteToken",
"SaveInviteToken",
"ShoppingListAddRecipeParams", "ShoppingListAddRecipeParams",
"ShoppingListAddRecipeParamsBulk", "ShoppingListAddRecipeParamsBulk",
"ShoppingListCreate", "ShoppingListCreate",
@ -136,5 +93,48 @@ __all__ = [
"ShoppingListSave", "ShoppingListSave",
"ShoppingListSummary", "ShoppingListSummary",
"ShoppingListUpdate", "ShoppingListUpdate",
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
"CreateGroupRecipeAction",
"GroupRecipeActionOut",
"GroupRecipeActionPagination",
"GroupRecipeActionPayload",
"GroupRecipeActionType",
"SaveGroupRecipeAction",
"CreateHouseholdPreferences",
"ReadHouseholdPreferences",
"SaveHouseholdPreferences",
"UpdateHouseholdPreferences",
"SetPermissions", "SetPermissions",
"CreateInviteToken",
"EmailInitationResponse",
"EmailInvitation",
"ReadInviteToken",
"SaveInviteToken",
"HouseholdStatistics",
"CreateWebhook",
"ReadWebhook",
"SaveWebhook",
"WebhookPagination",
"WebhookType",
"HouseholdCreate",
"HouseholdInDB",
"HouseholdPagination",
"HouseholdRecipeBase",
"HouseholdRecipeCreate",
"HouseholdRecipeOut",
"HouseholdRecipeSummary",
"HouseholdRecipeUpdate",
"HouseholdSave",
"HouseholdSummary",
"HouseholdUserSummary",
"UpdateHousehold",
"UpdateHouseholdAdmin",
] ]

View file

@ -47,6 +47,10 @@ class GroupEventNotifierOptions(MealieModel):
category_updated: bool = False category_updated: bool = False
category_deleted: bool = False category_deleted: bool = False
label_created: bool = False
label_updated: bool = False
label_deleted: bool = False
class GroupEventNotifierOptionsSave(GroupEventNotifierOptions): class GroupEventNotifierOptionsSave(GroupEventNotifierOptions):
notifier_id: UUID4 notifier_id: UUID4

View file

@ -12,9 +12,6 @@ from .plan_rules import PlanRulesCreate, PlanRulesDay, PlanRulesOut, PlanRulesPa
from .shopping_list import ListItem, ShoppingListIn, ShoppingListOut from .shopping_list import ListItem, ShoppingListIn, ShoppingListOut
__all__ = [ __all__ = [
"ListItem",
"ShoppingListIn",
"ShoppingListOut",
"CreatePlanEntry", "CreatePlanEntry",
"CreateRandomEntry", "CreateRandomEntry",
"PlanEntryPagination", "PlanEntryPagination",
@ -22,6 +19,9 @@ __all__ = [
"ReadPlanEntry", "ReadPlanEntry",
"SavePlanEntry", "SavePlanEntry",
"UpdatePlanEntry", "UpdatePlanEntry",
"ListItem",
"ShoppingListIn",
"ShoppingListOut",
"PlanRulesCreate", "PlanRulesCreate",
"PlanRulesDay", "PlanRulesDay",
"PlanRulesOut", "PlanRulesOut",

View file

@ -89,35 +89,6 @@ from .recipe_tool import RecipeToolCreate, RecipeToolOut, RecipeToolResponse, Re
from .request_helpers import RecipeDuplicate, RecipeSlug, RecipeZipTokenResponse, SlugResponse, UpdateImageResponse from .request_helpers import RecipeDuplicate, RecipeSlug, RecipeZipTokenResponse, SlugResponse, UpdateImageResponse
__all__ = [ __all__ = [
"IngredientReferences",
"RecipeStep",
"RecipeNote",
"CategoryBase",
"CategoryIn",
"CategoryOut",
"CategorySave",
"RecipeCategoryResponse",
"RecipeTagResponse",
"TagBase",
"TagIn",
"TagOut",
"TagSave",
"RecipeAsset",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"RecipeSuggestionQuery",
"RecipeSuggestionResponse",
"RecipeSuggestionResponseItem",
"Nutrition",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"CreateIngredientFood", "CreateIngredientFood",
"CreateIngredientFoodAlias", "CreateIngredientFoodAlias",
"CreateIngredientUnit", "CreateIngredientUnit",
@ -140,13 +111,27 @@ __all__ = [
"SaveIngredientFood", "SaveIngredientFood",
"SaveIngredientUnit", "SaveIngredientUnit",
"UnitFoodBase", "UnitFoodBase",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"Nutrition",
"AssignCategories",
"AssignSettings",
"AssignTags",
"DeleteRecipes",
"ExportBase",
"ExportRecipes",
"ExportTypes",
"RecipeCommentCreate", "RecipeCommentCreate",
"RecipeCommentOut", "RecipeCommentOut",
"RecipeCommentPagination", "RecipeCommentPagination",
"RecipeCommentSave", "RecipeCommentSave",
"RecipeCommentUpdate", "RecipeCommentUpdate",
"UserBase", "UserBase",
"RecipeSettings",
"CreateRecipe", "CreateRecipe",
"CreateRecipeBulk", "CreateRecipeBulk",
"CreateRecipeByUrlBulk", "CreateRecipeByUrlBulk",
@ -160,25 +145,40 @@ __all__ = [
"RecipeTagPagination", "RecipeTagPagination",
"RecipeTool", "RecipeTool",
"RecipeToolPagination", "RecipeToolPagination",
"ScrapeRecipe", "IngredientReferences",
"ScrapeRecipeBase", "RecipeStep",
"ScrapeRecipeData", "RecipeNote",
"ScrapeRecipeTest", "RecipeSuggestionQuery",
"AssignCategories", "RecipeSuggestionResponse",
"AssignSettings", "RecipeSuggestionResponseItem",
"AssignTags", "RecipeSettings",
"DeleteRecipes", "RecipeShareToken",
"ExportBase", "RecipeShareTokenCreate",
"ExportRecipes", "RecipeShareTokenSave",
"ExportTypes", "RecipeShareTokenSummary",
"RecipeToolCreate", "RecipeAsset",
"RecipeToolOut",
"RecipeToolResponse",
"RecipeToolSave",
"RecipeImageTypes",
"RecipeDuplicate", "RecipeDuplicate",
"RecipeSlug", "RecipeSlug",
"RecipeZipTokenResponse", "RecipeZipTokenResponse",
"SlugResponse", "SlugResponse",
"UpdateImageResponse", "UpdateImageResponse",
"RecipeToolCreate",
"RecipeToolOut",
"RecipeToolResponse",
"RecipeToolSave",
"CategoryBase",
"CategoryIn",
"CategoryOut",
"CategorySave",
"RecipeCategoryResponse",
"RecipeTagResponse",
"TagBase",
"TagIn",
"TagOut",
"TagSave",
"ScrapeRecipe",
"ScrapeRecipeBase",
"ScrapeRecipeData",
"ScrapeRecipeTest",
"RecipeImageTypes",
] ]

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