mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
Merge branch 'mealie-next' into mealie-next
This commit is contained in:
commit
8170e66f4f
@ -22,7 +22,15 @@ def populate_normalized_fields():
|
|||||||
bind = op.get_bind()
|
bind = op.get_bind()
|
||||||
session = orm.Session(bind=bind)
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
units = session.execute(select(IngredientUnitModel)).scalars().all()
|
units = (
|
||||||
|
session.execute(
|
||||||
|
select(IngredientUnitModel).options(
|
||||||
|
orm.load_only(IngredientUnitModel.name, IngredientUnitModel.abbreviation)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.scalars()
|
||||||
|
.all()
|
||||||
|
)
|
||||||
for unit in units:
|
for unit in units:
|
||||||
if unit.name is not None:
|
if unit.name is not None:
|
||||||
unit.name_normalized = IngredientUnitModel.normalize(unit.name)
|
unit.name_normalized = IngredientUnitModel.normalize(unit.name)
|
||||||
@ -32,7 +40,9 @@ def populate_normalized_fields():
|
|||||||
|
|
||||||
session.add(unit)
|
session.add(unit)
|
||||||
|
|
||||||
foods = session.execute(select(IngredientFoodModel)).scalars().all()
|
foods = (
|
||||||
|
session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all()
|
||||||
|
)
|
||||||
for food in foods:
|
for food in foods:
|
||||||
if food.name is not None:
|
if food.name is not None:
|
||||||
food.name_normalized = IngredientFoodModel.normalize(food.name)
|
food.name_normalized = IngredientFoodModel.normalize(food.name)
|
||||||
|
@ -10,7 +10,7 @@ from dataclasses import dataclass
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session, load_only
|
||||||
|
|
||||||
import mealie.db.migration_types
|
import mealie.db.migration_types
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@ -44,7 +44,7 @@ def _is_postgres():
|
|||||||
|
|
||||||
def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list[str]]:
|
def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list[str]]:
|
||||||
duplicate_map: defaultdict[str, list[str]] = defaultdict(list)
|
duplicate_map: defaultdict[str, list[str]] = defaultdict(list)
|
||||||
for obj in session.query(model).all():
|
for obj in session.query(model).options(load_only(model.id, model.group_id, model.name)).all():
|
||||||
key = f"{obj.group_id}$${obj.name}"
|
key = f"{obj.group_id}$${obj.name}"
|
||||||
duplicate_map[key].append(str(obj.id))
|
duplicate_map[key].append(str(obj.id))
|
||||||
|
|
||||||
@ -117,9 +117,9 @@ def _resolve_duplivate_foods_units_labels():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
keep_id = ids[0]
|
keep_id = ids[0]
|
||||||
keep_obj = session.query(model).filter_by(id=keep_id).first()
|
keep_obj = session.query(model).options(load_only(model.id)).filter_by(id=keep_id).first()
|
||||||
for dupe_id in ids[1:]:
|
for dupe_id in ids[1:]:
|
||||||
dupe_obj = session.query(model).filter_by(id=dupe_id).first()
|
dupe_obj = session.query(model).options(load_only(model.id)).filter_by(id=dupe_id).first()
|
||||||
resolve_func(session, keep_obj, dupe_obj)
|
resolve_func(session, keep_obj, dupe_obj)
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
"""added plural names and alias tables for foods and units
|
||||||
|
|
||||||
|
Revision ID: ba1e4a6cfe99
|
||||||
|
Revises: dded3119c1fe
|
||||||
|
Create Date: 2023-10-19 19:22:55.369319
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
import mealie.db.migration_types
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "ba1e4a6cfe99"
|
||||||
|
down_revision = "dded3119c1fe"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"ingredient_units_aliases",
|
||||||
|
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(), nullable=False),
|
||||||
|
sa.Column("name_normalized", sa.String(), nullable=True),
|
||||||
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("update_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["unit_id"],
|
||||||
|
["ingredient_units.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id", "unit_id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_units_aliases_created_at"), "ingredient_units_aliases", ["created_at"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_units_aliases_name_normalized"),
|
||||||
|
"ingredient_units_aliases",
|
||||||
|
["name_normalized"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"ingredient_foods_aliases",
|
||||||
|
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(), nullable=False),
|
||||||
|
sa.Column("name_normalized", sa.String(), nullable=True),
|
||||||
|
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("update_at", sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["food_id"],
|
||||||
|
["ingredient_foods.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id", "food_id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_foods_aliases_created_at"), "ingredient_foods_aliases", ["created_at"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_foods_aliases_name_normalized"),
|
||||||
|
"ingredient_foods_aliases",
|
||||||
|
["name_normalized"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.add_column("ingredient_foods", sa.Column("plural_name", sa.String(), nullable=True))
|
||||||
|
op.add_column("ingredient_foods", sa.Column("plural_name_normalized", sa.String(), nullable=True))
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_foods_plural_name_normalized"), "ingredient_foods", ["plural_name_normalized"], unique=False
|
||||||
|
)
|
||||||
|
op.add_column("ingredient_units", sa.Column("plural_name", sa.String(), nullable=True))
|
||||||
|
op.add_column("ingredient_units", sa.Column("plural_name_normalized", sa.String(), nullable=True))
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_units_plural_name_normalized"), "ingredient_units", ["plural_name_normalized"], unique=False
|
||||||
|
)
|
||||||
|
op.add_column("ingredient_units", sa.Column("plural_abbreviation", sa.String(), nullable=True))
|
||||||
|
op.add_column("ingredient_units", sa.Column("plural_abbreviation_normalized", sa.String(), nullable=True))
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_ingredient_units_plural_abbreviation_normalized"),
|
||||||
|
"ingredient_units",
|
||||||
|
["plural_abbreviation_normalized"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f("ix_ingredient_units_plural_abbreviation_normalized"), table_name="ingredient_units")
|
||||||
|
op.drop_column("ingredient_units", "plural_abbreviation_normalized")
|
||||||
|
op.drop_column("ingredient_units", "plural_abbreviation")
|
||||||
|
op.drop_index(op.f("ix_ingredient_units_plural_name_normalized"), table_name="ingredient_units")
|
||||||
|
op.drop_column("ingredient_units", "plural_name_normalized")
|
||||||
|
op.drop_column("ingredient_units", "plural_name")
|
||||||
|
op.drop_index(op.f("ix_ingredient_foods_plural_name_normalized"), table_name="ingredient_foods")
|
||||||
|
op.drop_column("ingredient_foods", "plural_name_normalized")
|
||||||
|
op.drop_column("ingredient_foods", "plural_name")
|
||||||
|
op.drop_index(op.f("ix_ingredient_foods_aliases_name_normalized"), table_name="ingredient_foods_aliases")
|
||||||
|
op.drop_index(op.f("ix_ingredient_foods_aliases_created_at"), table_name="ingredient_foods_aliases")
|
||||||
|
op.drop_table("ingredient_foods_aliases")
|
||||||
|
op.drop_index(op.f("ix_ingredient_units_aliases_name_normalized"), table_name="ingredient_units_aliases")
|
||||||
|
op.drop_index(op.f("ix_ingredient_units_aliases_created_at"), table_name="ingredient_units_aliases")
|
||||||
|
op.drop_table("ingredient_units_aliases")
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<BaseDialog
|
||||||
|
v-model="dialog"
|
||||||
|
:title="$t('data-pages.manage-aliases')"
|
||||||
|
:icon="$globals.icons.edit"
|
||||||
|
:submit-icon="$globals.icons.check"
|
||||||
|
:submit-text="$tc('general.confirm')"
|
||||||
|
@submit="saveAliases"
|
||||||
|
@cancel="$emit('cancel')"
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
<v-row v-for="alias, i in aliases" :key="i">
|
||||||
|
<v-col cols="10">
|
||||||
|
<v-text-field
|
||||||
|
v-model="alias.name"
|
||||||
|
:label="$t('general.name')"
|
||||||
|
:rules="[validators.required]"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="2">
|
||||||
|
<BaseButtonGroup
|
||||||
|
:buttons="[
|
||||||
|
{
|
||||||
|
icon: $globals.icons.delete,
|
||||||
|
text: $tc('general.delete'),
|
||||||
|
event: 'delete'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
@delete="deleteAlias(i)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<template #custom-card-action>
|
||||||
|
<BaseButton edit @click="createAlias">{{ $t('data-pages.create-alias') }}
|
||||||
|
<template #icon>
|
||||||
|
{{ $globals.icons.create }}
|
||||||
|
</template>
|
||||||
|
</BaseButton>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { whenever } from "@vueuse/core";
|
||||||
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
export interface GenericAlias {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object as () => IngredientFood | IngredientUnit,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
// V-Model Support
|
||||||
|
const dialog = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.value;
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
context.emit("input", val);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function createAlias() {
|
||||||
|
aliases.value.push({
|
||||||
|
"name": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAlias(index: number) {
|
||||||
|
aliases.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const aliases = ref<GenericAlias[]>(props.data.aliases || []);
|
||||||
|
function initAliases() {
|
||||||
|
aliases.value = [...props.data.aliases || []];
|
||||||
|
if (!aliases.value.length) {
|
||||||
|
createAlias();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initAliases();
|
||||||
|
whenever(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
initAliases();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function saveAliases() {
|
||||||
|
const seenAliasNames: string[] = [];
|
||||||
|
const keepAliases: GenericAlias[] = [];
|
||||||
|
aliases.value.forEach((alias) => {
|
||||||
|
if (
|
||||||
|
!alias.name
|
||||||
|
|| alias.name === props.data.name
|
||||||
|
|| alias.name === props.data.pluralName
|
||||||
|
// @ts-ignore only applies to units
|
||||||
|
|| alias.name === props.data.abbreviation
|
||||||
|
// @ts-ignore only applies to units
|
||||||
|
|| alias.name === props.data.pluralAbbreviation
|
||||||
|
|| seenAliasNames.includes(alias.name)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keepAliases.push(alias);
|
||||||
|
seenAliasNames.push(alias.name);
|
||||||
|
})
|
||||||
|
|
||||||
|
aliases.value = keepAliases;
|
||||||
|
context.emit("submit", keepAliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
aliases,
|
||||||
|
createAlias,
|
||||||
|
dialog,
|
||||||
|
deleteAlias,
|
||||||
|
saveAliases,
|
||||||
|
validators,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -58,8 +58,12 @@
|
|||||||
</template>
|
</template>
|
||||||
{{ $t("general.confirm") }}
|
{{ $t("general.confirm") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
|
<slot name="custom-card-action"></slot>
|
||||||
<BaseButton v-if="$listeners.submit" type="submit" @click="submitEvent">
|
<BaseButton v-if="$listeners.submit" type="submit" @click="submitEvent">
|
||||||
{{ submitText }}
|
{{ submitText }}
|
||||||
|
<template v-if="submitIcon" #icon>
|
||||||
|
{{ submitIcon }}
|
||||||
|
</template>
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</slot>
|
</slot>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
@ -109,6 +113,10 @@ export default defineComponent({
|
|||||||
default: null,
|
default: null,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
submitIcon: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
submitText: {
|
submitText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: function () {
|
default: function () {
|
||||||
|
@ -48,4 +48,74 @@ describe(parseIngredientText.name, () => {
|
|||||||
|
|
||||||
expect(parseIngredientText(ingredient, false)).not.toContain("<script>");
|
expect(parseIngredientText(ingredient, false)).not.toContain("<script>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("plural test : plural qty : use abbreviation", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 2,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("2 tbsps diced onions");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plural test : plural qty : not abbreviation", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 2,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("2 tablespoons diced onions");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plural test : single qty : use abbreviation", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 1,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("1 tbsp diced onion");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plural test : single qty : not abbreviation", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 1,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("1 tablespoon diced onion");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plural test : small qty : use abbreviation", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 0.5,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("0.5 tbsp diced onion");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plural test : small qty : not abbreviation", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 0.5,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("0.5 tablespoon diced onion");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plural test : zero qty", () => {
|
||||||
|
const ingredient = createRecipeIngredient({
|
||||||
|
quantity: 0,
|
||||||
|
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
|
||||||
|
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseIngredientText(ingredient, false)).toEqual("diced onions");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import DOMPurify from "isomorphic-dompurify";
|
import DOMPurify from "isomorphic-dompurify";
|
||||||
import { useFraction } from "./use-fraction";
|
import { useFraction } from "./use-fraction";
|
||||||
import { RecipeIngredient } from "~/lib/api/types/recipe";
|
import { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit, RecipeIngredient } from "~/lib/api/types/recipe";
|
||||||
const { frac } = useFraction();
|
const { frac } = useFraction();
|
||||||
|
|
||||||
export function sanitizeIngredientHTML(rawHtml: string) {
|
export function sanitizeIngredientHTML(rawHtml: string) {
|
||||||
@ -10,6 +10,31 @@ export function sanitizeIngredientHTML(rawHtml: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useFoodName(food: CreateIngredientFood | IngredientFood | undefined, usePlural: boolean) {
|
||||||
|
if (!food) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (usePlural ? food.pluralName || food.name : food.name) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function useUnitName(unit: CreateIngredientUnit | IngredientUnit | undefined, usePlural: boolean) {
|
||||||
|
if (!unit) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnVal = "";
|
||||||
|
if (unit.useAbbreviation) {
|
||||||
|
returnVal = (usePlural ? unit.pluralAbbreviation || unit.abbreviation : unit.abbreviation) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!returnVal) {
|
||||||
|
returnVal = (usePlural ? unit.pluralName || unit.name : unit.name) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmount: boolean, scale = 1, includeFormating = true) {
|
export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmount: boolean, scale = 1, includeFormating = true) {
|
||||||
if (disableAmount) {
|
if (disableAmount) {
|
||||||
return {
|
return {
|
||||||
@ -21,11 +46,11 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { quantity, food, unit, note } = ingredient;
|
const { quantity, food, unit, note } = ingredient;
|
||||||
|
const usePluralUnit = quantity !== undefined && quantity > 1;
|
||||||
|
const usePluralFood = (!quantity) || quantity > 1
|
||||||
|
|
||||||
let returnQty = "";
|
let returnQty = "";
|
||||||
|
|
||||||
let unitDisplay = unit?.name;
|
|
||||||
|
|
||||||
// casting to number is required as sometimes quantity is a string
|
// casting to number is required as sometimes quantity is a string
|
||||||
if (quantity && Number(quantity) !== 0) {
|
if (quantity && Number(quantity) !== 0) {
|
||||||
if (unit?.fraction) {
|
if (unit?.fraction) {
|
||||||
@ -42,16 +67,15 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
|
|||||||
} else {
|
} else {
|
||||||
returnQty = (quantity * scale).toString();
|
returnQty = (quantity * scale).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit?.useAbbreviation && unit.abbreviation) {
|
|
||||||
unitDisplay = unit.abbreviation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unitName = useUnitName(unit, usePluralUnit);
|
||||||
|
const foodName = useFoodName(food, usePluralFood);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined,
|
quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined,
|
||||||
unit: unitDisplay ? sanitizeIngredientHTML(unitDisplay) : undefined,
|
unit: unitName && quantity ? sanitizeIngredientHTML(unitName) : undefined,
|
||||||
name: food?.name ? sanitizeIngredientHTML(food.name) : undefined,
|
name: foodName ? sanitizeIngredientHTML(foodName) : undefined,
|
||||||
note: note ? sanitizeIngredientHTML(note) : undefined,
|
note: note ? sanitizeIngredientHTML(note) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -227,11 +227,11 @@
|
|||||||
"group-preferences": "Nastavení skupiny",
|
"group-preferences": "Nastavení skupiny",
|
||||||
"private-group": "Soukromá skupina",
|
"private-group": "Soukromá skupina",
|
||||||
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
|
"allow-users-outside-of-your-group-to-see-your-recipes": "Povolit uživatelům mimo vaši skupinu vidět vaše recepty",
|
||||||
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
"allow-users-outside-of-your-group-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your group or with a pre-generated private link",
|
||||||
"show-nutrition-information": "Show nutrition information",
|
"show-nutrition-information": "Show nutrition information",
|
||||||
"show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown",
|
"show-nutrition-information-description": "When enabled the nutrition information will be shown on the recipe if available. If there is no nutrition information available, the nutrition information will not be shown",
|
||||||
"show-recipe-assets": "Show recipe assets",
|
"show-recipe-assets": "Zobrazit položky receptu",
|
||||||
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available",
|
"show-recipe-assets-description": "When enabled the recipe assets will be shown on the recipe if available",
|
||||||
"default-to-landscape-view": "Default to landscape view",
|
"default-to-landscape-view": "Default to landscape view",
|
||||||
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
|
"default-to-landscape-view-description": "When enabled the recipe header section will be shown in landscape view",
|
||||||
@ -242,9 +242,9 @@
|
|||||||
"general-preferences": "General Preferences",
|
"general-preferences": "General Preferences",
|
||||||
"group-recipe-preferences": "Group Recipe Preferences",
|
"group-recipe-preferences": "Group Recipe Preferences",
|
||||||
"report": "Report",
|
"report": "Report",
|
||||||
"group-management": "Group Management",
|
"group-management": "Správa skupin",
|
||||||
"admin-group-management": "Admin Group Management",
|
"admin-group-management": "Admin Group Management",
|
||||||
"admin-group-management-text": "Changes to this group will be reflected immediately.",
|
"admin-group-management-text": "Změny v této skupině budou okamžitě zohledněny.",
|
||||||
"group-id-value": "Group Id: {0}"
|
"group-id-value": "Group Id: {0}"
|
||||||
},
|
},
|
||||||
"meal-plan": {
|
"meal-plan": {
|
||||||
@ -284,10 +284,10 @@
|
|||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"meal-recipe": "Meal Recipe",
|
"meal-recipe": "Meal Recipe",
|
||||||
"meal-title": "Meal Title",
|
"meal-title": "Meal Title",
|
||||||
"meal-note": "Meal Note",
|
"meal-note": "Poznámka k jídlu",
|
||||||
"note-only": "Note Only",
|
"note-only": "Note Only",
|
||||||
"random-meal": "Náhodné jídlo",
|
"random-meal": "Náhodné jídlo",
|
||||||
"random-dinner": "Random Dinner",
|
"random-dinner": "Náhodná večeře",
|
||||||
"random-side": "Random Side",
|
"random-side": "Random Side",
|
||||||
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
|
"this-rule-will-apply": "This rule will apply {dayCriteria} {mealTypeCriteria}.",
|
||||||
"to-all-days": "to all days",
|
"to-all-days": "to all days",
|
||||||
|
@ -124,6 +124,7 @@
|
|||||||
"no-recipe-found": "No Recipe Found",
|
"no-recipe-found": "No Recipe Found",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"options": "Options:",
|
"options": "Options:",
|
||||||
|
"plural-name": "Plural Name",
|
||||||
"print": "Print",
|
"print": "Print",
|
||||||
"print-preferences": "Print Preferences",
|
"print-preferences": "Print Preferences",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
@ -889,7 +890,9 @@
|
|||||||
"create-food": "Create Food",
|
"create-food": "Create Food",
|
||||||
"food-label": "Food Label",
|
"food-label": "Food Label",
|
||||||
"edit-food": "Edit Food",
|
"edit-food": "Edit Food",
|
||||||
"food-data": "Food Data"
|
"food-data": "Food Data",
|
||||||
|
"example-food-singular": "ex: Onion",
|
||||||
|
"example-food-plural": "ex: Onions"
|
||||||
},
|
},
|
||||||
"units": {
|
"units": {
|
||||||
"seed-dialog-text": "Seed the database with common units based on your local language.",
|
"seed-dialog-text": "Seed the database with common units based on your local language.",
|
||||||
@ -900,13 +903,18 @@
|
|||||||
"merging-unit-into-unit": "Merging {0} into {1}",
|
"merging-unit-into-unit": "Merging {0} into {1}",
|
||||||
"create-unit": "Create Unit",
|
"create-unit": "Create Unit",
|
||||||
"abbreviation": "Abbreviation",
|
"abbreviation": "Abbreviation",
|
||||||
|
"plural-abbreviation": "Plural Abbreviation",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"display-as-fraction": "Display as Fraction",
|
"display-as-fraction": "Display as Fraction",
|
||||||
"use-abbreviation": "Use Abbreviation",
|
"use-abbreviation": "Use Abbreviation",
|
||||||
"edit-unit": "Edit Unit",
|
"edit-unit": "Edit Unit",
|
||||||
"unit-data": "Unit Data",
|
"unit-data": "Unit Data",
|
||||||
"use-abbv": "Use Abbv.",
|
"use-abbv": "Use Abbv.",
|
||||||
"fraction": "Fraction"
|
"fraction": "Fraction",
|
||||||
|
"example-unit-singular": "ex: Tablespoon",
|
||||||
|
"example-unit-plural": "ex: Tablespoons",
|
||||||
|
"example-unit-abbreviation-singular": "ex: Tbsp",
|
||||||
|
"example-unit-abbreviation-plural": "ex: Tbsps"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"seed-dialog-text": "Seed the database with common labels based on your local language.",
|
"seed-dialog-text": "Seed the database with common labels based on your local language.",
|
||||||
@ -935,6 +943,8 @@
|
|||||||
"delete-recipes": "Delete Recipes",
|
"delete-recipes": "Delete Recipes",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
|
"create-alias": "Create Alias",
|
||||||
|
"manage-aliases": "Manage Aliases",
|
||||||
"seed-data": "Seed Data",
|
"seed-data": "Seed Data",
|
||||||
"seed": "Seed",
|
"seed": "Seed",
|
||||||
"data-management": "Data Management",
|
"data-management": "Data Management",
|
||||||
|
@ -543,7 +543,7 @@
|
|||||||
"unit": "Unidades",
|
"unit": "Unidades",
|
||||||
"upload-image": "Subir imagen",
|
"upload-image": "Subir imagen",
|
||||||
"screen-awake": "Mantener la pantalla encendida",
|
"screen-awake": "Mantener la pantalla encendida",
|
||||||
"remove-image": "Remove image"
|
"remove-image": "Eliminar imagen"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Búsqueda avanzada",
|
"advanced-search": "Búsqueda avanzada",
|
||||||
@ -552,7 +552,7 @@
|
|||||||
"include": "Incluir",
|
"include": "Incluir",
|
||||||
"max-results": "Resultados máximos",
|
"max-results": "Resultados máximos",
|
||||||
"or": "O",
|
"or": "O",
|
||||||
"has-any": "Has Any",
|
"has-any": "Tiene alguna",
|
||||||
"has-all": "Tiene todo",
|
"has-all": "Tiene todo",
|
||||||
"results": "Resultados",
|
"results": "Resultados",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
@ -898,19 +898,19 @@
|
|||||||
"target-unit": "Target Unit",
|
"target-unit": "Target Unit",
|
||||||
"merging-unit-into-unit": "Merging {0} into {1}",
|
"merging-unit-into-unit": "Merging {0} into {1}",
|
||||||
"create-unit": "Create Unit",
|
"create-unit": "Create Unit",
|
||||||
"abbreviation": "Abbreviation",
|
"abbreviation": "Abreviatura",
|
||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"display-as-fraction": "Display as Fraction",
|
"display-as-fraction": "Display as Fraction",
|
||||||
"use-abbreviation": "Use Abbreviation",
|
"use-abbreviation": "Usar Abreviaturas",
|
||||||
"edit-unit": "Edit Unit",
|
"edit-unit": "Editar unidad",
|
||||||
"unit-data": "Unit Data",
|
"unit-data": "Unit Data",
|
||||||
"use-abbv": "Use Abbv.",
|
"use-abbv": "Use Abbv.",
|
||||||
"fraction": "Fraction"
|
"fraction": "Fraction"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"seed-dialog-text": "Añade a la base de datos etiquetas comunes basadas en su idioma local.",
|
"seed-dialog-text": "Añade a la base de datos etiquetas comunes basadas en su idioma local.",
|
||||||
"edit-label": "Edit Label",
|
"edit-label": "Editar etiqueta",
|
||||||
"new-label": "New Label",
|
"new-label": "Nueva etiqueta",
|
||||||
"labels": "Labels"
|
"labels": "Labels"
|
||||||
},
|
},
|
||||||
"recipes": {
|
"recipes": {
|
||||||
@ -920,17 +920,17 @@
|
|||||||
"the-following-recipes-selected-length-will-be-exported": "The following recipes ({0}) will be exported.",
|
"the-following-recipes-selected-length-will-be-exported": "The following recipes ({0}) will be exported.",
|
||||||
"settings-chosen-explanation": "Settings chosen here, excluding the locked option, will be applied to all selected recipes.",
|
"settings-chosen-explanation": "Settings chosen here, excluding the locked option, will be applied to all selected recipes.",
|
||||||
"selected-length-recipe-s-settings-will-be-updated": "{count} recipe(s) settings will be updated.",
|
"selected-length-recipe-s-settings-will-be-updated": "{count} recipe(s) settings will be updated.",
|
||||||
"recipe-data": "Recipe Data",
|
"recipe-data": "Datos de la receta",
|
||||||
"recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.",
|
"recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.",
|
||||||
"recipe-columns": "Recipe Columns",
|
"recipe-columns": "Recipe Columns",
|
||||||
"data-exports-description": "This section provides links to available exports that are ready to download. These exports do expire, so be sure to grab them while they're still available.",
|
"data-exports-description": "This section provides links to available exports that are ready to download. These exports do expire, so be sure to grab them while they're still available.",
|
||||||
"data-exports": "Data Exports",
|
"data-exports": "Exportación de datos",
|
||||||
"tag": "Etiqueta",
|
"tag": "Etiqueta",
|
||||||
"categorize": "Categorize",
|
"categorize": "Clasificar",
|
||||||
"update-settings": "Update Settings",
|
"update-settings": "Actualizar configuración",
|
||||||
"tag-recipes": "Tag Recipes",
|
"tag-recipes": "Tag Recipes",
|
||||||
"categorize-recipes": "Categorize Recipes",
|
"categorize-recipes": "Categorizar recetas",
|
||||||
"export-recipes": "Export Recipes",
|
"export-recipes": "Exportar recetas",
|
||||||
"delete-recipes": "Borrar Recetas",
|
"delete-recipes": "Borrar Recetas",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||||
},
|
},
|
||||||
@ -938,8 +938,8 @@
|
|||||||
"seed": "Seed",
|
"seed": "Seed",
|
||||||
"data-management": "Data Management",
|
"data-management": "Data Management",
|
||||||
"data-management-description": "Select which data set you want to make changes to.",
|
"data-management-description": "Select which data set you want to make changes to.",
|
||||||
"select-data": "Select Data",
|
"select-data": "Seleccionar datos",
|
||||||
"select-language": "Select Language",
|
"select-language": "Seleccionar idioma",
|
||||||
"columns": "Columnas",
|
"columns": "Columnas",
|
||||||
"combine": "Combinar"
|
"combine": "Combinar"
|
||||||
},
|
},
|
||||||
@ -958,7 +958,7 @@
|
|||||||
"group-name-is-taken": "El nombre de grupo ya está en uso",
|
"group-name-is-taken": "El nombre de grupo ya está en uso",
|
||||||
"username-is-taken": "El nombre de usuario ya está en uso",
|
"username-is-taken": "El nombre de usuario ya está en uso",
|
||||||
"email-is-taken": "Este correo ya está en uso",
|
"email-is-taken": "Este correo ya está en uso",
|
||||||
"this-field-is-required": "This Field is Required"
|
"this-field-is-required": "Este campo es obligatorio"
|
||||||
},
|
},
|
||||||
"export": {
|
"export": {
|
||||||
"export": "Exportar",
|
"export": "Exportar",
|
||||||
@ -991,7 +991,7 @@
|
|||||||
},
|
},
|
||||||
"ocr-editor": {
|
"ocr-editor": {
|
||||||
"ocr-editor": "Editor de OCR",
|
"ocr-editor": "Editor de OCR",
|
||||||
"selection-mode": "Selection mode",
|
"selection-mode": "Modo de selección",
|
||||||
"pan-and-zoom-picture": "Pan and zoom picture",
|
"pan-and-zoom-picture": "Pan and zoom picture",
|
||||||
"split-text": "Split text",
|
"split-text": "Split text",
|
||||||
"preserve-line-breaks": "Preserve original line breaks",
|
"preserve-line-breaks": "Preserve original line breaks",
|
||||||
|
@ -296,7 +296,7 @@
|
|||||||
"for-type-meal-types": "kaikille {0} ateriatyypeille",
|
"for-type-meal-types": "kaikille {0} ateriatyypeille",
|
||||||
"meal-plan-rules": "Ateriasuunnitelman määritykset",
|
"meal-plan-rules": "Ateriasuunnitelman määritykset",
|
||||||
"new-rule": "Uusi sääntö",
|
"new-rule": "Uusi sääntö",
|
||||||
"meal-plan-rules-description": "You can create rules for auto selecting recipes for your meal plans. These rules are used by the server to determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same day/type constraints then the categories of the rules will be merged. In practice, it's unnecessary to create duplicate rules, but it's possible to do so.",
|
"meal-plan-rules-description": "Voit luoda sääntöjä reseptien automaattiseen valitsemiseen ateriasuunnitelmia varten. Palvelin käyttää näitä sääntöjä määrittääkseen satunnaisen valikoiman reseptejä, joista valita luodessaan ateriasuunnitelmia. Huomaa, että jos säännöillä on samat päivä-/tyyppirajoitukset, sääntöjen luokat yhdistetään. Käytännössä ei ole tarpeen luoda päällekkäisiä sääntöjä, mutta se on mahdollista.",
|
||||||
"new-rule-description": "Kun luot uuden säännön ateriasuunnitelmalle, voit rajoittaa säännön koskemaan tiettyä viikonpäivää ja/tai tietyntyyppistä ateriaa. Jos haluat soveltaa sääntöä kaikkiin päiviin tai kaikkiin ateriatyyppeihin, voit asettaa säännön asetukseksi \"Mikä tahansa\", jolloin sitä sovelletaan kaikkiin mahdollisiin päivän ja/tai ateriatyypin arvoihin.",
|
"new-rule-description": "Kun luot uuden säännön ateriasuunnitelmalle, voit rajoittaa säännön koskemaan tiettyä viikonpäivää ja/tai tietyntyyppistä ateriaa. Jos haluat soveltaa sääntöä kaikkiin päiviin tai kaikkiin ateriatyyppeihin, voit asettaa säännön asetukseksi \"Mikä tahansa\", jolloin sitä sovelletaan kaikkiin mahdollisiin päivän ja/tai ateriatyypin arvoihin.",
|
||||||
"recipe-rules": "Reseptimääritykset",
|
"recipe-rules": "Reseptimääritykset",
|
||||||
"applies-to-all-days": "Sovelletaan kaikkiin päiviin",
|
"applies-to-all-days": "Sovelletaan kaikkiin päiviin",
|
||||||
@ -348,7 +348,7 @@
|
|||||||
"mealie-text": "Mealie voi tuoda reseptejä Mealie sovelluksesta ennen v1.0 julkaisua. Vie reseptisi vanhasta asennuksesta ja lataa zip-tiedosto. Huomaa, että viennistä voidaan tuoda vain reseptejä.",
|
"mealie-text": "Mealie voi tuoda reseptejä Mealie sovelluksesta ennen v1.0 julkaisua. Vie reseptisi vanhasta asennuksesta ja lataa zip-tiedosto. Huomaa, että viennistä voidaan tuoda vain reseptejä.",
|
||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie can import recipies from Plan to Eat."
|
"description-long": "Mealieen voi tuoda reseptejä Plan to Eat -sovelluksesta."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"new-recipe": {
|
"new-recipe": {
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"production": "Production",
|
"production": "Production",
|
||||||
"support": "お問い合わせ",
|
"support": "お問い合わせ",
|
||||||
"version": "バージョン",
|
"version": "バージョン",
|
||||||
"unknown-version": "unknown",
|
"unknown-version": "不明",
|
||||||
"sponsor": "Sponsor"
|
"sponsor": "Sponsor"
|
||||||
},
|
},
|
||||||
"asset": {
|
"asset": {
|
||||||
@ -57,11 +57,11 @@
|
|||||||
"event-deleted": "イベントを削除しました",
|
"event-deleted": "イベントを削除しました",
|
||||||
"event-updated": "イベントを更新しました",
|
"event-updated": "イベントを更新しました",
|
||||||
"new-notification-form-description": "Mealie uses the Apprise library to generate notifications. They offer many options for services to use for notifications. Refer to their wiki for a comprehensive guide on how to create the URL for your service. If available, selecting the type of your notification may include extra features.",
|
"new-notification-form-description": "Mealie uses the Apprise library to generate notifications. They offer many options for services to use for notifications. Refer to their wiki for a comprehensive guide on how to create the URL for your service. If available, selecting the type of your notification may include extra features.",
|
||||||
"new-version": "New version available!",
|
"new-version": "新しいバージョンがあります!",
|
||||||
"notification": "通知",
|
"notification": "通知",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"scheduled": "Scheduled",
|
"scheduled": "Scheduled",
|
||||||
"something-went-wrong": "Something Went Wrong!",
|
"something-went-wrong": "問題が発生しました",
|
||||||
"subscribed-events": "Subscribed Events",
|
"subscribed-events": "Subscribed Events",
|
||||||
"test-message-sent": "Test Message Sent",
|
"test-message-sent": "Test Message Sent",
|
||||||
"new-notification": "新着通知",
|
"new-notification": "新着通知",
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"cookbook-events": "料理本イベント",
|
"cookbook-events": "料理本イベント",
|
||||||
"tag-events": "タグイベント",
|
"tag-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": "新しいユーザーがあなたのグループに参加する際"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
@ -90,11 +90,11 @@
|
|||||||
"custom": "カスタム",
|
"custom": "カスタム",
|
||||||
"dashboard": "ダッシュボード",
|
"dashboard": "ダッシュボード",
|
||||||
"delete": "削除",
|
"delete": "削除",
|
||||||
"disabled": "Disabled",
|
"disabled": "無効",
|
||||||
"download": "ダウンロード",
|
"download": "ダウンロード",
|
||||||
"duplicate": "複製",
|
"duplicate": "複製",
|
||||||
"edit": "編集",
|
"edit": "編集",
|
||||||
"enabled": "Enabled",
|
"enabled": "有効",
|
||||||
"exception": "Exception",
|
"exception": "Exception",
|
||||||
"failed-count": "Failed: {count}",
|
"failed-count": "Failed: {count}",
|
||||||
"failure-uploading-file": "Failure uploading file",
|
"failure-uploading-file": "Failure uploading file",
|
||||||
@ -156,10 +156,10 @@
|
|||||||
"updated": "更新しました",
|
"updated": "更新しました",
|
||||||
"upload": "アップロード",
|
"upload": "アップロード",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"view": "View",
|
"view": "表示",
|
||||||
"wednesday": "水曜日",
|
"wednesday": "水曜日",
|
||||||
"yes": "はい",
|
"yes": "はい",
|
||||||
"foods": "Foods",
|
"foods": "食材",
|
||||||
"units": "Units",
|
"units": "Units",
|
||||||
"back": "戻る",
|
"back": "戻る",
|
||||||
"next": "次へ",
|
"next": "次へ",
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
"event-delete-confirmation": "Tem a certeza que pretende eliminar este evento?",
|
"event-delete-confirmation": "Tem a certeza que pretende eliminar este evento?",
|
||||||
"event-deleted": "Evento eliminado",
|
"event-deleted": "Evento eliminado",
|
||||||
"event-updated": "Evento atualizado",
|
"event-updated": "Evento atualizado",
|
||||||
"new-notification-form-description": "O Mealie usa a biblioteca Apprise para gerar notificações. Eles oferecem muitas opções de serviços para notificações. Consulte a sua wiki para um guia abrangente sobre como criar o URL para o seu serviço. Se disponível, selecionar o tipo de notificação pode incluir recursos extras.",
|
"new-notification-form-description": "O Mealie usa a biblioteca Apprise para gerar notificações. Eles oferecem muitas opções de serviços para notificações. Consulte a wiki para um guia abrangente sobre como criar o URL para o seu serviço. Se disponível, selecionar o tipo de notificação pode incluir recursos extras.",
|
||||||
"new-version": "Nova versão disponível!",
|
"new-version": "Nova versão disponível!",
|
||||||
"notification": "Notificação",
|
"notification": "Notificação",
|
||||||
"refresh": "Atualizar",
|
"refresh": "Atualizar",
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
"mealplan-events": "用餐计划事件",
|
"mealplan-events": "用餐计划事件",
|
||||||
"when-a-user-in-your-group-creates-a-new-mealplan": "当你群组中的用户创建新用餐计划时",
|
"when-a-user-in-your-group-creates-a-new-mealplan": "当你群组中的用户创建新用餐计划时",
|
||||||
"shopping-list-events": "购物清单事件",
|
"shopping-list-events": "购物清单事件",
|
||||||
"cookbook-events": "食谱活动",
|
"cookbook-events": "食谱合集事件",
|
||||||
"tag-events": "标签事件",
|
"tag-events": "标签事件",
|
||||||
"category-events": "目录事件",
|
"category-events": "目录事件",
|
||||||
"when-a-new-user-joins-your-group": "当新用户加入您的群组时"
|
"when-a-new-user-joins-your-group": "当新用户加入您的群组时"
|
||||||
@ -318,7 +318,7 @@
|
|||||||
"nextcloud": {
|
"nextcloud": {
|
||||||
"description": "从Nextcloud Cookbook迁移数据",
|
"description": "从Nextcloud Cookbook迁移数据",
|
||||||
"description-long": "Nextcloud食谱可以从存储在Nextcloud云端的含有食谱数据的zip文件导入。请参阅下方的文件夹结构示例确保您的食谱可以被正确导入。",
|
"description-long": "Nextcloud食谱可以从存储在Nextcloud云端的含有食谱数据的zip文件导入。请参阅下方的文件夹结构示例确保您的食谱可以被正确导入。",
|
||||||
"title": "Nextcloud 食谱"
|
"title": "Nextcloud Cookbook"
|
||||||
},
|
},
|
||||||
"copymethat": {
|
"copymethat": {
|
||||||
"description-long": "Mealie 可以从 Copy Mee That导入食谱。将您的食谱以HTML 格式导出,然后在下面上传 .zip压缩包",
|
"description-long": "Mealie 可以从 Copy Mee That导入食谱。将您的食谱以HTML 格式导出,然后在下面上传 .zip压缩包",
|
||||||
@ -345,7 +345,7 @@
|
|||||||
"recipe-1": "食谱 1",
|
"recipe-1": "食谱 1",
|
||||||
"recipe-2": "食谱 2",
|
"recipe-2": "食谱 2",
|
||||||
"paprika-text": "Mealie 可以从 Paprika 导入食谱。请从paprika 导出食谱,重命名导出文件并压缩成.zip格式后,在下方上传",
|
"paprika-text": "Mealie 可以从 Paprika 导入食谱。请从paprika 导出食谱,重命名导出文件并压缩成.zip格式后,在下方上传",
|
||||||
"mealie-text": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.",
|
"mealie-text": "Mealie支持从其(1.0版本之前的)早期版本中导入食谱。你需要在老版本服务器上导出食谱,并在下方上传zip文件。注意,只有食谱数据能被导入。",
|
||||||
"plantoeat": {
|
"plantoeat": {
|
||||||
"title": "Plan to Eat",
|
"title": "Plan to Eat",
|
||||||
"description-long": "Mealie支持从 Plan to Eat 中导入食谱。"
|
"description-long": "Mealie支持从 Plan to Eat 中导入食谱。"
|
||||||
@ -716,8 +716,8 @@
|
|||||||
"label": "标签",
|
"label": "标签",
|
||||||
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
|
"linked-item-warning": "This item is linked to one or more recipe. Adjusting the units or foods will yield unexpected results when adding or removing the recipe from this list.",
|
||||||
"toggle-food": "Toggle Food",
|
"toggle-food": "Toggle Food",
|
||||||
"manage-labels": "Manage Labels",
|
"manage-labels": "管理标签",
|
||||||
"are-you-sure-you-want-to-delete-this-item": "Are you sure you want to delete this item?",
|
"are-you-sure-you-want-to-delete-this-item": "你确定要删除该条目吗?",
|
||||||
"copy-as-text": "复制文本",
|
"copy-as-text": "复制文本",
|
||||||
"copy-as-markdown": "以Markdown格式复制",
|
"copy-as-markdown": "以Markdown格式复制",
|
||||||
"delete-checked": "删除选中",
|
"delete-checked": "删除选中",
|
||||||
@ -734,7 +734,7 @@
|
|||||||
"all-recipes": "全部食谱",
|
"all-recipes": "全部食谱",
|
||||||
"backups": "备份",
|
"backups": "备份",
|
||||||
"categories": "分类",
|
"categories": "分类",
|
||||||
"cookbooks": "食谱",
|
"cookbooks": "食谱合集",
|
||||||
"dashboard": "仪表盘",
|
"dashboard": "仪表盘",
|
||||||
"home-page": "首页",
|
"home-page": "首页",
|
||||||
"manage-users": "管理用户",
|
"manage-users": "管理用户",
|
||||||
@ -749,8 +749,8 @@
|
|||||||
"background-tasks": "后台任务",
|
"background-tasks": "后台任务",
|
||||||
"parser": "Parser",
|
"parser": "Parser",
|
||||||
"developer": "开发人员",
|
"developer": "开发人员",
|
||||||
"cookbook": "食谱",
|
"cookbook": "食谱合集",
|
||||||
"create-cookbook": "Create a new cookbook"
|
"create-cookbook": "新建一个食谱合集"
|
||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
"error-signing-up": "注册时出错",
|
"error-signing-up": "注册时出错",
|
||||||
@ -872,7 +872,7 @@
|
|||||||
"language-dialog": {
|
"language-dialog": {
|
||||||
"translated": "已翻译",
|
"translated": "已翻译",
|
||||||
"choose-language": "Choose Language",
|
"choose-language": "Choose Language",
|
||||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
"select-description": "选择Mealie UI的语言。该设置仅对你生效,不会影响其他用户。",
|
||||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||||
"read-the-docs": "Read the docs"
|
"read-the-docs": "Read the docs"
|
||||||
},
|
},
|
||||||
@ -1048,7 +1048,7 @@
|
|||||||
"actions-description-destructive": "destructive",
|
"actions-description-destructive": "destructive",
|
||||||
"actions-description-irreversible": "irreversible",
|
"actions-description-irreversible": "irreversible",
|
||||||
"logs-action-refresh": "Refresh Logs",
|
"logs-action-refresh": "Refresh Logs",
|
||||||
"logs-page-title": "Mealie Logs",
|
"logs-page-title": "Mealie日志",
|
||||||
"logs-tail-lines-label": "Tail Lines"
|
"logs-tail-lines-label": "Tail Lines"
|
||||||
},
|
},
|
||||||
"mainentance": {
|
"mainentance": {
|
||||||
@ -1107,7 +1107,7 @@
|
|||||||
"looking-for-privacy-settings": "Looking for Privacy Settings?",
|
"looking-for-privacy-settings": "Looking for Privacy Settings?",
|
||||||
"manage-your-api-tokens": "Manage Your API Tokens",
|
"manage-your-api-tokens": "Manage Your API Tokens",
|
||||||
"manage-user-profile": "Manage User Profile",
|
"manage-user-profile": "Manage User Profile",
|
||||||
"manage-cookbooks": "Manage Cookbooks",
|
"manage-cookbooks": "管理食谱合集",
|
||||||
"manage-members": "管理成员",
|
"manage-members": "管理成员",
|
||||||
"manage-webhooks": "管理 Webhooks",
|
"manage-webhooks": "管理 Webhooks",
|
||||||
"manage-notifiers": "Manage Notifiers",
|
"manage-notifiers": "Manage Notifiers",
|
||||||
@ -1116,8 +1116,8 @@
|
|||||||
"cookbook": {
|
"cookbook": {
|
||||||
"cookbooks": "食谱合集",
|
"cookbooks": "食谱合集",
|
||||||
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes and tags. Creating a cookbook will add an entry to the side-bar and all the recipes with the tags and categories chosen will be displayed in the cookbook.",
|
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes and tags. Creating a cookbook will add an entry to the side-bar and all the recipes with the tags and categories chosen will be displayed in the cookbook.",
|
||||||
"public-cookbook": "Public Cookbook",
|
"public-cookbook": "公开食谱合集",
|
||||||
"public-cookbook-description": "Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.",
|
"public-cookbook-description": "公开食谱合集可以分享和非Mealie用户,同时也会显示在你的群组页面上。",
|
||||||
"filter-options": "Filter Options",
|
"filter-options": "Filter Options",
|
||||||
"filter-options-description": "When require all is selected the cookbook will only include recipes that have all of the items selected. This applies to each subset of selectors and not a cross section of the selected items.",
|
"filter-options-description": "When require all is selected the cookbook will only include recipes that have all of the items selected. This applies to each subset of selectors and not a cross section of the selected items.",
|
||||||
"require-all-categories": "Require All Categories",
|
"require-all-categories": "Require All Categories",
|
||||||
|
@ -56,21 +56,32 @@ export interface CategorySave {
|
|||||||
}
|
}
|
||||||
export interface CreateIngredientFood {
|
export interface CreateIngredientFood {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
labelId?: string;
|
labelId?: string;
|
||||||
|
aliases?: CreateIngredientFoodAlias[];
|
||||||
|
}
|
||||||
|
export interface CreateIngredientFoodAlias {
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
export interface CreateIngredientUnit {
|
export interface CreateIngredientUnit {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
fraction?: boolean;
|
fraction?: boolean;
|
||||||
abbreviation?: string;
|
abbreviation?: string;
|
||||||
|
pluralAbbreviation?: string;
|
||||||
useAbbreviation?: boolean;
|
useAbbreviation?: boolean;
|
||||||
|
aliases?: CreateIngredientUnitAlias[];
|
||||||
|
}
|
||||||
|
export interface CreateIngredientUnitAlias {
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
export interface CreateRecipe {
|
export interface CreateRecipe {
|
||||||
name: string;
|
name: string;
|
||||||
@ -113,16 +124,21 @@ export interface IngredientConfidence {
|
|||||||
}
|
}
|
||||||
export interface IngredientFood {
|
export interface IngredientFood {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
labelId?: string;
|
labelId?: string;
|
||||||
|
aliases?: IngredientFoodAlias[];
|
||||||
id: string;
|
id: string;
|
||||||
label?: MultiPurposeLabelSummary;
|
label?: MultiPurposeLabelSummary;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updateAt?: string;
|
updateAt?: string;
|
||||||
}
|
}
|
||||||
|
export interface IngredientFoodAlias {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
export interface MultiPurposeLabelSummary {
|
export interface MultiPurposeLabelSummary {
|
||||||
name: string;
|
name: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
@ -141,17 +157,23 @@ export interface IngredientRequest {
|
|||||||
}
|
}
|
||||||
export interface IngredientUnit {
|
export interface IngredientUnit {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
fraction?: boolean;
|
fraction?: boolean;
|
||||||
abbreviation?: string;
|
abbreviation?: string;
|
||||||
|
pluralAbbreviation?: string;
|
||||||
useAbbreviation?: boolean;
|
useAbbreviation?: boolean;
|
||||||
|
aliases?: IngredientUnitAlias[];
|
||||||
id: string;
|
id: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updateAt?: string;
|
updateAt?: string;
|
||||||
}
|
}
|
||||||
|
export interface IngredientUnitAlias {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
export interface IngredientsRequest {
|
export interface IngredientsRequest {
|
||||||
parser?: RegisteredParser & string;
|
parser?: RegisteredParser & string;
|
||||||
ingredients: string[];
|
ingredients: string[];
|
||||||
@ -206,7 +228,7 @@ export interface Recipe {
|
|||||||
recipeCategory?: RecipeCategory[];
|
recipeCategory?: RecipeCategory[];
|
||||||
tags?: RecipeTag[];
|
tags?: RecipeTag[];
|
||||||
tools?: RecipeTool[];
|
tools?: RecipeTool[];
|
||||||
rating?: number | null;
|
rating?: number;
|
||||||
orgURL?: string;
|
orgURL?: string;
|
||||||
dateAdded?: string;
|
dateAdded?: string;
|
||||||
dateUpdated?: string;
|
dateUpdated?: string;
|
||||||
@ -413,22 +435,27 @@ export interface RecipeZipTokenResponse {
|
|||||||
}
|
}
|
||||||
export interface SaveIngredientFood {
|
export interface SaveIngredientFood {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
labelId?: string;
|
labelId?: string;
|
||||||
|
aliases?: CreateIngredientFoodAlias[];
|
||||||
groupId: string;
|
groupId: string;
|
||||||
}
|
}
|
||||||
export interface SaveIngredientUnit {
|
export interface SaveIngredientUnit {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
fraction?: boolean;
|
fraction?: boolean;
|
||||||
abbreviation?: string;
|
abbreviation?: string;
|
||||||
|
pluralAbbreviation?: string;
|
||||||
useAbbreviation?: boolean;
|
useAbbreviation?: boolean;
|
||||||
|
aliases?: CreateIngredientUnitAlias[];
|
||||||
groupId: string;
|
groupId: string;
|
||||||
}
|
}
|
||||||
export interface ScrapeRecipe {
|
export interface ScrapeRecipe {
|
||||||
@ -438,7 +465,7 @@ export interface ScrapeRecipe {
|
|||||||
export interface ScrapeRecipeTest {
|
export interface ScrapeRecipeTest {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
export interface SlugResponse {}
|
export interface SlugResponse { }
|
||||||
export interface TagIn {
|
export interface TagIn {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
@ -454,6 +481,7 @@ export interface TagSave {
|
|||||||
}
|
}
|
||||||
export interface UnitFoodBase {
|
export interface UnitFoodBase {
|
||||||
name: string;
|
name: string;
|
||||||
|
pluralName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
v-model="createDialog"
|
v-model="createDialog"
|
||||||
:icon="$globals.icons.foods"
|
:icon="$globals.icons.foods"
|
||||||
:title="$t('data-pages.foods.create-food')"
|
:title="$t('data-pages.foods.create-food')"
|
||||||
|
:submit-icon="$globals.icons.save"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$tc('general.save')"
|
||||||
@submit="createFood"
|
@submit="createFood"
|
||||||
>
|
>
|
||||||
@ -68,8 +69,14 @@
|
|||||||
v-model="createTarget.name"
|
v-model="createTarget.name"
|
||||||
autofocus
|
autofocus
|
||||||
:label="$t('general.name')"
|
:label="$t('general.name')"
|
||||||
|
:hint="$t('data-pages.foods.example-food-singular')"
|
||||||
:rules="[validators.required]"
|
:rules="[validators.required]"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="createTarget.pluralName"
|
||||||
|
:label="$t('general.plural-name')"
|
||||||
|
:hint="$t('data-pages.foods.example-food-plural')"
|
||||||
|
></v-text-field>
|
||||||
<v-text-field v-model="createTarget.description" :label="$t('recipe.description')"></v-text-field>
|
<v-text-field v-model="createTarget.description" :label="$t('recipe.description')"></v-text-field>
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
v-model="createTarget.labelId"
|
v-model="createTarget.labelId"
|
||||||
@ -83,18 +90,41 @@
|
|||||||
</v-form> </v-card-text
|
</v-form> </v-card-text
|
||||||
></BaseDialog>
|
></BaseDialog>
|
||||||
|
|
||||||
|
<!-- Alias Sub-Dialog -->
|
||||||
|
<RecipeDataAliasManagerDialog
|
||||||
|
v-if="editTarget"
|
||||||
|
:value="aliasManagerDialog"
|
||||||
|
:data="editTarget"
|
||||||
|
@submit="updateFoodAlias"
|
||||||
|
@cancel="aliasManagerDialog = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Edit Dialog -->
|
<!-- Edit Dialog -->
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-model="editDialog"
|
v-model="editDialog"
|
||||||
:icon="$globals.icons.foods"
|
:icon="$globals.icons.foods"
|
||||||
:title="$t('data-pages.foods.edit-food')"
|
:title="$t('data-pages.foods.edit-food')"
|
||||||
|
:submit-icon="$globals.icons.save"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$tc('general.save')"
|
||||||
@submit="editSaveFood"
|
@submit="editSaveFood"
|
||||||
>
|
>
|
||||||
<v-card-text v-if="editTarget">
|
<v-card-text v-if="editTarget">
|
||||||
<v-form ref="domEditFoodForm">
|
<v-form ref="domEditFoodForm">
|
||||||
<v-text-field v-model="editTarget.name" :label="$t('general.name')" :rules="[validators.required]"></v-text-field>
|
<v-text-field
|
||||||
<v-text-field v-model="editTarget.description" :label="$t('recipe.description')"></v-text-field>
|
v-model="editTarget.name"
|
||||||
|
:label="$t('general.name')"
|
||||||
|
:hint="$t('data-pages.foods.example-food-singular')"
|
||||||
|
:rules="[validators.required]"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="editTarget.pluralName"
|
||||||
|
:label="$t('general.plural-name')"
|
||||||
|
:hint="$t('data-pages.foods.example-food-plural')"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="editTarget.description"
|
||||||
|
:label="$t('recipe.description')"
|
||||||
|
></v-text-field>
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
v-model="editTarget.labelId"
|
v-model="editTarget.labelId"
|
||||||
clearable
|
clearable
|
||||||
@ -104,8 +134,12 @@
|
|||||||
:label="$t('data-pages.foods.food-label')"
|
:label="$t('data-pages.foods.food-label')"
|
||||||
>
|
>
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</v-form> </v-card-text
|
</v-form>
|
||||||
></BaseDialog>
|
</v-card-text>
|
||||||
|
<template #custom-card-action>
|
||||||
|
<BaseButton edit @click="aliasManagerEventHandler">{{ $t('data-pages.manage-aliases') }}</BaseButton>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
|
||||||
<!-- Delete Dialog -->
|
<!-- Delete Dialog -->
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
@ -156,16 +190,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, ref, computed, useContext } from "@nuxtjs/composition-api";
|
import { defineComponent, onMounted, ref, computed, useContext } from "@nuxtjs/composition-api";
|
||||||
import type { LocaleObject } from "@nuxtjs/i18n";
|
import type { LocaleObject } from "@nuxtjs/i18n";
|
||||||
|
import RecipeDataAliasManagerDialog from "~/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { CreateIngredientFood, IngredientFood } from "~/lib/api/types/recipe";
|
import { CreateIngredientFood, IngredientFood, IngredientFoodAlias } from "~/lib/api/types/recipe";
|
||||||
import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue";
|
import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue";
|
||||||
import { useLocales } from "~/composables/use-locales";
|
import { useLocales } from "~/composables/use-locales";
|
||||||
import { useFoodStore, useLabelStore } from "~/composables/store";
|
import { useFoodStore, useLabelStore } from "~/composables/store";
|
||||||
import { VForm } from "~/types/vuetify";
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { MultiPurposeLabel },
|
|
||||||
|
components: { MultiPurposeLabel, RecipeDataAliasManagerDialog },
|
||||||
setup() {
|
setup() {
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
@ -184,6 +220,11 @@ export default defineComponent({
|
|||||||
value: "name",
|
value: "name",
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: i18n.tc("general.plural-name"),
|
||||||
|
value: "pluralName",
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: i18n.tc("recipe.description"),
|
text: i18n.tc("recipe.description"),
|
||||||
value: "description",
|
value: "description",
|
||||||
@ -264,6 +305,22 @@ export default defineComponent({
|
|||||||
deleteDialog.value = false;
|
deleteDialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Alias Manager
|
||||||
|
|
||||||
|
const aliasManagerDialog = ref(false);
|
||||||
|
function aliasManagerEventHandler() {
|
||||||
|
aliasManagerDialog.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFoodAlias(newAliases: IngredientFoodAlias[]) {
|
||||||
|
if (!editTarget.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editTarget.value.aliases = newAliases;
|
||||||
|
aliasManagerDialog.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Merge Foods
|
// Merge Foods
|
||||||
|
|
||||||
@ -337,6 +394,10 @@ export default defineComponent({
|
|||||||
deleteEventHandler,
|
deleteEventHandler,
|
||||||
deleteDialog,
|
deleteDialog,
|
||||||
deleteFood,
|
deleteFood,
|
||||||
|
// Alias Manager
|
||||||
|
aliasManagerDialog,
|
||||||
|
aliasManagerEventHandler,
|
||||||
|
updateFoodAlias,
|
||||||
// Merge
|
// Merge
|
||||||
canMerge,
|
canMerge,
|
||||||
mergeFoods,
|
mergeFoods,
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
v-model="createDialog"
|
v-model="createDialog"
|
||||||
:icon="$globals.icons.units"
|
:icon="$globals.icons.units"
|
||||||
:title="$t('data-pages.units.create-unit')"
|
:title="$t('data-pages.units.create-unit')"
|
||||||
|
:submit-icon="$globals.icons.save"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$tc('general.save')"
|
||||||
@submit="createUnit"
|
@submit="createUnit"
|
||||||
>
|
>
|
||||||
@ -38,9 +39,24 @@
|
|||||||
v-model="createTarget.name"
|
v-model="createTarget.name"
|
||||||
autofocus
|
autofocus
|
||||||
:label="$t('general.name')"
|
:label="$t('general.name')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-singular')"
|
||||||
:rules="[validators.required]"
|
:rules="[validators.required]"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<v-text-field v-model="createTarget.abbreviation" :label="$t('data-pages.units.abbreviation')"></v-text-field>
|
<v-text-field
|
||||||
|
v-model="createTarget.pluralName"
|
||||||
|
:label="$t('general.plural-name')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-plural')"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="createTarget.abbreviation"
|
||||||
|
:label="$t('data-pages.units.abbreviation')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-abbreviation-singular')"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="createTarget.pluralAbbreviation"
|
||||||
|
:label="$t('data-pages.units.plural-abbreviation')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-abbreviation-plural')"
|
||||||
|
></v-text-field>
|
||||||
<v-text-field v-model="createTarget.description" :label="$t('data-pages.units.description')"></v-text-field>
|
<v-text-field v-model="createTarget.description" :label="$t('data-pages.units.description')"></v-text-field>
|
||||||
<v-checkbox v-model="createTarget.fraction" hide-details :label="$t('data-pages.units.display-as-fraction')"></v-checkbox>
|
<v-checkbox v-model="createTarget.fraction" hide-details :label="$t('data-pages.units.display-as-fraction')"></v-checkbox>
|
||||||
<v-checkbox v-model="createTarget.useAbbreviation" hide-details :label="$t('data-pages.units.use-abbreviation')"></v-checkbox>
|
<v-checkbox v-model="createTarget.useAbbreviation" hide-details :label="$t('data-pages.units.use-abbreviation')"></v-checkbox>
|
||||||
@ -48,23 +64,55 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
|
<!-- Alias Sub-Dialog -->
|
||||||
|
<RecipeDataAliasManagerDialog
|
||||||
|
v-if="editTarget"
|
||||||
|
:value="aliasManagerDialog"
|
||||||
|
:data="editTarget"
|
||||||
|
@submit="updateUnitAlias"
|
||||||
|
@cancel="aliasManagerDialog = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Edit Dialog -->
|
<!-- Edit Dialog -->
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-model="editDialog"
|
v-model="editDialog"
|
||||||
:icon="$globals.icons.units"
|
:icon="$globals.icons.units"
|
||||||
:title="$t('data-pages.units.edit-unit')"
|
:title="$t('data-pages.units.edit-unit')"
|
||||||
|
:submit-icon="$globals.icons.save"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$tc('general.save')"
|
||||||
@submit="editSaveUnit"
|
@submit="editSaveUnit"
|
||||||
>
|
>
|
||||||
<v-card-text v-if="editTarget">
|
<v-card-text v-if="editTarget">
|
||||||
<v-form ref="domEditUnitForm">
|
<v-form ref="domEditUnitForm">
|
||||||
<v-text-field v-model="editTarget.name" :label="$t('general.name')" :rules="[validators.required]"></v-text-field>
|
<v-text-field
|
||||||
<v-text-field v-model="editTarget.abbreviation" :label="$t('data-pages.units.abbreviation')"></v-text-field>
|
v-model="editTarget.name"
|
||||||
|
:label="$t('general.name')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-singular')"
|
||||||
|
:rules="[validators.required]"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="editTarget.pluralName"
|
||||||
|
:label="$t('general.plural-name')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-plural')"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="editTarget.abbreviation"
|
||||||
|
:label="$t('data-pages.units.abbreviation')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-abbreviation-singular')"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="editTarget.pluralAbbreviation"
|
||||||
|
:label="$t('data-pages.units.plural-abbreviation')"
|
||||||
|
:hint="$t('data-pages.units.example-unit-abbreviation-plural')"
|
||||||
|
></v-text-field>
|
||||||
<v-text-field v-model="editTarget.description" :label="$t('data-pages.units.description')"></v-text-field>
|
<v-text-field v-model="editTarget.description" :label="$t('data-pages.units.description')"></v-text-field>
|
||||||
<v-checkbox v-model="editTarget.fraction" hide-details :label="$t('data-pages.units.display-as-fraction')"></v-checkbox>
|
<v-checkbox v-model="editTarget.fraction" hide-details :label="$t('data-pages.units.display-as-fraction')"></v-checkbox>
|
||||||
<v-checkbox v-model="editTarget.useAbbreviation" hide-details :label="$t('data-pages.units.use-abbreviation')"></v-checkbox>
|
<v-checkbox v-model="editTarget.useAbbreviation" hide-details :label="$t('data-pages.units.use-abbreviation')"></v-checkbox>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<template #custom-card-action>
|
||||||
|
<BaseButton edit @click="aliasManagerEventHandler">{{ $t('data-pages.manage-aliases') }}</BaseButton>
|
||||||
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
<!-- Delete Dialog -->
|
<!-- Delete Dialog -->
|
||||||
@ -159,14 +207,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import type { LocaleObject } from "@nuxtjs/i18n";
|
import type { LocaleObject } from "@nuxtjs/i18n";
|
||||||
|
import RecipeDataAliasManagerDialog from "~/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { CreateIngredientUnit, IngredientUnit } from "~/lib/api/types/recipe";
|
import { CreateIngredientUnit, IngredientUnit, IngredientUnitAlias } from "~/lib/api/types/recipe";
|
||||||
import { useLocales } from "~/composables/use-locales";
|
import { useLocales } from "~/composables/use-locales";
|
||||||
import { useUnitStore } from "~/composables/store";
|
import { useUnitStore } from "~/composables/store";
|
||||||
import { VForm } from "~/types/vuetify";
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: { RecipeDataAliasManagerDialog },
|
||||||
setup() {
|
setup() {
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
@ -185,11 +235,21 @@ export default defineComponent({
|
|||||||
value: "name",
|
value: "name",
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("general.plural-name"),
|
||||||
|
value: "pluralName",
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("data-pages.units.abbreviation"),
|
text: i18n.t("data-pages.units.abbreviation"),
|
||||||
value: "abbreviation",
|
value: "abbreviation",
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: i18n.t("data-pages.units.plural-abbreviation"),
|
||||||
|
value: "pluralAbbreviation",
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: i18n.t("data-pages.units.use-abbv"),
|
text: i18n.t("data-pages.units.use-abbv"),
|
||||||
value: "useAbbreviation",
|
value: "useAbbreviation",
|
||||||
@ -278,6 +338,22 @@ export default defineComponent({
|
|||||||
deleteDialog.value = false;
|
deleteDialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Alias Manager
|
||||||
|
|
||||||
|
const aliasManagerDialog = ref(false);
|
||||||
|
function aliasManagerEventHandler() {
|
||||||
|
aliasManagerDialog.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUnitAlias(newAliases: IngredientUnitAlias[]) {
|
||||||
|
if (!editTarget.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editTarget.value.aliases = newAliases;
|
||||||
|
aliasManagerDialog.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Merge Units
|
// Merge Units
|
||||||
|
|
||||||
@ -345,13 +421,16 @@ export default defineComponent({
|
|||||||
deleteEventHandler,
|
deleteEventHandler,
|
||||||
deleteDialog,
|
deleteDialog,
|
||||||
deleteUnit,
|
deleteUnit,
|
||||||
|
// Alias Manager
|
||||||
|
aliasManagerDialog,
|
||||||
|
aliasManagerEventHandler,
|
||||||
|
updateUnitAlias,
|
||||||
// Merge
|
// Merge
|
||||||
canMerge,
|
canMerge,
|
||||||
mergeUnits,
|
mergeUnits,
|
||||||
mergeDialog,
|
mergeDialog,
|
||||||
fromUnit,
|
fromUnit,
|
||||||
toUnit,
|
toUnit,
|
||||||
|
|
||||||
// Seed
|
// Seed
|
||||||
seedDatabase,
|
seedDatabase,
|
||||||
locales,
|
locales,
|
||||||
|
@ -25,25 +25,44 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
|||||||
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_units", foreign_keys=[group_id])
|
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_units", foreign_keys=[group_id])
|
||||||
|
|
||||||
name: Mapped[str | None] = mapped_column(String)
|
name: Mapped[str | None] = mapped_column(String)
|
||||||
|
plural_name: Mapped[str | None] = mapped_column(String)
|
||||||
description: Mapped[str | None] = mapped_column(String)
|
description: Mapped[str | None] = mapped_column(String)
|
||||||
abbreviation: Mapped[str | None] = mapped_column(String)
|
abbreviation: Mapped[str | None] = mapped_column(String)
|
||||||
|
plural_abbreviation: Mapped[str | None] = mapped_column(String)
|
||||||
use_abbreviation: Mapped[bool | None] = mapped_column(Boolean, default=False)
|
use_abbreviation: Mapped[bool | None] = mapped_column(Boolean, default=False)
|
||||||
fraction: Mapped[bool | None] = mapped_column(Boolean, default=True)
|
fraction: Mapped[bool | None] = mapped_column(Boolean, default=True)
|
||||||
|
|
||||||
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
|
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
|
||||||
"RecipeIngredientModel", back_populates="unit"
|
"RecipeIngredientModel", back_populates="unit"
|
||||||
)
|
)
|
||||||
|
aliases: Mapped[list["IngredientUnitAliasModel"]] = orm.relationship(
|
||||||
|
"IngredientUnitAliasModel", back_populates="unit", cascade="all, delete, delete-orphan"
|
||||||
|
)
|
||||||
|
|
||||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||||
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
|
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
|
abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
|
||||||
|
plural_abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, session: Session, name: str | None = None, abbreviation: str | None = None, **_) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
session: Session,
|
||||||
|
name: str | None = None,
|
||||||
|
plural_name: str | None = None,
|
||||||
|
abbreviation: str | None = None,
|
||||||
|
plural_abbreviation: str | None = None,
|
||||||
|
**_,
|
||||||
|
) -> None:
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self.name_normalized = self.normalize(name)
|
self.name_normalized = self.normalize(name)
|
||||||
|
if plural_name is not None:
|
||||||
|
self.plural_name_normalized = self.normalize(plural_name)
|
||||||
if abbreviation is not None:
|
if abbreviation is not None:
|
||||||
self.abbreviation = self.normalize(abbreviation)
|
self.abbreviation_normalized = self.normalize(abbreviation)
|
||||||
|
if plural_abbreviation is not None:
|
||||||
|
self.plural_abbreviation_normalized = self.normalize(plural_abbreviation)
|
||||||
|
|
||||||
tableargs = [
|
tableargs = [
|
||||||
sa.UniqueConstraint("name", "group_id", name="ingredient_units_name_group_id_key"),
|
sa.UniqueConstraint("name", "group_id", name="ingredient_units_name_group_id_key"),
|
||||||
@ -52,11 +71,21 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
|||||||
"name_normalized",
|
"name_normalized",
|
||||||
unique=False,
|
unique=False,
|
||||||
),
|
),
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_units_plural_name_normalized",
|
||||||
|
"plural_name_normalized",
|
||||||
|
unique=False,
|
||||||
|
),
|
||||||
sa.Index(
|
sa.Index(
|
||||||
"ix_ingredient_units_abbreviation_normalized",
|
"ix_ingredient_units_abbreviation_normalized",
|
||||||
"abbreviation_normalized",
|
"abbreviation_normalized",
|
||||||
unique=False,
|
unique=False,
|
||||||
),
|
),
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_units_plural_abbreviation_normalized",
|
||||||
|
"plural_abbreviation_normalized",
|
||||||
|
unique=False,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
if session.get_bind().name == "postgresql":
|
if session.get_bind().name == "postgresql":
|
||||||
@ -71,6 +100,15 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
|||||||
"name_normalized": "gin_trgm_ops",
|
"name_normalized": "gin_trgm_ops",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_units_plural_name_normalized_gin",
|
||||||
|
"name_normalized",
|
||||||
|
unique=False,
|
||||||
|
postgresql_using="gin",
|
||||||
|
postgresql_ops={
|
||||||
|
"plural_name_normalized": "gin_trgm_ops",
|
||||||
|
},
|
||||||
|
),
|
||||||
sa.Index(
|
sa.Index(
|
||||||
"ix_ingredient_units_abbreviation_normalized_gin",
|
"ix_ingredient_units_abbreviation_normalized_gin",
|
||||||
"abbreviation_normalized",
|
"abbreviation_normalized",
|
||||||
@ -80,6 +118,15 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
|||||||
"abbreviation_normalized": "gin_trgm_ops",
|
"abbreviation_normalized": "gin_trgm_ops",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_units_plural_abbreviation_normalized_gin",
|
||||||
|
"plural_abbreviation_normalized",
|
||||||
|
unique=False,
|
||||||
|
postgresql_using="gin",
|
||||||
|
postgresql_ops={
|
||||||
|
"plural_abbreviation_normalized": "gin_trgm_ops",
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -95,10 +142,15 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
|||||||
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id])
|
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id])
|
||||||
|
|
||||||
name: Mapped[str | None] = mapped_column(String)
|
name: Mapped[str | None] = mapped_column(String)
|
||||||
|
plural_name: Mapped[str | None] = mapped_column(String)
|
||||||
description: Mapped[str | None] = mapped_column(String)
|
description: Mapped[str | None] = mapped_column(String)
|
||||||
|
|
||||||
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
|
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
|
||||||
"RecipeIngredientModel", back_populates="food"
|
"RecipeIngredientModel", back_populates="food"
|
||||||
)
|
)
|
||||||
|
aliases: Mapped[list["IngredientFoodAliasModel"]] = orm.relationship(
|
||||||
|
"IngredientFoodAliasModel", back_populates="food", cascade="all, delete, delete-orphan"
|
||||||
|
)
|
||||||
extras: Mapped[list[IngredientFoodExtras]] = orm.relationship("IngredientFoodExtras", cascade="all, delete-orphan")
|
extras: Mapped[list[IngredientFoodExtras]] = orm.relationship("IngredientFoodExtras", cascade="all, delete-orphan")
|
||||||
|
|
||||||
label_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), index=True)
|
label_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), index=True)
|
||||||
@ -106,12 +158,15 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
|||||||
|
|
||||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||||
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
|
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
|
|
||||||
@api_extras
|
@api_extras
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, session: Session, name: str | None = None, **_) -> None:
|
def __init__(self, session: Session, name: str | None = None, plural_name: str | None = None, **_) -> None:
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self.name_normalized = self.normalize(name)
|
self.name_normalized = self.normalize(name)
|
||||||
|
if plural_name is not None:
|
||||||
|
self.plural_name_normalized = self.normalize(plural_name)
|
||||||
|
|
||||||
tableargs = [
|
tableargs = [
|
||||||
sa.UniqueConstraint("name", "group_id", name="ingredient_foods_name_group_id_key"),
|
sa.UniqueConstraint("name", "group_id", name="ingredient_foods_name_group_id_key"),
|
||||||
@ -120,6 +175,11 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
|||||||
"name_normalized",
|
"name_normalized",
|
||||||
unique=False,
|
unique=False,
|
||||||
),
|
),
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_foods_plural_name_normalized",
|
||||||
|
"plural_name_normalized",
|
||||||
|
unique=False,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
if session.get_bind().name == "postgresql":
|
if session.get_bind().name == "postgresql":
|
||||||
@ -133,13 +193,104 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
|||||||
postgresql_ops={
|
postgresql_ops={
|
||||||
"name_normalized": "gin_trgm_ops",
|
"name_normalized": "gin_trgm_ops",
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_foods_plural_name_normalized_gin",
|
||||||
|
"plural_name_normalized",
|
||||||
|
unique=False,
|
||||||
|
postgresql_using="gin",
|
||||||
|
postgresql_ops={
|
||||||
|
"plural_name_normalized": "gin_trgm_ops",
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.__table_args__ = tuple(tableargs)
|
self.__table_args__ = tuple(tableargs)
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientUnitAliasModel(SqlAlchemyBase, BaseMixins):
|
||||||
|
__tablename__ = "ingredient_units_aliases"
|
||||||
|
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
|
||||||
|
|
||||||
|
unit_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("ingredient_units.id"), primary_key=True)
|
||||||
|
unit: Mapped["IngredientUnitModel"] = orm.relationship("IngredientUnitModel", back_populates="aliases")
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String)
|
||||||
|
|
||||||
|
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||||
|
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
|
|
||||||
|
@auto_init()
|
||||||
|
def __init__(self, session: Session, name: str, **_) -> None:
|
||||||
|
self.name_normalized = self.normalize(name)
|
||||||
|
tableargs = [
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_units_aliases_name_normalized",
|
||||||
|
"name_normalized",
|
||||||
|
unique=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if session.get_bind().name == "postgresql":
|
||||||
|
tableargs.extend(
|
||||||
|
[
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_units_aliases_name_normalized_gin",
|
||||||
|
"name_normalized",
|
||||||
|
unique=False,
|
||||||
|
postgresql_using="gin",
|
||||||
|
postgresql_ops={
|
||||||
|
"name_normalized": "gin_trgm_ops",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.__table_args__ = tableargs
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientFoodAliasModel(SqlAlchemyBase, BaseMixins):
|
||||||
|
__tablename__ = "ingredient_foods_aliases"
|
||||||
|
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
|
||||||
|
|
||||||
|
food_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("ingredient_foods.id"), primary_key=True)
|
||||||
|
food: Mapped["IngredientFoodModel"] = orm.relationship("IngredientFoodModel", back_populates="aliases")
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String)
|
||||||
|
|
||||||
|
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||||
|
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
|
|
||||||
|
@auto_init()
|
||||||
|
def __init__(self, session: Session, name: str, **_) -> None:
|
||||||
|
self.name_normalized = self.normalize(name)
|
||||||
|
tableargs = [
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_foods_aliases_name_normalized",
|
||||||
|
"name_normalized",
|
||||||
|
unique=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if session.get_bind().name == "postgresql":
|
||||||
|
tableargs.extend(
|
||||||
|
[
|
||||||
|
sa.Index(
|
||||||
|
"ix_ingredient_foods_aliases_name_normalized_gin",
|
||||||
|
"name_normalized",
|
||||||
|
unique=False,
|
||||||
|
postgresql_using="gin",
|
||||||
|
postgresql_ops={
|
||||||
|
"name_normalized": "gin_trgm_ops",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.__table_args__ = tableargs
|
||||||
|
|
||||||
|
|
||||||
class RecipeIngredientModel(SqlAlchemyBase, BaseMixins):
|
class RecipeIngredientModel(SqlAlchemyBase, BaseMixins):
|
||||||
__tablename__ = "recipes_ingredients"
|
__tablename__ = "recipes_ingredients"
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
@ -221,6 +372,14 @@ def receive_unit_name(target: IngredientUnitModel, value: str | None, oldvalue,
|
|||||||
target.name_normalized = None
|
target.name_normalized = None
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(IngredientUnitModel.plural_name, "set")
|
||||||
|
def receive_plural_unit_name(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
||||||
|
if value is not None:
|
||||||
|
target.plural_name_normalized = IngredientUnitModel.normalize(value)
|
||||||
|
else:
|
||||||
|
target.plural_name_normalized = None
|
||||||
|
|
||||||
|
|
||||||
@event.listens_for(IngredientUnitModel.abbreviation, "set")
|
@event.listens_for(IngredientUnitModel.abbreviation, "set")
|
||||||
def receive_unit_abbreviation(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
def receive_unit_abbreviation(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
@ -229,6 +388,14 @@ def receive_unit_abbreviation(target: IngredientUnitModel, value: str | None, ol
|
|||||||
target.abbreviation_normalized = None
|
target.abbreviation_normalized = None
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(IngredientUnitModel.plural_abbreviation, "set")
|
||||||
|
def receive_unit_plural_abbreviation(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
||||||
|
if value is not None:
|
||||||
|
target.plural_abbreviation_normalized = IngredientUnitModel.normalize(value)
|
||||||
|
else:
|
||||||
|
target.plural_abbreviation_normalized = None
|
||||||
|
|
||||||
|
|
||||||
@event.listens_for(IngredientFoodModel.name, "set")
|
@event.listens_for(IngredientFoodModel.name, "set")
|
||||||
def receive_food_name(target: IngredientFoodModel, value: str | None, oldvalue, initiator):
|
def receive_food_name(target: IngredientFoodModel, value: str | None, oldvalue, initiator):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
@ -237,6 +404,24 @@ def receive_food_name(target: IngredientFoodModel, value: str | None, oldvalue,
|
|||||||
target.name_normalized = None
|
target.name_normalized = None
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(IngredientFoodModel.plural_name, "set")
|
||||||
|
def receive_food_plural_name(target: IngredientFoodModel, value: str | None, oldvalue, initiator):
|
||||||
|
if value is not None:
|
||||||
|
target.plural_name_normalized = IngredientFoodModel.normalize(value)
|
||||||
|
else:
|
||||||
|
target.plural_name_normalized = None
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(IngredientUnitAliasModel.name, "set")
|
||||||
|
def receive_unit_alias_name(target: IngredientUnitAliasModel, value: str, oldvalue, initiator):
|
||||||
|
target.name_normalized = IngredientUnitAliasModel.normalize(value)
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(IngredientFoodAliasModel.name, "set")
|
||||||
|
def receive_food_alias_name(target: IngredientFoodAliasModel, value: str, oldvalue, initiator):
|
||||||
|
target.name_normalized = IngredientFoodAliasModel.normalize(value)
|
||||||
|
|
||||||
|
|
||||||
@event.listens_for(RecipeIngredientModel.note, "set")
|
@event.listens_for(RecipeIngredientModel.note, "set")
|
||||||
def receive_ingredient_note(target: RecipeIngredientModel, value: str | None, oldvalue, initiator):
|
def receive_ingredient_note(target: RecipeIngredientModel, value: str | None, oldvalue, initiator):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
"castor-sugar": "castor sugar",
|
"castor-sugar": "castor sugar",
|
||||||
"cayenne-pepper": "cayenne pepper",
|
"cayenne-pepper": "cayenne pepper",
|
||||||
"celeriac": "celeriac",
|
"celeriac": "celeriac",
|
||||||
"celery": "celery",
|
"celery": "セロリ",
|
||||||
"cereal-grains": "cereal grains",
|
"cereal-grains": "穀物",
|
||||||
"rice": "米",
|
"rice": "米",
|
||||||
"chard": "chard",
|
"chard": "chard",
|
||||||
"cheese": "チーズ",
|
"cheese": "チーズ",
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"coffee": "コーヒー",
|
"coffee": "コーヒー",
|
||||||
"confectioners-sugar": "粉糖",
|
"confectioners-sugar": "粉糖",
|
||||||
"coriander": "coriander",
|
"coriander": "coriander",
|
||||||
"corn": "corn",
|
"corn": "トウモロコシ",
|
||||||
"corn-syrup": "corn syrup",
|
"corn-syrup": "corn syrup",
|
||||||
"cottonseed-oil": "cottonseed oil",
|
"cottonseed-oil": "cottonseed oil",
|
||||||
"courgette": "courgette",
|
"courgette": "courgette",
|
||||||
@ -93,10 +93,10 @@
|
|||||||
"trout": "trout",
|
"trout": "trout",
|
||||||
"tuna": "tuna",
|
"tuna": "tuna",
|
||||||
"five-spice-powder": "five spice powder",
|
"five-spice-powder": "five spice powder",
|
||||||
"flour": "flour",
|
"flour": "小麦粉",
|
||||||
"frisee": "frisee",
|
"frisee": "frisee",
|
||||||
"fructose": "fructose",
|
"fructose": "fructose",
|
||||||
"fruit": "fruit",
|
"fruit": "果物",
|
||||||
"apple": "りんご",
|
"apple": "りんご",
|
||||||
"oranges": "オレンジ",
|
"oranges": "オレンジ",
|
||||||
"pear": "ナシ",
|
"pear": "ナシ",
|
||||||
@ -136,7 +136,7 @@
|
|||||||
"beans": "beans",
|
"beans": "beans",
|
||||||
"lentils": "lentils",
|
"lentils": "lentils",
|
||||||
"lemongrass": "lemongrass",
|
"lemongrass": "lemongrass",
|
||||||
"lettuce": "lettuce",
|
"lettuce": "レタス",
|
||||||
"liver": "liver",
|
"liver": "liver",
|
||||||
"maple-syrup": "maple syrup",
|
"maple-syrup": "maple syrup",
|
||||||
"meat": "meat",
|
"meat": "meat",
|
||||||
@ -150,7 +150,7 @@
|
|||||||
"nuts": "ナッツ",
|
"nuts": "ナッツ",
|
||||||
"nanaimo-bar-mix": "nanaimo bar mix",
|
"nanaimo-bar-mix": "nanaimo bar mix",
|
||||||
"octopuses": "タコ",
|
"octopuses": "タコ",
|
||||||
"oils": "oils",
|
"oils": "油",
|
||||||
"olive-oil": "オリーブ油",
|
"olive-oil": "オリーブ油",
|
||||||
"okra": "okra",
|
"okra": "okra",
|
||||||
"olive": "オリーブ",
|
"olive": "オリーブ",
|
||||||
@ -172,10 +172,10 @@
|
|||||||
"potatoes": "ジャガイモ",
|
"potatoes": "ジャガイモ",
|
||||||
"poultry": "鶏肉",
|
"poultry": "鶏肉",
|
||||||
"powdered-sugar": "粉糖",
|
"powdered-sugar": "粉糖",
|
||||||
"pumpkin": "pumpkin",
|
"pumpkin": "カボチャ",
|
||||||
"pumpkin-seeds": "pumpkin seeds",
|
"pumpkin-seeds": "pumpkin seeds",
|
||||||
"radish": "radish",
|
"radish": "radish",
|
||||||
"raw-sugar": "raw sugar",
|
"raw-sugar": "生の砂糖",
|
||||||
"refined-sugar": "refined sugar",
|
"refined-sugar": "refined sugar",
|
||||||
"rice-flour": "rice flour",
|
"rice-flour": "rice flour",
|
||||||
"rock-sugar": "rock sugar",
|
"rock-sugar": "rock sugar",
|
||||||
@ -185,7 +185,7 @@
|
|||||||
"seeds": "seeds",
|
"seeds": "seeds",
|
||||||
"sesame-seeds": "sesame seeds",
|
"sesame-seeds": "sesame seeds",
|
||||||
"sunflower-seeds": "sunflower seeds",
|
"sunflower-seeds": "sunflower seeds",
|
||||||
"soda": "soda",
|
"soda": "ソーダ",
|
||||||
"soda-baking": "soda, baking",
|
"soda-baking": "soda, baking",
|
||||||
"soybean": "soybean",
|
"soybean": "soybean",
|
||||||
"spaghetti-squash": "spaghetti squash",
|
"spaghetti-squash": "spaghetti squash",
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"name": "Печива"
|
"name": "Печива"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Продукти в консерва"
|
"name": "Canned Goods"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Допълнения"
|
"name": "Допълнения"
|
||||||
|
@ -47,13 +47,17 @@ from .recipe_comments import (
|
|||||||
from .recipe_image_types import RecipeImageTypes
|
from .recipe_image_types import RecipeImageTypes
|
||||||
from .recipe_ingredient import (
|
from .recipe_ingredient import (
|
||||||
CreateIngredientFood,
|
CreateIngredientFood,
|
||||||
|
CreateIngredientFoodAlias,
|
||||||
CreateIngredientUnit,
|
CreateIngredientUnit,
|
||||||
|
CreateIngredientUnitAlias,
|
||||||
IngredientConfidence,
|
IngredientConfidence,
|
||||||
IngredientFood,
|
IngredientFood,
|
||||||
|
IngredientFoodAlias,
|
||||||
IngredientFoodPagination,
|
IngredientFoodPagination,
|
||||||
IngredientRequest,
|
IngredientRequest,
|
||||||
IngredientsRequest,
|
IngredientsRequest,
|
||||||
IngredientUnit,
|
IngredientUnit,
|
||||||
|
IngredientUnitAlias,
|
||||||
IngredientUnitPagination,
|
IngredientUnitPagination,
|
||||||
MergeFood,
|
MergeFood,
|
||||||
MergeUnit,
|
MergeUnit,
|
||||||
@ -88,25 +92,6 @@ __all__ = [
|
|||||||
"RecipeToolOut",
|
"RecipeToolOut",
|
||||||
"RecipeToolResponse",
|
"RecipeToolResponse",
|
||||||
"RecipeToolSave",
|
"RecipeToolSave",
|
||||||
"RecipeTimelineEventCreate",
|
|
||||||
"RecipeTimelineEventIn",
|
|
||||||
"RecipeTimelineEventOut",
|
|
||||||
"RecipeTimelineEventPagination",
|
|
||||||
"RecipeTimelineEventUpdate",
|
|
||||||
"TimelineEventImage",
|
|
||||||
"TimelineEventType",
|
|
||||||
"RecipeAsset",
|
|
||||||
"RecipeSettings",
|
|
||||||
"RecipeShareToken",
|
|
||||||
"RecipeShareTokenCreate",
|
|
||||||
"RecipeShareTokenSave",
|
|
||||||
"RecipeShareTokenSummary",
|
|
||||||
"RecipeDuplicate",
|
|
||||||
"RecipeSlug",
|
|
||||||
"RecipeZipTokenResponse",
|
|
||||||
"SlugResponse",
|
|
||||||
"UpdateImageResponse",
|
|
||||||
"RecipeNote",
|
|
||||||
"CategoryBase",
|
"CategoryBase",
|
||||||
"CategoryIn",
|
"CategoryIn",
|
||||||
"CategoryOut",
|
"CategoryOut",
|
||||||
@ -117,12 +102,6 @@ __all__ = [
|
|||||||
"TagIn",
|
"TagIn",
|
||||||
"TagOut",
|
"TagOut",
|
||||||
"TagSave",
|
"TagSave",
|
||||||
"RecipeCommentCreate",
|
|
||||||
"RecipeCommentOut",
|
|
||||||
"RecipeCommentPagination",
|
|
||||||
"RecipeCommentSave",
|
|
||||||
"RecipeCommentUpdate",
|
|
||||||
"UserBase",
|
|
||||||
"AssignCategories",
|
"AssignCategories",
|
||||||
"AssignSettings",
|
"AssignSettings",
|
||||||
"AssignTags",
|
"AssignTags",
|
||||||
@ -130,28 +109,19 @@ __all__ = [
|
|||||||
"ExportBase",
|
"ExportBase",
|
||||||
"ExportRecipes",
|
"ExportRecipes",
|
||||||
"ExportTypes",
|
"ExportTypes",
|
||||||
"IngredientReferences",
|
"RecipeShareToken",
|
||||||
"RecipeStep",
|
"RecipeShareTokenCreate",
|
||||||
|
"RecipeShareTokenSave",
|
||||||
|
"RecipeShareTokenSummary",
|
||||||
|
"ScrapeRecipe",
|
||||||
|
"ScrapeRecipeTest",
|
||||||
|
"RecipeCommentCreate",
|
||||||
|
"RecipeCommentOut",
|
||||||
|
"RecipeCommentPagination",
|
||||||
|
"RecipeCommentSave",
|
||||||
|
"RecipeCommentUpdate",
|
||||||
|
"UserBase",
|
||||||
"RecipeImageTypes",
|
"RecipeImageTypes",
|
||||||
"Nutrition",
|
|
||||||
"CreateIngredientFood",
|
|
||||||
"CreateIngredientUnit",
|
|
||||||
"IngredientConfidence",
|
|
||||||
"IngredientFood",
|
|
||||||
"IngredientFoodPagination",
|
|
||||||
"IngredientRequest",
|
|
||||||
"IngredientUnit",
|
|
||||||
"IngredientUnitPagination",
|
|
||||||
"IngredientsRequest",
|
|
||||||
"MergeFood",
|
|
||||||
"MergeUnit",
|
|
||||||
"ParsedIngredient",
|
|
||||||
"RecipeIngredient",
|
|
||||||
"RecipeIngredientBase",
|
|
||||||
"RegisteredParser",
|
|
||||||
"SaveIngredientFood",
|
|
||||||
"SaveIngredientUnit",
|
|
||||||
"UnitFoodBase",
|
|
||||||
"CreateRecipe",
|
"CreateRecipe",
|
||||||
"CreateRecipeBulk",
|
"CreateRecipeBulk",
|
||||||
"CreateRecipeByUrlBulk",
|
"CreateRecipeByUrlBulk",
|
||||||
@ -165,6 +135,44 @@ __all__ = [
|
|||||||
"RecipeTagPagination",
|
"RecipeTagPagination",
|
||||||
"RecipeTool",
|
"RecipeTool",
|
||||||
"RecipeToolPagination",
|
"RecipeToolPagination",
|
||||||
"ScrapeRecipe",
|
"IngredientReferences",
|
||||||
"ScrapeRecipeTest",
|
"RecipeStep",
|
||||||
|
"CreateIngredientFood",
|
||||||
|
"CreateIngredientFoodAlias",
|
||||||
|
"CreateIngredientUnit",
|
||||||
|
"CreateIngredientUnitAlias",
|
||||||
|
"IngredientConfidence",
|
||||||
|
"IngredientFood",
|
||||||
|
"IngredientFoodAlias",
|
||||||
|
"IngredientFoodPagination",
|
||||||
|
"IngredientRequest",
|
||||||
|
"IngredientUnit",
|
||||||
|
"IngredientUnitAlias",
|
||||||
|
"IngredientUnitPagination",
|
||||||
|
"IngredientsRequest",
|
||||||
|
"MergeFood",
|
||||||
|
"MergeUnit",
|
||||||
|
"ParsedIngredient",
|
||||||
|
"RecipeIngredient",
|
||||||
|
"RecipeIngredientBase",
|
||||||
|
"RegisteredParser",
|
||||||
|
"SaveIngredientFood",
|
||||||
|
"SaveIngredientUnit",
|
||||||
|
"UnitFoodBase",
|
||||||
|
"RecipeAsset",
|
||||||
|
"RecipeTimelineEventCreate",
|
||||||
|
"RecipeTimelineEventIn",
|
||||||
|
"RecipeTimelineEventOut",
|
||||||
|
"RecipeTimelineEventPagination",
|
||||||
|
"RecipeTimelineEventUpdate",
|
||||||
|
"TimelineEventImage",
|
||||||
|
"TimelineEventType",
|
||||||
|
"RecipeDuplicate",
|
||||||
|
"RecipeSlug",
|
||||||
|
"RecipeZipTokenResponse",
|
||||||
|
"SlugResponse",
|
||||||
|
"UpdateImageResponse",
|
||||||
|
"Nutrition",
|
||||||
|
"RecipeSettings",
|
||||||
|
"RecipeNote",
|
||||||
]
|
]
|
||||||
|
@ -33,12 +33,23 @@ def display_fraction(fraction: Fraction):
|
|||||||
|
|
||||||
class UnitFoodBase(MealieModel):
|
class UnitFoodBase(MealieModel):
|
||||||
name: str
|
name: str
|
||||||
|
plural_name: str | None = None
|
||||||
description: str = ""
|
description: str = ""
|
||||||
extras: dict | None = {}
|
extras: dict | None = {}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateIngredientFoodAlias(MealieModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientFoodAlias(CreateIngredientFoodAlias):
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class CreateIngredientFood(UnitFoodBase):
|
class CreateIngredientFood(UnitFoodBase):
|
||||||
label_id: UUID4 | None = None
|
label_id: UUID4 | None = None
|
||||||
|
aliases: list[CreateIngredientFoodAlias] = []
|
||||||
|
|
||||||
|
|
||||||
class SaveIngredientFood(CreateIngredientFood):
|
class SaveIngredientFood(CreateIngredientFood):
|
||||||
@ -48,10 +59,12 @@ class SaveIngredientFood(CreateIngredientFood):
|
|||||||
class IngredientFood(CreateIngredientFood):
|
class IngredientFood(CreateIngredientFood):
|
||||||
id: UUID4
|
id: UUID4
|
||||||
label: MultiPurposeLabelSummary | None = None
|
label: MultiPurposeLabelSummary | None = None
|
||||||
|
aliases: list[IngredientFoodAlias] = []
|
||||||
|
|
||||||
created_at: datetime.datetime | None
|
created_at: datetime.datetime | None
|
||||||
update_at: datetime.datetime | None
|
update_at: datetime.datetime | None
|
||||||
|
|
||||||
_searchable_properties: ClassVar[list[str]] = ["name_normalized"]
|
_searchable_properties: ClassVar[list[str]] = ["name_normalized", "plural_name_normalized"]
|
||||||
_normalize_search: ClassVar[bool] = True
|
_normalize_search: ClassVar[bool] = True
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@ -67,10 +80,21 @@ class IngredientFoodPagination(PaginationBase):
|
|||||||
items: list[IngredientFood]
|
items: list[IngredientFood]
|
||||||
|
|
||||||
|
|
||||||
|
class CreateIngredientUnitAlias(MealieModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientUnitAlias(CreateIngredientUnitAlias):
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class CreateIngredientUnit(UnitFoodBase):
|
class CreateIngredientUnit(UnitFoodBase):
|
||||||
fraction: bool = True
|
fraction: bool = True
|
||||||
abbreviation: str = ""
|
abbreviation: str = ""
|
||||||
|
plural_abbreviation: str | None = ""
|
||||||
use_abbreviation: bool = False
|
use_abbreviation: bool = False
|
||||||
|
aliases: list[CreateIngredientUnitAlias] = []
|
||||||
|
|
||||||
|
|
||||||
class SaveIngredientUnit(CreateIngredientUnit):
|
class SaveIngredientUnit(CreateIngredientUnit):
|
||||||
@ -79,10 +103,17 @@ class SaveIngredientUnit(CreateIngredientUnit):
|
|||||||
|
|
||||||
class IngredientUnit(CreateIngredientUnit):
|
class IngredientUnit(CreateIngredientUnit):
|
||||||
id: UUID4
|
id: UUID4
|
||||||
|
aliases: list[IngredientUnitAlias] = []
|
||||||
|
|
||||||
created_at: datetime.datetime | None
|
created_at: datetime.datetime | None
|
||||||
update_at: datetime.datetime | None
|
update_at: datetime.datetime | None
|
||||||
|
|
||||||
_searchable_properties: ClassVar[list[str]] = ["name_normalized", "abbreviation_normalized"]
|
_searchable_properties: ClassVar[list[str]] = [
|
||||||
|
"name_normalized",
|
||||||
|
"plural_name_normalized",
|
||||||
|
"abbreviation_normalized",
|
||||||
|
"plural_abbreviation_normalized",
|
||||||
|
]
|
||||||
_normalize_search: ClassVar[bool] = True
|
_normalize_search: ClassVar[bool] = True
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@ -165,6 +196,36 @@ class RecipeIngredientBase(MealieModel):
|
|||||||
|
|
||||||
return f"{whole_number} {display_fraction(qty)}"
|
return f"{whole_number} {display_fraction(qty)}"
|
||||||
|
|
||||||
|
def _format_unit_for_display(self) -> str:
|
||||||
|
if not self.unit:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
use_plural = self.quantity and self.quantity > 1
|
||||||
|
unit_val = ""
|
||||||
|
if self.unit.use_abbreviation:
|
||||||
|
if use_plural:
|
||||||
|
unit_val = self.unit.plural_abbreviation or self.unit.abbreviation
|
||||||
|
else:
|
||||||
|
unit_val = self.unit.abbreviation
|
||||||
|
|
||||||
|
if not unit_val:
|
||||||
|
if use_plural:
|
||||||
|
unit_val = self.unit.plural_name or self.unit.name
|
||||||
|
else:
|
||||||
|
unit_val = self.unit.name
|
||||||
|
|
||||||
|
return unit_val
|
||||||
|
|
||||||
|
def _format_food_for_display(self) -> str:
|
||||||
|
if not self.food:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
use_plural = (not self.quantity) or self.quantity > 1
|
||||||
|
if use_plural:
|
||||||
|
return self.food.plural_name or self.food.name
|
||||||
|
else:
|
||||||
|
return self.food.name
|
||||||
|
|
||||||
def _format_display(self) -> str:
|
def _format_display(self) -> str:
|
||||||
components = []
|
components = []
|
||||||
|
|
||||||
@ -183,15 +244,15 @@ class RecipeIngredientBase(MealieModel):
|
|||||||
components.append(self.note or "")
|
components.append(self.note or "")
|
||||||
else:
|
else:
|
||||||
if self.quantity and self.unit:
|
if self.quantity and self.unit:
|
||||||
components.append(self.unit.abbreviation if self.unit.use_abbreviation else self.unit.name)
|
components.append(self._format_unit_for_display())
|
||||||
|
|
||||||
if self.food:
|
if self.food:
|
||||||
components.append(self.food.name)
|
components.append(self._format_food_for_display())
|
||||||
|
|
||||||
if self.note:
|
if self.note:
|
||||||
components.append(self.note)
|
components.append(self.note)
|
||||||
|
|
||||||
return " ".join(components)
|
return " ".join(components).strip()
|
||||||
|
|
||||||
|
|
||||||
class IngredientUnitPagination(PaginationBase):
|
class IngredientUnitPagination(PaginationBase):
|
||||||
|
@ -38,36 +38,60 @@ class ABCIngredientParser(ABC):
|
|||||||
self.group_id = group_id
|
self.group_id = group_id
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
self._foods_by_name: dict[str, IngredientFood] | None = None
|
self._foods_by_alias: dict[str, IngredientFood] | None = None
|
||||||
self._units_by_name: dict[str, IngredientUnit] | None = None
|
self._units_by_alias: dict[str, IngredientUnit] | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _repos(self) -> AllRepositories:
|
def _repos(self) -> AllRepositories:
|
||||||
return get_repositories(self.session)
|
return get_repositories(self.session)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def foods_by_normalized_name(self) -> dict[str, IngredientFood]:
|
def foods_by_alias(self) -> dict[str, IngredientFood]:
|
||||||
if self._foods_by_name is None:
|
if self._foods_by_alias is None:
|
||||||
foods_repo = self._repos.ingredient_foods.by_group(self.group_id)
|
foods_repo = self._repos.ingredient_foods.by_group(self.group_id)
|
||||||
|
|
||||||
query = PaginationQuery(page=1, per_page=-1)
|
query = PaginationQuery(page=1, per_page=-1)
|
||||||
all_foods = foods_repo.page_all(query).items
|
all_foods = foods_repo.page_all(query).items
|
||||||
self._foods_by_name = {IngredientFoodModel.normalize(food.name): food for food in all_foods if food.name}
|
|
||||||
|
|
||||||
return self._foods_by_name
|
foods_by_alias: dict[str, IngredientFood] = {}
|
||||||
|
for food in all_foods:
|
||||||
|
if food.name:
|
||||||
|
foods_by_alias[IngredientFoodModel.normalize(food.name)] = food
|
||||||
|
if food.plural_name:
|
||||||
|
foods_by_alias[IngredientFoodModel.normalize(food.plural_name)] = food
|
||||||
|
|
||||||
|
for alias in food.aliases or []:
|
||||||
|
if alias.name:
|
||||||
|
foods_by_alias[IngredientFoodModel.normalize(alias.name)] = food
|
||||||
|
|
||||||
|
self._foods_by_alias = foods_by_alias
|
||||||
|
|
||||||
|
return self._foods_by_alias
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def units_by_normalized_name_or_abbreviation(self) -> dict[str, IngredientUnit]:
|
def units_by_alias(self) -> dict[str, IngredientUnit]:
|
||||||
if self._units_by_name is None:
|
if self._units_by_alias is None:
|
||||||
units_repo = self._repos.ingredient_units.by_group(self.group_id)
|
units_repo = self._repos.ingredient_units.by_group(self.group_id)
|
||||||
|
|
||||||
query = PaginationQuery(page=1, per_page=-1)
|
query = PaginationQuery(page=1, per_page=-1)
|
||||||
all_units = units_repo.page_all(query).items
|
all_units = units_repo.page_all(query).items
|
||||||
self._units_by_name = {
|
|
||||||
IngredientUnitModel.normalize(unit.name): unit for unit in all_units if unit.name
|
|
||||||
} | {IngredientUnitModel.normalize(unit.abbreviation): unit for unit in all_units if unit.abbreviation}
|
|
||||||
|
|
||||||
return self._units_by_name
|
units_by_alias: dict[str, IngredientUnit] = {}
|
||||||
|
for unit in all_units:
|
||||||
|
if unit.name:
|
||||||
|
units_by_alias[IngredientUnitModel.normalize(unit.name)] = unit
|
||||||
|
if unit.plural_name:
|
||||||
|
units_by_alias[IngredientUnitModel.normalize(unit.plural_name)] = unit
|
||||||
|
if unit.abbreviation:
|
||||||
|
units_by_alias[IngredientUnitModel.normalize(unit.abbreviation)] = unit
|
||||||
|
if unit.plural_abbreviation:
|
||||||
|
units_by_alias[IngredientUnitModel.normalize(unit.plural_abbreviation)] = unit
|
||||||
|
|
||||||
|
for alias in unit.aliases or []:
|
||||||
|
if alias.name:
|
||||||
|
units_by_alias[IngredientUnitModel.normalize(alias.name)] = unit
|
||||||
|
|
||||||
|
self._units_by_alias = units_by_alias
|
||||||
|
|
||||||
|
return self._units_by_alias
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def food_fuzzy_match_threshold(self) -> int:
|
def food_fuzzy_match_threshold(self) -> int:
|
||||||
@ -111,7 +135,7 @@ class ABCIngredientParser(ABC):
|
|||||||
match_value = IngredientFoodModel.normalize(food.name)
|
match_value = IngredientFoodModel.normalize(food.name)
|
||||||
return self.find_match(
|
return self.find_match(
|
||||||
match_value,
|
match_value,
|
||||||
store_map=self.foods_by_normalized_name,
|
store_map=self.foods_by_alias,
|
||||||
fuzzy_match_threshold=self.food_fuzzy_match_threshold,
|
fuzzy_match_threshold=self.food_fuzzy_match_threshold,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,7 +146,7 @@ class ABCIngredientParser(ABC):
|
|||||||
match_value = IngredientUnitModel.normalize(unit.name)
|
match_value = IngredientUnitModel.normalize(unit.name)
|
||||||
return self.find_match(
|
return self.find_match(
|
||||||
match_value,
|
match_value,
|
||||||
store_map=self.units_by_normalized_name_or_abbreviation,
|
store_map=self.units_by_alias,
|
||||||
fuzzy_match_threshold=self.unit_fuzzy_match_threshold,
|
fuzzy_match_threshold=self.unit_fuzzy_match_threshold,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -0,0 +1,202 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit, RecipeIngredient
|
||||||
|
from tests.utils.factories import random_string
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
["quantity", "quantity_display_decimal", "quantity_display_fraction", "expect_plural_unit", "expect_plural_food"],
|
||||||
|
[
|
||||||
|
[0, "", "", False, True],
|
||||||
|
[0.5, "0.5", "¹/₂", False, False],
|
||||||
|
[1, "1", "1", False, False],
|
||||||
|
[1.5, "1.5", "1 ¹/₂", True, True],
|
||||||
|
[2, "2", "2", True, True],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
["unit", "expect_display_fraction", "expected_unit_singular_string", "expected_unit_plural_string"],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name=None,
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation=None,
|
||||||
|
use_abbreviation=False,
|
||||||
|
fraction=True,
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
"tablespoon",
|
||||||
|
"tablespoon",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name=None,
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation=None,
|
||||||
|
use_abbreviation=False,
|
||||||
|
fraction=False,
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
"tablespoon",
|
||||||
|
"tablespoon",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name=None,
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation=None,
|
||||||
|
use_abbreviation=True,
|
||||||
|
fraction=True,
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
"tbsp",
|
||||||
|
"tbsp",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name=None,
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation=None,
|
||||||
|
use_abbreviation=True,
|
||||||
|
fraction=False,
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
"tbsp",
|
||||||
|
"tbsp",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name="tablespoons",
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation="tbsps",
|
||||||
|
use_abbreviation=False,
|
||||||
|
fraction=True,
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
"tablespoon",
|
||||||
|
"tablespoons",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name="tablespoons",
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation="tbsps",
|
||||||
|
use_abbreviation=False,
|
||||||
|
fraction=False,
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
"tablespoon",
|
||||||
|
"tablespoons",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name="tablespoons",
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation="tbsps",
|
||||||
|
use_abbreviation=True,
|
||||||
|
fraction=True,
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
"tbsp",
|
||||||
|
"tbsps",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientUnit(
|
||||||
|
id=uuid4(),
|
||||||
|
name="tablespoon",
|
||||||
|
plural_name="tablespoons",
|
||||||
|
abbreviation="tbsp",
|
||||||
|
plural_abbreviation="tbsps",
|
||||||
|
use_abbreviation=True,
|
||||||
|
fraction=False,
|
||||||
|
),
|
||||||
|
False,
|
||||||
|
"tbsp",
|
||||||
|
"tbsps",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
["food", "expected_food_singular_string", "expected_food_plural_string"],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
IngredientFood(id=uuid4(), name="chopped onion", plural_name=None),
|
||||||
|
"chopped onion",
|
||||||
|
"chopped onion",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
IngredientFood(id=uuid4(), name="chopped onion", plural_name="chopped onions"),
|
||||||
|
"chopped onion",
|
||||||
|
"chopped onions",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("note", ["very thin", ""])
|
||||||
|
@pytest.mark.parametrize("use_food", [True, False])
|
||||||
|
def test_ingredient_display(
|
||||||
|
quantity: float | None,
|
||||||
|
quantity_display_decimal: str,
|
||||||
|
quantity_display_fraction: str,
|
||||||
|
unit: IngredientUnit,
|
||||||
|
food: IngredientFood,
|
||||||
|
note: str,
|
||||||
|
use_food: bool,
|
||||||
|
expect_display_fraction: bool,
|
||||||
|
expect_plural_unit: bool,
|
||||||
|
expect_plural_food: bool,
|
||||||
|
expected_unit_singular_string: str,
|
||||||
|
expected_unit_plural_string: str,
|
||||||
|
expected_food_singular_string: str,
|
||||||
|
expected_food_plural_string: str,
|
||||||
|
):
|
||||||
|
expected_components = []
|
||||||
|
if use_food:
|
||||||
|
if expect_display_fraction:
|
||||||
|
expected_components.append(quantity_display_fraction)
|
||||||
|
else:
|
||||||
|
expected_components.append(quantity_display_decimal)
|
||||||
|
|
||||||
|
if quantity:
|
||||||
|
if expect_plural_unit:
|
||||||
|
expected_components.append(expected_unit_plural_string)
|
||||||
|
else:
|
||||||
|
expected_components.append(expected_unit_singular_string)
|
||||||
|
|
||||||
|
if expect_plural_food:
|
||||||
|
expected_components.append(expected_food_plural_string)
|
||||||
|
else:
|
||||||
|
expected_components.append(expected_food_singular_string)
|
||||||
|
|
||||||
|
expected_components.append(note)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if quantity != 0 and quantity != 1:
|
||||||
|
if expect_display_fraction:
|
||||||
|
expected_components.append(quantity_display_fraction)
|
||||||
|
else:
|
||||||
|
expected_components.append(quantity_display_decimal)
|
||||||
|
|
||||||
|
expected_components.append(note)
|
||||||
|
|
||||||
|
expected_display_value = " ".join(c for c in expected_components if c)
|
||||||
|
ingredient = RecipeIngredient(
|
||||||
|
quantity=quantity, unit=unit, food=food, note=note, use_food=use_food, disable_amount=not use_food
|
||||||
|
)
|
||||||
|
assert ingredient.display == expected_display_value
|
@ -9,7 +9,9 @@ from mealie.db.db_setup import session_context
|
|||||||
from mealie.repos.repository_factory import AllRepositories
|
from mealie.repos.repository_factory import AllRepositories
|
||||||
from mealie.schema.recipe.recipe_ingredient import (
|
from mealie.schema.recipe.recipe_ingredient import (
|
||||||
CreateIngredientFood,
|
CreateIngredientFood,
|
||||||
|
CreateIngredientFoodAlias,
|
||||||
CreateIngredientUnit,
|
CreateIngredientUnit,
|
||||||
|
CreateIngredientUnitAlias,
|
||||||
IngredientFood,
|
IngredientFood,
|
||||||
IngredientUnit,
|
IngredientUnit,
|
||||||
ParsedIngredient,
|
ParsedIngredient,
|
||||||
@ -67,6 +69,12 @@ def parsed_ingredient_data(
|
|||||||
SaveIngredientFood(name="fresh ginger", group_id=unique_local_group_id),
|
SaveIngredientFood(name="fresh ginger", group_id=unique_local_group_id),
|
||||||
SaveIngredientFood(name="ground ginger", group_id=unique_local_group_id),
|
SaveIngredientFood(name="ground ginger", group_id=unique_local_group_id),
|
||||||
SaveIngredientFood(name="ñör̃m̈ãl̈ĩz̈ẽm̈ẽ", group_id=unique_local_group_id),
|
SaveIngredientFood(name="ñör̃m̈ãl̈ĩz̈ẽm̈ẽ", group_id=unique_local_group_id),
|
||||||
|
SaveIngredientFood(name="PluralFoodTest", plural_name="myfoodisplural", group_id=unique_local_group_id),
|
||||||
|
SaveIngredientFood(
|
||||||
|
name="IHaveAnAlias",
|
||||||
|
group_id=unique_local_group_id,
|
||||||
|
aliases=[CreateIngredientFoodAlias(name="thisismyalias")],
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -86,6 +94,18 @@ def parsed_ingredient_data(
|
|||||||
SaveIngredientUnit(name="Teaspoon", group_id=unique_local_group_id),
|
SaveIngredientUnit(name="Teaspoon", group_id=unique_local_group_id),
|
||||||
SaveIngredientUnit(name="Stalk", group_id=unique_local_group_id),
|
SaveIngredientUnit(name="Stalk", group_id=unique_local_group_id),
|
||||||
SaveIngredientUnit(name="My Very Long Unit Name", abbreviation="mvlun", group_id=unique_local_group_id),
|
SaveIngredientUnit(name="My Very Long Unit Name", abbreviation="mvlun", group_id=unique_local_group_id),
|
||||||
|
SaveIngredientUnit(
|
||||||
|
name="PluralUnitName",
|
||||||
|
plural_name="abc123",
|
||||||
|
abbreviation="doremiabc",
|
||||||
|
plural_abbreviation="doremi123",
|
||||||
|
group_id=unique_local_group_id,
|
||||||
|
),
|
||||||
|
SaveIngredientUnit(
|
||||||
|
name="IHaveAnAliasToo",
|
||||||
|
group_id=unique_local_group_id,
|
||||||
|
aliases=[CreateIngredientUnitAlias(name="thisismyalias")],
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -267,6 +287,46 @@ def test_brute_parser(unique_user: TestUser):
|
|||||||
True,
|
True,
|
||||||
id="normalization",
|
id="normalization",
|
||||||
),
|
),
|
||||||
|
pytest.param(
|
||||||
|
build_parsed_ing(unit=None, food="myfoodisplural"),
|
||||||
|
None,
|
||||||
|
"PluralFoodTest",
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
id="plural food name",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
build_parsed_ing(unit="abc123", food=None),
|
||||||
|
"PluralUnitName",
|
||||||
|
None,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
id="plural unit name",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
build_parsed_ing(unit="doremi123", food=None),
|
||||||
|
"PluralUnitName",
|
||||||
|
None,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
id="plural unit abbreviation",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
build_parsed_ing(unit=None, food="thisismyalias"),
|
||||||
|
None,
|
||||||
|
"IHaveAnAlias",
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
id="food alias",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
build_parsed_ing(unit="thisismyalias", food=None),
|
||||||
|
"IHaveAnAliasToo",
|
||||||
|
None,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
id="unit alias",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_parser_ingredient_match(
|
def test_parser_ingredient_match(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user