Merge pull request #1860 from Knalltuete5000/feature/simple_plural

Add optional plural name for unit and food
This commit is contained in:
vabene1111 2022-11-22 07:34:23 +01:00 committed by GitHub
commit f92ee32c01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 442 additions and 62 deletions

View File

@ -36,7 +36,7 @@ def delete_space_action(modeladmin, request, queryset):
class SpaceAdmin(admin.ModelAdmin): class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing') list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing', 'use_plural')
search_fields = ('name', 'created_by__username') search_fields = ('name', 'created_by__username')
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing') list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
date_hierarchy = 'created_at' date_hierarchy = 'created_at'

View File

@ -533,11 +533,13 @@ class SpacePreferenceForm(forms.ModelForm):
class Meta: class Meta:
model = Space model = Space
fields = ('food_inherit', 'reset_food_inherit', 'show_facet_count') fields = ('food_inherit', 'reset_food_inherit', 'show_facet_count', 'use_plural')
help_texts = { help_texts = {
'food_inherit': _('Fields on food that should be inherited by default.'), 'food_inherit': _('Fields on food that should be inherited by default.'),
'show_facet_count': _('Show recipe counts on search filters'), } 'show_facet_count': _('Show recipe counts on search filters'),
'use_plural': _('Use the plural form for units and food inside this space.'),
}
widgets = { widgets = {
'food_inherit': MultiSelectWidget 'food_inherit': MultiSelectWidget

View File

@ -22,10 +22,25 @@ class IngredientObject(object):
else: else:
self.amount = f"<scalable-number v-bind:number='{bleach.clean(str(ingredient.amount))}' v-bind:factor='ingredient_factor'></scalable-number>" self.amount = f"<scalable-number v-bind:number='{bleach.clean(str(ingredient.amount))}' v-bind:factor='ingredient_factor'></scalable-number>"
if ingredient.unit: if ingredient.unit:
self.unit = bleach.clean(str(ingredient.unit)) if ingredient.unit.plural_name in (None, ""):
self.unit = bleach.clean(str(ingredient.unit))
else:
if ingredient.always_use_plural_unit or ingredient.amount > 1 and not ingredient.no_amount:
self.unit = bleach.clean(ingredient.unit.plural_name)
else:
self.unit = bleach.clean(str(ingredient.unit))
else: else:
self.unit = "" self.unit = ""
self.food = bleach.clean(str(ingredient.food)) if ingredient.food:
if ingredient.food.plural_name in (None, ""):
self.food = bleach.clean(str(ingredient.food))
else:
if ingredient.always_use_plural_food or ingredient.amount > 1 and not ingredient.no_amount:
self.food = bleach.clean(str(ingredient.food.plural_name))
else:
self.food = bleach.clean(str(ingredient.food))
else:
self.food = ""
self.note = bleach.clean(str(ingredient.note)) self.note = bleach.clean(str(ingredient.note))
def __str__(self): def __str__(self):

View File

@ -260,6 +260,7 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
max_recipes = models.IntegerField(default=0) max_recipes = models.IntegerField(default=0)
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')) max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
max_users = models.IntegerField(default=0) max_users = models.IntegerField(default=0)
use_plural = models.BooleanField(default=False)
allow_sharing = models.BooleanField(default=True) allow_sharing = models.BooleanField(default=True)
demo = models.BooleanField(default=False) demo = models.BooleanField(default=False)
food_inherit = models.ManyToManyField(FoodInheritField, blank=True) food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
@ -530,6 +531,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), TreeModel, PermissionModelM
class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin): class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
plural_name = models.CharField(max_length=128, null=True, blank=True, default=None)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
@ -554,6 +556,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
if SORT_TREE_BY_NAME: if SORT_TREE_BY_NAME:
node_order_by = ['name'] node_order_by = ['name']
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
plural_name = models.CharField(max_length=128, null=True, blank=True, default=None)
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL) recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field
ignore_shopping = models.BooleanField(default=False) # inherited field ignore_shopping = models.BooleanField(default=False) # inherited field
@ -654,6 +657,8 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
note = models.CharField(max_length=256, null=True, blank=True) note = models.CharField(max_length=256, null=True, blank=True)
is_header = models.BooleanField(default=False) is_header = models.BooleanField(default=False)
no_amount = models.BooleanField(default=False) no_amount = models.BooleanField(default=False)
always_use_plural_unit = models.BooleanField(default=False)
always_use_plural_food = models.BooleanField(default=False)
order = models.IntegerField(default=0) order = models.IntegerField(default=0)
original_text = models.CharField(max_length=512, null=True, blank=True, default=None) original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
@ -663,7 +668,23 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
def __str__(self): def __str__(self):
return str(self.amount) + ' ' + str(self.unit) + ' ' + str(self.food) food = ""
unit = ""
if self.always_use_plural_food and self.food.plural_name not in (None, "") and not self.no_amount:
food = self.food.plural_name
else:
if self.amount > 1 and self.food.plural_name not in (None, "") and not self.no_amount:
food = self.food.plural_name
else:
food = str(self.food)
if self.always_use_plural_unit and self.unit.plural_name not in (None, "") and not self.no_amount:
unit = self.unit.plural_name
else:
if self.amount > 1 and self.unit.plural_name not in (None, "") and not self.no_amount:
unit = self.unit.plural_name
else:
unit = str(self.unit)
return str(self.amount) + ' ' + str(unit) + ' ' + str(food)
class Meta: class Meta:
ordering = ['order', 'pk'] ordering = ['order', 'pk']

View File

