mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-26 11:49:41 -05:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d26faf14b1 | ||
|
|
d5d5c2c52b | ||
|
|
7ffabfe711 | ||
|
|
49e0b5b962 | ||
|
|
a05f1ece24 | ||
|
|
748b91bb8a | ||
|
|
bd2e9cc3d9 | ||
|
|
c40bb20a7a | ||
|
|
b377d2cd35 | ||
|
|
dc0e91d0f9 | ||
|
|
5f12907544 | ||
|
|
889ddac7dc | ||
|
|
b369e2618a | ||
|
|
5a4e0204c9 | ||
|
|
bfc2e96b54 | ||
|
|
f065ef80aa | ||
|
|
61c14b8b05 | ||
|
|
35d5d64809 | ||
|
|
63c711d18c | ||
|
|
59e3ea70d1 | ||
|
|
6771662a9f | ||
|
|
9b792a1393 | ||
|
|
862957c121 | ||
|
|
bdcbafd52f | ||
|
|
5e454a5212 | ||
|
|
20bea63997 | ||
|
|
8a265772c0 | ||
|
|
6febb4e3e8 | ||
|
|
04f9167fd8 | ||
|
|
8f29e01daf | ||
|
|
e810363b22 | ||
|
|
b5a2120bdf | ||
|
|
643fcbad9b | ||
|
|
4a3b834463 | ||
|
|
003149133a | ||
|
|
a43de0ca4d | ||
|
|
e05aaed75c | ||
|
|
4984e3e31b | ||
|
|
11dce4c6ad | ||
|
|
8d0d338ea2 | ||
|
|
d09e629415 | ||
|
|
53ef2ef99f | ||
|
|
d7b26d1b29 | ||
|
|
673d12d233 | ||
|
|
6359245925 | ||
|
|
a7c4822322 | ||
|
|
e94419f320 | ||
|
|
01f46483ff | ||
|
|
d6da5688af | ||
|
|
680ae39201 | ||
|
|
2472ee9c26 | ||
|
|
4428b06d4a | ||
|
|
e9c38d7d5e | ||
|
|
6f28d58807 | ||
|
|
88db611f0a | ||
|
|
f3302b4014 | ||
|
|
d4bb161275 | ||
|
|
32f1538938 | ||
|
|
029baea4c7 | ||
|
|
38d1b7cef5 | ||
|
|
85821bcc94 | ||
|
|
e292b72e34 | ||
|
|
4e795ecf55 | ||
|
|
e3c2a66723 | ||
|
|
eec3e97f97 | ||
|
|
3f481d6922 | ||
|
|
0810ab7210 | ||
|
|
abd621145c | ||
|
|
7d218aa93d | ||
|
|
1b41bd9115 | ||
|
|
d456fcf0f2 | ||
|
|
d4f654554b | ||
|
|
c8115545b8 | ||
|
|
6dbf0871ec | ||
|
|
f1c5c8bc43 | ||
|
|
22e0108992 | ||
|
|
e2e05c8d1d | ||
|
|
b02b36812d | ||
|
|
7f6025c99c | ||
|
|
b97e04ead8 | ||
|
|
fc236c97b4 | ||
|
|
5653aca056 | ||
|
|
fdb05c5a9e | ||
|
|
2dffde4091 | ||
|
|
cdd700d2e6 | ||
|
|
ad6fe5fa4d | ||
|
|
ac31c112f3 | ||
|
|
0104b600cc | ||
|
|
7baad85112 | ||
|
|
4b0bfa9a85 | ||
|
|
5e7c75ef68 | ||
|
|
954a35bea2 | ||
|
|
88347d44c8 | ||
|
|
2c13e76fbb | ||
|
|
362f634828 | ||
|
|
2fb968cfd3 | ||
|
|
4d3dab6edd | ||
|
|
8f1b593ad1 | ||
|
|
1002f0d61f | ||
|
|
20cb218688 | ||
|
|
bba44b0c1e |
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
fi
|
||||
|
||||
# Build Vue 3 frontend
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: yarn
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -13,14 +13,14 @@ jobs:
|
||||
node-version: ["22"]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1.5.3
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libsasl2-dev python3-dev libxml2-dev libxmlsec1-dev libxslt-dev libxmlsec1-openssl libxslt-dev libldap2-dev libssl-dev gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev xmlsec-dev xmlsec build-base g++ curl
|
||||
version: 1.0
|
||||
|
||||
# Setup python & dependencies
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "pip"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
# Build Vue frontend & Dependencies
|
||||
- name: Set up Node ${{ matrix.node-version }}
|
||||
if: steps.django_cache.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "yarn"
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
with:
|
||||
languages: python, javascript
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
languages: javascript, python
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material mkdocs-include-markdown-plugin
|
||||
|
||||
2
boot.sh
2
boot.sh
@@ -104,5 +104,5 @@ chmod -R 755 ${MEDIA_ROOT:-/opt/recipes/mediafiles}
|
||||
ipv6_disable=$(cat /sys/module/ipv6/parameters/disable)
|
||||
|
||||
echo "Starting gunicorn"
|
||||
exec gunicorn --bind unix:/run/tandoor.sock --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
|
||||
exec gunicorn --bind unix:/run/tandoor.sock --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --timeout ${GUNICORN_TIMEOUT:-30} --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class FoodPropertyHelper:
|
||||
found_property = False
|
||||
# if food has a value for the given property type (no matter if conversion is possible)
|
||||
has_property_value = False
|
||||
if i.food.properties_food_amount == 0 or i.food.properties_food_unit is None and not (i.amount == 0 or i.no_amount): # if food is configured incorrectly
|
||||
if (i.food.properties_food_amount == 0 or i.food.properties_food_unit is None) and not (i.amount == 0 or i.no_amount): # if food is configured incorrectly
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
|
||||
computed_properties[pt.id]['missing_value'] = True
|
||||
else:
|
||||
@@ -63,8 +63,9 @@ class FoodPropertyHelper:
|
||||
computed_properties[p.property_type.id]['food_values'], c.food.id, (c.amount / i.food.properties_food_amount) * p.property_amount, c.food)
|
||||
if not found_property:
|
||||
# if no amount and food does not exist yet add it but don't count as missing
|
||||
if i.amount == 0 or i.no_amount and i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': 0}
|
||||
if i.amount == 0 or i.no_amount:
|
||||
if i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': 0}
|
||||
# if amount is present but unit is missing indicate it in the result
|
||||
elif i.unit is None:
|
||||
if i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
@@ -72,7 +73,8 @@ class FoodPropertyHelper:
|
||||
computed_properties[pt.id]['food_values'][i.food.id]['missing_unit'] = True
|
||||
else:
|
||||
computed_properties[pt.id]['missing_value'] = True
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
|
||||
if i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
|
||||
if has_property_value and i.unit is not None:
|
||||
computed_properties[pt.id]['food_values'][i.food.id]['missing_conversion'] = {'base_unit': {'id': i.unit.id, 'name': i.unit.name}, 'converted_unit': {'id': i.food.properties_food_unit.id, 'name': i.food.properties_food_unit.name}}
|
||||
|
||||
@@ -82,8 +84,12 @@ class FoodPropertyHelper:
|
||||
# TODO move to central helper ? --> use defaultdict
|
||||
@staticmethod
|
||||
def add_or_create(d, key, value, food):
|
||||
if key in d and d[key]['value']:
|
||||
d[key]['value'] += value
|
||||
if key in d:
|
||||
# value can be None if a previous instance of the same food was missing a conversion
|
||||
if d[key]['value']:
|
||||
d[key]['value'] += value
|
||||
else:
|
||||
d[key]['value'] = value
|
||||
else:
|
||||
d[key] = {'id': food.id, 'food': {'id': food.id, 'name': food.name}, 'value': value}
|
||||
return d
|
||||
|
||||
@@ -69,15 +69,8 @@ def get_from_scraper(scrape, request):
|
||||
recipe_json['description'] = parse_description(description)
|
||||
recipe_json['description'] = automation_engine.apply_regex_replace_automation(recipe_json['description'], Automation.DESCRIPTION_REPLACE)
|
||||
|
||||
# assign servings attributes
|
||||
try:
|
||||
# dont use scrape.yields() as this will always return "x servings" or "x items", should be improved in scrapers directly
|
||||
servings = scrape.schema.data.get('recipeYield') or 1
|
||||
except Exception:
|
||||
servings = 1
|
||||
|
||||
recipe_json['servings'] = parse_servings(servings)
|
||||
recipe_json['servings_text'] = parse_servings_text(servings)
|
||||
recipe_json['servings'] = parse_servings(scrape.schema.data.get('recipeYield'))
|
||||
recipe_json['servings_text'] = parse_servings_text(scrape.schema.data.get('recipeYield'))
|
||||
|
||||
# assign time attributes
|
||||
try:
|
||||
@@ -406,7 +399,7 @@ def parse_servings(servings):
|
||||
def parse_servings_text(servings):
|
||||
if isinstance(servings, str):
|
||||
try:
|
||||
servings = re.sub("\\d+", '', servings).strip()
|
||||
servings = re.sub("\\d+", '', servings, 1).strip()
|
||||
except Exception:
|
||||
servings = ''
|
||||
if isinstance(servings, list):
|
||||
|
||||
2453
cookbook/locale/ko/LC_MESSAGES/django.po
Normal file
2453
cookbook/locale/ko/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2562,6 +2562,13 @@ class AiImportView(APIView):
|
||||
'msg': "Error parsing AI results. Response Text:\n\n" + response_text
|
||||
}
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': "Error processing AI results. Response Text:\n\n" + response_text + "\n\n" + traceback.format_exc()
|
||||
}
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
response = {
|
||||
'error': True,
|
||||
|
||||
@@ -33,4 +33,4 @@ Convert pictures of recipes to a structure that can be imported to Tandoor with
|
||||
|
||||
Maintained by [smilerz](https://github.com/smilerz/tandoor-menu-generator)
|
||||
|
||||
Generate a mealplan tbased on complex criteria and optionally insert it into an SVG menu template.
|
||||
Generate a meal plan based on complex criteria and optionally insert it into an SVG menu template.
|
||||
|
||||
@@ -36,7 +36,7 @@ then make sure you have set [all required headers](install/docker.md#required-he
|
||||
If that doesn't fix it, you can also refer to the appropriate sub section in the [reverse proxy documentation](install/docker.md#reverse-proxy) and verify your general webserver configuration.
|
||||
|
||||
### Required Headers
|
||||
Navigate to `/system` and review the headers listed in the DEBUG section. At a minimum, if you are using a reverse proxy the headers must match the below conditions.
|
||||
Navigate to `/system/` and review the headers listed in the DEBUG section. At a minimum, if you are using a reverse proxy the headers must match the below conditions.
|
||||
|
||||
| Header | Requirement |
|
||||
| :--- | :---- |
|
||||
|
||||
@@ -69,8 +69,6 @@ wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/d
|
||||
|
||||
Most deployments will likely use a reverse proxy.
|
||||
|
||||
If your reverse proxy is not listed below, please refer to chapter [Others](#others).
|
||||
|
||||
#### **Traefik**
|
||||
|
||||
If you use Traefik, this configuration is the one for you.
|
||||
@@ -115,6 +113,17 @@ wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/d
|
||||
{% include "./docker/nginx-proxy/docker-compose.yml" %}
|
||||
~~~
|
||||
|
||||
|
||||
#### **Apache proxy**
|
||||
|
||||
If you use Apache as a reverse proxy, this configuration is the one for you.
|
||||
|
||||
~~~yaml
|
||||
{% include "./docker/apache-proxy/docker-compose.yml" %}
|
||||
~~~
|
||||
|
||||
Keep in mind, that the port configured for the service `web_recipes` should be the same as in chapter [Required Headers: Apache](#apache).
|
||||
|
||||
## **DockSTARTer**
|
||||
|
||||
The main goal of [DockSTARTer](https://dockstarter.com/) is to make it quick and easy to get up and running with Docker.
|
||||
@@ -139,7 +148,8 @@ if you manually change it/bind the folder as a volume.
|
||||
|
||||
Please be sure to supply all required headers in your nginx/Apache/Caddy/... configuration!
|
||||
|
||||
nginx:
|
||||
#### **nginx**
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_set_header Host $http_host; # try $host instead if this doesn't work
|
||||
@@ -149,7 +159,8 @@ location / {
|
||||
}
|
||||
```
|
||||
|
||||
Apache:
|
||||
#### **Apache**
|
||||
|
||||
```apache
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
|
||||
24
docs/install/docker/apache-proxy/docker-compose.yml
Normal file
24
docs/install/docker/apache-proxy/docker-compose.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
services:
|
||||
db_recipes:
|
||||
restart: always
|
||||
image: postgres:16-alpine
|
||||
volumes:
|
||||
- ./postgresql:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- ./.env
|
||||
|
||||
web_recipes:
|
||||
restart: always
|
||||
image: vabene1111/recipes
|
||||
ports:
|
||||
- 127.0.0.1:8080:80 # replace port
|
||||
env_file:
|
||||
- ./.env
|
||||
volumes:
|
||||
- staticfiles:/opt/recipes/staticfiles
|
||||
- ./mediafiles:/opt/recipes/mediafiles
|
||||
depends_on:
|
||||
- db_recipes
|
||||
|
||||
volumes:
|
||||
staticfiles:
|
||||
@@ -77,10 +77,10 @@ Using binaries from the virtual env:
|
||||
/var/www/recipes/bin/pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
You will also need to install front end requirements and build them. For this navigate to the `./vue` folder and run
|
||||
You will also need to install front end requirements and build them. For this navigate to the `./vue3` folder and run
|
||||
|
||||
```shell
|
||||
cd ./vue
|
||||
cd ./vue3
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
@@ -224,7 +224,7 @@ bin/python3 manage.py migrate
|
||||
bin/python3 manage.py collectstatic --no-input
|
||||
bin/python3 manage.py collectstatic_js_reverse
|
||||
# change to frontend directory
|
||||
cd vue
|
||||
cd vue3
|
||||
# install and build frontend
|
||||
yarn install
|
||||
yarn build
|
||||
|
||||
@@ -189,6 +189,19 @@ See [Gunicorn docs](https://docs.gunicorn.org/en/stable/design.html#how-many-wor
|
||||
GUNICORN_THREADS=2
|
||||
```
|
||||
|
||||
|
||||
#### Gunicorn Timeout
|
||||
|
||||
> default `30` - options `1-X`
|
||||
|
||||
Set the timeout in seconds of gunicorn when starting using `boot.sh` (all container installations).
|
||||
The default is likely appropriate for most installations. However, if you are using a LLM which high response times gunicornmight time out during the wait until the LLM finished, in such cases you might want to increase the timeout.
|
||||
See [Gunicorn docs]([https://docs.gunicorn.org/en/stable/design.html#how-many-workers](https://docs.gunicorn.org/en/stable/settings.html#timeout)) for default settings.
|
||||
|
||||
```
|
||||
GUNICORN_TIMEOUT=30
|
||||
```
|
||||
|
||||
#### Gunicorn Media
|
||||
|
||||
> default `0` - options `0`, `1`
|
||||
|
||||
@@ -3,7 +3,7 @@ server {
|
||||
listen [::]:${TANDOOR_PORT} ipv6only=on;
|
||||
server_name localhost;
|
||||
|
||||
client_max_body_size 128M;
|
||||
client_max_body_size 512M;
|
||||
|
||||
# serve media files
|
||||
location /media {
|
||||
@@ -21,6 +21,9 @@ server {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://unix:/run/tandoor.sock;
|
||||
|
||||
# param needed by django allauth sessions to log IP
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# disabled for now because it redirects to the error page and not back, also not showing html
|
||||
#error_page 502 /errors/http502.html;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ urlpatterns = [
|
||||
),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG and settings.DEBUG_TOOLBAR:
|
||||
urlpatterns += path('__debug__/', include('debug_toolbar.urls')),
|
||||
|
||||
if settings.ENABLE_METRICS:
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Django==5.2.7
|
||||
Django==5.2.8
|
||||
cryptography===45.0.5
|
||||
django-annoying==0.10.6
|
||||
django-cleanup==9.0.0
|
||||
django-crispy-forms==2.4
|
||||
crispy-bootstrap4==2025.6
|
||||
djangorestframework==3.16.1
|
||||
drf-spectacular==0.27.1
|
||||
drf-spectacular==0.28.0
|
||||
drf-spectacular-sidecar==2025.8.1
|
||||
drf-writable-nested==0.7.2
|
||||
django-oauth-toolkit==2.4.0
|
||||
django-debug-toolbar==4.3.0
|
||||
django-debug-toolbar==6.0.0
|
||||
bleach==6.2.0
|
||||
gunicorn==23.0.0
|
||||
lxml==5.3.1
|
||||
lxml==6.0.2
|
||||
Markdown==3.7
|
||||
Pillow==11.3.0
|
||||
psycopg2-binary==2.9.10
|
||||
@@ -22,14 +22,14 @@ six==1.17.0
|
||||
webdavclient3==3.14.6
|
||||
whitenoise==6.8.2
|
||||
icalendar==6.3.1
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
uritemplate==4.1.1
|
||||
beautifulsoup4==4.12.3
|
||||
microdata==0.8.0
|
||||
mock==5.2.0
|
||||
Jinja2==3.1.6
|
||||
django-allauth[mfa,socialaccount]==65.9.0
|
||||
recipe-scrapers==15.8.0
|
||||
recipe-scrapers==15.9.0
|
||||
django-scopes==2.0.0
|
||||
django-treebeard==4.7.1
|
||||
django-cors-headers==4.6.0
|
||||
@@ -37,7 +37,7 @@ django-storages==1.14.6
|
||||
boto3==1.28.75
|
||||
django-prometheus==2.4.1
|
||||
django-hCaptcha==0.2.0
|
||||
python-ldap==3.4.4
|
||||
python-ldap==3.4.5
|
||||
django-auth-ldap==4.6.0
|
||||
pyppeteer==2.0.0
|
||||
pytubefix==9.2.2
|
||||
@@ -53,7 +53,7 @@ django-vite==3.1.0
|
||||
litellm==1.64.1
|
||||
|
||||
# Development
|
||||
pytest==8.4.1
|
||||
pytest==8.4.2
|
||||
pytest-django==4.11.0
|
||||
pytest-cov===6.2.1
|
||||
pytest-factoryboy==2.8.1
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@vueform/multiselect": "^2.6.11",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"@vueuse/router": "^13.6.0",
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"luxon": "^3.7.1",
|
||||
"mavon-editor": "^3.0.1",
|
||||
"pinia": "^3.0.2",
|
||||
@@ -23,7 +23,7 @@
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-simple-calendar": "7.1.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "^3.9.7"
|
||||
"vuetify": "^3.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
@@ -31,11 +31,11 @@
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^24.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"esbuild-register": "^3.6.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "7.1.5",
|
||||
"vite": "7.1.11",
|
||||
"vite-plugin-pwa": "^1.0.3",
|
||||
"vite-plugin-vuetify": "^2.1.1",
|
||||
"vue-tsc": "^3.0.6",
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
<script lang="ts" setup>
|
||||
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
|
||||
|
||||
import {useDisplay} from "vuetify"
|
||||
import {useDisplay, useLocale} from "vuetify"
|
||||
import VSnackbarQueued from "@/components/display/VSnackbarQueued.vue";
|
||||
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
@@ -152,6 +152,7 @@ const {t} = useI18n()
|
||||
|
||||
const title = useTitle()
|
||||
const router = useRouter()
|
||||
const i18n = useI18n()
|
||||
|
||||
const isPrintMode = useMediaQuery('print')
|
||||
|
||||
@@ -161,13 +162,20 @@ onMounted(() => {
|
||||
router.push({name: 'WelcomePage'})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const {current} = useLocale()
|
||||
let locale = document.querySelector('html')!.getAttribute('lang')
|
||||
if (locale != null) {
|
||||
current.value = locale
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* global title update handler, might be overridden by page specific handlers
|
||||
*/
|
||||
router.afterEach((to, from) => {
|
||||
if(to.name == 'StartPage' && useUserPreferenceStore().initCompleted && !useUserPreferenceStore().activeSpace.spaceSetupCompleted != undefined &&!useUserPreferenceStore().activeSpace.spaceSetupCompleted && useUserPreferenceStore().activeSpace.createdBy.id! == useUserPreferenceStore().userSettings.user.id!){
|
||||
if (to.name == 'StartPage' && useUserPreferenceStore().initCompleted && !useUserPreferenceStore().activeSpace.spaceSetupCompleted != undefined && !useUserPreferenceStore().activeSpace.spaceSetupCompleted && useUserPreferenceStore().activeSpace.createdBy.id! == useUserPreferenceStore().userSettings.user.id!) {
|
||||
router.push({name: 'WelcomePage'})
|
||||
}
|
||||
nextTick(() => {
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn @click="useMessageStore().deleteAllMessages()" color="error">{{$t('Delete_All')}}</v-btn>
|
||||
<v-btn @click="addTestMessage()" color="warning">{{$t('Add')}}</v-btn>
|
||||
<!-- <v-btn @click="addTestMessage()" color="warning">{{$t('Add')}}</v-btn>-->
|
||||
<v-btn @click="isActive.value = false">{{ $t('Close')}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
@@ -157,6 +157,7 @@ function dropCalendarItemOnDate(undefinedItem: IMealPlanNormalizedCalendarItem,
|
||||
let new_entry = Object.assign({}, mealPlan)
|
||||
new_entry.fromDate = targetDate
|
||||
new_entry.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||
new_entry.addshopping = mealPlan.shopping
|
||||
useMealPlanStore().createObject(new_entry)
|
||||
} else {
|
||||
mealPlan.fromDate = targetDate
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
{{ fv.food.name }}
|
||||
</span>
|
||||
<template #append>
|
||||
<v-chip v-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
|
||||
<v-chip color="create" v-else-if="fv.missing_conversion" class="cursor-pointer" prepend-icon="$create">
|
||||
<v-chip color="create" v-if="fv.missing_conversion" class="cursor-pointer" prepend-icon="$create">
|
||||
{{ $t('Conversion') }}: {{ fv.missing_conversion.base_unit.name }} <i class="fa-solid fa-arrow-right me-1 ms-1"></i>
|
||||
{{ fv.missing_conversion.converted_unit.name }}
|
||||
<model-edit-dialog model="UnitConversion" @create="refreshRecipe()"
|
||||
:item-defaults="{baseAmount: 1, baseUnit: fv.missing_conversion.base_unit, convertedUnit: fv.missing_conversion.converted_unit, food: fv.food}"></model-edit-dialog>
|
||||
</v-chip>
|
||||
<v-chip v-else-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
|
||||
<v-chip color="warning" prepend-icon="$edit" class="cursor-pointer" :to="{name: 'ModelEditPage', params: {model: 'Recipe', id: recipe.id}}" v-else-if="fv.missing_unit">
|
||||
{{ $t('NoUnit') }}
|
||||
</v-chip>
|
||||
|
||||
@@ -229,7 +229,7 @@ const selectedAiProvider = ref<undefined | AiProvider>(useUserPreferenceStore().
|
||||
* factor for multiplying ingredient amounts based on recipe base servings and user selected servings
|
||||
*/
|
||||
const ingredientFactor = computed(() => {
|
||||
return servings.value / ((recipe.value.servings != undefined) ? recipe.value.servings : 1)
|
||||
return servings.value / ((recipe.value.servings != undefined) ? Math.max(recipe.value.servings, 1) : 1)
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-btn-group density="compact">
|
||||
<v-btn color="create" @click="editingObj.properties.push({} as Property)" prepend-icon="$create">{{ $t('Add') }}</v-btn>
|
||||
<v-btn color="create" @click="editingObj.properties.push({} as Property); addPropertiesFoodUnit()" prepend-icon="$create">{{ $t('Add') }}</v-btn>
|
||||
<v-btn color="secondary" @click="addAllProperties" prepend-icon="fa-solid fa-list">{{ $t('AddAll') }}</v-btn>
|
||||
<ai-action-button color="info" @selected="propertiesFromAi" :loading="aiLoading" prepend-icon="$ai">{{ $t('AI') }}</ai-action-button>
|
||||
</v-btn-group>
|
||||
@@ -41,12 +41,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ApiApi, Food, Property, Recipe} from "@/openapi";
|
||||
import {ApiApi, Food, Property, Recipe, Unit} from "@/openapi";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {computed, nextTick, onMounted, ref} from "vue";
|
||||
import AiActionButton from "@/components/buttons/AiActionButton.vue";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
|
||||
const props = defineProps({
|
||||
amountFor: {type: String, required: true},
|
||||
@@ -78,9 +79,11 @@ function deleteProperty(property: Property) {
|
||||
function addAllProperties() {
|
||||
const api = new ApiApi()
|
||||
|
||||
if (editingObj.value.properties) {
|
||||
editingObj.value.properties = []
|
||||
}
|
||||
// if (editingObj.value.properties) {
|
||||
// editingObj.value.properties = []
|
||||
// }
|
||||
|
||||
addPropertiesFoodUnit()
|
||||
|
||||
api.apiPropertyTypeList().then(r => {
|
||||
r.results.forEach(pt => {
|
||||
@@ -98,6 +101,9 @@ function propertiesFromAi(providerId: number) {
|
||||
if (isFood.value) {
|
||||
api.apiFoodAipropertiesCreate({id: editingObj.value.id!, food: editingObj.value, provider: providerId}).then(r => {
|
||||
editingObj.value = r
|
||||
nextTick(() => {
|
||||
addPropertiesFoodUnit()
|
||||
})
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
@@ -115,6 +121,17 @@ function propertiesFromAi(providerId: number) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* if its empty add the properties food unit
|
||||
*/
|
||||
function addPropertiesFoodUnit(){
|
||||
console.log('ADDING UNIT', !editingObj.value.propertiesFoodUnit)
|
||||
if (isFood.value && !editingObj.value.propertiesFoodUnit) {
|
||||
console.log('ADDING UNIT ACTUALLY')
|
||||
editingObj.value.propertiesFoodUnit = (useUserPreferenceStore().defaultUnitObj != null) ? useUserPreferenceStore().defaultUnitObj! : {name: 'g'} as Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
</div>
|
||||
<div class="d-flex flex-nowrap">
|
||||
<div class="flex-col flex-grow-0 ma-1" style="min-width: 15%" v-if="!ingredient.isHeader">
|
||||
<v-text-field :id="`id_input_amount_${props.stepIndex}_${index}`" :label="$t('Amount')" type="number" v-model="ingredient.amount" density="compact"
|
||||
hide-details :disabled="ingredient.noAmount">
|
||||
<v-number-input :id="`id_input_amount_${props.stepIndex}_${index}`" :label="$t('Amount')" v-model="ingredient.amount" density="compact"
|
||||
hide-details control-variant="hidden" :disabled="ingredient.noAmount" :precision="useUserPreferenceStore().userSettings.ingredientDecimals">
|
||||
|
||||
<template #prepend>
|
||||
<v-icon icon="$dragHandle" class="drag-handle cursor-grab"></v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-number-input>
|
||||
</div>
|
||||
<div class="flex-col flex-grow-0 ma-1" style="min-width: 15%" v-if="!ingredient.isHeader ">
|
||||
<model-select model="Unit" v-model="ingredient.unit" density="compact" allow-create hide-details :disabled="ingredient.noAmount"></model-select>
|
||||
@@ -195,7 +195,7 @@
|
||||
<v-text-field :label="$t('Original_Text')" readonly v-model="step.ingredients[editingIngredientIndex].originalText"
|
||||
v-if="step.ingredients[editingIngredientIndex].originalText"></v-text-field>
|
||||
<v-number-input v-model="step.ingredients[editingIngredientIndex].amount" inset control-variant="stacked" autofocus :label="$t('Amount')"
|
||||
:min="0" :precision="2" v-if="!step.ingredients[editingIngredientIndex].isHeader"></v-number-input>
|
||||
:min="0" :precision="useUserPreferenceStore().userSettings.ingredientDecimals" v-if="!step.ingredients[editingIngredientIndex].isHeader"></v-number-input>
|
||||
<model-select model="Unit" v-model="step.ingredients[editingIngredientIndex].unit" :label="$t('Unit')" v-if="!step.ingredients[editingIngredientIndex].isHeader"
|
||||
allow-create></model-select>
|
||||
<model-select model="Food" v-model="step.ingredients[editingIngredientIndex].food" :label="$t('Food')" v-if="!step.ingredients[editingIngredientIndex].isHeader"
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<v-list density="compact">
|
||||
<v-list-subheader>{{$t('Ingredients')}}</v-list-subheader>
|
||||
<v-list-item
|
||||
v-for="template in templates"
|
||||
@click="insertTextAtPosition(template.template + ' ')"
|
||||
v-for="t in templates"
|
||||
@click="insertTextAtPosition(t.template + ' ')"
|
||||
>
|
||||
<ingredient-string :ingredient="template.ingredient"></ingredient-string>
|
||||
<ingredient-string :ingredient="t.ingredient"></ingredient-string>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
@@ -65,7 +65,7 @@ const templates = computed(() => {
|
||||
function insertTextAtPosition(text: string){
|
||||
let textarea = markdownEditor.value.getTextareaDom()
|
||||
let position = textarea.selectionStart
|
||||
if (step.value.instruction){
|
||||
if (step.value.instruction != undefined){
|
||||
step.value.instruction = step.value.instruction.slice(0, position) + text + step.value.instruction.slice(position)
|
||||
|
||||
nextTick(() => {
|
||||
|
||||
@@ -226,7 +226,7 @@ function initializeEditor() {
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.propertiesFoodAmount = 100
|
||||
editingObj.value.propertiesFoodUnit = {name: (useUserPreferenceStore().userSettings.defaultUnit != undefined ? useUserPreferenceStore().userSettings.defaultUnit : 'g')} as Unit
|
||||
editingObj.value.propertiesFoodUnit = (useUserPreferenceStore().defaultUnitObj != null) ? useUserPreferenceStore().defaultUnitObj! : {name: 'g'} as Unit
|
||||
},
|
||||
itemDefaults: props.itemDefaults,
|
||||
})
|
||||
|
||||
@@ -87,6 +87,12 @@
|
||||
</v-row>
|
||||
|
||||
<v-form :disabled="loading || fileApiLoading">
|
||||
<v-row v-if="editingObj.steps.length == 0">
|
||||
<v-col class="text-center">
|
||||
<v-btn icon="$create" variant="outlined" size="x-small" @click="addStep(i+1)"></v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-for="(s,i ) in editingObj.steps" :key="s.id" dense>
|
||||
<v-col>
|
||||
<step-editor v-model="editingObj.steps[i]" v-model:recipe="editingObj" :step-index="i" @delete="deleteStepAtIndex(i)" @move="dialogStepManager = true"></step-editor>
|
||||
|
||||
@@ -29,7 +29,11 @@
|
||||
<v-checkbox v-model="useUserPreferenceStore().deviceSettings.start_showMealPlan" :label="$t('ShowMealPlanOnStartPage')"></v-checkbox>
|
||||
|
||||
<v-btn @click="useUserPreferenceStore().resetDeviceSettings()" color="warning">{{ $t('Reset') }}</v-btn> <br/>
|
||||
<v-btn @click="useUserPreferenceStore().deviceSettings.general_closedHelpAlerts = []" color="warning" class="mt-1">{{ $t('ResetHelp') }}</v-btn>
|
||||
<v-btn @click="useUserPreferenceStore().deviceSettings.general_closedHelpAlerts = []" color="warning" class="mt-1">{{ $t('ResetHelp') }}</v-btn> <br/>
|
||||
<v-btn color="info" class="mt-1">
|
||||
<message-list-dialog></message-list-dialog>
|
||||
{{ $t('Messages') }}
|
||||
</v-btn>
|
||||
|
||||
</v-form>
|
||||
</template>
|
||||
@@ -43,6 +47,7 @@ import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/Messa
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
import ThankYouNote from "@/components/display/ThankYouNote.vue";
|
||||
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
Authentication works by proving the word <code>Bearer</code> followed by an API Token as a request Authorization
|
||||
header as shown below. <br/>
|
||||
<code>Authorization: Bearer TOKEN</code> -or-<br/>
|
||||
<code>curl -X GET http://your.domain.com/api/recipes/ -H 'Authorization:
|
||||
<code>curl -X GET http://your.domain.com/api/recipe/ -H 'Authorization:
|
||||
Bearer TOKEN'</code>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -124,6 +124,8 @@ export function useFileApi() {
|
||||
* @returns Promise resolving to the import ID of the app import
|
||||
*/
|
||||
function doAppImport(files: File[], app: string, includeDuplicates: boolean, mealPlans: boolean = true, shoppingLists: boolean = true, nutritionPerServing: boolean = false,) {
|
||||
fileApiLoading.value = true
|
||||
|
||||
let formData = new FormData()
|
||||
formData.append('type', app);
|
||||
formData.append('duplicates', includeDuplicates ? 'true' : 'false')
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"BaseUnit": "Basiseinheit",
|
||||
"BaseUnitHelp": "Optionale Standardeinheit zur automatischen Umrechnung",
|
||||
"Basics": "Grundlagen",
|
||||
"BatchDeleteConfirm": "Möchtest du alle angezeigten Objekte löschen? Dies kann nicht rückgängig gemacht werden!",
|
||||
"BatchDeleteConfirm": "Möchtest du alle angezeigten Objekte löschen? Dies kann nicht rückgängig gemacht werden! ACHTUNG: Es ist möglich das Objekte gelöscht werden die an anderen Stellen verwendet werden!",
|
||||
"BatchDeleteHelp": "Wenn ein Objekt nicht gelöscht werden kann, wird es noch irgendwo verwendet. ",
|
||||
"BatchEdit": "Massenbearbeitung",
|
||||
"BatchEditUpdatingItemsCount": "Bearbeite {count} {type}",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"BaseUnit": "Base Unit",
|
||||
"BaseUnitHelp": "Standard unit for automatic unit conversion",
|
||||
"Basics": "Basics",
|
||||
"BatchDeleteConfirm": "Do you want to delete all shown items? This cannot be undone!",
|
||||
"BatchDeleteConfirm": "Do you want to delete all shown items? This cannot be undone! WARNING: It is possible that this deletes objects that are used elsewhere. ",
|
||||
"BatchDeleteHelp": "If an item cannot be deleted it is used somewhere. ",
|
||||
"BatchEdit": "Batch Edit",
|
||||
"BatchEditUpdatingItemsCount": "Editing {count} {type}",
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"BaseUnit": "Unità di base",
|
||||
"BaseUnitHelp": "Unità standard per la conversione automatica di unità",
|
||||
"Basics": "Informazioni di base",
|
||||
"BatchDeleteConfirm": "Vuoi eliminare tutti gli elementi mostrati? Questo non può essere annullato!",
|
||||
"BatchDeleteConfirm": "",
|
||||
"BatchDeleteHelp": "Se un elemento non può essere eliminato, è utilizzato altrove. ",
|
||||
"BatchEdit": "Modifica massiva",
|
||||
"BatchEditUpdatingItemsCount": "Modifica di {count} {type}",
|
||||
|
||||
866
vue3/src/locales/ko.json
Normal file
866
vue3/src/locales/ko.json
Normal file
@@ -0,0 +1,866 @@
|
||||
{
|
||||
"AI": "",
|
||||
"AIImportSubtitle": "",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "",
|
||||
"APIKey": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"AccessTokenHelp": "",
|
||||
"Access_Token": "",
|
||||
"Account": "",
|
||||
"Actions": "",
|
||||
"Active": "",
|
||||
"Activity": "",
|
||||
"Add": "",
|
||||
"AddAll": "",
|
||||
"AddChild": "",
|
||||
"AddFilter": "",
|
||||
"AddFoodToShopping": "",
|
||||
"AddMany": "",
|
||||
"AddToShopping": "",
|
||||
"Add_Servings_to_Shopping": "",
|
||||
"Add_Step": "",
|
||||
"Add_nutrition_recipe": "",
|
||||
"Add_to_Plan": "",
|
||||
"Add_to_Shopping": "",
|
||||
"Added_To_Shopping_List": "",
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Admin": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "",
|
||||
"AllRecipes": "",
|
||||
"Amount": "",
|
||||
"App": "",
|
||||
"AppImportSubtitle": "",
|
||||
"Apply": "",
|
||||
"Are_You_Sure": "",
|
||||
"Auto_Planner": "",
|
||||
"Auto_Sort": "",
|
||||
"Auto_Sort_Help": "",
|
||||
"Automate": "",
|
||||
"Automation": "",
|
||||
"AutomationHelp": "",
|
||||
"Available": "",
|
||||
"AvailableCategories": "",
|
||||
"Back": "",
|
||||
"BaseUnit": "",
|
||||
"BaseUnitHelp": "",
|
||||
"Basics": "",
|
||||
"BatchDeleteConfirm": "",
|
||||
"BatchDeleteHelp": "",
|
||||
"BatchEdit": "",
|
||||
"BatchEditUpdatingItemsCount": "",
|
||||
"Blocking": "",
|
||||
"BlockingHelp": "",
|
||||
"Book": "",
|
||||
"Bookmarklet": "",
|
||||
"BookmarkletHelp1": "",
|
||||
"BookmarkletHelp2": "",
|
||||
"BookmarkletHelp3": "",
|
||||
"BookmarkletImportSubtitle": "",
|
||||
"Books": "",
|
||||
"CREATE_ERROR": "",
|
||||
"Calculator": "",
|
||||
"Calories": "",
|
||||
"Cancel": "",
|
||||
"Cannot_Add_Notes_To_Shopping": "",
|
||||
"Carbohydrates": "",
|
||||
"Cards": "",
|
||||
"Cascading": "",
|
||||
"CascadingHelp": "",
|
||||
"Categories": "",
|
||||
"Category": "",
|
||||
"CategoryInstruction": "",
|
||||
"CategoryName": "",
|
||||
"Change_Password": "",
|
||||
"Changing": "",
|
||||
"ChildInheritFields": "",
|
||||
"ChildInheritFields_help": "",
|
||||
"Choose_Category": "",
|
||||
"Clear": "",
|
||||
"Click_To_Edit": "",
|
||||
"Clone": "",
|
||||
"Close": "",
|
||||
"Color": "",
|
||||
"Combine_All_Steps": "",
|
||||
"Coming_Soon": "",
|
||||
"Comment": "",
|
||||
"Comments_setting": "",
|
||||
"Completed": "",
|
||||
"Confirm": "",
|
||||
"ConnectorConfig": "",
|
||||
"ConnectorConfigHelp": "",
|
||||
"Continue": "",
|
||||
"Conversion": "",
|
||||
"ConversionsHelp": "",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "",
|
||||
"CookLogHelp": "",
|
||||
"Cooked": "",
|
||||
"Copied": "",
|
||||
"Copy": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
"Copy_template_reference": "",
|
||||
"Cosmetic": "",
|
||||
"CountMore": "",
|
||||
"Create": "",
|
||||
"Create Food": "",
|
||||
"Create Recipe": "",
|
||||
"CreateFirstRecipe": "",
|
||||
"CreateInvitation": "",
|
||||
"Create_Meal_Plan_Entry": "",
|
||||
"Create_New_Food": "",
|
||||
"Create_New_Keyword": "",
|
||||
"Create_New_Meal_Type": "",
|
||||
"Create_New_Shopping Category": "",
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Created": "",
|
||||
"CreatedBy": "",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"CustomImageHelp": "",
|
||||
"CustomLogoHelp": "",
|
||||
"CustomLogos": "",
|
||||
"CustomNavLogoHelp": "",
|
||||
"CustomTheme": "",
|
||||
"CustomThemeHelp": "",
|
||||
"DELETE_ERROR": "",
|
||||
"Data_Import_Info": "",
|
||||
"Database": "",
|
||||
"DatabaseHelp": "",
|
||||
"Datatype": "",
|
||||
"Date": "",
|
||||
"Day": "",
|
||||
"Days": "",
|
||||
"Decimals": "",
|
||||
"Default": "",
|
||||
"DefaultPage": "",
|
||||
"Default_Unit": "",
|
||||
"DelayFor": "",
|
||||
"DelayUntil": "",
|
||||
"Delete": "",
|
||||
"DeleteConfirmQuestion": "",
|
||||
"DeleteShoppingConfirm": "",
|
||||
"DeleteSomething": "",
|
||||
"Delete_All": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"Description_Replace": "",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
"Disable": "",
|
||||
"Disable_Amount": "",
|
||||
"Disabled": "",
|
||||
"Documentation": "",
|
||||
"DontChange": "",
|
||||
"Down": "",
|
||||
"Download": "",
|
||||
"DragToUpload": "",
|
||||
"Drag_Here_To_Delete": "",
|
||||
"Duplicate": "",
|
||||
"DuplicateFoundInfo": "",
|
||||
"Edit": "",
|
||||
"Edit_Food": "",
|
||||
"Edit_Keyword": "",
|
||||
"Edit_Meal_Plan_Entry": "",
|
||||
"Edit_Recipe": "",
|
||||
"Email": "",
|
||||
"Empty": "",
|
||||
"Enable": "",
|
||||
"Enable_Amount": "",
|
||||
"Enabled": "",
|
||||
"EndDate": "",
|
||||
"Energy": "",
|
||||
"Entries": "",
|
||||
"Error": "",
|
||||
"ErrorUrlListImport": "",
|
||||
"Events": "",
|
||||
"Export": "",
|
||||
"Export_As_ICal": "",
|
||||
"Export_Not_Yet_Supported": "",
|
||||
"Export_Supported": "",
|
||||
"Export_To_ICal": "",
|
||||
"External": "",
|
||||
"ExternalRecipe": "",
|
||||
"ExternalRecipeImport": "",
|
||||
"ExternalRecipeImportHelp": "",
|
||||
"ExternalStorage": "",
|
||||
"External_Recipe_Image": "",
|
||||
"FDC_ID": "",
|
||||
"FDC_ID_help": "",
|
||||
"FDC_Search": "",
|
||||
"FETCH_ERROR": "",
|
||||
"Failure": "",
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"FinishedAt": "",
|
||||
"First": "",
|
||||
"First_name": "",
|
||||
"Food": "",
|
||||
"FoodHelp": "",
|
||||
"FoodInherit": "",
|
||||
"FoodNotOnHand": "",
|
||||
"FoodOnHand": "",
|
||||
"Food_Alias": "",
|
||||
"Food_Replace": "",
|
||||
"Foods": "",
|
||||
"Friday": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"GettingStarted": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"Group": "",
|
||||
"GroupBy": "",
|
||||
"HeaderWarning": "",
|
||||
"Headline": "",
|
||||
"Help": "",
|
||||
"Hide_External": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
"Hide_Keywords": "",
|
||||
"Hide_Recipes": "",
|
||||
"Hide_as_header": "",
|
||||
"Hierarchy": "",
|
||||
"History": "",
|
||||
"HostedFreeVersion": "",
|
||||
"Hour": "",
|
||||
"Hours": "",
|
||||
"Icon": "",
|
||||
"IgnoreAccents": "",
|
||||
"IgnoreAccentsHelp": "",
|
||||
"IgnoreThis": "",
|
||||
"Ignore_Shopping": "",
|
||||
"IgnoredFood": "",
|
||||
"Image": "",
|
||||
"Import": "",
|
||||
"Import Recipe": "",
|
||||
"ImportAll": "",
|
||||
"ImportFirstRecipe": "",
|
||||
"ImportIntoTandoor": "",
|
||||
"ImportMealPlans": "",
|
||||
"ImportShoppingList": "",
|
||||
"Import_Error": "",
|
||||
"Import_Not_Yet_Supported": "",
|
||||
"Import_Result_Info": "",
|
||||
"Import_Supported": "",
|
||||
"Import_finished": "",
|
||||
"Imported": "",
|
||||
"Imported_From": "",
|
||||
"Importer_Help": "",
|
||||
"Information": "",
|
||||
"Ingredient": "",
|
||||
"Ingredient Editor": "",
|
||||
"Ingredient Overview": "",
|
||||
"IngredientEditorHelp": "",
|
||||
"IngredientHelp": "",
|
||||
"IngredientInShopping": "",
|
||||
"Ingredients": "",
|
||||
"Inherit": "",
|
||||
"InheritFields": "",
|
||||
"InheritFields_help": "",
|
||||
"InheritWarning": "",
|
||||
"Input": "",
|
||||
"Instruction_Replace": "",
|
||||
"Instructions": "",
|
||||
"InstructionsEditHelp": "",
|
||||
"Internal": "",
|
||||
"InviteLinkHelp": "",
|
||||
"Invite_Link": "",
|
||||
"Invites": "",
|
||||
"Key_Ctrl": "",
|
||||
"Key_Shift": "",
|
||||
"Keyword": "",
|
||||
"KeywordHelp": "",
|
||||
"Keyword_Alias": "",
|
||||
"Keywords": "",
|
||||
"Language": "",
|
||||
"Last": "",
|
||||
"Last_name": "",
|
||||
"Learn_More": "",
|
||||
"LeaveSpace": "",
|
||||
"Link": "",
|
||||
"Load": "",
|
||||
"Load_More": "",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "",
|
||||
"Log_Recipe_Cooking": "",
|
||||
"Logo": "",
|
||||
"Logout": "",
|
||||
"Make_Header": "",
|
||||
"Make_Ingredient": "",
|
||||
"ManageSubscription": "",
|
||||
"Manage_Books": "",
|
||||
"Manage_Emails": "",
|
||||
"MealPlanHelp": "",
|
||||
"MealPlanShoppingHelp": "",
|
||||
"MealTypeHelp": "",
|
||||
"Meal_Plan": "",
|
||||
"Meal_Plan_Days": "",
|
||||
"Meal_Type": "",
|
||||
"Meal_Type_Required": "",
|
||||
"Meal_Types": "",
|
||||
"Merge": "",
|
||||
"MergeAutomateHelp": "",
|
||||
"MergeInsteadOfDelete": "",
|
||||
"Merge_Keyword": "",
|
||||
"Message": "",
|
||||
"Messages": "",
|
||||
"Miscellaneous": "",
|
||||
"MissingConversion": "",
|
||||
"MissingProperties": "",
|
||||
"Model": "",
|
||||
"ModelSelectResultsHelp": "",
|
||||
"Monday": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "",
|
||||
"Move": "",
|
||||
"MoveCategory": "",
|
||||
"MoveToStep": "",
|
||||
"Move_Down": "",
|
||||
"Move_Food": "",
|
||||
"Move_Keyword": "",
|
||||
"Move_Up": "",
|
||||
"Multiple": "",
|
||||
"Name": "",
|
||||
"Name_Replace": "",
|
||||
"Nav_Color": "",
|
||||
"Nav_Color_Help": "",
|
||||
"Nav_Text_Mode": "",
|
||||
"Nav_Text_Mode_Help": "",
|
||||
"Never_Unit": "",
|
||||
"New": "",
|
||||
"New_Cookbook": "",
|
||||
"New_Entry": "",
|
||||
"New_Food": "",
|
||||
"New_Keyword": "",
|
||||
"New_Meal_Type": "",
|
||||
"New_Recipe": "",
|
||||
"New_Supermarket": "",
|
||||
"New_Supermarket_Category": "",
|
||||
"New_Unit": "",
|
||||
"Next": "",
|
||||
"Next_Day": "",
|
||||
"Next_Period": "",
|
||||
"No": "",
|
||||
"NoCategory": "",
|
||||
"NoMoreUndo": "",
|
||||
"NoUnit": "",
|
||||
"No_ID": "",
|
||||
"No_Results": "",
|
||||
"NotFound": "",
|
||||
"NotFoundHelp": "",
|
||||
"NotInShopping": "",
|
||||
"Note": "",
|
||||
"NullingHelp": "",
|
||||
"Number of Objects": "",
|
||||
"Nutrition": "",
|
||||
"NutritionsPerServing": "",
|
||||
"NutritionsPerServingHelp": "",
|
||||
"OfflineAlert": "",
|
||||
"Ok": "",
|
||||
"OnHand": "",
|
||||
"OnHand_help": "",
|
||||
"Open": "",
|
||||
"Open_Data_Import": "",
|
||||
"Open_Data_Slug": "",
|
||||
"Options": "",
|
||||
"Order": "",
|
||||
"OrderInformation": "",
|
||||
"Original_Text": "",
|
||||
"Owner": "",
|
||||
"Page": "",
|
||||
"Parameter": "",
|
||||
"Parent": "",
|
||||
"PartialMatch": "",
|
||||
"PartialMatchHelp": "",
|
||||
"Password": "",
|
||||
"Path": "",
|
||||
"PerPage": "",
|
||||
"Period": "",
|
||||
"Periods": "",
|
||||
"Pin": "",
|
||||
"Pinned": "",
|
||||
"PinnedConfirmation": "",
|
||||
"Plan_Period_To_Show": "",
|
||||
"Plan_Show_How_Many_Periods": "",
|
||||
"Planned": "",
|
||||
"Planner": "",
|
||||
"Planner_Settings": "",
|
||||
"Planning&Shopping": "",
|
||||
"Plural": "",
|
||||
"Postpone": "",
|
||||
"PostponedUntil": "",
|
||||
"PrecisionSearchHelp": "",
|
||||
"Preferences": "",
|
||||
"Preparation": "",
|
||||
"Preview": "",
|
||||
"Previous_Day": "",
|
||||
"Previous_Period": "",
|
||||
"Print": "",
|
||||
"Private": "",
|
||||
"Private_Recipe": "",
|
||||
"Private_Recipe_Help": "",
|
||||
"Profile": "",
|
||||
"Properties": "",
|
||||
"PropertiesFoodHelp": "",
|
||||
"Properties_Food_Amount": "",
|
||||
"Properties_Food_Unit": "",
|
||||
"Property": "",
|
||||
"PropertyHelp": "",
|
||||
"PropertyType": "",
|
||||
"PropertyTypeHelp": "",
|
||||
"Property_Editor": "",
|
||||
"Protected": "",
|
||||
"Proteins": "",
|
||||
"Quick actions": "",
|
||||
"QuickEntry": "",
|
||||
"Random Recipes": "",
|
||||
"RandomOrder": "",
|
||||
"RateLimit": "",
|
||||
"RateLimitHelp": "",
|
||||
"Rating": "",
|
||||
"Ratings": "",
|
||||
"Recently_Viewed": "",
|
||||
"Recipe": "",
|
||||
"RecipeBookEntryHelp": "",
|
||||
"RecipeBookHelp": "",
|
||||
"RecipeHelp": "",
|
||||
"RecipeStepsHelp": "",
|
||||
"Recipe_Book": "",
|
||||
"Recipe_Image": "",
|
||||
"Recipes": "",
|
||||
"Recipes_In_Import": "",
|
||||
"Recipes_per_page": "",
|
||||
"Refresh": "",
|
||||
"Remove": "",
|
||||
"RemoveAllType": "",
|
||||
"RemoveFoodFromShopping": "",
|
||||
"RemoveParent": "",
|
||||
"Remove_nutrition_recipe": "",
|
||||
"Reset": "",
|
||||
"ResetHelp": "",
|
||||
"Reset_Search": "",
|
||||
"Reusable": "",
|
||||
"Role": "",
|
||||
"Root": "",
|
||||
"Saturday": "",
|
||||
"Save": "",
|
||||
"Save/Load": "",
|
||||
"Save_and_View": "",
|
||||
"SavedSearch": "",
|
||||
"SavedSearchHelp": "",
|
||||
"ScalableNumber": "",
|
||||
"Search": "",
|
||||
"Search Settings": "",
|
||||
"SearchMethod": "",
|
||||
"SearchSettingsOverview": "",
|
||||
"SearchSettingsWarning": "",
|
||||
"Second": "",
|
||||
"Seconds": "",
|
||||
"Select": "",
|
||||
"SelectAll": "",
|
||||
"SelectNone": "",
|
||||
"Select_App_To_Import": "",
|
||||
"Select_Book": "",
|
||||
"Select_File": "",
|
||||
"Selected": "",
|
||||
"SelectedCategories": "",
|
||||
"Serving": "",
|
||||
"Servings": "",
|
||||
"ServingsText": "",
|
||||
"Settings": "",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"ShopLater": "",
|
||||
"ShopNow": "",
|
||||
"ShoppingBackgroundSyncWarning": "",
|
||||
"ShoppingListEntry": "",
|
||||
"ShoppingListEntryHelp": "",
|
||||
"ShoppingListRecipe": "",
|
||||
"Shopping_Categories": "",
|
||||
"Shopping_Category": "",
|
||||
"Shopping_List_Empty": "",
|
||||
"Shopping_input_placeholder": "",
|
||||
"Shopping_list": "",
|
||||
"ShowDelayed": "",
|
||||
"ShowIngredients": "",
|
||||
"ShowMealPlanOnStartPage": "",
|
||||
"ShowRecentlyCompleted": "",
|
||||
"ShowUncategorizedFood": "",
|
||||
"Show_Logo": "",
|
||||
"Show_Logo_Help": "",
|
||||
"Show_Week_Numbers": "",
|
||||
"Show_as_header": "",
|
||||
"Single": "",
|
||||
"Size": "",
|
||||
"Skip": "",
|
||||
"Social_Authentication": "",
|
||||
"Sort_by_new": "",
|
||||
"Source": "",
|
||||
"SourceImportHelp": "",
|
||||
"SourceImportSubtitle": "",
|
||||
"Space": "",
|
||||
"SpaceHelp": "",
|
||||
"SpaceLimitExceeded": "",
|
||||
"SpaceLimitReached": "",
|
||||
"SpaceMemberHelp": "",
|
||||
"SpaceMembers": "",
|
||||
"SpaceMembersHelp": "",
|
||||
"SpaceName": "",
|
||||
"SpacePrivateObjectsHelp": "",
|
||||
"SpaceSettings": "",
|
||||
"Space_Cosmetic_Settings": "",
|
||||
"Split": "",
|
||||
"Split_All_Steps": "",
|
||||
"StartDate": "",
|
||||
"Starting_Day": "",
|
||||
"StartsWith": "",
|
||||
"StartsWithHelp": "",
|
||||
"Step": "",
|
||||
"StepHelp": "",
|
||||
"Step_Name": "",
|
||||
"Step_Type": "",
|
||||
"Step_start_time": "",
|
||||
"Steps": "",
|
||||
"StepsOverview": "",
|
||||
"Sticky_Nav": "",
|
||||
"Sticky_Nav_Help": "",
|
||||
"Storage": "",
|
||||
"StorageHelp": "",
|
||||
"StoragePasswordTokenHelp": "",
|
||||
"Structured": "",
|
||||
"SubstituteOnHand": "",
|
||||
"Substitutes": "",
|
||||
"Success": "",
|
||||
"SuccessClipboard": "",
|
||||
"Summary": "",
|
||||
"Sunday": "",
|
||||
"Supermarket": "",
|
||||
"SupermarketCategoriesOnly": "",
|
||||
"SupermarketCategoryHelp": "",
|
||||
"SupermarketHelp": "",
|
||||
"SupermarketName": "",
|
||||
"Supermarkets": "",
|
||||
"SupportsDescriptionField": "",
|
||||
"SyncLog": "",
|
||||
"SyncLogHelp": "",
|
||||
"SyncedPath": "",
|
||||
"SyncedPathHelp": "",
|
||||
"System": "",
|
||||
"Table": "",
|
||||
"Table_of_Contents": "",
|
||||
"Text": "",
|
||||
"ThankYou": "",
|
||||
"ThanksTextHosted": "",
|
||||
"ThanksTextSelfhosted": "",
|
||||
"Theme": "",
|
||||
"Thursday": "",
|
||||
"Time": "",
|
||||
"Title": "",
|
||||
"Title_or_Recipe_Required": "",
|
||||
"Today": "",
|
||||
"Toggle": "",
|
||||
"Transpose_Words": "",
|
||||
"TrigramThreshold": "",
|
||||
"TrigramThresholdHelp": "",
|
||||
"Tuesday": "",
|
||||
"Type": "",
|
||||
"UPDATE_ERROR": "",
|
||||
"Unchanged": "",
|
||||
"Undefined": "",
|
||||
"Undo": "",
|
||||
"Unit": "",
|
||||
"UnitConversion": "",
|
||||
"UnitConversionHelp": "",
|
||||
"UnitHelp": "",
|
||||
"Unit_Alias": "",
|
||||
"Unit_Replace": "",
|
||||
"Units": "",
|
||||
"Unpin": "",
|
||||
"UnpinnedConfirmation": "",
|
||||
"Unrated": "",
|
||||
"Up": "",
|
||||
"Update": "",
|
||||
"Update_Existing_Data": "",
|
||||
"Updated": "",
|
||||
"UpgradeNow": "",
|
||||
"Url": "",
|
||||
"UrlImportSubtitle": "",
|
||||
"UrlList": "",
|
||||
"UrlListSubtitle": "",
|
||||
"Url_Import": "",
|
||||
"Use_Fractions": "",
|
||||
"Use_Fractions_Help": "",
|
||||
"Use_Kj": "",
|
||||
"Use_Metric": "",
|
||||
"Use_Plural_Food_Always": "",
|
||||
"Use_Plural_Food_Simple": "",
|
||||
"Use_Plural_Unit_Always": "",
|
||||
"Use_Plural_Unit_Simple": "",
|
||||
"User": "",
|
||||
"UserFileHelp": "",
|
||||
"UserHelp": "",
|
||||
"Username": "",
|
||||
"Users": "",
|
||||
"Valid Until": "",
|
||||
"View": "",
|
||||
"ViewLogHelp": "",
|
||||
"View_Recipes": "",
|
||||
"Viewed": "",
|
||||
"Visibility": "",
|
||||
"Waiting": "",
|
||||
"WaitingTime": "",
|
||||
"WarnPageLeave": "",
|
||||
"Warning": "",
|
||||
"WarningRecipeBookEntryDuplicate": "",
|
||||
"Warning_Delete_Supermarket_Category": "",
|
||||
"Website": "",
|
||||
"Wednesday": "",
|
||||
"Week": "",
|
||||
"Week_Numbers": "",
|
||||
"Welcome": "",
|
||||
"WelcomeSettingsHelp": "",
|
||||
"WelcometoTandoor": "",
|
||||
"WorkingTime": "",
|
||||
"Year": "",
|
||||
"Yes": "",
|
||||
"YourSpaces": "",
|
||||
"active": "",
|
||||
"add_keyword": "",
|
||||
"additional_options": "",
|
||||
"advanced": "",
|
||||
"advanced_search_settings": "",
|
||||
"after": "",
|
||||
"all": "",
|
||||
"all_fields_optional": "",
|
||||
"and": "",
|
||||
"and_down": "",
|
||||
"and_up": "",
|
||||
"any": "",
|
||||
"asc": "",
|
||||
"base_amount": "",
|
||||
"base_unit": "",
|
||||
"before": "",
|
||||
"book_filter_help": "",
|
||||
"click_image_import": "",
|
||||
"confirm_delete": "",
|
||||
"convert_internal": "",
|
||||
"converted_amount": "",
|
||||
"converted_unit": "",
|
||||
"copy_markdown_table": "",
|
||||
"copy_to_clipboard": "",
|
||||
"copy_to_new": "",
|
||||
"create_food_desc": "",
|
||||
"create_rule": "",
|
||||
"create_title": "",
|
||||
"created_by": "",
|
||||
"created_on": "",
|
||||
"csv_delim_help": "",
|
||||
"csv_delim_label": "",
|
||||
"csv_prefix_help": "",
|
||||
"csv_prefix_label": "",
|
||||
"date_created": "",
|
||||
"date_viewed": "",
|
||||
"default_delay": "",
|
||||
"default_delay_desc": "",
|
||||
"del_confirmation_tree": "",
|
||||
"delete_confirmation": "",
|
||||
"delete_title": "",
|
||||
"desc": "",
|
||||
"download_csv": "",
|
||||
"download_pdf": "",
|
||||
"edit_title": "",
|
||||
"empty_list": "",
|
||||
"enable_expert": "",
|
||||
"err_creating_resource": "",
|
||||
"err_deleting_protected_resource": "",
|
||||
"err_deleting_resource": "",
|
||||
"err_fetching_resource": "",
|
||||
"err_importing_recipe": "",
|
||||
"err_merge_self": "",
|
||||
"err_merging_resource": "",
|
||||
"err_move_self": "",
|
||||
"err_moving_resource": "",
|
||||
"err_updating_resource": "",
|
||||
"exact": "",
|
||||
"exclude": "",
|
||||
"expert_mode": "",
|
||||
"explain": "",
|
||||
"fields": "",
|
||||
"file_upload_disabled": "",
|
||||
"filter": "",
|
||||
"filter_name": "",
|
||||
"filter_to_supermarket": "",
|
||||
"filter_to_supermarket_desc": "",
|
||||
"fluid_ounce": "",
|
||||
"food_inherit_info": "",
|
||||
"food_recipe_help": "",
|
||||
"g": "",
|
||||
"gallon": "",
|
||||
"hide_step_ingredients": "",
|
||||
"hours": "",
|
||||
"ignore_shopping_help": "",
|
||||
"imperial_fluid_ounce": "",
|
||||
"imperial_gallon": "",
|
||||
"imperial_pint": "",
|
||||
"imperial_quart": "",
|
||||
"imperial_tbsp": "",
|
||||
"imperial_tsp": "",
|
||||
"import_duplicates": "",
|
||||
"import_running": "",
|
||||
"in_shopping": "",
|
||||
"ingredient_list": "",
|
||||
"kg": "",
|
||||
"l": "",
|
||||
"last_cooked": "",
|
||||
"last_viewed": "",
|
||||
"left_handed": "",
|
||||
"left_handed_help": "",
|
||||
"make_now": "",
|
||||
"make_now_count": "",
|
||||
"mark_complete": "",
|
||||
"mealplan_autoadd_shopping": "",
|
||||
"mealplan_autoadd_shopping_desc": "",
|
||||
"mealplan_autoexclude_onhand": "",
|
||||
"mealplan_autoexclude_onhand_desc": "",
|
||||
"mealplan_autoinclude_related": "",
|
||||
"mealplan_autoinclude_related_desc": "",
|
||||
"merge_confirmation": "",
|
||||
"merge_selection": "",
|
||||
"merge_title": "",
|
||||
"min": "",
|
||||
"ml": "",
|
||||
"move_confirmation": "",
|
||||
"move_selection": "",
|
||||
"move_title": "",
|
||||
"no_more_images_found": "",
|
||||
"no_pinned_recipes": "",
|
||||
"not": "",
|
||||
"nothing": "",
|
||||
"nothing_planned_today": "",
|
||||
"on": "",
|
||||
"one_url_per_line": "",
|
||||
"open_data_help_text": "",
|
||||
"or": "",
|
||||
"ounce": "",
|
||||
"parameter_count": "",
|
||||
"paste_ingredients": "",
|
||||
"paste_ingredients_placeholder": "",
|
||||
"paste_json": "",
|
||||
"per_serving": "",
|
||||
"pint": "",
|
||||
"plan_share_desc": "",
|
||||
"plural_short": "",
|
||||
"plural_usage_info": "",
|
||||
"pound": "",
|
||||
"property_type_fdc_hint": "",
|
||||
"quart": "",
|
||||
"recipe_filter": "",
|
||||
"recipe_name": "",
|
||||
"recipe_property_info": "",
|
||||
"related_recipes": "",
|
||||
"remember_hours": "",
|
||||
"remember_search": "",
|
||||
"remove_selection": "",
|
||||
"reset_children": "",
|
||||
"reset_children_help": "",
|
||||
"reset_food_inheritance": "",
|
||||
"reset_food_inheritance_info": "",
|
||||
"reusable_help_text": "",
|
||||
"review_shopping": "",
|
||||
"save_filter": "",
|
||||
"searchFilterCreatedByHelp": "",
|
||||
"searchFilterObjectsAndHelp": "",
|
||||
"searchFilterObjectsAndNotHelp": "",
|
||||
"searchFilterObjectsHelp": "",
|
||||
"searchFilterObjectsOrNotHelp": "",
|
||||
"search_create_help_text": "",
|
||||
"search_import_help_text": "",
|
||||
"search_no_recipes": "",
|
||||
"search_rank": "",
|
||||
"seconds": "",
|
||||
"select_file": "",
|
||||
"select_food": "",
|
||||
"select_keyword": "",
|
||||
"select_recipe": "",
|
||||
"select_unit": "",
|
||||
"shared_with": "",
|
||||
"shopping_add_onhand": "",
|
||||
"shopping_add_onhand_desc": "",
|
||||
"shopping_auto_sync": "",
|
||||
"shopping_auto_sync_desc": "",
|
||||
"shopping_category_help": "",
|
||||
"shopping_recent_days": "",
|
||||
"shopping_recent_days_desc": "",
|
||||
"shopping_share": "",
|
||||
"shopping_share_desc": "",
|
||||
"show_books": "",
|
||||
"show_filters": "",
|
||||
"show_foods": "",
|
||||
"show_ingredient_overview": "",
|
||||
"show_ingredients_table": "",
|
||||
"show_keywords": "",
|
||||
"show_only_internal": "",
|
||||
"show_rating": "",
|
||||
"show_sortby": "",
|
||||
"show_split_screen": "",
|
||||
"show_sql": "",
|
||||
"show_step_ingredients": "",
|
||||
"show_step_ingredients_setting": "",
|
||||
"show_step_ingredients_setting_help": "",
|
||||
"show_units": "",
|
||||
"simple_mode": "",
|
||||
"sort_by": "",
|
||||
"sql_debug": "",
|
||||
"step_time_minutes": "",
|
||||
"substitute_children": "",
|
||||
"substitute_children_help": "",
|
||||
"substitute_help": "",
|
||||
"substitute_siblings": "",
|
||||
"substitute_siblings_help": "",
|
||||
"success_creating_resource": "",
|
||||
"success_deleting_resource": "",
|
||||
"success_fetching_resource": "",
|
||||
"success_merging_resource": "",
|
||||
"success_moving_resource": "",
|
||||
"success_updating_resource": "",
|
||||
"tbsp": "",
|
||||
"theUsernameCannotBeChanged": "",
|
||||
"times_cooked": "",
|
||||
"to_close": "",
|
||||
"to_navigate": "",
|
||||
"to_select": "",
|
||||
"today_recipes": "",
|
||||
"total": "",
|
||||
"tree_root": "",
|
||||
"tree_select": "",
|
||||
"tsp": "",
|
||||
"unsaved": "",
|
||||
"updatedon": "",
|
||||
"view_recipe": "",
|
||||
"warning_duplicate_filter": "",
|
||||
"warning_feature_beta": "",
|
||||
"warning_space_delete": ""
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@
|
||||
"Auto_Sort_Help": "Mover todos os ingredientes para o passo mais indicado.",
|
||||
"Automate": "Automatizar",
|
||||
"Automation": "Automação",
|
||||
"BatchDeleteConfirm": "",
|
||||
"Books": "Livros",
|
||||
"Calculator": "Calculadora",
|
||||
"Calories": "Calorias",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -49,10 +49,10 @@
|
||||
<td>
|
||||
{{ ingredient.food.name }}
|
||||
<!-- TODO weird mixture of using ingredients but not in the correct relation to the recipe not good, properly sort out and add easy unitconversion/food edit features -->
|
||||
<!-- <v-btn variant="outlined" block>-->
|
||||
<!-- {{ ingredient.food.name }}-->
|
||||
<!-- <model-edit-dialog model="Food" :item="ingredient.food!" @save="args => ingredient.food = args"></model-edit-dialog>-->
|
||||
<!-- </v-btn>-->
|
||||
<!-- <v-btn variant="outlined" block>-->
|
||||
<!-- {{ ingredient.food.name }}-->
|
||||
<!-- <model-edit-dialog model="Food" :item="ingredient.food!" @save="args => ingredient.food = args"></model-edit-dialog>-->
|
||||
<!-- </v-btn>-->
|
||||
<!-- <v-chip v-if="ingredient.unit && ingredient.food.propertiesFoodUnit && ingredient.unit.id == ingredient.food.propertiesFoodUnit.id" color="success"-->
|
||||
<!-- size="small">{{ ingredient.unit.name }}-->
|
||||
<!-- </v-chip>-->
|
||||
@@ -73,7 +73,8 @@
|
||||
@click="fdcSelectedIngredient = ingredient; fdcDialog = true"></v-btn>
|
||||
<v-btn @click="updateFoodFdcData(ingredient)" icon="fa-solid fa-arrows-rotate" size="small" density="compact" variant="plain"
|
||||
v-if="ingredient.food.fdcId"></v-btn>
|
||||
<v-btn @click="openFdcPage(ingredient.food.fdcId)" :href="`https://fdc.nal.usda.gov/food-details/${ingredient.food.fdcId}/nutrients`" target="_blank"
|
||||
<v-btn @click="openFdcPage(ingredient.food.fdcId)" :href="`https://fdc.nal.usda.gov/food-details/${ingredient.food.fdcId}/nutrients`"
|
||||
target="_blank"
|
||||
icon="fa-solid fa-arrow-up-right-from-square"
|
||||
size="small" variant="plain" v-if="ingredient.food.fdcId"></v-btn>
|
||||
</template>
|
||||
@@ -81,7 +82,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<v-number-input v-model="ingredient.food.propertiesFoodAmount" density="compact" hide-details @change="updateFood(ingredient)"
|
||||
:loading="ingredient.loading" style="min-width: 100px" control-variant="hidden" :precision="2">
|
||||
:loading="ingredient.loading" style="min-width: 100px" control-variant="hidden" :precision="2">
|
||||
|
||||
</v-number-input>
|
||||
</td>
|
||||
@@ -90,8 +91,10 @@
|
||||
:loading="ingredient.loading"></model-select>
|
||||
</td>
|
||||
<td v-for="p in ingredient.food.properties" v-bind:key="`${ingredient.food.id}_${p.propertyType.id}`">
|
||||
<v-number-input v-model="p.propertyAmount" density="compact" hide-details v-if="p.propertyAmount != null" @change="updateFood(ingredient)" :precision="2"
|
||||
:loading="ingredient.loading" @click:clear="deleteFoodProperty(p, ingredient)" style="min-width: 120px" control-variant="hidden" clearable>
|
||||
<v-number-input v-model="p.propertyAmount" density="compact" hide-details v-if="p.propertyAmount != null" @change="updateFood(ingredient)"
|
||||
:precision="2"
|
||||
:loading="ingredient.loading" @click:clear="deleteFoodProperty(p, ingredient)" style="min-width: 120px" control-variant="hidden"
|
||||
clearable>
|
||||
|
||||
</v-number-input>
|
||||
|
||||
@@ -104,11 +107,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<!-- TODO remove once append to body for model select is working properly -->
|
||||
<v-spacer style="margin-top: 120px;"></v-spacer>
|
||||
</v-table>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
|
||||
<v-card prepend-icon="fa-solid fa-calculator" :title="$t('Calculator')">
|
||||
<v-card-text>
|
||||
<v-row dense>
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<template v-if="totalRecipes > 0">
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="recent" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="new" v-if="totalRecipes > 1"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="recent" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="new" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 1"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="created_by" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="2" mode="rating" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 0"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="created_by" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="2" mode="rating" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 25"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 25"></horizontal-recipe-scroller>
|
||||
|
||||
<v-row>
|
||||
|
||||
@@ -6,6 +6,9 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
*/
|
||||
export function roundDecimals(num: number) {
|
||||
let decimals = useUserPreferenceStore().userSettings.ingredientDecimals
|
||||
if (decimals === undefined) {
|
||||
decimals = 2
|
||||
}
|
||||
return Number(num.toFixed(decimals))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {aliases, fa} from 'vuetify/iconsets/fa'
|
||||
// Composables
|
||||
import {createVuetify} from 'vuetify'
|
||||
import {DateTime} from "luxon";
|
||||
import {af, ar, az, bg, ca, ckb, cs, da, de, el, en, es, et, fi, fr, he, hr, hu, id, it, ja, km, ko, lt, lv, nl, no, pl, pt, ro, ru, sk, sl, srCyrl, srLatn, sv, th, tr, uk, vi, zhHans, zhHant} from "vuetify/locale";
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export default createVuetify({
|
||||
@@ -22,17 +23,23 @@ export default createVuetify({
|
||||
maxWidth: '1400px'
|
||||
},
|
||||
// always localize the date display of DateInputs
|
||||
VDateInput: {
|
||||
displayFormat : (date: Date) => DateTime.fromJSDate(date).toLocaleString()
|
||||
},
|
||||
// VDateInput: {
|
||||
// displayFormat: (date: Date) => DateTime.fromJSDate(date).toLocaleString()
|
||||
// },
|
||||
// always use color for switches to properly see if enabled or not
|
||||
VSwitch: {
|
||||
color: 'primary'
|
||||
},
|
||||
// globally set the correct decimal seperator
|
||||
VNumberInput: {
|
||||
decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
|
||||
}
|
||||
// VNumberInput: {
|
||||
// decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
|
||||
// }
|
||||
},
|
||||
locale: {
|
||||
locale: 'en',
|
||||
fallback: 'en',
|
||||
messages: {af, ar, az, bg, ca, ckb, cs, da, de, el, en, es, et, fi, fr, he, hr, hu, id, it, ja, km, ko, lt, lv, nl, no, pl, pt, ro, ru, sk, sl, srCyrl, srLatn, sv, th, tr, uk, vi, zhHans, zhHant},
|
||||
decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
|
||||
},
|
||||
theme: {
|
||||
defaultTheme: 'light',
|
||||
|
||||
@@ -1417,10 +1417,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.17.tgz#e8b3a41f0be76499882a89e8ed40d86a70fa4b70"
|
||||
integrity sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==
|
||||
|
||||
"@vue/tsconfig@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.7.0.tgz#67044c847b7a137b8cbfd6b23104c36dbaf80d1d"
|
||||
integrity sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==
|
||||
"@vue/tsconfig@^0.8.1":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.8.1.tgz#4732251fa58945024424385cf3be0b1708fad5fe"
|
||||
integrity sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==
|
||||
|
||||
"@vueform/multiselect@^2.6.11":
|
||||
version "2.6.11"
|
||||
@@ -1448,18 +1448,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-13.6.0.tgz#49196025c96c7daeb591c20a54b61cc336af99b6"
|
||||
integrity sha512-rnIH7JvU7NjrpexTsl2Iwv0V0yAx9cw7+clymjKuLSXG0QMcLD0LDgdNmXic+qL0SGvgSVPEpM9IDO/wqo1vkQ==
|
||||
|
||||
"@vueuse/router@^13.6.0":
|
||||
version "13.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-13.6.0.tgz#29456dab42eb75a0dc5fe4c62f59dd3f7c21a6ab"
|
||||
integrity sha512-iXRwR4K7nz4PReW0QudhnM9NtYGvN4KrskFgF9G7NouM43big3bpSNRRocJKFWK7iu97ww5y82B3QA2zz3S/vw==
|
||||
"@vueuse/router@^13.9.0":
|
||||
version "13.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-13.9.0.tgz#44235e6732a30b53d1c8e2ef13ce783fdd189ca6"
|
||||
integrity sha512-7AYay8Pv/0fC4D0eygbIyZuLyVs+9D7dsnO5D8aqat9qcOz91v/XFWR667WE1+p+OkU0ib+FjQUdnTVBNoIw8g==
|
||||
dependencies:
|
||||
"@vueuse/shared" "13.6.0"
|
||||
"@vueuse/shared" "13.9.0"
|
||||
|
||||
"@vueuse/shared@13.6.0":
|
||||
version "13.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-13.6.0.tgz#872fdbd725fb4e3a12bd5aab85af9a5db0b1e481"
|
||||
integrity sha512-pDykCSoS2T3fsQrYqf9SyF0QXWHmcGPQ+qiOVjlYSzlWd9dgppB2bFSM1GgKKkt7uzn0BBMV3IbJsUfHG2+BCg==
|
||||
|
||||
"@vueuse/shared@13.9.0":
|
||||
version "13.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-13.9.0.tgz#7168b4ed647e625b05eb4e7e80fe8aabd00e3923"
|
||||
integrity sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==
|
||||
|
||||
acorn@^8.14.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
|
||||
@@ -3343,10 +3348,10 @@ vite-plugin-vuetify@^2.1.1:
|
||||
debug "^4.3.3"
|
||||
upath "^2.0.1"
|
||||
|
||||
vite@7.1.5:
|
||||
version "7.1.5"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.5.tgz#4dbcb48c6313116689be540466fc80faa377be38"
|
||||
integrity sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==
|
||||
vite@7.1.11:
|
||||
version "7.1.11"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.11.tgz#4d006746112fee056df64985191e846ebfb6007e"
|
||||
integrity sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==
|
||||
dependencies:
|
||||
esbuild "^0.25.0"
|
||||
fdir "^6.5.0"
|
||||
@@ -3418,10 +3423,10 @@ vuedraggable@^4.1.0:
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
vuetify@^3.9.7:
|
||||
version "3.9.7"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.9.7.tgz#aea996f35111f25dd7e31ab956fbb40911841c24"
|
||||
integrity sha512-Ib8PB3ItcguCol8f0DXLpoGyy7FvoOYW23SEWqXX+in1CSItJZHxUXXGSus94m5JWqYqQrFiwCykbHm7UWPi4Q==
|
||||
vuetify@^3.10.3:
|
||||
version "3.10.3"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.10.3.tgz#f04e507bb5efee6b52f11b2fd60a20dced1a8831"
|
||||
integrity sha512-psc7oZfjz3LwH96ZRzSm4iGcOKKoeoVZIyO5Q5xO4vcUfWYxobL7TvMQv53jv1PnNvaMIXWeVIrQmiyce5dpTg==
|
||||
|
||||
w3c-xmlserializer@^5.0.0:
|
||||
version "5.0.0"
|
||||
|
||||
Reference in New Issue
Block a user