@ -277,7 +277,8 @@ class SpaceSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = Space model = Space
fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb', 'image',) 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',
'image', 'use_plural',)
read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',)
@ -431,17 +432,22 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
def create(self, validated_data): def create(self, validated_data):
name = validated_data.pop('name').strip() name = validated_data.pop('name').strip()
plural_name = validated_data.pop('plural_name', None)
if plural_name:
plural_name = plural_name.strip()
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
obj, created = Unit.objects.get_or_create(name=name, space=space, defaults=validated_data) obj, created = Unit.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data)
return obj return obj
def update(self, instance, validated_data): def update(self, instance, validated_data):
validated_data['name'] = validated_data['name'].strip() validated_data['name'] = validated_data['name'].strip()
if plural_name := validated_data.get('plural_name', None):
validated_data['plural_name'] = plural_name.strip()
return super(UnitSerializer, self).update(instance, validated_data) return super(UnitSerializer, self).update(instance, validated_data)
class Meta: class Meta:
model = Unit model = Unit
fields = ('id', 'name', 'description', 'numrecipe', 'image') fields = ('id', 'name', 'plural_name', 'description', 'numrecipe', 'image')
read_only_fields = ('id', 'numrecipe', 'image') read_only_fields = ('id', 'numrecipe', 'image')
@ -499,7 +505,7 @@ class RecipeSimpleSerializer(WritableNestedModelSerializer):
class FoodSimpleSerializer(serializers.ModelSerializer): class FoodSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Food model = Food
fields = ('id', 'name') fields = ('id', 'name', 'plural_name')
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin): class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin):
@ -538,6 +544,9 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
def create(self, validated_data): def create(self, validated_data):
name = validated_data.pop('name').strip() name = validated_data.pop('name').strip()
plural_name = validated_data.pop('plural_name', None)
if plural_name:
plural_name = plural_name.strip()
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer # supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
if 'supermarket_category' in validated_data and validated_data['supermarket_category']: if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
@ -562,12 +571,14 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
else: else:
validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users)) validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users))
obj, created = Food.objects.get_or_create(name=name, space=space, defaults=validated_data) obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data)
return obj return obj
def update(self, instance, validated_data): def update(self, instance, validated_data):
if name := validated_data.get('name', None): if name := validated_data.get('name', None):
validated_data['name'] = name.strip() validated_data['name'] = name.strip()
if plural_name := validated_data.get('plural_name', None):
validated_data['plural_name'] = plural_name.strip()
# assuming if on hand for user also onhand for shopping_share users # assuming if on hand for user also onhand for shopping_share users
onhand = validated_data.get('food_onhand', None) onhand = validated_data.get('food_onhand', None)
reset_inherit = self.initial_data.get('reset_inherit', False) reset_inherit = self.initial_data.get('reset_inherit', False)
@ -587,7 +598,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
class Meta: class Meta:
model = Food model = Food
fields = ( fields = (
'id', 'name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category', 'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category',
'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping', 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields' 'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields'
) )
@ -616,6 +627,7 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
fields = ( fields = (
'id', 'food', 'unit', 'amount', 'note', 'order', 'id', 'food', 'unit', 'amount', 'note', 'order',
'is_header', 'no_amount', 'original_text', 'used_in_recipes', 'is_header', 'no_amount', 'original_text', 'used_in_recipes',
'always_use_plural_unit', 'always_use_plural_food',
) )
@ -1162,7 +1174,7 @@ class SupermarketCategoryExportSerializer(SupermarketCategorySerializer):
class UnitExportSerializer(UnitSerializer): class UnitExportSerializer(UnitSerializer):
class Meta: class Meta:
model = Unit model = Unit
fields = ('name', 'description') fields = ('name', 'plural_name', 'description')
class FoodExportSerializer(FoodSerializer): class FoodExportSerializer(FoodSerializer):
@ -1170,7 +1182,7 @@ class FoodExportSerializer(FoodSerializer):
class Meta: class Meta:
model = Food model = Food
fields = ('name', 'ignore_shopping', 'supermarket_category',) fields = ('name', 'plural_name', 'ignore_shopping', 'supermarket_category',)
class IngredientExportSerializer(WritableNestedModelSerializer): class IngredientExportSerializer(WritableNestedModelSerializer):
@ -1184,7 +1196,7 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = Ingredient model = Ingredient
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount') fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount', 'always_use_plural_unit', 'always_use_plural_food')
class StepExportSerializer(WritableNestedModelSerializer): class StepExportSerializer(WritableNestedModelSerializer):

File diff suppressed because one or more lines are too long

View File

@ -48,6 +48,9 @@
<script type="text/javascript"> <script type="text/javascript">
$.fn.select2.defaults.set("theme", "bootstrap"); $.fn.select2.defaults.set("theme", "bootstrap");
{% if request.user.is_authenticated %}
window.ACTIVE_SPACE_ID = '{{request.space.id}}';
{% endif %}
</script> </script>
<!-- Fontawesome icons --> <!-- Fontawesome icons -->
@ -409,6 +412,7 @@
localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}") localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}")
localStorage.setItem('DEBUG', "{% is_debug %}") localStorage.setItem('DEBUG', "{% is_debug %}")
localStorage.setItem('USER_ID', "{{request.user.pk}}") localStorage.setItem('USER_ID', "{{request.user.pk}}")
window.addEventListener("load", () => { window.addEventListener("load", () => {
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: "{% base_path request 'base' %}" + '/'}).then(function (reg) { navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: "{% base_path request 'base' %}" + '/'}).then(function (reg) {

View File

@ -98,6 +98,7 @@ class SupermarketCategoryFactory(factory.django.DjangoModelFactory):
class FoodFactory(factory.django.DjangoModelFactory): class FoodFactory(factory.django.DjangoModelFactory):
"""Food factory.""" """Food factory."""
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3, variable_nb_words=False)) name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3, variable_nb_words=False))
plural_name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3, variable_nb_words=False))
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10)) description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
supermarket_category = factory.Maybe( supermarket_category = factory.Maybe(
factory.LazyAttribute(lambda x: x.has_category), factory.LazyAttribute(lambda x: x.has_category),
@ -126,7 +127,7 @@ class FoodFactory(factory.django.DjangoModelFactory):
class Meta: class Meta:
model = 'cookbook.Food' model = 'cookbook.Food'
django_get_or_create = ('name', 'space',) django_get_or_create = ('name', 'plural_name', 'space',)
@register @register
@ -160,12 +161,13 @@ class RecipeBookEntryFactory(factory.django.DjangoModelFactory):
class UnitFactory(factory.django.DjangoModelFactory): class UnitFactory(factory.django.DjangoModelFactory):
"""Unit factory.""" """Unit factory."""
name = factory.LazyAttribute(lambda x: faker.word()) name = factory.LazyAttribute(lambda x: faker.word())
plural_name = factory.LazyAttribute(lambda x: faker.word())
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10)) description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
space = factory.SubFactory(SpaceFactory) space = factory.SubFactory(SpaceFactory)
class Meta: class Meta:
model = 'cookbook.Unit' model = 'cookbook.Unit'
django_get_or_create = ('name', 'space',) django_get_or_create = ('name', 'plural_name', 'space',)
@register @register

27
docs/system/settings.md Normal file
View File

@ -0,0 +1,27 @@
Following is a description of the different settings for a space
!!! WARNING WIP
Some settings and especially this page is work in Progress and the settings may
behave differently the described here.
## Use Plural form
Default Value: `off`
This setting enables tandoor to display a plural form of a food or unit, if the
plural version is entered for the food or unit. The plural version is displayed if the
amount needed for a recipe is greater than 1 and will be adjusted to the current amount.
In addition, this setting enables two new settings for an ingredient:
- Always show the plural version of the food: This will always display the plural version for
a food, even if the amount is below or equal to 1. Requirement for this setting to activate
is a plural version available in the database.
- Always show the plural version of the unit: This will always display the plural version for
a unit, even if the amount is below or equal to 1. Requirement for this setting to activate
is a plural version available in the database.
!!! WARNING Note
This setting is only meant to be a very simple version to enable some kind of pluralization
for food and units. This feature may not meet your needs, but pluralization is a difficult
topic and was discussed [here](https://github.com/TandoorRecipes/recipes/pull/1860).

View File

@ -204,7 +204,7 @@
v-if="!import_multiple"> v-if="!import_multiple">
<recipe-card :recipe="recipe_json" :detailed="false" <recipe-card :recipe="recipe_json" :detailed="false"
:show_context_menu="false" :show_context_menu="false" :use_plural="use_plural"
></recipe-card> ></recipe-card>
</b-col> </b-col>
<b-col> <b-col>
@ -461,6 +461,7 @@ export default {
recent_urls: [], recent_urls: [],
source_data: '', source_data: '',
recipe_json: undefined, recipe_json: undefined,
use_plural: false,
// recipe_html: undefined, // recipe_html: undefined,
// recipe_tree: undefined, // recipe_tree: undefined,
recipe_images: [], recipe_images: [],
@ -490,6 +491,10 @@ export default {
this.INTEGRATIONS.forEach((int) => { this.INTEGRATIONS.forEach((int) => {
int.icon = this.getRandomFoodIcon() int.icon = this.getRandomFoodIcon()
}) })
let apiClient = new ApiApiFactory()
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.use_plural = r.data.use_plural
})
}, },
methods: { methods: {
/** /**

View File

@ -42,6 +42,7 @@
<!-- model isn't paginated and loads in one API call --> <!-- model isn't paginated and loads in one API call -->
<div v-if="!paginated"> <div v-if="!paginated">
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i" <generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i"
:use_plural="use_plural"
:model="this_model" @item-action="startAction($event, 'left')" :model="this_model" @item-action="startAction($event, 'left')"
@finish-action="finishAction"/> @finish-action="finishAction"/>
</div> </div>
@ -51,6 +52,7 @@
<template v-slot:cards> <template v-slot:cards>
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i" <generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i"
:model="this_model" :model="this_model"
:use_plural="use_plural"
@item-action="startAction($event, 'left')" @item-action="startAction($event, 'left')"
@finish-action="finishAction"/> @finish-action="finishAction"/>
</template> </template>
@ -62,6 +64,7 @@
<template v-slot:cards> <template v-slot:cards>
<generic-horizontal-card v-for="i in items_right" v-bind:key="i.id" :item="i" <generic-horizontal-card v-for="i in items_right" v-bind:key="i.id" :item="i"
:model="this_model" :model="this_model"
:use_plural="use_plural"
@item-action="startAction($event, 'right')" @item-action="startAction($event, 'right')"
@finish-action="finishAction"/> @finish-action="finishAction"/>
</template> </template>
@ -120,6 +123,7 @@ export default {
show_split: false, show_split: false,
paginated: false, paginated: false,
header_component_name: undefined, header_component_name: undefined,
use_plural: false,
} }
}, },
computed: { computed: {
@ -145,6 +149,17 @@ export default {
} }
}) })
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory()
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.use_plural = r.data.use_plural
if (!this.use_plural && this.this_model !== null && this.this_model.create.params[0] !== null && this.this_model.create.params[0].includes('plural_name')) {
let index = this.this_model.create.params[0].indexOf('plural_name')
if (index > -1){
this.this_model.create.params[0].splice(index, 1)
}
delete this.this_model.create.form.plural_name
}
})
}, },
methods: { methods: {
// this.genericAPI inherited from ApiMixin // this.genericAPI inherited from ApiMixin

View File

@ -571,6 +571,37 @@
<i class="fas fa-balance-scale-right fa-fw"></i> <i class="fas fa-balance-scale-right fa-fw"></i>
{{ $t("Enable_Amount") }} {{ $t("Enable_Amount") }}
</button> </button>
<template v-if="use_plural">
<button type="button" class="dropdown-item"
v-if="!ingredient.always_use_plural_unit"
@click="ingredient.always_use_plural_unit = true">
<i class="fas fa-filter fa-fw"></i>
{{ $t("Use_Plural_Unit_Always") }}
</button>
<button type="button" class="dropdown-item"
v-if="ingredient.always_use_plural_unit"
@click="ingredient.always_use_plural_unit = false">
<i class="fas fa-filter fa-fw"></i>
{{ $t("Use_Plural_Unit_Simple") }}
</button>
<button type="button" class="dropdown-item"
v-if="!ingredient.always_use_plural_food"
@click="ingredient.always_use_plural_food = true">
<i class="fas fa-filter fa-fw"></i>
{{ $t("Use_Plural_Food_Always") }}
</button>
<button type="button" class="dropdown-item"
v-if="ingredient.always_use_plural_food"
@click="ingredient.always_use_plural_food = false">
<i class="fas fa-filter fa-fw"></i>
{{ $t("Use_Plural_Food_Simple") }}
</button>
</template>
<button type="button" class="dropdown-item" <button type="button" class="dropdown-item"
@click="copyTemplateReference(index, ingredient)"> @click="copyTemplateReference(index, ingredient)">
<i class="fas fa-code"></i> <i class="fas fa-code"></i>
@ -793,6 +824,7 @@ export default {
paste_step: undefined, paste_step: undefined,
show_file_create: false, show_file_create: false,
step_for_file_create: undefined, step_for_file_create: undefined,
use_plural: false,
additional_visible: false, additional_visible: false,
create_food: undefined, create_food: undefined,
md_editor_toolbars: { md_editor_toolbars: {
@ -840,6 +872,10 @@ export default {
this.searchRecipes("") this.searchRecipes("")
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory()
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.use_plural = r.data.use_plural
})
}, },
created() { created() {
window.addEventListener("keydown", this.keyboardListener) window.addEventListener("keydown", this.keyboardListener)
@ -1037,6 +1073,8 @@ export default {
order: 0, order: 0,
is_header: false, is_header: false,
no_amount: false, no_amount: false,
always_use_plural_unit: false,
always_use_plural_food: false,
original_text: null, original_text: null,
}) })
this.sortIngredients(step) this.sortIngredients(step)

View File

@ -839,13 +839,16 @@
v-for="m in meal_plans" v-for="m in meal_plans"
:recipe="m.recipe" :recipe="m.recipe"
:meal_plan="m" :meal_plan="m"
:use_plural="use_plural"
:footer_text="m.meal_type_name" :footer_text="m.meal_type_name"
footer_icon="far fa-calendar-alt" footer_icon="far fa-calendar-alt"
></recipe-card> ></recipe-card>
</template> </template>
<recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r" <recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r"
:footer_text="isRecentOrNew(r)[0]" :footer_text="isRecentOrNew(r)[0]"
:footer_icon="isRecentOrNew(r)[1]"></recipe-card> :footer_icon="isRecentOrNew(r)[1]"
:use_plural="use_plural">
</recipe-card>
</div> </div>
</div> </div>
</div> </div>
@ -913,6 +916,7 @@ import LoadingSpinner from "@/components/LoadingSpinner" // TODO: is this deprec
import RecipeCard from "@/components/RecipeCard" import RecipeCard from "@/components/RecipeCard"
import GenericMultiselect from "@/components/GenericMultiselect" import GenericMultiselect from "@/components/GenericMultiselect"
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher" import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
import { ApiApiFactory } from "@/utils/openapi/api"
Vue.use(VueCookies) Vue.use(VueCookies)
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
@ -933,7 +937,7 @@ export default {
meal_plans: [], meal_plans: [],
last_viewed_recipes: [], last_viewed_recipes: [],
sortMenu: false, sortMenu: false,
use_plural: false,
search: { search: {
advanced_search_visible: false, advanced_search_visible: false,
explain_visible: false, explain_visible: false,
@ -1160,6 +1164,10 @@ export default {
this.loadMealPlan() this.loadMealPlan()
this.refreshData(false) this.refreshData(false)
}) })
let apiClient = new ApiApiFactory()
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.use_plural = r.data.use_plural
})
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
this.debug = localStorage.getItem("DEBUG") == "True" || false this.debug = localStorage.getItem("DEBUG") == "True" || false
}, },

View File

@ -151,6 +151,9 @@
<b-form-checkbox v-model="space.show_facet_count"> Facet Count</b-form-checkbox> <b-form-checkbox v-model="space.show_facet_count"> Facet Count</b-form-checkbox>
<span class="text-muted small">{{ $t('facet_count_info') }}</span><br/> <span class="text-muted small">{{ $t('facet_count_info') }}</span><br/>
<b-form-checkbox v-model="space.use_plural">Use Plural form</b-form-checkbox>
<span class="text-muted small">{{ $t('plural_usage_info') }}</span><br/>
<label>{{ $t('FoodInherit') }}</label> <label>{{ $t('FoodInherit') }}</label>
<generic-multiselect :initial_selection="space.food_inherit" <generic-multiselect :initial_selection="space.food_inherit"
:model="Models.FOOD_INHERIT_FIELDS" :model="Models.FOOD_INHERIT_FIELDS"
@ -204,7 +207,7 @@ Vue.use(VueClipboard)
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
export default { export default {
name: "SupermarketView", name: "SpaceManageView",
mixins: [ResolveUrlMixin, ToastMixin, ApiMixin], mixins: [ResolveUrlMixin, ToastMixin, ApiMixin],
components: {GenericMultiselect, GenericModalForm}, components: {GenericMultiselect, GenericModalForm},
data() { data() {
@ -225,7 +228,7 @@ export default {
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
let apiFactory = new ApiApiFactory() let apiFactory = new ApiApiFactory()
apiFactory.retrieveSpace(this.ACTIVE_SPACE_ID).then(r => { apiFactory.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.space = r.data this.space = r.data
}) })
apiFactory.listUserSpaces().then(r => { apiFactory.listUserSpaces().then(r => {
@ -249,7 +252,7 @@ export default {
}, },
updateSpace: function () { updateSpace: function () {
let apiFactory = new ApiApiFactory() let apiFactory = new ApiApiFactory()
apiFactory.partialUpdateSpace(this.ACTIVE_SPACE_ID, this.space).then(r => { apiFactory.partialUpdateSpace(window.ACTIVE_SPACE_ID, this.space).then(r => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch(err => { }).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)

View File

@ -12,7 +12,7 @@
<cookbook-edit-card :book="book" v-if="current_page === 1" v-on:editing="cookbook_editing = $event" v-on:refresh="$emit('refresh')" @reload="$emit('reload')"></cookbook-edit-card> <cookbook-edit-card :book="book" v-if="current_page === 1" v-on:editing="cookbook_editing = $event" v-on:refresh="$emit('refresh')" @reload="$emit('reload')"></cookbook-edit-card>
</transition> </transition>
<transition name="flip" mode="out-in"> <transition name="flip" mode="out-in">
<recipe-card :recipe="display_recipes[0].recipe_content" v-if="current_page > 1" :key="display_recipes[0].recipe"></recipe-card> <recipe-card :recipe="display_recipes[0].recipe_content" v-if="current_page > 1" :key="display_recipes[0].recipe" :use_plural="use_plural"></recipe-card>
</transition> </transition>
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
@ -20,7 +20,7 @@
<cookbook-toc :recipes="recipes" v-if="current_page === 1" v-on:switchRecipe="switchRecipe($event)"></cookbook-toc> <cookbook-toc :recipes="recipes" v-if="current_page === 1" v-on:switchRecipe="switchRecipe($event)"></cookbook-toc>
</transition> </transition>
<transition name="flip" mode="out-in"> <transition name="flip" mode="out-in">
<recipe-card :recipe="display_recipes[1].recipe_content" v-if="current_page > 1 && display_recipes.length === 2" :key="display_recipes[1].recipe"></recipe-card> <recipe-card :recipe="display_recipes[1].recipe_content" v-if="current_page > 1 && display_recipes.length === 2" :key="display_recipes[1].recipe" :use_plural="use_plural"></recipe-card>
</transition> </transition>
</div> </div>
<div class="col-md-1" @click="swipeLeft" style="cursor: pointer"></div> <div class="col-md-1" @click="swipeLeft" style="cursor: pointer"></div>
@ -34,7 +34,7 @@ import CookbookEditCard from "./CookbookEditCard"
import CookbookToc from "./CookbookToc" import CookbookToc from "./CookbookToc"
import Vue2TouchEvents from "vue2-touch-events" import Vue2TouchEvents from "vue2-touch-events"
import Vue from "vue" import Vue from "vue"
import { ApiApiFactory } from "../utils/openapi/api" import { ApiApiFactory } from "@/utils/openapi/api"
Vue.use(Vue2TouchEvents) Vue.use(Vue2TouchEvents)
@ -56,6 +56,12 @@ export default {
return this.recipes.slice((this.current_page - 1 - 1) * 2, (this.current_page - 1) * 2) return this.recipes.slice((this.current_page - 1 - 1) * 2, (this.current_page - 1) * 2)
} }
}, },
mounted(){
let apiClient = new ApiApiFactory()
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.use_plural = r.data.use_plural
})
},
data() { data() {
return { return {
current_page: 1, current_page: 1,
@ -63,6 +69,7 @@ export default {
bounce_left: false, bounce_left: false,
bounce_right: false, bounce_right: false,
cookbook_editing: false, cookbook_editing: false,
use_plural: false,
} }
}, },
methods: { methods: {

View File

@ -23,6 +23,9 @@
<b-card-body class="m-0 py-0"> <b-card-body class="m-0 py-0">
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis"> <b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
<h5 class="m-0 mt-1 text-truncate">{{ item[title] }}</h5> <h5 class="m-0 mt-1 text-truncate">{{ item[title] }}</h5>
<template v-if="use_plural">
<div v-if="item[plural] !== '' && item[plural] !== null" class="m-0 text-truncate">({{ $t("plural_short") }}: {{ item[plural] }})</div>
</template>
<div class="m-0 text-truncate">{{ item[subtitle] }}</div> <div class="m-0 text-truncate">{{ item[subtitle] }}</div>
<div class="m-0 text-truncate small text-muted" v-if="getFullname">{{ getFullname }}</div> <div class="m-0 text-truncate small text-muted" v-if="getFullname">{{ getFullname }}</div>
@ -71,7 +74,11 @@
<!-- recursively add child cards --> <!-- recursively add child cards -->
<div class="row" v-if="item.show_children"> <div class="row" v-if="item.show_children">
<div class="col-md-10 offset-md-2"> <div class="col-md-10 offset-md-2">
<generic-horizontal-card v-for="child in item[children]" v-bind:key="child.id" :item="child" :model="model" @item-action="$emit('item-action', $event)"> </generic-horizontal-card> <generic-horizontal-card v-for="child in item[children]"
v-bind:key="child.id"
:item="child" :model="model"
:use_plural="use_plural"
@item-action="$emit('item-action', $event)"></generic-horizontal-card>
</div> </div>
</div> </div>
<!-- conditionally view recipes --> <!-- conditionally view recipes -->
@ -146,12 +153,14 @@ export default {
item: { type: Object }, item: { type: Object },
model: { type: Object }, model: { type: Object },
title: { type: String, default: "name" }, // this and the following props need to be moved to model.js and made computed values title: { type: String, default: "name" }, // this and the following props need to be moved to model.js and made computed values
plural: { type: String, default: "plural_name" },
subtitle: { type: String, default: "description" }, subtitle: { type: String, default: "description" },
child_count: { type: String, default: "numchild" }, child_count: { type: String, default: "numchild" },
children: { type: String, default: "children" }, children: { type: String, default: "children" },
recipe_count: { type: String, default: "numrecipe" }, recipe_count: { type: String, default: "numrecipe" },
recipes: { type: String, default: "recipes" }, recipes: { type: String, default: "recipes" },
show_context_menu: { type: Boolean, default: true }, show_context_menu: { type: Boolean, default: true },
use_plural: { type: Boolean, default: false},
}, },
data() { data() {
return { return {

View File

@ -16,14 +16,43 @@
v-html="calculateAmount(ingredient.amount)"></span> v-html="calculateAmount(ingredient.amount)"></span>
</td> </td>
<td @click="done"> <td @click="done">
<span v-if="ingredient.unit !== null && !ingredient.no_amount">{{ ingredient.unit.name }}</span> <template v-if="ingredient.unit !== null && !ingredient.no_amount">
<template v-if="!use_plural">
<span>{{ ingredient.unit.name }}
</template>
<template v-else>
<template v-if="ingredient.unit.plural_name === '' || ingredient.unit.plural_name === null">
<span>{{ ingredient.unit.name }}
</template>
<template v-else>
<span v-if="ingredient.always_use_plural_unit">{{ ingredient.unit.plural_name}}</span>
<span v-else-if="(ingredient.amount * this.ingredient_factor) > 1">{{ ingredient.unit.plural_name }}</span>
<span v-else>{{ ingredient.unit.name }}</span>
</template>
</template>
</template>
</td> </td>
<td @click="done"> <td @click="done">
<template v-if="ingredient.food !== null"> <template v-if="ingredient.food !== null">
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" <a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)"
v-if="ingredient.food.recipe !== null" target="_blank" v-if="ingredient.food.recipe !== null" target="_blank"
rel="noopener noreferrer">{{ ingredient.food.name }}</a> rel="noopener noreferrer">{{ ingredient.food.name }}</a>
<span v-if="ingredient.food.recipe === null">{{ ingredient.food.name }}</span> <template v-if="ingredient.food.recipe === null">
<template v-if="!use_plural">
<span>{{ ingredient.food.name }}</span>
</template>
<template v-else>
<template v-if="ingredient.food.plural_name === '' || ingredient.food.plural_name === null">
<span>{{ ingredient.food.name }}</span>
</template>
<template v-else>
<span v-if="ingredient.always_use_plural_food">{{ ingredient.food.plural_name }}</span>
<span v-else-if="ingredient.no_amount">{{ ingredient.food.name }}</span>
<span v-else-if="(ingredient.amount * this.ingredient_factor) > 1">{{ ingredient.food.plural_name }}</span>
<span v-else>{{ ingredient.food.name }}</span>
</template>
</template>
</template>
</template> </template>
</td> </td>
<td v-if="detailed"> <td v-if="detailed">
@ -55,6 +84,7 @@ export default {
props: { props: {
ingredient: Object, ingredient: Object,
ingredient_factor: {type: Number, default: 1}, ingredient_factor: {type: Number, default: 1},
use_plural:{type: Boolean, default: false},
detailed: {type: Boolean, default: true}, detailed: {type: Boolean, default: true},
}, },
mixins: [ResolveUrlMixin], mixins: [ResolveUrlMixin],

View File

@ -24,6 +24,7 @@
<ingredient-component <ingredient-component
:ingredient="i" :ingredient="i"
:ingredient_factor="ingredient_factor" :ingredient_factor="ingredient_factor"
:use_plural="use_plural"
:key="i.id" :key="i.id"
:detailed="detailed" :detailed="detailed"
@checked-state-changed="$emit('checked-state-changed', $event)" @checked-state-changed="$emit('checked-state-changed', $event)"
@ -63,6 +64,7 @@ export default {
recipe: {type: Number}, recipe: {type: Number},
ingredient_factor: {type: Number, default: 1}, ingredient_factor: {type: Number, default: 1},
servings: {type: Number, default: 1}, servings: {type: Number, default: 1},
use_plural: {type: Boolean, default: false},
detailed: {type: Boolean, default: true}, detailed: {type: Boolean, default: true},
header: {type: Boolean, default: false}, header: {type: Boolean, default: false},
recipe_list: {type: Number, default: undefined}, recipe_list: {type: Number, default: undefined},

View File

@ -84,7 +84,7 @@
</b-input-group> </b-input-group>
</div> </div>
<div class="col-lg-6 d-none d-lg-block d-xl-block"> <div class="col-lg-6 d-none d-lg-block d-xl-block">
<recipe-card v-if="entryEditing.recipe" :recipe="entryEditing.recipe" :detailed="false"></recipe-card> <recipe-card v-if="entryEditing.recipe" :recipe="entryEditing.recipe" :detailed="false" :use_plural="use_plural"></recipe-card>
</div> </div>
</div> </div>
<div class="row mt-3 mb-3"> <div class="row mt-3 mb-3">
@ -144,6 +144,7 @@ export default {
addshopping: false, addshopping: false,
reviewshopping: false, reviewshopping: false,
}, },
use_plural: false,
} }
}, },
watch: { watch: {
@ -171,7 +172,12 @@ export default {
this.entryEditing.servings = newVal this.entryEditing.servings = newVal
}, },
}, },
mounted: function () {}, mounted: function () {
let apiClient = new ApiApiFactory()
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
this.use_plural = r.data.use_plural
})
},
computed: { computed: {
autoMealPlan: function () { autoMealPlan: function () {
return getUserPreference("mealplan_autoadd_shopping") return getUserPreference("mealplan_autoadd_shopping")

View File

@ -75,11 +75,15 @@
<h6 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }} <h6 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }}
</h6> </h6>
<ingredients-card :steps="recipe.steps" :header="false" :detailed="false" <ingredients-card
:servings="recipe.servings"/> :steps="recipe.steps"
</div> :header="false"
</div> :detailed="false"
</transition> :servings="recipe.servings"
:use_plural="use_plural" />
</div>
</div>
</transition>
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t("External") }}</b-badge> <b-badge pill variant="info" v-if="!recipe.internal">{{ $t("External") }}</b-badge>
</template> </template>
@ -121,6 +125,7 @@ export default {
props: { props: {
recipe: Object, recipe: Object,
meal_plan: Object, meal_plan: Object,
use_plural: { type: Boolean, default: false},
footer_text: String, footer_text: String,
footer_icon: String, footer_icon: String,
detailed: {type: Boolean, default: true}, detailed: {type: Boolean, default: true},

View File

@ -425,5 +425,12 @@
"New_Supermarket": "", "New_Supermarket": "",
"New_Supermarket_Category": "", "New_Supermarket_Category": "",
"Are_You_Sure": "", "Are_You_Sure": "",
"Valid Until": "" "Valid Until": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -410,5 +410,12 @@
"Warning_Delete_Supermarket_Category": "Изтриването на категория супермаркет ще изтрие и всички връзки с храни. Сигурен ли си?", "Warning_Delete_Supermarket_Category": "Изтриването на категория супермаркет ще изтрие и всички връзки с храни. Сигурен ли си?",
"New_Supermarket": "Създайте нов супермаркет", "New_Supermarket": "Създайте нов супермаркет",
"New_Supermarket_Category": "Създаване на нова категория супермаркет", "New_Supermarket_Category": "Създаване на нова категория супермаркет",
"Are_You_Sure": "Сигурен ли си?" "Are_You_Sure": "Сигурен ли си?",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -458,5 +458,12 @@
"Days": "Dage", "Days": "Dage",
"Message": "Besked", "Message": "Besked",
"Sticky_Nav": "Fastlåst navigation", "Sticky_Nav": "Fastlåst navigation",
"reset_food_inheritance": "Nulstil nedarvning" "reset_food_inheritance": "Nulstil nedarvning",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -460,5 +460,12 @@
"Comments_setting": "Kommentare anzeigen", "Comments_setting": "Kommentare anzeigen",
"reset_food_inheritance": "Vererbung zurücksetzen", "reset_food_inheritance": "Vererbung zurücksetzen",
"food_inherit_info": "Datenfelder des Lebensmittels, die standardmäßig vererbt werden sollen.", "food_inherit_info": "Datenfelder des Lebensmittels, die standardmäßig vererbt werden sollen.",
"Are_You_Sure": "Bist du dir sicher?" "Are_You_Sure": "Bist du dir sicher?",
"Plural": "Plural",
"plural_short": "pl.",
"Use_Plural_Unit_Always": "Pluralform der Maßeinheit immer verwenden",
"Use_Plural_Unit_Simple": "Pluralform der Maßeinheit dynamisch anpassen",
"Use_Plural_Food_Always": "Pluralform des Essens immer verwenden",
"Use_Plural_Food_Simple": "Pluralform des Essens dynamisch anpassen",
"plural_usage_info": "Pluralform für Einheiten und Essen in diesem Space verwenden."
} }

View File

@ -459,5 +459,12 @@
"New_Supermarket": "Create new supermarket", "New_Supermarket": "Create new supermarket",
"New_Supermarket_Category": "Create new supermarket category", "New_Supermarket_Category": "Create new supermarket category",
"Are_You_Sure": "Are you sure?", "Are_You_Sure": "Are you sure?",
"Valid Until": "Valid Until" "Valid Until": "Valid Until",
"Plural": "Plural",
"plural_short": "plural",
"Use_Plural_Unit_Always": "Use plural form for unit always",
"Use_Plural_Unit_Simple": "Use plural form for unit dynamically",
"Use_Plural_Food_Always": "Use plural form for food always",
"Use_Plural_Food_Simple": "Use plural form for food dynamically",
"plural_usage_info": "Use the plural form for units and food inside this space."
} }

View File

@ -436,5 +436,12 @@
"Default_Unit": "Unidad Predeterminada", "Default_Unit": "Unidad Predeterminada",
"Language": "Lenguaje", "Language": "Lenguaje",
"Hour": "Hora", "Hour": "Hora",
"Username": "Nombre de Usuario" "Username": "Nombre de Usuario",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -212,5 +212,12 @@
"success_moving_resource": "Resurssin siirto onnistui!", "success_moving_resource": "Resurssin siirto onnistui!",
"success_merging_resource": "Resurssin yhdistäminen onnistui!", "success_merging_resource": "Resurssin yhdistäminen onnistui!",
"Search Settings": "Hakuasetukset", "Search Settings": "Hakuasetukset",
"Shopping_Categories": "Ostoskategoriat" "Shopping_Categories": "Ostoskategoriat",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -402,5 +402,12 @@
"Comments_setting": "Montrer les commentaires", "Comments_setting": "Montrer les commentaires",
"import_duplicates": "Pour éviter les doublons, les recettes de même nom seront ignorées. Cocher la case pour tout importer.", "import_duplicates": "Pour éviter les doublons, les recettes de même nom seront ignorées. Cocher la case pour tout importer.",
"Account": "Compte", "Account": "Compte",
"Change_Password": "Modifier le mot de passe" "Change_Password": "Modifier le mot de passe",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -412,5 +412,12 @@
"Warning_Delete_Supermarket_Category": "", "Warning_Delete_Supermarket_Category": "",
"New_Supermarket": "", "New_Supermarket": "",
"New_Supermarket_Category": "", "New_Supermarket_Category": "",
"Are_You_Sure": "" "Are_You_Sure": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -122,5 +122,12 @@
"Save_and_View": "Պահպանել և Դիտել", "Save_and_View": "Պահպանել և Դիտել",
"Select_File": "Ընտրել Ֆայլ", "Select_File": "Ընտրել Ֆայլ",
"Edit_Keyword": "Խմբագրել բանալի բառը", "Edit_Keyword": "Խմբագրել բանալի բառը",
"Hide_Recipes": "Թաքցնել բաղադրատոմսերը" "Hide_Recipes": "Թաքցնել բաղադրատոմսերը",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -346,5 +346,12 @@
"csv_delim_help": "Delimitatore usato per le esportazioni CSV.", "csv_delim_help": "Delimitatore usato per le esportazioni CSV.",
"csv_prefix_label": "Prefisso lista", "csv_prefix_label": "Prefisso lista",
"not": "not", "not": "not",
"Keyword": "Parola chiave" "Keyword": "Parola chiave",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -461,5 +461,12 @@
"Valid Until": "Geldig tot", "Valid Until": "Geldig tot",
"warning_space_delete": "Je kunt jouw space verwijderen inclusief alle recepten, boodschappenlijstjes, maaltijdplannen en alles wat je verder aangemaakt hebt. Dit kan niet ongedaan worden gemaakt! Weet je het zeker?", "warning_space_delete": "Je kunt jouw space verwijderen inclusief alle recepten, boodschappenlijstjes, maaltijdplannen en alles wat je verder aangemaakt hebt. Dit kan niet ongedaan worden gemaakt! Weet je het zeker?",
"food_inherit_info": "Voedselvelden die standaard geërfd worden.", "food_inherit_info": "Voedselvelden die standaard geërfd worden.",
"facet_count_info": "Geef receptenaantal bij zoekfilters weer." "facet_count_info": "Geef receptenaantal bij zoekfilters weer.",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -460,5 +460,12 @@
"First_name": "Imię", "First_name": "Imię",
"Last_name": "Nazwisko", "Last_name": "Nazwisko",
"Disabled": "Wyłączone", "Disabled": "Wyłączone",
"Disable": "Wyłączyć" "Disable": "Wyłączyć",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -382,5 +382,12 @@
"err_deleting_protected_resource": "O objeto que você está tentando deletar ainda está sendo utilizado, portanto não pode ser deletado.", "err_deleting_protected_resource": "O objeto que você está tentando deletar ainda está sendo utilizado, portanto não pode ser deletado.",
"food_inherit_info": "Campos no alimento que devem ser herdados por padrão.", "food_inherit_info": "Campos no alimento que devem ser herdados por padrão.",
"warning_space_delete": "Você pode deletar seu espaço, inclusive todas as receitas, listas de mercado, planos de comida e tudo mais que você criou. Esta ação não poderá ser desfeita! Você tem certeza que quer fazer isto?", "warning_space_delete": "Você pode deletar seu espaço, inclusive todas as receitas, listas de mercado, planos de comida e tudo mais que você criou. Esta ação não poderá ser desfeita! Você tem certeza que quer fazer isto?",
"facet_count_info": "Mostrar quantidade de receitas nos filtros de busca." "facet_count_info": "Mostrar quantidade de receitas nos filtros de busca.",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -387,5 +387,12 @@
"Copy Token": "Copiar Token", "Copy Token": "Copiar Token",
"warning_space_delete": "Você pode deletar seu espaço, inclusive todas as receitas, listas de mercado, planos de comida e tudo mais que você criou. Esta ação não poderá ser desfeita! Você tem certeza que quer fazer isto?", "warning_space_delete": "Você pode deletar seu espaço, inclusive todas as receitas, listas de mercado, planos de comida e tudo mais que você criou. Esta ação não poderá ser desfeita! Você tem certeza que quer fazer isto?",
"food_inherit_info": "Campos no alimento que devem ser herdados por padrão.", "food_inherit_info": "Campos no alimento que devem ser herdados por padrão.",
"facet_count_info": "Mostrar quantidade de receitas nos filtros de busca." "facet_count_info": "Mostrar quantidade de receitas nos filtros de busca.",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -206,5 +206,12 @@
"Auto_Planner": "", "Auto_Planner": "",
"New_Cookbook": "", "New_Cookbook": "",
"Hide_Keyword": "", "Hide_Keyword": "",
"Clear": "" "Clear": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -342,5 +342,12 @@
"IgnoreThis": "Никогда не добавлять {food} в список покупок автоматически", "IgnoreThis": "Никогда не добавлять {food} в список покупок автоматически",
"DelayFor": "Отложить на {hours} часов", "DelayFor": "Отложить на {hours} часов",
"New_Entry": "Новая запись", "New_Entry": "Новая запись",
"GroupBy": "Сгруппировать по" "GroupBy": "Сгруппировать по",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -284,5 +284,12 @@
"sql_debug": "SQL razhroščevanje", "sql_debug": "SQL razhroščevanje",
"remember_search": "Zapomni si iskanje", "remember_search": "Zapomni si iskanje",
"remember_hours": "Ure, ki si jih zapomni", "remember_hours": "Ure, ki si jih zapomni",
"tree_select": "Uporabi drevesno označbo" "tree_select": "Uporabi drevesno označbo",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -380,5 +380,12 @@
"create_food_desc": "Skapa ett livsmedel och länka det till det här receptet.", "create_food_desc": "Skapa ett livsmedel och länka det till det här receptet.",
"additional_options": "Ytterligare alternativ", "additional_options": "Ytterligare alternativ",
"remember_hours": "Timmar att komma ihåg", "remember_hours": "Timmar att komma ihåg",
"tree_select": "Använd trädval" "tree_select": "Använd trädval",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -412,5 +412,12 @@
"Warning_Delete_Supermarket_Category": "", "Warning_Delete_Supermarket_Category": "",
"New_Supermarket": "", "New_Supermarket": "",
"New_Supermarket_Category": "", "New_Supermarket_Category": "",
"Are_You_Sure": "" "Are_You_Sure": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -459,5 +459,12 @@
"reset_children_help": "用继承字段中的值覆盖所有子项。 继承的子字段将设置为继承,除非它们已设置为继承。", "reset_children_help": "用继承字段中的值覆盖所有子项。 继承的子字段将设置为继承,除非它们已设置为继承。",
"substitute_siblings": "代替品", "substitute_siblings": "代替品",
"book_filter_help": "除手动选择的食谱外,还包括筛选中的食谱。", "book_filter_help": "除手动选择的食谱外,还包括筛选中的食谱。",
"Internal": "内部" "Internal": "内部",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -77,5 +77,12 @@
"and": "", "and": "",
"Information": "", "Information": "",
"Download": "", "Download": "",
"Create": "" "Create": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": ""
} }

View File

@ -78,6 +78,7 @@ export class Models {
params: [ params: [
[ [
"name", "name",
"plural_name",
"description", "description",
"recipe", "recipe",
"food_onhand", "food_onhand",
@ -103,6 +104,13 @@ export class Models {
placeholder: "", // form.placeholder always translated placeholder: "", // form.placeholder always translated
subtitle_field: "full_name", subtitle_field: "full_name",
}, },
plural_name: {
form_field: true,
type: "text",
field: "plural_name",
label: "Plural",
placeholder: "",
},
description: { description: {
form_field: true, form_field: true,
type: "text", type: "text",
@ -261,7 +269,7 @@ export class Models {
apiName: "Unit", apiName: "Unit",
paginated: true, paginated: true,
create: { create: {
params: [["name", "description"]], params: [["name", "plural_name", "description",]],
form: { form: {
name: { name: {
form_field: true, form_field: true,
@ -270,6 +278,13 @@ export class Models {
label: "Name", label: "Name",
placeholder: "", placeholder: "",
}, },
plural_name: {
form_field: true,
type: "text",
field: "plural_name",
label: "Plural name",
placeholder: "",
},
description: { description: {
form_field: true, form_field: true,
type: "text", type: "text",