mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
Merge branch 'mealie-next' into fix/warn-on-edit-nav
This commit is contained in:
commit
618c567392
@ -49,7 +49,9 @@
|
|||||||
"onCreateCommand": "sudo chown -R vscode:vscode /workspaces/mealie/frontend/node_modules && task setup",
|
"onCreateCommand": "sudo chown -R vscode:vscode /workspaces/mealie/frontend/node_modules && task setup",
|
||||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
"remoteUser": "vscode",
|
"remoteUser": "vscode",
|
||||||
// "features": {
|
"features": {
|
||||||
// "git": "latest"
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||||
// }
|
"dockerDashComposeVersion": "v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
.github/workflows/nightly.yml
vendored
13
.github/workflows/nightly.yml
vendored
@ -4,6 +4,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- mealie-next
|
- mealie-next
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
|
- '.devcontainer/**'
|
||||||
|
# I'm not excluding .github as changes in there might be to workflows etc
|
||||||
|
- '.vscode/**'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: nightly-${{ github.ref }}
|
group: nightly-${{ github.ref }}
|
||||||
@ -22,7 +28,13 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
# The id-token write permission is needed to connect to Depot.dev
|
||||||
|
# as part of the partial-builder.yml action. It needs to be declared
|
||||||
|
# in the parent action, as noted here:
|
||||||
|
# https://github.com/orgs/community/discussions/76409#discussioncomment-8131390
|
||||||
|
id-token: write
|
||||||
name: Build Tagged Release
|
name: Build Tagged Release
|
||||||
|
if: github.repository == 'mealie-recipes/mealie'
|
||||||
uses: ./.github/workflows/partial-builder.yml
|
uses: ./.github/workflows/partial-builder.yml
|
||||||
needs:
|
needs:
|
||||||
- frontend-tests
|
- frontend-tests
|
||||||
@ -35,6 +47,7 @@ jobs:
|
|||||||
|
|
||||||
notify-discord:
|
notify-discord:
|
||||||
name: Notify Discord
|
name: Notify Discord
|
||||||
|
if: github.repository == 'mealie-recipes/mealie'
|
||||||
needs:
|
needs:
|
||||||
- build-release
|
- build-release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
16
.github/workflows/partial-builder.yml
vendored
16
.github/workflows/partial-builder.yml
vendored
@ -35,19 +35,16 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Override __init__.py
|
- name: Override __init__.py
|
||||||
run: |
|
run: |
|
||||||
echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py
|
echo "__version__ = \"${{ inputs.tag }}\"" > ./mealie/__init__.py
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- uses: depot/setup-action@v1
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
|
- name: Build and push Docker image, via Depot.dev
|
||||||
|
uses: depot/build-push-action@v1
|
||||||
with:
|
with:
|
||||||
|
project: srzjb6mhzm
|
||||||
file: ./docker/Dockerfile
|
file: ./docker/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@ -58,6 +55,3 @@ jobs:
|
|||||||
${{ inputs.tags }}
|
${{ inputs.tags }}
|
||||||
build-args: |
|
build-args: |
|
||||||
COMMIT=${{ github.sha }}
|
COMMIT=${{ github.sha }}
|
||||||
# https://docs.docker.com/build/ci/github-actions/cache/#github-cache
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@ -17,6 +17,11 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
# The id-token write permission is needed to connect to Depot.dev
|
||||||
|
# as part of the partial-builder.yml action. It needs to be declared
|
||||||
|
# in the parent action, as noted here:
|
||||||
|
# https://github.com/orgs/community/discussions/76409#discussioncomment-8131390
|
||||||
|
id-token: write
|
||||||
name: Build Tagged Release
|
name: Build Tagged Release
|
||||||
uses: ./.github/workflows/partial-builder.yml
|
uses: ./.github/workflows/partial-builder.yml
|
||||||
needs:
|
needs:
|
||||||
@ -49,6 +54,9 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- build-release
|
- build-release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout 🛎
|
- name: Checkout 🛎
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -58,11 +66,12 @@ jobs:
|
|||||||
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/sqlite.md
|
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/sqlite.md
|
||||||
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/postgres.md
|
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/postgres.md
|
||||||
|
|
||||||
- name: Commit updates
|
- name: Create Pull Request
|
||||||
uses: test-room-7/action-update-file@v1
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
file-path: |
|
commit-message: "Update image tag, for release ${{ github.event.release.tag_name }}"
|
||||||
docs/docs/documentation/getting-started/installation/sqlite.md
|
branch: "docs/newrelease-update-version-${{ github.event.release.tag_name }}"
|
||||||
docs/docs/documentation/getting-started/installation/postgres.md
|
delete-branch: true
|
||||||
commit-msg: "Change image tag, for release ${{ github.event.release.tag_name }}"
|
base: mealie-next
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
title: "docs(auto): Update image tag, for release ${{ github.event.release.tag_name }}"
|
||||||
|
body: "Auto-generated by `.github/workflows/release.yml`, on publish of release ${{ github.event.release.tag_name }}"
|
||||||
|
@ -71,12 +71,9 @@ Distributed under the AGPL License. See `LICENSE` for more information.
|
|||||||
|
|
||||||
Huge thanks to all the sponsors of this project on [Github Sponsors](https://github.com/sponsors/hay-kot) and Buy Me a Coffee. Without you, this project would surely not be possible.
|
Huge thanks to all the sponsors of this project on [Github Sponsors](https://github.com/sponsors/hay-kot) and Buy Me a Coffee. Without you, this project would surely not be possible.
|
||||||
|
|
||||||
Thanks to Linode for providing Hosting for the Demo, Beta, and Documentation sites! Another big thanks to JetBrains for providing their IDEs for development.
|
Thanks to Depot for providing build instances for our Docker image builds.
|
||||||
|
|
||||||
<div align='center'>
|
[](https://depot.dev?utm_source=Mealie)
|
||||||
<img height="100" src="docs/docs/assets/img/sponsors-linode.svg" />
|
|
||||||
<img height="100" src="docs/docs/assets/img/sponsors-jetbrains.png" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,9 @@ env:
|
|||||||
SMTP_HOST: localhost
|
SMTP_HOST: localhost
|
||||||
SMTP_PORT: 1025
|
SMTP_PORT: 1025
|
||||||
SMTP_FROM_NAME: MealieDev
|
SMTP_FROM_NAME: MealieDev
|
||||||
|
SMTP_FROM_EMAIL: mealie@example.com
|
||||||
SMTP_AUTH_STRATEGY: NONE
|
SMTP_AUTH_STRATEGY: NONE
|
||||||
|
BASE_URL: http://localhost:3000
|
||||||
LANG: en-US
|
LANG: en-US
|
||||||
|
|
||||||
# loads .env file if it exists
|
# loads .env file if it exists
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 88 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 230 90" width="2500" height="978"><path d="M93.8 27.8l5.8-1.4v28c0 3.1.9 4.9 2.7 5.5-.9 1.7-2.4 2.6-4.6 2.6-2.6 0-4-1.8-4-5.5V27.8zM108.4 62V41.8h-3.2V37h9.1v25h-5.9zm3-34.6c.9 0 1.7.3 2.4 1s1 1.5 1 2.4c0 .9-.3 1.7-1 2.4s-1.5 1-2.4 1c-.9 0-1.7-.3-2.4-1s-1-1.5-1-2.4c0-.9.3-1.7 1-2.4s1.5-1 2.4-1zM137.1 62V47.6c0-2.1-.4-3.7-1.2-4.6-.8-1-2.1-1.5-4-1.5-.9 0-1.8.2-2.7.7-1 .5-1.7 1.1-2.3 1.8v18h-5.8V37.1h4.2l1.1 2.3c1.6-1.9 3.9-2.8 7-2.8 3 0 5.3.9 7 2.7 1.7 1.8 2.6 4.3 2.6 7.4V62h-5.9zM147.5 49.5c0-3.8 1.1-6.9 3.3-9.3 2.2-2.4 5.1-3.6 8.7-3.6 3.8 0 6.7 1.1 8.8 3.4 2.1 2.3 3.1 5.4 3.1 9.4s-1.1 7.1-3.2 9.5c-2.1 2.3-5 3.5-8.8 3.5-3.8 0-6.7-1.2-8.8-3.5-2-2.4-3.1-5.5-3.1-9.4zm6.1 0c0 5.5 2 8.2 5.9 8.2 1.8 0 3.2-.7 4.3-2.1 1.1-1.4 1.6-3.5 1.6-6.1 0-5.4-2-8.1-5.9-8.1-1.8 0-3.3.7-4.3 2.1-1.1 1.4-1.6 3.4-1.6 6zM192.1 62v-1.5c-.5.5-1.3 1-2.4 1.4-1.1.4-2.3.6-3.6.6-3.5 0-6.2-1.1-8.2-3.3-2-2.2-3-5.3-3-9.2 0-3.9 1.1-7.1 3.4-9.6s5.1-3.7 8.6-3.7c1.9 0 3.6.4 5.2 1.2v-10l5.8-1.4V62h-5.8zm0-19c-1.2-1-2.5-1.5-3.9-1.5-2.3 0-4.1.7-5.4 2.1-1.3 1.4-1.9 3.5-1.9 6.1 0 5.2 2.5 7.8 7.5 7.8.6 0 1.2-.2 2.1-.5.8-.3 1.3-.7 1.6-1V43zM226 51.3h-17.8c.1 2 .8 3.5 2 4.6 1.3 1.1 2.9 1.7 5.1 1.7 2.6 0 4.7-.7 6-2.1l2.3 4.4c-2 1.7-5.1 2.5-9.2 2.5-3.8 0-6.8-1.1-9-3.3-2.2-2.2-3.3-5.3-3.3-9.3 0-3.9 1.2-7.1 3.6-9.5 2.4-2.4 5.3-3.6 8.7-3.6 3.6 0 6.5 1.1 8.7 3.2 2.2 2.2 3.3 4.9 3.3 8.2.1.7-.1 1.7-.4 3.2zm-17.6-4.4h12.2c-.4-3.6-2.4-5.5-6-5.5-3.3.1-5.4 1.9-6.2 5.5z"/><g><path fill="#004712" d="M65.9 47.4l-1 11.5-3.3-2.3.4-5.8v-.1-.1l-.1-.1-.1-.1-7.1-4.7.1-5.1 11.1 6.8zM48.5 59.9L43.4 56v.9c0 .2-.1.4-.2.5L39.4 60l4.2 3.4.1.1v.2l.2 4 4.7 3.9-.1-11.7zm-32.1 5l2.4 11.5 9.9 10.5L27 75.3 16.4 64.9zm9.3 1.7l-2.4-16.1-12-10 3.2 15.6 11.2 10.5zm-3.8-26l-3.3-22.8L4.8 9.2l4.5 21.5 12.6 9.9z"/><path fill="#00B259" d="M75.7 41.2l-1.5 10.9-8.2 6.6 1-11.2 8.7-6.3zM49.6 59.9l.1 11.8 10.5-8.4.7-11.5-11.3 8.1zm-6.8 4.8L28 75.3l1.8 12.2 13.4-10.7-.4-12.1zm-.4-8l-.7-16-17.3 9.9 2.4 16.6 15.6-10.5zm-1.1-25.3l-.9-21.6-20.8 8L23 41l18.3-9.6z"/><path d="M76.9 40c0-.1 0-.1 0 0v-.2s0-.1-.1-.1c0 0-.1 0-.1-.1l-12-6.7c-.2-.1-.4-.1-.5 0L54 39.1h-.1v.6l-.1 5.4-4.1-2.7c-.2-.1-.4-.1-.6 0L43 45.8l-.3-6v-.1-.1-.1-.1-.1h-.1l-6.2-4.1 5.8-3c.2-.1.3-.3.3-.5L41.4 9v-.1s0-.1-.1-.1c0 0 0-.1-.1-.1L25.5 1.1c-.1-.1-.2-.1-.3-.1L3.9 7.6s-.1 0-.1.1c0 0-.1 0-.1.1v.6l4.7 22.9c0 .1.1.2.2.3l6.4 5-4.7 2.2s-.1 0-.1.1c0 0 0 .1-.1.1v.2l3.6 17.2c0 .1.1.2.2.3l4.5 4.2-3 1.8-.1.1s0 .1-.1.1V63.2L18 76.5c0 .1.1.2.1.3l10.9 12h.1s.1 0 .1.1h.5l14.4-11.5c.1-.1.2-.3.2-.4l-.3-7.9 4.8 4s.1 0 .1.1h.5L61 64c.1-.1.2-.2.2-.4l.4-5.8 3.5 2.4h.4s.1 0 .1-.1l9.4-7.5c.1-.1.2-.2.2-.3L76.9 40c0 .1 0 .1 0 0zM66 58.7l1-11.2 8.8-6.3-1.5 10.9-8.3 6.6zm-4.4-2.1l.4-5.8v-.1-.1l-.1-.1-.1-.1-7.1-4.7.1-5.1 11.1 6.9-1 11.5-3.3-2.4zm-1.5 6.7l-10.5 8.4-.1-11.8 11.3-8.1-.7 11.5zM43.3 76.8L29.8 87.5 28 75.3l14.7-10.5.6 12zm-24.6-.4l-2.4-11.5L27 75.3l1.7 11.6-10-10.5zm-.2-58.6l3.3 22.8-12.5-9.9L4.8 9.2l13.7 8.6zm21.9-8l.9 21.6L23 41l-3.4-23.2 20.8-8zm2 46.9L26.8 67.1l-2.4-16.6 17.3-9.9.7 16.1zm-19.1-6.1l2.4 16.1-11.2-10.6-3.2-15.6 12 10.1zm20.5 13.1v-.1-.1l-.1-.1-4.2-3.4 3.8-2.6c.2-.1.2-.3.2-.5V56l5.1 3.9.1 11.8-4.7-3.9-.2-4.1z"/></g></svg>
|
|
Before Width: | Height: | Size: 3.1 KiB |
@ -54,8 +54,8 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
|
|||||||
| ---------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| WEB_GUNICORN | false | Enables Gunicorn to manage Uvicorn web for multiple works |
|
| WEB_GUNICORN | false | Enables Gunicorn to manage Uvicorn web for multiple works |
|
||||||
| WORKERS_PER_CORE | 1 | Set the number of workers to the number of CPU cores multiplied by this value (Value \* CPUs). More info [here][workers_per_core] |
|
| WORKERS_PER_CORE | 1 | Set the number of workers to the number of CPU cores multiplied by this value (Value \* CPUs). More info [here][workers_per_core] |
|
||||||
| MAX_WORKERS | 1 | Set the maximum number of workers to use. Default is not set meaning unlimited. More info [here][max_workers] |
|
| MAX_WORKERS | None | Set the maximum number of workers to use. Default is not set meaning unlimited. More info [here][max_workers] |
|
||||||
| WEB_CONCURRENCY | 1 | Override the automatic definition of number of workers. More info [here][web_concurrency] |
|
| WEB_CONCURRENCY | 2 | Override the automatic definition of number of workers. More info [here][web_concurrency] |
|
||||||
|
|
||||||
### LDAP
|
### LDAP
|
||||||
|
|
||||||
@ -95,3 +95,8 @@ Setting the following environmental variables will change the theme of the front
|
|||||||
| THEME_DARK_INFO | #1976D2 | Dark Theme Config Variable |
|
| THEME_DARK_INFO | #1976D2 | Dark Theme Config Variable |
|
||||||
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
|
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
|
||||||
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
|
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
|
||||||
|
|
||||||
|
|
||||||
|
[workers_per_core]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#workers_per_core
|
||||||
|
[max_workers]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#max_workers
|
||||||
|
[web_concurrency]: https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/2daa3e3873c837d5781feb4ff6a40a89f791f81b/README.md#web_concurrency
|
||||||
|
File diff suppressed because one or more lines are too long
@ -133,7 +133,7 @@ export default defineComponent({
|
|||||||
const domMadeThisForm = ref<VForm>();
|
const domMadeThisForm = ref<VForm>();
|
||||||
const newTimelineEvent = ref<RecipeTimelineEventIn>({
|
const newTimelineEvent = ref<RecipeTimelineEventIn>({
|
||||||
// @ts-expect-error - TS doesn't like the $auth global user attribute
|
// @ts-expect-error - TS doesn't like the $auth global user attribute
|
||||||
subject: i18n.t("recipe.user-made-this", { user: $auth.user.fullName } as string),
|
subject: i18n.tc("recipe.user-made-this", { user: $auth.user.fullName }),
|
||||||
eventType: "comment",
|
eventType: "comment",
|
||||||
eventMessage: "",
|
eventMessage: "",
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
|
@ -19,11 +19,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-list v-if="showViewer" dense class="mt-0 pt-0">
|
<v-list v-if="showViewer" dense class="mt-0 pt-0">
|
||||||
<v-list-item v-for="(item, key, index) in labels" :key="index" style="min-height: 25px" dense>
|
<v-list-item v-for="(item, key, index) in renderedList" :key="index" style="min-height: 25px" dense>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title class="pl-4 caption flex row">
|
<v-list-item-title class="pl-4 caption flex row">
|
||||||
<div>{{ item.label }}</div>
|
<div>{{ item.label }}</div>
|
||||||
<div class="ml-auto mr-1">{{ value[key] }}</div>
|
<div class="ml-auto mr-1">{{ item.value }}</div>
|
||||||
<div>{{ item.suffix }}</div>
|
<div>{{ item.suffix }}</div>
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
@ -37,6 +37,14 @@
|
|||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import { Nutrition } from "~/lib/api/types/recipe";
|
import { Nutrition } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
type NutritionLabelType = {
|
||||||
|
[key: string]: {
|
||||||
|
label: string;
|
||||||
|
suffix: string;
|
||||||
|
value?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@ -50,34 +58,34 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
const labels = {
|
const labels = <NutritionLabelType>{
|
||||||
calories: {
|
calories: {
|
||||||
label: i18n.t("recipe.calories"),
|
label: i18n.tc("recipe.calories"),
|
||||||
suffix: i18n.t("recipe.calories-suffix"),
|
suffix: i18n.tc("recipe.calories-suffix"),
|
||||||
},
|
},
|
||||||
fatContent: {
|
fatContent: {
|
||||||
label: i18n.t("recipe.fat-content"),
|
label: i18n.tc("recipe.fat-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
fiberContent: {
|
fiberContent: {
|
||||||
label: i18n.t("recipe.fiber-content"),
|
label: i18n.tc("recipe.fiber-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
proteinContent: {
|
proteinContent: {
|
||||||
label: i18n.t("recipe.protein-content"),
|
label: i18n.tc("recipe.protein-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
sodiumContent: {
|
sodiumContent: {
|
||||||
label: i18n.t("recipe.sodium-content"),
|
label: i18n.tc("recipe.sodium-content"),
|
||||||
suffix: i18n.t("recipe.milligrams"),
|
suffix: i18n.tc("recipe.milligrams"),
|
||||||
},
|
},
|
||||||
sugarContent: {
|
sugarContent: {
|
||||||
label: i18n.t("recipe.sugar-content"),
|
label: i18n.tc("recipe.sugar-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
carbohydrateContent: {
|
carbohydrateContent: {
|
||||||
label: i18n.t("recipe.carbohydrate-content"),
|
label: i18n.tc("recipe.carbohydrate-content"),
|
||||||
suffix: i18n.t("recipe.grams"),
|
suffix: i18n.tc("recipe.grams"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const valueNotNull = computed(() => {
|
const valueNotNull = computed(() => {
|
||||||
@ -96,11 +104,25 @@ export default defineComponent({
|
|||||||
context.emit("input", { ...props.value, [key]: event });
|
context.emit("input", { ...props.value, [key]: event });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a new list that only contains nutritional information that has a value
|
||||||
|
const renderedList = computed(() => {
|
||||||
|
return Object.entries(labels).reduce((item: NutritionLabelType, [key, label]) => {
|
||||||
|
if (props.value[key]?.trim()) {
|
||||||
|
item[key] = {
|
||||||
|
...label,
|
||||||
|
value: props.value[key],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels,
|
labels,
|
||||||
valueNotNull,
|
valueNotNull,
|
||||||
showViewer,
|
showViewer,
|
||||||
updateValue,
|
updateValue,
|
||||||
|
renderedList,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
:label="$t('shopping-list.note')"
|
:label="$t('shopping-list.note')"
|
||||||
rows="1"
|
rows="1"
|
||||||
auto-grow
|
auto-grow
|
||||||
|
@keypress="handleNoteKeyPress"
|
||||||
></v-textarea>
|
></v-textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-end" style="gap: 20px">
|
<div class="d-flex align-end" style="gap: 20px">
|
||||||
@ -95,7 +96,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
import { defineComponent, computed, watch } from "@nuxtjs/composition-api";
|
||||||
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
|
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
|
||||||
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||||
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
@ -128,9 +129,28 @@ export default defineComponent({
|
|||||||
context.emit("input", val);
|
context.emit("input", val);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value.food,
|
||||||
|
(newFood) => {
|
||||||
|
// @ts-ignore our logic already assumes there's a label attribute, even if TS doesn't think there is
|
||||||
|
listItem.value.label = newFood?.label || null;
|
||||||
|
listItem.value.labelId = listItem.value.label?.id || null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listItem,
|
listItem,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
handleNoteKeyPress(event) {
|
||||||
|
// Save on Enter
|
||||||
|
if (!event.shiftKey && event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.$emit("save");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -200,7 +200,7 @@
|
|||||||
"created-on-date": "Създадено на {0}",
|
"created-on-date": "Създадено на {0}",
|
||||||
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
"unsaved-changes": "Имате незапазени промени. Желаете ли да ги запазите преди да излезете? Натиснете Ок за запазване и Отказ за отхвърляне на промените.",
|
||||||
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
|
"clipboard-copy-failure": "Линкът към рецептата е копиран в клипборда.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Сигурни ли сте, че желаете да изтриете следните елементи?"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
"are-you-sure-you-want-to-delete-the-group": "Сигурни ли сте, че искате да изтриете <b>{groupName}<b/>?",
|
||||||
@ -259,7 +259,7 @@
|
|||||||
},
|
},
|
||||||
"meal-plan": {
|
"meal-plan": {
|
||||||
"create-a-new-meal-plan": "Създаване на нов хранителен план",
|
"create-a-new-meal-plan": "Създаване на нов хранителен план",
|
||||||
"update-this-meal-plan": "Update this Meal Plan",
|
"update-this-meal-plan": "Обнови този План за хранене",
|
||||||
"dinner-this-week": "Вечеря тази седмица",
|
"dinner-this-week": "Вечеря тази седмица",
|
||||||
"dinner-today": "Вечеря Днес",
|
"dinner-today": "Вечеря Днес",
|
||||||
"dinner-tonight": "Вечеря ТАЗИ ВЕЧЕР",
|
"dinner-tonight": "Вечеря ТАЗИ ВЕЧЕР",
|
||||||
@ -474,11 +474,11 @@
|
|||||||
"add-to-timeline": "Добави към времевата линия",
|
"add-to-timeline": "Добави към времевата линия",
|
||||||
"recipe-added-to-list": "Рецептата е добавена към списъка",
|
"recipe-added-to-list": "Рецептата е добавена към списъка",
|
||||||
"recipes-added-to-list": "Рецептите са добавени към списъка",
|
"recipes-added-to-list": "Рецептите са добавени към списъка",
|
||||||
"successfully-added-to-list": "Successfully added to list",
|
"successfully-added-to-list": "Успешно добавено в списъка",
|
||||||
"recipe-added-to-mealplan": "Рецептата е добавена към хранителния план",
|
"recipe-added-to-mealplan": "Рецептата е добавена към хранителния план",
|
||||||
"failed-to-add-recipes-to-list": "Неуспешно добавяне на рецепта към списъка",
|
"failed-to-add-recipes-to-list": "Неуспешно добавяне на рецепта към списъка",
|
||||||
"failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план",
|
"failed-to-add-recipe-to-mealplan": "Рецептата не беше добавена към хранителния план",
|
||||||
"failed-to-add-to-list": "Failed to add to list",
|
"failed-to-add-to-list": "Неуспешно добавяне към списъка",
|
||||||
"yield": "Добив",
|
"yield": "Добив",
|
||||||
"quantity": "Количество",
|
"quantity": "Количество",
|
||||||
"choose-unit": "Избери единица",
|
"choose-unit": "Избери единица",
|
||||||
@ -537,8 +537,8 @@
|
|||||||
"new-recipe-names-must-be-unique": "Името на рецептата трябва да бъде уникално",
|
"new-recipe-names-must-be-unique": "Името на рецептата трябва да бъде уникално",
|
||||||
"scrape-recipe": "Обхождане на рецепта",
|
"scrape-recipe": "Обхождане на рецепта",
|
||||||
"scrape-recipe-description": "Обходи рецепта по линк. Предоставете линк за сайт, който искате да бъде обходен. Mealie ще опита да обходи рецептата от този сайт и да я добави във Вашата колекция.",
|
"scrape-recipe-description": "Обходи рецепта по линк. Предоставете линк за сайт, който искате да бъде обходен. Mealie ще опита да обходи рецептата от този сайт и да я добави във Вашата колекция.",
|
||||||
"scrape-recipe-have-a-lot-of-recipes": "Have a lot of recipes you want to scrape at once?",
|
"scrape-recipe-have-a-lot-of-recipes": "Имате много рецепти, които искате да обходите наведнъж?",
|
||||||
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
|
"scrape-recipe-suggest-bulk-importer": "Пробвайте масовото импорторане",
|
||||||
"import-original-keywords-as-tags": "Импортирай оригиналните ключови думи като тагове",
|
"import-original-keywords-as-tags": "Импортирай оригиналните ключови думи като тагове",
|
||||||
"stay-in-edit-mode": "Остани в режим на редакция",
|
"stay-in-edit-mode": "Остани в режим на редакция",
|
||||||
"import-from-zip": "Импортирай от Zip",
|
"import-from-zip": "Импортирай от Zip",
|
||||||
@ -562,7 +562,7 @@
|
|||||||
"upload-image": "Качване на изображение",
|
"upload-image": "Качване на изображение",
|
||||||
"screen-awake": "Запази екрана активен",
|
"screen-awake": "Запази екрана активен",
|
||||||
"remove-image": "Премахване на изображение",
|
"remove-image": "Премахване на изображение",
|
||||||
"nextStep": "Next step"
|
"nextStep": "Следваща стъпка"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Разширено търсене",
|
"advanced-search": "Разширено търсене",
|
||||||
@ -1187,7 +1187,7 @@
|
|||||||
"require-all-tools": "Изискване на всички инструменти",
|
"require-all-tools": "Изискване на всички инструменти",
|
||||||
"cookbook-name": "Име на книгата с рецепти",
|
"cookbook-name": "Име на книгата с рецепти",
|
||||||
"cookbook-with-name": "Книга с рецепти {0}",
|
"cookbook-with-name": "Книга с рецепти {0}",
|
||||||
"create-a-cookbook": "Create a Cookbook",
|
"create-a-cookbook": "Създай Готварска книга",
|
||||||
"cookbook": "Cookbook"
|
"cookbook": "Готварска книга"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
"database-url": "URL de base de datos",
|
"database-url": "URL de base de datos",
|
||||||
"default-group": "Grupo Predeterminado",
|
"default-group": "Grupo Predeterminado",
|
||||||
"demo": "Versión Demo",
|
"demo": "Versión Demo",
|
||||||
"demo-status": "Estado Demo",
|
"demo-status": "Modo Demo",
|
||||||
"development": "Desarrollo",
|
"development": "Desarrollo",
|
||||||
"docs": "Documentación",
|
"docs": "Documentación",
|
||||||
"download-log": "Descargar Log",
|
"download-log": "Descargar Log",
|
||||||
"download-recipe-json": "Último JSON recuperado",
|
"download-recipe-json": "Último JSON extraído",
|
||||||
"github": "Github",
|
"github": "Github",
|
||||||
"log-lines": "Líneas de registro",
|
"log-lines": "Líneas de registro",
|
||||||
"not-demo": "No Demo",
|
"not-demo": "No Demo",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"pdf": "PDF",
|
"pdf": "PDF",
|
||||||
"recipe": "Receta",
|
"recipe": "Receta",
|
||||||
"show-assets": "Mostrar recursos",
|
"show-assets": "Mostrar recursos",
|
||||||
"error-submitting-form": "Se ha producido un error al enviar el formulario"
|
"error-submitting-form": "Error al enviar el formulario"
|
||||||
},
|
},
|
||||||
"category": {
|
"category": {
|
||||||
"categories": "Categorías",
|
"categories": "Categorías",
|
||||||
@ -200,7 +200,7 @@
|
|||||||
"created-on-date": "Creado el {0}",
|
"created-on-date": "Creado el {0}",
|
||||||
"unsaved-changes": "Tienes cambios sin guardar. ¿Quieres guardar antes de salir? Aceptar para guardar, Cancelar para descartar cambios.",
|
"unsaved-changes": "Tienes cambios sin guardar. ¿Quieres guardar antes de salir? Aceptar para guardar, Cancelar para descartar cambios.",
|
||||||
"clipboard-copy-failure": "No se pudo copiar al portapapeles.",
|
"clipboard-copy-failure": "No se pudo copiar al portapapeles.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "¿Estás seguro que quieres eliminar los siguientes elementos?"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar <b>{groupName}<b/>",
|
"are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar <b>{groupName}<b/>",
|
||||||
@ -599,9 +599,9 @@
|
|||||||
"import-summary": "Importar resumen",
|
"import-summary": "Importar resumen",
|
||||||
"partial-backup": "Copia de seguridad parcial",
|
"partial-backup": "Copia de seguridad parcial",
|
||||||
"unable-to-delete-backup": "No se puede eliminar la copia de seguridad.",
|
"unable-to-delete-backup": "No se puede eliminar la copia de seguridad.",
|
||||||
"experimental-description": "Backups a total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time. Currently, {not-crossed-version} (data migrations are not done automatically). These serve as a database agnostic way to export and import data or backup the site to an external location.",
|
"experimental-description": "Las copias de seguridad son instantáneas completas de la base de datos y del directorio de datos del sitio. Esto incluye todos los datos y no se pueden configurar para excluir subconjuntos de datos. Puedes pensar en esto como una instantánea de Mealie en un momento específico. Estas sirven como una forma agnóstica de la base de datos para exportar e importar datos, o respaldar el sitio en una ubicación externa.",
|
||||||
"backup-restore": "Restaurar Copia de Seguridad",
|
"backup-restore": "Restaurar Copia de Seguridad",
|
||||||
"back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.",
|
"back-restore-description": "Restaurar esta copia de seguridad sobrescribirá todos los datos actuales de su base de datos y del directorio de datos y los sustituirá por el contenido de esta copia. {cannot-be-undone} Si la restauración se realiza correctamente, se cerrará su sesión.",
|
||||||
"cannot-be-undone": "Esta acción no se puede deshacer, use con precaución.",
|
"cannot-be-undone": "Esta acción no se puede deshacer, use con precaución.",
|
||||||
"postgresql-note": "Si estás usando PostGreSQL, por favor revisa el {backup-restore-process} antes de restaurar.",
|
"postgresql-note": "Si estás usando PostGreSQL, por favor revisa el {backup-restore-process} antes de restaurar.",
|
||||||
"backup-restore-process-in-the-documentation": "copia de seguridad/proceso de restauración en la documentación",
|
"backup-restore-process-in-the-documentation": "copia de seguridad/proceso de restauración en la documentación",
|
||||||
@ -723,7 +723,7 @@
|
|||||||
"ldap-ready": "LDAP Listo",
|
"ldap-ready": "LDAP Listo",
|
||||||
"ldap-ready-error-text": "No todos los valores LDAP están configurados. Esto puede ignorarse si no está usando autenticación LDAP.",
|
"ldap-ready-error-text": "No todos los valores LDAP están configurados. Esto puede ignorarse si no está usando autenticación LDAP.",
|
||||||
"ldap-ready-success-text": "Las variables LDAP requeridas están todas definidas.",
|
"ldap-ready-success-text": "Las variables LDAP requeridas están todas definidas.",
|
||||||
"build": "Compilar",
|
"build": "Compilación",
|
||||||
"recipe-scraper-version": "Versión de Analizador de Recetas"
|
"recipe-scraper-version": "Versión de Analizador de Recetas"
|
||||||
},
|
},
|
||||||
"shopping-list": {
|
"shopping-list": {
|
||||||
@ -962,9 +962,9 @@
|
|||||||
"settings-chosen-explanation": "Los ajustes seleccionados aquí, excluyendo la opción bloqueada, se aplicarán a todas las recetas seleccionadas.",
|
"settings-chosen-explanation": "Los ajustes seleccionados aquí, excluyendo la opción bloqueada, se aplicarán a todas las recetas seleccionadas.",
|
||||||
"selected-length-recipe-s-settings-will-be-updated": "Se actualizarán los ajustes de {count} receta(s).",
|
"selected-length-recipe-s-settings-will-be-updated": "Se actualizarán los ajustes de {count} receta(s).",
|
||||||
"recipe-data": "Datos de la receta",
|
"recipe-data": "Datos de la receta",
|
||||||
"recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.",
|
"recipe-data-description": "Utiliza esta sección para gestionar los datos asociados a tus recetas. Puedes realizar varias acciones de forma masiva en tus recetas, como exportar, eliminar, etiquetar y asignar categorías.",
|
||||||
"recipe-columns": "Recipe Columns",
|
"recipe-columns": "Columnas de Recetas",
|
||||||
"data-exports-description": "This section provides links to available exports that are ready to download. These exports do expire, so be sure to grab them while they're still available.",
|
"data-exports-description": "Esta sección proporciona enlaces a las exportaciones disponibles listas para descargar. Estas exportaciones caducan, así que asegúrate de descargarlas mientras estén disponibles.",
|
||||||
"data-exports": "Exportación de datos",
|
"data-exports": "Exportación de datos",
|
||||||
"tag": "Etiqueta",
|
"tag": "Etiqueta",
|
||||||
"categorize": "Clasificar",
|
"categorize": "Clasificar",
|
||||||
@ -973,14 +973,14 @@
|
|||||||
"categorize-recipes": "Categorizar recetas",
|
"categorize-recipes": "Categorizar recetas",
|
||||||
"export-recipes": "Exportar recetas",
|
"export-recipes": "Exportar recetas",
|
||||||
"delete-recipes": "Borrar Recetas",
|
"delete-recipes": "Borrar Recetas",
|
||||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
"source-unit-will-be-deleted": "Se eliminará la unidad de origen"
|
||||||
},
|
},
|
||||||
"create-alias": "Crear un Alias",
|
"create-alias": "Crear un Alias",
|
||||||
"manage-aliases": "Administrar Alias",
|
"manage-aliases": "Administrar Alias",
|
||||||
"seed-data": "Datos de ejemplo",
|
"seed-data": "Datos de ejemplo",
|
||||||
"seed": "Semilla",
|
"seed": "Semilla",
|
||||||
"data-management": "Data Management",
|
"data-management": "Gestión de Datos",
|
||||||
"data-management-description": "Select which data set you want to make changes to.",
|
"data-management-description": "Selecciona el conjunto de datos al que deseas hacer cambios.",
|
||||||
"select-data": "Seleccionar datos",
|
"select-data": "Seleccionar datos",
|
||||||
"select-language": "Seleccionar idioma",
|
"select-language": "Seleccionar idioma",
|
||||||
"columns": "Columnas",
|
"columns": "Columnas",
|
||||||
@ -1003,7 +1003,7 @@
|
|||||||
},
|
},
|
||||||
"user-registration": {
|
"user-registration": {
|
||||||
"user-registration": "Registro de usuario",
|
"user-registration": "Registro de usuario",
|
||||||
"registration-success": "Registration Success",
|
"registration-success": "Registrado con éxito",
|
||||||
"join-a-group": "Unirse a un grupo",
|
"join-a-group": "Unirse a un grupo",
|
||||||
"create-a-new-group": "Crear un grupo nuevo",
|
"create-a-new-group": "Crear un grupo nuevo",
|
||||||
"provide-registration-token-description": "Por favor, proporcione el token de registro asociado con el grupo al que desea unirse. Necesitará obtenerlo de un miembro del grupo.",
|
"provide-registration-token-description": "Por favor, proporcione el token de registro asociado con el grupo al que desea unirse. Necesitará obtenerlo de un miembro del grupo.",
|
||||||
@ -1050,57 +1050,57 @@
|
|||||||
},
|
},
|
||||||
"ocr-editor": {
|
"ocr-editor": {
|
||||||
"ocr-editor": "Editor de OCR",
|
"ocr-editor": "Editor de OCR",
|
||||||
"toolbar": "Toolbar",
|
"toolbar": "Barra de Herramientas",
|
||||||
"selection-mode": "Modo de selección",
|
"selection-mode": "Modo de selección",
|
||||||
"pan-and-zoom-picture": "Desplazar y hacer zoom en la imagen",
|
"pan-and-zoom-picture": "Desplazar y hacer zoom en la imagen",
|
||||||
"split-text": "Split text",
|
"split-text": "Dividir texto",
|
||||||
"preserve-line-breaks": "Preserve original line breaks",
|
"preserve-line-breaks": "Mantener los saltos de línea originales",
|
||||||
"split-by-block": "Split by text block",
|
"split-by-block": "División por bloques de texto",
|
||||||
"flatten": "Flatten regardless of original formating",
|
"flatten": "Acoplar independientemente del formato original",
|
||||||
"help": {
|
"help": {
|
||||||
"help": "Help",
|
"help": "Ayuda",
|
||||||
"mouse-modes": "Mouse modes",
|
"mouse-modes": "Modos de ratón",
|
||||||
"selection-mode": "Selection Mode (default)",
|
"selection-mode": "Modo selección (por defecto)",
|
||||||
"selection-mode-desc": "The selection mode is the main mode that can be used to enter data:",
|
"selection-mode-desc": "El modo de selección es el modo principal que puede utilizarse para introducir datos:",
|
||||||
"selection-mode-steps": {
|
"selection-mode-steps": {
|
||||||
"draw": "Draw a rectangle on the text you want to select.",
|
"draw": "Dibuja un rectángulo sobre el texto que deseas seleccionar.",
|
||||||
"click": "Click on any field on the right and then click back on the rectangle above the image.",
|
"click": "Click on any field on the right and then click back on the rectangle above the image.",
|
||||||
"result": "The selected text will appear inside the previously selected field."
|
"result": "The selected text will appear inside the previously selected field."
|
||||||
},
|
},
|
||||||
"pan-and-zoom-mode": "Pan and Zoom Mode",
|
"pan-and-zoom-mode": "Modo Panorámico y Zoom",
|
||||||
"pan-and-zoom-desc": "Select pan and zoom by clicking the icon. This mode allows to zoom inside the image and move around to make using big images easier.",
|
"pan-and-zoom-desc": "Select pan and zoom by clicking the icon. This mode allows to zoom inside the image and move around to make using big images easier.",
|
||||||
"split-text-mode": "Split Text modes",
|
"split-text-mode": "Modos de división de texto",
|
||||||
"split-modes": {
|
"split-modes": {
|
||||||
"line-mode": "Line mode (default)",
|
"line-mode": "Modo de línea (por defecto)",
|
||||||
"line-mode-desc": "In line mode, the text will be propagated by keeping the original line breaks. This mode is useful when using bulk add on a list of ingredients where one ingredient is one line.",
|
"line-mode-desc": "In line mode, the text will be propagated by keeping the original line breaks. This mode is useful when using bulk add on a list of ingredients where one ingredient is one line.",
|
||||||
"block-mode": "Block mode",
|
"block-mode": "Modo en bloque",
|
||||||
"block-mode-desc": "In block mode, the text will be split in blocks. This mode is useful when bulk adding instructions that are usually written in paragraphs.",
|
"block-mode-desc": "En el modo de bloque, el texto se dividirá en bloques. Este modo es útil cuando se agregan instrucciones que están escritas en párrafos.",
|
||||||
"flat-mode": "Flat mode",
|
"flat-mode": "Modo texto sin formato",
|
||||||
"flat-mode-desc": "In flat mode, the text will be added to the selected recipe field with no line breaks."
|
"flat-mode-desc": "En modo texto sin formato, el texto se añadirá al campo de la receta seleccionado sin saltos de línea."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"storage-details": "Storage Details",
|
"storage-details": "Detalle del almacenamiento",
|
||||||
"page-title": "Site Maintenance",
|
"page-title": "Mantenimiento del sitio",
|
||||||
"summary-title": "Summary",
|
"summary-title": "Índice",
|
||||||
"button-label-get-summary": "Get Summary",
|
"button-label-get-summary": "Obtener Resumen",
|
||||||
"button-label-open-details": "Detalles",
|
"button-label-open-details": "Detalles",
|
||||||
"info-description-data-dir-size": "Data Directory Size",
|
"info-description-data-dir-size": "Tamaño del directorio de datos",
|
||||||
"info-description-log-file-size": "Log File Size",
|
"info-description-log-file-size": "Tamaño del archivo de registro",
|
||||||
"info-description-cleanable-directories": "Cleanable Directories",
|
"info-description-cleanable-directories": "Directorios eliminables",
|
||||||
"info-description-cleanable-images": "Cleanable Images",
|
"info-description-cleanable-images": "Imágenes eliminables",
|
||||||
"storage": {
|
"storage": {
|
||||||
"title-temporary-directory": "Directorio temporal (.temp)",
|
"title-temporary-directory": "Directorio temporal (.temp)",
|
||||||
"title-backups-directory": "Backups Directory (backups)",
|
"title-backups-directory": "Directorio de Copias de Seguridad (respaldos)",
|
||||||
"title-groups-directory": "Groups Directory (groups)",
|
"title-groups-directory": "Directorio de Grupos (grupos)",
|
||||||
"title-recipes-directory": "Recipes Directory (recipes)",
|
"title-recipes-directory": "Directorio de Recetas (recetas)",
|
||||||
"title-user-directory": "User Directory (user)"
|
"title-user-directory": "Directorio de usuario (usuario)"
|
||||||
},
|
},
|
||||||
"action-delete-log-files-name": "Delete Log Files",
|
"action-delete-log-files-name": "Borrar archivos de registro",
|
||||||
"action-delete-log-files-description": "Deletes all the log files",
|
"action-delete-log-files-description": "Elimina todos los archivos de registro",
|
||||||
"action-clean-directories-name": "Clean Directories",
|
"action-clean-directories-name": "Limpiar directorios",
|
||||||
"action-clean-directories-description": "Removes all the recipe folders that are not valid UUIDs",
|
"action-clean-directories-description": "Removes all the recipe folders that are not valid UUIDs",
|
||||||
"action-clean-temporary-files-name": "Eliminar archivos temporales",
|
"action-clean-temporary-files-name": "Eliminar archivos temporales",
|
||||||
"action-clean-temporary-files-description": "Eliminar todos los archivos y carpetas del directorio .temp",
|
"action-clean-temporary-files-description": "Eliminar todos los archivos y carpetas del directorio .temp",
|
||||||
@ -1119,8 +1119,8 @@
|
|||||||
"ingredients-natural-language-processor": "Ingredients Natural Language Processor",
|
"ingredients-natural-language-processor": "Ingredients Natural Language Processor",
|
||||||
"ingredients-natural-language-processor-explanation": "Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times. Note that as the model is trained in English only, you may have varied results when using the model in other languages. This page is a playground for testing the model.",
|
"ingredients-natural-language-processor-explanation": "Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times. Note that as the model is trained in English only, you may have varied results when using the model in other languages. This page is a playground for testing the model.",
|
||||||
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
|
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
|
||||||
"nlp": "NLP",
|
"nlp": "PLN",
|
||||||
"brute": "Brute",
|
"brute": "En bruto",
|
||||||
"show-individual-confidence": "Mostrar confianza individual",
|
"show-individual-confidence": "Mostrar confianza individual",
|
||||||
"ingredient-text": "Texto del ingrediente",
|
"ingredient-text": "Texto del ingrediente",
|
||||||
"average-confident": "{0} Confianza",
|
"average-confident": "{0} Confianza",
|
||||||
@ -1148,31 +1148,31 @@
|
|||||||
"user-settings-description": "Administrar preferencias, cambiar contraseña y actualizar correo electrónico",
|
"user-settings-description": "Administrar preferencias, cambiar contraseña y actualizar correo electrónico",
|
||||||
"api-tokens-description": "Administra tus API Tokens para acceder desde aplicaciones externas",
|
"api-tokens-description": "Administra tus API Tokens para acceder desde aplicaciones externas",
|
||||||
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
|
||||||
"group-settings": "Group Settings",
|
"group-settings": "Ajustes de grupo",
|
||||||
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
|
||||||
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
|
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
|
||||||
"members": "Members",
|
"members": "Miembros",
|
||||||
"members-description": "See who's in your group and manage their permissions.",
|
"members-description": "Ver quién está en tu grupo y administrar sus permisos.",
|
||||||
"webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.",
|
"webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.",
|
||||||
"notifiers": "Notifiers",
|
"notifiers": "Notificaciones",
|
||||||
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
|
||||||
"manage-data": "Manage Data",
|
"manage-data": "Administrar datos",
|
||||||
"manage-data-description": "Manage your Food and Units (more options coming soon)",
|
"manage-data-description": "Administra tu Comida y Unidades (próximamente más opciones)",
|
||||||
"data-migrations": "Data Migrations",
|
"data-migrations": "Migración de datos",
|
||||||
"data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown",
|
"data-migrations-description": "Migrar tus datos existentes de otras aplicaciones como Nextcloud Recipes y Chowdown",
|
||||||
"email-sent": "Email Sent",
|
"email-sent": "Email enviado",
|
||||||
"error-sending-email": "Error Sending Email",
|
"error-sending-email": "Error enviando email",
|
||||||
"personal-information": "Personal Information",
|
"personal-information": "Datos Personales",
|
||||||
"preferences": "Preferences",
|
"preferences": "Preferencias",
|
||||||
"show-advanced-description": "Show advanced features (API Keys, Webhooks, and Data Management)",
|
"show-advanced-description": "Mostrar características avanzadas (API Keys, Webhooks y Gestión de Datos)",
|
||||||
"back-to-profile": "Back to Profile",
|
"back-to-profile": "Volver al perfil",
|
||||||
"looking-for-privacy-settings": "Looking for Privacy Settings?",
|
"looking-for-privacy-settings": "¿Buscas los ajustes de privacidad?",
|
||||||
"manage-your-api-tokens": "Manage Your API Tokens",
|
"manage-your-api-tokens": "Gestiona tus API tokens",
|
||||||
"manage-user-profile": "Manage User Profile",
|
"manage-user-profile": "Administrar Perfil de Usuario",
|
||||||
"manage-cookbooks": "Manage Cookbooks",
|
"manage-cookbooks": "Administrar Recetario",
|
||||||
"manage-members": "Gestionar miembros",
|
"manage-members": "Gestionar miembros",
|
||||||
"manage-webhooks": "Manage Webhooks",
|
"manage-webhooks": "Gestionar Webhooks",
|
||||||
"manage-notifiers": "Manage Notifiers",
|
"manage-notifiers": "Administrar Notificadores",
|
||||||
"manage-data-migrations": "Administrar Migraciones de Datos"
|
"manage-data-migrations": "Administrar Migraciones de Datos"
|
||||||
},
|
},
|
||||||
"cookbook": {
|
"cookbook": {
|
||||||
@ -1187,7 +1187,7 @@
|
|||||||
"require-all-tools": "Requiere todos los utensilios",
|
"require-all-tools": "Requiere todos los utensilios",
|
||||||
"cookbook-name": "Nombre del recetario",
|
"cookbook-name": "Nombre del recetario",
|
||||||
"cookbook-with-name": "Recetario {0}",
|
"cookbook-with-name": "Recetario {0}",
|
||||||
"create-a-cookbook": "Create a Cookbook",
|
"create-a-cookbook": "Crear Recetario",
|
||||||
"cookbook": "Cookbook"
|
"cookbook": "Recetario"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@
|
|||||||
"created-on-date": "Gemaakt op {0}",
|
"created-on-date": "Gemaakt op {0}",
|
||||||
"unsaved-changes": "Er zijn niet-opgeslagen wijzigingen. Wil je eerst opslaan voordat je vertrekt? Okay om op te slaan, Annuleren om wijzigingen ongedaan te maken.",
|
"unsaved-changes": "Er zijn niet-opgeslagen wijzigingen. Wil je eerst opslaan voordat je vertrekt? Okay om op te slaan, Annuleren om wijzigingen ongedaan te maken.",
|
||||||
"clipboard-copy-failure": "Kopiëren naar klembord mislukt.",
|
"clipboard-copy-failure": "Kopiëren naar klembord mislukt.",
|
||||||
"confirm-delete-generic-items": "Are you sure you want to delete the following items?"
|
"confirm-delete-generic-items": "Weet u zeker dat u de volgende items wilt verwijderen?"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"are-you-sure-you-want-to-delete-the-group": "Weet je zeker dat je <b>{groupName}<b/> wil verwijderen?",
|
"are-you-sure-you-want-to-delete-the-group": "Weet je zeker dat je <b>{groupName}<b/> wil verwijderen?",
|
||||||
|
@ -707,7 +707,7 @@
|
|||||||
"email-configured": "Email настроен",
|
"email-configured": "Email настроен",
|
||||||
"email-test-results": "Результаты теста Email",
|
"email-test-results": "Результаты теста Email",
|
||||||
"ready": "Готово",
|
"ready": "Готово",
|
||||||
"not-ready": "Не готово - Проверьте переменные окружающей среды",
|
"not-ready": "Не готово - Проверьте переменные окружения",
|
||||||
"succeeded": "Выполнено успешно",
|
"succeeded": "Выполнено успешно",
|
||||||
"failed": "Ошибка",
|
"failed": "Ошибка",
|
||||||
"general-about": "Общая информация",
|
"general-about": "Общая информация",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
interface AuthRedirectParams {
|
interface AdminRedirectParams {
|
||||||
$auth: any
|
$auth: any
|
||||||
redirect: (path: string) => void
|
redirect: (path: string) => void
|
||||||
}
|
}
|
||||||
export default function ({ $auth, redirect }: AuthRedirectParams) {
|
export default function ({ $auth, redirect }: AdminRedirectParams) {
|
||||||
// If the user is not an admin redirect to the home page
|
// If the user is not an admin redirect to the home page
|
||||||
if (!$auth.user.admin) {
|
if (!$auth.user.admin) {
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
11
frontend/middleware/advanced-only.ts
Normal file
11
frontend/middleware/advanced-only.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
interface AdvancedOnlyRedirectParams {
|
||||||
|
$auth: any
|
||||||
|
redirect: (path: string) => void
|
||||||
|
}
|
||||||
|
export default function ({ $auth, redirect }: AdvancedOnlyRedirectParams) {
|
||||||
|
// If the user is not allowed to access advanced features redirect to the home page
|
||||||
|
if (!$auth.user.advanced) {
|
||||||
|
console.warn("User is not allowed to access advanced features");
|
||||||
|
return redirect("/")
|
||||||
|
}
|
||||||
|
}
|
12
frontend/middleware/can-manage-only.ts
Normal file
12
frontend/middleware/can-manage-only.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
interface CanManageRedirectParams {
|
||||||
|
$auth: any
|
||||||
|
redirect: (path: string) => void
|
||||||
|
}
|
||||||
|
export default function ({ $auth, redirect }: CanManageRedirectParams) {
|
||||||
|
// If the user is not allowed to manage group settings redirect to the home page
|
||||||
|
console.log($auth.user)
|
||||||
|
if (!$auth.user.canManage) {
|
||||||
|
console.warn("User is not allowed to manage group settings");
|
||||||
|
return redirect("/")
|
||||||
|
}
|
||||||
|
}
|
11
frontend/middleware/can-organize-only.ts
Normal file
11
frontend/middleware/can-organize-only.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
interface CanOrganizeRedirectParams {
|
||||||
|
$auth: any
|
||||||
|
redirect: (path: string) => void
|
||||||
|
}
|
||||||
|
export default function ({ $auth, redirect }: CanOrganizeRedirectParams) {
|
||||||
|
// If the user is not allowed to organize redirect to the home page
|
||||||
|
if (!$auth.user.canOrganize) {
|
||||||
|
console.warn("User is not allowed to organize data");
|
||||||
|
return redirect("/")
|
||||||
|
}
|
||||||
|
}
|
12
frontend/middleware/group-only.ts
Normal file
12
frontend/middleware/group-only.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
interface GroupOnlyRedirectParams {
|
||||||
|
$auth: any
|
||||||
|
route: any
|
||||||
|
redirect: (path: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ({ $auth, route, redirect }: GroupOnlyRedirectParams) {
|
||||||
|
// this can only be used for routes that have a groupSlug parameter (e.g. /g/:groupSlug/...)
|
||||||
|
if (route.params.groupSlug !== $auth.user.groupSlug) {
|
||||||
|
redirect("/")
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@
|
|||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"isomorphic-dompurify": "^1.0.0",
|
"isomorphic-dompurify": "^1.0.0",
|
||||||
"nuxt": "^2.16.0",
|
"nuxt": "^2.17.3",
|
||||||
"v-jsoneditor": "^1.4.5",
|
"v-jsoneditor": "^1.4.5",
|
||||||
"vue-advanced-cropper": "^1.11.6",
|
"vue-advanced-cropper": "^1.11.6",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
@ -56,8 +56,8 @@
|
|||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@nuxtjs/i18n/**/ufo": "0.7.9",
|
"@nuxtjs/i18n/**/ufo": "0.7.9",
|
||||||
"vue-template-compiler": "2.7.14",
|
|
||||||
"vue-demi": "^0.13.11",
|
"vue-demi": "^0.13.11",
|
||||||
|
"vue-template-compiler": "2.7.16",
|
||||||
"postcss-preset-env": "^7.0.0",
|
"postcss-preset-env": "^7.0.0",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,24 @@
|
|||||||
<BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset">
|
<BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset">
|
||||||
{{ $t("user.generate-password-reset-link") }}
|
{{ $t("user.generate-password-reset-link") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<AppButtonCopy v-if="resetUrl" :copy-text="resetUrl"></AppButtonCopy>
|
</div>
|
||||||
|
<div v-if="resetUrl" class="mb-2">
|
||||||
|
<v-card-text>
|
||||||
|
<p class="text-center pb-0">
|
||||||
|
{{ resetUrl }}
|
||||||
|
</p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="align-center pt-0" style="gap: 4px">
|
||||||
|
<BaseButton cancel @click="resetUrl = ''"> {{ $t("general.close") }} </BaseButton>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<BaseButton v-if="user.email" color="info" class="mr-1" @click="sendResetEmail">
|
||||||
|
<template #icon>
|
||||||
|
{{ $globals.icons.email }}
|
||||||
|
</template>
|
||||||
|
{{ $t("user.email") }}
|
||||||
|
</BaseButton>
|
||||||
|
<AppButtonCopy :icon="false" color="info" :copy-text="resetUrl" />
|
||||||
|
</v-card-actions>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AutoForm v-model="user" :items="userForm" update-mode :disabled-fields="disabledFields" />
|
<AutoForm v-model="user" :items="userForm" update-mode :disabled-fields="disabledFields" />
|
||||||
@ -46,7 +63,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useAdminApi } from "~/composables/api";
|
import { useAdminApi, useUserApi } from "~/composables/api";
|
||||||
import { useGroups } from "~/composables/use-groups";
|
import { useGroups } from "~/composables/use-groups";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { useUserForm } from "~/composables/use-users";
|
import { useUserForm } from "~/composables/use-users";
|
||||||
@ -118,6 +135,17 @@ export default defineComponent({
|
|||||||
generatingToken.value = false;
|
generatingToken.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userApi = useUserApi();
|
||||||
|
async function sendResetEmail() {
|
||||||
|
if (!user.value?.email) return;
|
||||||
|
const { response } = await userApi.email.sendForgotPassword({ email: user.value.email });
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
alert.success(i18n.tc("profile.email-sent"));
|
||||||
|
} else {
|
||||||
|
alert.error(i18n.tc("profile.error-sending-email"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
disabledFields,
|
disabledFields,
|
||||||
@ -130,6 +158,7 @@ export default defineComponent({
|
|||||||
handlePasswordReset,
|
handlePasswordReset,
|
||||||
resetUrl,
|
resetUrl,
|
||||||
generatingToken,
|
generatingToken,
|
||||||
|
sendResetEmail,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -98,31 +98,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, ref, useRouter } from "@nuxtjs/composition-api";
|
|
||||||
|
import { defineComponent, reactive, ref } from "@nuxtjs/composition-api";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
|
||||||
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
||||||
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { CookbookEditor, draggable },
|
components: { CookbookEditor, draggable },
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { isOwnGroup, loggedIn } = useLoggedInState();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
if (!(loggedIn.value && isOwnGroup.value)) {
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialogStates = reactive({
|
const dialogStates = reactive({
|
||||||
create: false,
|
create: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cookbooks, actions } = useCookbooks();
|
const { cookbooks, actions } = useCookbooks();
|
||||||
|
|
||||||
|
|
||||||
// create
|
// create
|
||||||
const createTarget = ref<ReadCookBook | null>(null);
|
const createTarget = ref<ReadCookBook | null>(null);
|
||||||
async function createCookbook() {
|
async function createCookbook() {
|
||||||
@ -146,7 +138,6 @@ export default defineComponent({
|
|||||||
dialogStates.delete = false;
|
dialogStates.delete = false;
|
||||||
deleteTarget.value = null;
|
deleteTarget.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cookbooks,
|
cookbooks,
|
||||||
actions,
|
actions,
|
||||||
|
@ -48,46 +48,54 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-expansion-panels v-model="panels" multiple>
|
<v-expansion-panels v-model="panels" multiple>
|
||||||
<v-expansion-panel v-for="(ing, index) in parsedIng" :key="index">
|
<draggable
|
||||||
<v-expansion-panel-header class="my-0 py-0" disable-icon-rotate>
|
v-if="parsedIng.length > 0"
|
||||||
<template #default="{ open }">
|
v-model="parsedIng"
|
||||||
<v-fade-transition>
|
handle=".handle"
|
||||||
<span v-if="!open" key="0"> {{ ing.input }} </span>
|
:style="{ width: '100%' }"
|
||||||
</v-fade-transition>
|
ghost-class="ghost"
|
||||||
</template>
|
>
|
||||||
<template #actions>
|
<v-expansion-panel v-for="(ing, index) in parsedIng" :key="index">
|
||||||
<v-icon left :color="isError(ing) ? 'error' : 'success'">
|
<v-expansion-panel-header class="my-0 py-0" disable-icon-rotate>
|
||||||
{{ isError(ing) ? $globals.icons.alert : $globals.icons.check }}
|
<template #default="{ open }">
|
||||||
</v-icon>
|
<v-fade-transition>
|
||||||
<div class="my-auto" :color="isError(ing) ? 'error-text' : 'success-text'">
|
<span v-if="!open" key="0"> {{ ing.input }} </span>
|
||||||
{{ ing.confidence ? asPercentage(ing.confidence.average) : "" }}
|
</v-fade-transition>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
<template #actions>
|
||||||
</v-expansion-panel-header>
|
<v-icon left :color="isError(ing) ? 'error' : 'success'">
|
||||||
<v-expansion-panel-content class="pb-0 mb-0">
|
{{ isError(ing) ? $globals.icons.alert : $globals.icons.check }}
|
||||||
<RecipeIngredientEditor v-model="parsedIng[index].ingredient" allow-insert-ingredient @insert-ingredient="insertIngredient(index)" @delete="deleteIngredient(index)" />
|
</v-icon>
|
||||||
{{ ing.input }}
|
<div class="my-auto" :color="isError(ing) ? 'error-text' : 'success-text'">
|
||||||
<v-card-actions>
|
{{ ing.confidence ? asPercentage(ing.confidence.average) : "" }}
|
||||||
<v-spacer />
|
</div>
|
||||||
<BaseButton
|
</template>
|
||||||
v-if="errors[index].unitError && errors[index].unitErrorMessage !== ''"
|
</v-expansion-panel-header>
|
||||||
color="warning"
|
<v-expansion-panel-content class="pb-0 mb-0">
|
||||||
small
|
<RecipeIngredientEditor v-model="parsedIng[index].ingredient" allow-insert-ingredient @insert-ingredient="insertIngredient(index)" @delete="deleteIngredient(index)" />
|
||||||
@click="createUnit(ing.ingredient.unit, index)"
|
{{ ing.input }}
|
||||||
>
|
<v-card-actions>
|
||||||
{{ errors[index].unitErrorMessage }}
|
<v-spacer />
|
||||||
</BaseButton>
|
<BaseButton
|
||||||
<BaseButton
|
v-if="errors[index].unitError && errors[index].unitErrorMessage !== ''"
|
||||||
v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''"
|
color="warning"
|
||||||
color="warning"
|
small
|
||||||
small
|
@click="createUnit(ing.ingredient.unit, index)"
|
||||||
@click="createFood(ing.ingredient.food, index)"
|
>
|
||||||
>
|
{{ errors[index].unitErrorMessage }}
|
||||||
{{ errors[index].foodErrorMessage }}
|
</BaseButton>
|
||||||
</BaseButton>
|
<BaseButton
|
||||||
</v-card-actions>
|
v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''"
|
||||||
</v-expansion-panel-content>
|
color="warning"
|
||||||
</v-expansion-panel>
|
small
|
||||||
|
@click="createFood(ing.ingredient.food, index)"
|
||||||
|
>
|
||||||
|
{{ errors[index].foodErrorMessage }}
|
||||||
|
</BaseButton>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-expansion-panel-content>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</draggable>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-container>
|
</v-container>
|
||||||
@ -96,6 +104,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, ref, useContext, useRoute, useRouter } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, ref, useContext, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { invoke, until } from "@vueuse/core";
|
import { invoke, until } from "@vueuse/core";
|
||||||
|
import draggable from "vuedraggable";
|
||||||
import {
|
import {
|
||||||
CreateIngredientFood,
|
CreateIngredientFood,
|
||||||
CreateIngredientUnit,
|
CreateIngredientUnit,
|
||||||
@ -122,7 +131,9 @@ interface Error {
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipeIngredientEditor,
|
RecipeIngredientEditor,
|
||||||
|
draggable
|
||||||
},
|
},
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const panels = ref<number[]>([]);
|
const panels = ref<number[]>([]);
|
||||||
|
@ -33,6 +33,7 @@ import AdvancedOnly from "~/components/global/AdvancedOnly.vue";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AdvancedOnly },
|
components: { AdvancedOnly },
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth, $globals, i18n } = useContext();
|
const { $auth, $globals, i18n } = useContext();
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export default defineComponent({
|
|||||||
components: {
|
components: {
|
||||||
RecipeOrganizerPage,
|
RecipeOrganizerPage,
|
||||||
},
|
},
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { items, actions } = useCategoryStore();
|
const { items, actions } = useCategoryStore();
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export default defineComponent({
|
|||||||
components: {
|
components: {
|
||||||
RecipeOrganizerPage,
|
RecipeOrganizerPage,
|
||||||
},
|
},
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { items, actions } = useTagStore();
|
const { items, actions } = useTagStore();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import RecipeTimeline from "~/components/Domain/Recipe/RecipeTimeline.vue";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeTimeline },
|
components: { RecipeTimeline },
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const ready = ref<boolean>(false);
|
const ready = ref<boolean>(false);
|
||||||
|
@ -22,6 +22,7 @@ export default defineComponent({
|
|||||||
components: {
|
components: {
|
||||||
RecipeOrganizerPage,
|
RecipeOrganizerPage,
|
||||||
},
|
},
|
||||||
|
middleware: ["auth", "group-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const toolStore = useToolStore();
|
const toolStore = useToolStore();
|
||||||
const dialog = ref(false);
|
const dialog = ref(false);
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: ["auth", "can-organize-only"],
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -227,7 +227,6 @@ import { useFoodStore, useLabelStore } from "~/composables/store";
|
|||||||
import { VForm } from "~/types/vuetify";
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
||||||
components: { MultiPurposeLabel, RecipeDataAliasManagerDialog },
|
components: { MultiPurposeLabel, RecipeDataAliasManagerDialog },
|
||||||
setup() {
|
setup() {
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
|
@ -65,6 +65,7 @@ import { useGroupSelf } from "~/composables/use-groups";
|
|||||||
import { ReadGroupPreferences } from "~/lib/api/types/group";
|
import { ReadGroupPreferences } from "~/lib/api/types/group";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: ["auth", "can-manage-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { group, actions: groupActions } = useGroupSelf();
|
const { group, actions: groupActions } = useGroupSelf();
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ import { isSameDay, addDays, parseISO } from "date-fns";
|
|||||||
import { useMealplans } from "~/composables/use-group-mealplan";
|
import { useMealplans } from "~/composables/use-group-mealplan";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: ["auth"],
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
:recipe-id="mealplan.recipe ? mealplan.recipe.id : ''"
|
:recipe-id="mealplan.recipe ? mealplan.recipe.id : ''"
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
:route="mealplan.recipe ? true : false"
|
:route="mealplan.recipe ? true : false"
|
||||||
|
:rating="mealplan.recipe ? mealplan.recipe.rating : 0"
|
||||||
:slug="mealplan.recipe ? mealplan.recipe.slug : mealplan.title"
|
:slug="mealplan.recipe ? mealplan.recipe.slug : mealplan.title"
|
||||||
:description="mealplan.recipe ? mealplan.recipe.description : mealplan.text"
|
:description="mealplan.recipe ? mealplan.recipe.description : mealplan.text"
|
||||||
:name="mealplan.recipe ? mealplan.recipe.name : mealplan.title"
|
:name="mealplan.recipe ? mealplan.recipe.name : mealplan.title"
|
||||||
|
@ -98,6 +98,7 @@ export default defineComponent({
|
|||||||
GroupMealPlanRuleForm,
|
GroupMealPlanRuleForm,
|
||||||
RecipeChips,
|
RecipeChips,
|
||||||
},
|
},
|
||||||
|
middleware: ["auth"],
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -78,6 +78,7 @@ export default defineComponent({
|
|||||||
components: {
|
components: {
|
||||||
UserAvatar,
|
UserAvatar,
|
||||||
},
|
},
|
||||||
|
middleware: ["auth"],
|
||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ const MIGRATIONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: ["auth", "advanced-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { $globals, i18n } = useContext();
|
const { $globals, i18n } = useContext();
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ interface OptionSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: ["auth", "advanced-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { ReportOut } from "~/lib/api/types/reports";
|
import { ReportOut } from "~/lib/api/types/reports";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: "auth",
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const id = route.value.params.id;
|
const id = route.value.params.id;
|
||||||
|
@ -50,6 +50,7 @@ import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { GroupWebhookEditor },
|
components: { GroupWebhookEditor },
|
||||||
|
middleware: ["auth", "advanced-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const { actions, webhooks } = useGroupWebhooks();
|
const { actions, webhooks } = useGroupWebhooks();
|
||||||
|
|
||||||
|
@ -235,6 +235,7 @@ export default defineComponent({
|
|||||||
RecipeList,
|
RecipeList,
|
||||||
ShoppingListItemEditor,
|
ShoppingListItemEditor,
|
||||||
},
|
},
|
||||||
|
middleware: "auth",
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
const preferences = useShoppingListPreferences();
|
const preferences = useShoppingListPreferences();
|
||||||
@ -643,15 +644,15 @@ export default defineComponent({
|
|||||||
// Create New Item
|
// Create New Item
|
||||||
|
|
||||||
const createEditorOpen = ref(false);
|
const createEditorOpen = ref(false);
|
||||||
const createListItemData = ref<ShoppingListItemCreate>(ingredientResetFactory());
|
const createListItemData = ref<ShoppingListItemCreate>(listItemFactory());
|
||||||
|
|
||||||
function ingredientResetFactory(): ShoppingListItemCreate {
|
function listItemFactory(isFood = false): ShoppingListItemCreate {
|
||||||
return {
|
return {
|
||||||
shoppingListId: id,
|
shoppingListId: id,
|
||||||
checked: false,
|
checked: false,
|
||||||
position: shoppingList.value?.listItems?.length || 1,
|
position: shoppingList.value?.listItems?.length || 1,
|
||||||
isFood: false,
|
isFood,
|
||||||
quantity: 1,
|
quantity: 0,
|
||||||
note: "",
|
note: "",
|
||||||
labelId: undefined,
|
labelId: undefined,
|
||||||
unitId: undefined,
|
unitId: undefined,
|
||||||
@ -664,6 +665,11 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!createListItemData.value.foodId && !createListItemData.value.note) {
|
||||||
|
// don't create an empty item
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loadingCounter.value += 1;
|
loadingCounter.value += 1;
|
||||||
|
|
||||||
// make sure it's inserted into the end of the list, which may have been updated
|
// make sure it's inserted into the end of the list, which may have been updated
|
||||||
@ -674,8 +680,7 @@ export default defineComponent({
|
|||||||
loadingCounter.value -= 1;
|
loadingCounter.value -= 1;
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
createListItemData.value = ingredientResetFactory();
|
createListItemData.value = listItemFactory(createListItemData.value.isFood || false);
|
||||||
createEditorOpen.value = false;
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: "auth",
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
|
@ -14,6 +14,7 @@ import { useAsyncKey } from "~/composables/use-utils";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeCardSection },
|
components: { RecipeCardSection },
|
||||||
|
middleware: "auth",
|
||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -69,6 +69,7 @@ import { useUserApi } from "~/composables/api";
|
|||||||
import { VForm } from "~/types/vuetify";
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
middleware: ["auth", "advanced-only"],
|
||||||
setup() {
|
setup() {
|
||||||
const nuxtContext = useContext();
|
const nuxtContext = useContext();
|
||||||
|
|
||||||
|
@ -135,6 +135,7 @@ export default defineComponent({
|
|||||||
UserAvatar,
|
UserAvatar,
|
||||||
UserPasswordStrength,
|
UserPasswordStrength,
|
||||||
},
|
},
|
||||||
|
middleware: "auth",
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const user = computed(() => $auth.user as unknown as UserOut);
|
const user = computed(() => $auth.user as unknown as UserOut);
|
||||||
|
@ -113,7 +113,7 @@
|
|||||||
<p>{{ $t('profile.group-description') }}</p>
|
<p>{{ $t('profile.group-description') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<v-row tag="section">
|
<v-row tag="section">
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col v-if="$auth.user.canManage" cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.group-settings'), to: `/group` }"
|
:link="{ text: $tc('profile.group-settings'), to: `/group` }"
|
||||||
:image="require('~/static/svgs/manage-group-settings.svg')"
|
:image="require('~/static/svgs/manage-group-settings.svg')"
|
||||||
@ -162,17 +162,16 @@
|
|||||||
</UserProfileLinkCard>
|
</UserProfileLinkCard>
|
||||||
</v-col>
|
</v-col>
|
||||||
</AdvancedOnly>
|
</AdvancedOnly>
|
||||||
<AdvancedOnly>
|
<!-- $auth.user.canOrganize should not be null because of the auth middleware -->
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col v-if="$auth.user.canOrganize" cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-data'), to: `/group/data/foods` }"
|
:link="{ text: $tc('profile.manage-data'), to: `/group/data/foods` }"
|
||||||
:image="require('~/static/svgs/manage-recipes.svg')"
|
:image="require('~/static/svgs/manage-recipes.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('profile.manage-data') }} </template>
|
<template #title> {{ $t('profile.manage-data') }} </template>
|
||||||
{{ $t('profile.manage-data-description') }}
|
{{ $t('profile.manage-data-description') }}
|
||||||
</UserProfileLinkCard>
|
</UserProfileLinkCard>
|
||||||
</v-col>
|
</v-col>
|
||||||
</AdvancedOnly>
|
|
||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
@ -208,6 +207,7 @@ export default defineComponent({
|
|||||||
UserAvatar,
|
UserAvatar,
|
||||||
StatsCards,
|
StatsCards,
|
||||||
},
|
},
|
||||||
|
middleware: "auth",
|
||||||
scrollToTop: true,
|
scrollToTop: true,
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
|
2345
frontend/yarn.lock
2345
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -112,7 +112,7 @@ async def system_startup():
|
|||||||
logger.info("-----SYSTEM STARTUP----- \n")
|
logger.info("-----SYSTEM STARTUP----- \n")
|
||||||
logger.info("------APP SETTINGS------")
|
logger.info("------APP SETTINGS------")
|
||||||
logger.info(
|
logger.info(
|
||||||
settings.json(
|
settings.model_dump_json(
|
||||||
indent=4,
|
indent=4,
|
||||||
exclude={
|
exclude={
|
||||||
"SECRET",
|
"SECRET",
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
from pydantic import BaseModel, BaseSettings, PostgresDsn
|
from pydantic import BaseModel, PostgresDsn
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class AbstractDBProvider(ABC):
|
class AbstractDBProvider(ABC):
|
||||||
@ -38,15 +40,19 @@ class PostgresProvider(AbstractDBProvider, BaseSettings):
|
|||||||
POSTGRES_PORT: str = "5432"
|
POSTGRES_PORT: str = "5432"
|
||||||
POSTGRES_DB: str = "mealie"
|
POSTGRES_DB: str = "mealie"
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def db_url(self) -> str:
|
def db_url(self) -> str:
|
||||||
host = f"{self.POSTGRES_SERVER}:{self.POSTGRES_PORT}"
|
host = f"{self.POSTGRES_SERVER}:{self.POSTGRES_PORT}"
|
||||||
return PostgresDsn.build(
|
return str(
|
||||||
scheme="postgresql",
|
PostgresDsn.build(
|
||||||
user=self.POSTGRES_USER,
|
scheme="postgresql",
|
||||||
password=self.POSTGRES_PASSWORD,
|
username=self.POSTGRES_USER,
|
||||||
host=host,
|
password=urlparse.quote_plus(self.POSTGRES_PASSWORD),
|
||||||
path=f"/{self.POSTGRES_DB or ''}",
|
host=host,
|
||||||
|
path=f"{self.POSTGRES_DB or ''}",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import secrets
|
import secrets
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from pydantic import BaseSettings, NoneStr, validator
|
from pydantic import field_validator
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
from mealie.core.settings.themes import Theme
|
from mealie.core.settings.themes import Theme
|
||||||
|
|
||||||
@ -55,7 +56,8 @@ class AppSettings(BaseSettings):
|
|||||||
SECURITY_USER_LOCKOUT_TIME: int = 24
|
SECURITY_USER_LOCKOUT_TIME: int = 24
|
||||||
"time in hours"
|
"time in hours"
|
||||||
|
|
||||||
@validator("BASE_URL")
|
@field_validator("BASE_URL")
|
||||||
|
@classmethod
|
||||||
def remove_trailing_slash(cls, v: str) -> str:
|
def remove_trailing_slash(cls, v: str) -> str:
|
||||||
if v and v[-1] == "/":
|
if v and v[-1] == "/":
|
||||||
return v[:-1]
|
return v[:-1]
|
||||||
@ -100,12 +102,12 @@ class AppSettings(BaseSettings):
|
|||||||
# ===============================================
|
# ===============================================
|
||||||
# Email Configuration
|
# Email Configuration
|
||||||
|
|
||||||
SMTP_HOST: str | None
|
SMTP_HOST: str | None = None
|
||||||
SMTP_PORT: str | None = "587"
|
SMTP_PORT: str | None = "587"
|
||||||
SMTP_FROM_NAME: str | None = "Mealie"
|
SMTP_FROM_NAME: str | None = "Mealie"
|
||||||
SMTP_FROM_EMAIL: str | None
|
SMTP_FROM_EMAIL: str | None = None
|
||||||
SMTP_USER: str | None
|
SMTP_USER: str | None = None
|
||||||
SMTP_PASSWORD: str | None
|
SMTP_PASSWORD: str | None = None
|
||||||
SMTP_AUTH_STRATEGY: str | None = "TLS" # Options: 'TLS', 'SSL', 'NONE'
|
SMTP_AUTH_STRATEGY: str | None = "TLS" # Options: 'TLS', 'SSL', 'NONE'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -122,11 +124,11 @@ class AppSettings(BaseSettings):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_smtp(
|
def validate_smtp(
|
||||||
host: str | None,
|
host: str | None = None,
|
||||||
port: str | None,
|
port: str | None = None,
|
||||||
from_name: str | None,
|
from_name: str | None = None,
|
||||||
from_email: str | None,
|
from_email: str | None = None,
|
||||||
strategy: str | None,
|
strategy: str | None = None,
|
||||||
user: str | None = None,
|
user: str | None = None,
|
||||||
password: str | None = None,
|
password: str | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@ -143,15 +145,15 @@ class AppSettings(BaseSettings):
|
|||||||
# LDAP Configuration
|
# LDAP Configuration
|
||||||
|
|
||||||
LDAP_AUTH_ENABLED: bool = False
|
LDAP_AUTH_ENABLED: bool = False
|
||||||
LDAP_SERVER_URL: NoneStr = None
|
LDAP_SERVER_URL: str | None = None
|
||||||
LDAP_TLS_INSECURE: bool = False
|
LDAP_TLS_INSECURE: bool = False
|
||||||
LDAP_TLS_CACERTFILE: NoneStr = None
|
LDAP_TLS_CACERTFILE: str | None = None
|
||||||
LDAP_ENABLE_STARTTLS: bool = False
|
LDAP_ENABLE_STARTTLS: bool = False
|
||||||
LDAP_BASE_DN: NoneStr = None
|
LDAP_BASE_DN: str | None = None
|
||||||
LDAP_QUERY_BIND: NoneStr = None
|
LDAP_QUERY_BIND: str | None = None
|
||||||
LDAP_QUERY_PASSWORD: NoneStr = None
|
LDAP_QUERY_PASSWORD: str | None = None
|
||||||
LDAP_USER_FILTER: NoneStr = None
|
LDAP_USER_FILTER: str | None = None
|
||||||
LDAP_ADMIN_FILTER: NoneStr = None
|
LDAP_ADMIN_FILTER: str | None = None
|
||||||
LDAP_ID_ATTRIBUTE: str = "uid"
|
LDAP_ID_ATTRIBUTE: str = "uid"
|
||||||
LDAP_MAIL_ATTRIBUTE: str = "mail"
|
LDAP_MAIL_ATTRIBUTE: str = "mail"
|
||||||
LDAP_NAME_ATTRIBUTE: str = "name"
|
LDAP_NAME_ATTRIBUTE: str = "name"
|
||||||
@ -173,9 +175,7 @@ class AppSettings(BaseSettings):
|
|||||||
# Testing Config
|
# Testing Config
|
||||||
|
|
||||||
TESTING: bool = False
|
TESTING: bool = False
|
||||||
|
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow")
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
|
|
||||||
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from pydantic import BaseSettings
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class Theme(BaseSettings):
|
class Theme(BaseSettings):
|
||||||
@ -17,6 +17,4 @@ class Theme(BaseSettings):
|
|||||||
dark_info: str = "#1976D2"
|
dark_info: str = "#1976D2"
|
||||||
dark_warning: str = "#FF6D00"
|
dark_warning: str = "#FF6D00"
|
||||||
dark_error: str = "#EF5350"
|
dark_error: str = "#EF5350"
|
||||||
|
model_config = SettingsConfigDict(env_prefix="theme_", extra="allow")
|
||||||
class Config:
|
|
||||||
env_prefix = "theme_"
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@ -87,7 +88,12 @@ def main():
|
|||||||
if max_retry == 0:
|
if max_retry == 0:
|
||||||
raise ConnectionError("Database connection failed - exiting application.")
|
raise ConnectionError("Database connection failed - exiting application.")
|
||||||
|
|
||||||
alembic_cfg = Config(str(PROJECT_DIR / "alembic.ini"))
|
alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FILE", default=str(PROJECT_DIR / "alembic.ini"))
|
||||||
|
|
||||||
|
if not os.path.isfile(alembic_cfg_path):
|
||||||
|
raise Exception("Provided alembic config path doesn't exist")
|
||||||
|
|
||||||
|
alembic_cfg = Config(alembic_cfg_path)
|
||||||
if db_is_at_head(alembic_cfg):
|
if db_is_at_head(alembic_cfg):
|
||||||
logger.debug("Migration not needed.")
|
logger.debug("Migration not needed.")
|
||||||
else:
|
else:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, NoneStr
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import MANYTOMANY, MANYTOONE, ONETOMANY, Session
|
from sqlalchemy.orm import MANYTOMANY, MANYTOONE, ONETOMANY, Session
|
||||||
from sqlalchemy.orm.mapper import Mapper
|
from sqlalchemy.orm.mapper import Mapper
|
||||||
@ -21,7 +21,7 @@ class AutoInitConfig(BaseModel):
|
|||||||
Config class for `auto_init` decorator.
|
Config class for `auto_init` decorator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
get_attr: NoneStr = None
|
get_attr: str | None = None
|
||||||
exclude: set = Field(default_factory=_default_exclusion)
|
exclude: set = Field(default_factory=_default_exclusion)
|
||||||
# auto_create: bool = False
|
# auto_create: bool = False
|
||||||
|
|
||||||
@ -31,16 +31,16 @@ def _get_config(relation_cls: type[SqlAlchemyBase]) -> AutoInitConfig:
|
|||||||
Returns the config for the given class.
|
Returns the config for the given class.
|
||||||
"""
|
"""
|
||||||
cfg = AutoInitConfig()
|
cfg = AutoInitConfig()
|
||||||
cfgKeys = cfg.dict().keys()
|
cfgKeys = cfg.model_dump().keys()
|
||||||
# Get the config for the class
|
# Get the config for the class
|
||||||
try:
|
try:
|
||||||
class_config: AutoInitConfig = relation_cls.Config
|
class_config: ConfigDict = relation_cls.model_config
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return cfg
|
return cfg
|
||||||
# Map all matching attributes in Config to all AutoInitConfig attributes
|
# Map all matching attributes in Config to all AutoInitConfig attributes
|
||||||
for attr in dir(class_config):
|
for attr in class_config:
|
||||||
if attr in cfgKeys:
|
if attr in cfgKeys:
|
||||||
setattr(cfg, attr, getattr(class_config, attr))
|
setattr(cfg, attr, class_config[attr])
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ def handle_one_to_many_list(
|
|||||||
|
|
||||||
updated_elems.append(existing_elem)
|
updated_elems.append(existing_elem)
|
||||||
|
|
||||||
new_elems = [safe_call(relation_cls, elem, session=session) for elem in elems_to_create]
|
new_elems = [safe_call(relation_cls, elem.copy(), session=session) for elem in elems_to_create]
|
||||||
return new_elems + updated_elems
|
return new_elems + updated_elems
|
||||||
|
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ def auto_init(): # sourcery no-metrics
|
|||||||
setattr(self, key, instances)
|
setattr(self, key, instances)
|
||||||
|
|
||||||
elif relation_dir == ONETOMANY:
|
elif relation_dir == ONETOMANY:
|
||||||
instance = safe_call(relation_cls, val, session=session)
|
instance = safe_call(relation_cls, val.copy() if val else None, session=session)
|
||||||
setattr(self, key, instance)
|
setattr(self, key, instance)
|
||||||
|
|
||||||
elif relation_dir == MANYTOONE and not use_list:
|
elif relation_dir == MANYTOONE and not use_list:
|
||||||
|
@ -29,12 +29,15 @@ def get_valid_call(func: Callable, args_dict) -> dict:
|
|||||||
return {k: v for k, v in args_dict.items() if k in valid_args}
|
return {k: v for k, v in args_dict.items() if k in valid_args}
|
||||||
|
|
||||||
|
|
||||||
def safe_call(func, dict_args: dict, **kwargs) -> Any:
|
def safe_call(func, dict_args: dict | None, **kwargs) -> Any:
|
||||||
"""
|
"""
|
||||||
Safely calls the supplied function with the supplied dictionary of arguments.
|
Safely calls the supplied function with the supplied dictionary of arguments.
|
||||||
by removing any invalid arguments.
|
by removing any invalid arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if dict_args is None:
|
||||||
|
dict_args = {}
|
||||||
|
|
||||||
if kwargs:
|
if kwargs:
|
||||||
dict_args.update(kwargs)
|
dict_args.update(kwargs)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Optional
|
|||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import sqlalchemy.orm as orm
|
import sqlalchemy.orm as orm
|
||||||
|
from pydantic import ConfigDict
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
@ -79,9 +80,8 @@ class Group(SqlAlchemyBase, BaseMixins):
|
|||||||
ingredient_foods: Mapped[list["IngredientFoodModel"]] = orm.relationship("IngredientFoodModel", **common_args)
|
ingredient_foods: Mapped[list["IngredientFoodModel"]] = orm.relationship("IngredientFoodModel", **common_args)
|
||||||
tools: Mapped[list["Tool"]] = orm.relationship("Tool", **common_args)
|
tools: Mapped[list["Tool"]] = orm.relationship("Tool", **common_args)
|
||||||
tags: Mapped[list["Tag"]] = orm.relationship("Tag", **common_args)
|
tags: Mapped[list["Tag"]] = orm.relationship("Tag", **common_args)
|
||||||
|
model_config = ConfigDict(
|
||||||
class Config:
|
exclude={
|
||||||
exclude = {
|
|
||||||
"users",
|
"users",
|
||||||
"webhooks",
|
"webhooks",
|
||||||
"shopping_lists",
|
"shopping_lists",
|
||||||
@ -91,6 +91,7 @@ class Group(SqlAlchemyBase, BaseMixins):
|
|||||||
"mealplans",
|
"mealplans",
|
||||||
"data_exports",
|
"data_exports",
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, **_) -> None:
|
def __init__(self, **_) -> None:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from pydantic import ConfigDict
|
||||||
from sqlalchemy import ForeignKey, orm
|
from sqlalchemy import ForeignKey, orm
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from sqlalchemy.sql.sqltypes import Boolean, DateTime, String
|
from sqlalchemy.sql.sqltypes import Boolean, DateTime, String
|
||||||
@ -47,9 +48,7 @@ class ReportModel(SqlAlchemyBase, BaseMixins):
|
|||||||
# Relationships
|
# Relationships
|
||||||
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
group_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("groups.id"), nullable=False, index=True)
|
||||||
group: Mapped["Group"] = orm.relationship("Group", back_populates="group_reports", single_parent=True)
|
group: Mapped["Group"] = orm.relationship("Group", back_populates="group_reports", single_parent=True)
|
||||||
|
model_config = ConfigDict(exclude=["entries"])
|
||||||
class Config:
|
|
||||||
exclude = ["entries"]
|
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, **_) -> None:
|
def __init__(self, **_) -> None:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from pydantic import ConfigDict
|
||||||
from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, UniqueConstraint, orm
|
from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, UniqueConstraint, orm
|
||||||
from sqlalchemy.ext.orderinglist import ordering_list
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
@ -69,9 +70,7 @@ class ShoppingListItem(SqlAlchemyBase, BaseMixins):
|
|||||||
recipe_references: Mapped[list[ShoppingListItemRecipeReference]] = orm.relationship(
|
recipe_references: Mapped[list[ShoppingListItemRecipeReference]] = orm.relationship(
|
||||||
ShoppingListItemRecipeReference, cascade="all, delete, delete-orphan"
|
ShoppingListItemRecipeReference, cascade="all, delete, delete-orphan"
|
||||||
)
|
)
|
||||||
|
model_config = ConfigDict(exclude={"id", "label", "food", "unit"})
|
||||||
class Config:
|
|
||||||
exclude = {"id", "label", "food", "unit"}
|
|
||||||
|
|
||||||
@api_extras
|
@api_extras
|
||||||
@auto_init()
|
@auto_init()
|
||||||
@ -91,9 +90,7 @@ class ShoppingListRecipeReference(BaseMixins, SqlAlchemyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
recipe_quantity: Mapped[float] = mapped_column(Float, nullable=False)
|
recipe_quantity: Mapped[float] = mapped_column(Float, nullable=False)
|
||||||
|
model_config = ConfigDict(exclude={"id", "recipe"})
|
||||||
class Config:
|
|
||||||
exclude = {"id", "recipe"}
|
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, **_) -> None:
|
def __init__(self, **_) -> None:
|
||||||
@ -112,9 +109,7 @@ class ShoppingListMultiPurposeLabel(SqlAlchemyBase, BaseMixins):
|
|||||||
"MultiPurposeLabel", back_populates="shopping_lists_label_settings"
|
"MultiPurposeLabel", back_populates="shopping_lists_label_settings"
|
||||||
)
|
)
|
||||||
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||||
|
model_config = ConfigDict(exclude={"label"})
|
||||||
class Config:
|
|
||||||
exclude = {"label"}
|
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, **_) -> None:
|
def __init__(self, **_) -> None:
|
||||||
@ -146,9 +141,7 @@ class ShoppingList(SqlAlchemyBase, BaseMixins):
|
|||||||
collection_class=ordering_list("position"),
|
collection_class=ordering_list("position"),
|
||||||
)
|
)
|
||||||
extras: Mapped[list[ShoppingListExtras]] = orm.relationship("ShoppingListExtras", cascade="all, delete-orphan")
|
extras: Mapped[list[ShoppingListExtras]] = orm.relationship("ShoppingListExtras", cascade="all, delete-orphan")
|
||||||
|
model_config = ConfigDict(exclude={"id", "list_items"})
|
||||||
class Config:
|
|
||||||
exclude = {"id", "list_items"}
|
|
||||||
|
|
||||||
@api_extras
|
@api_extras
|
||||||
@auto_init()
|
@auto_init()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from pydantic import ConfigDict
|
||||||
from sqlalchemy import ForeignKey, Integer, String, orm
|
from sqlalchemy import ForeignKey, Integer, String, orm
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
@ -28,12 +29,12 @@ class RecipeInstruction(SqlAlchemyBase):
|
|||||||
ingredient_references: Mapped[list[RecipeIngredientRefLink]] = orm.relationship(
|
ingredient_references: Mapped[list[RecipeIngredientRefLink]] = orm.relationship(
|
||||||
RecipeIngredientRefLink, cascade="all, delete-orphan"
|
RecipeIngredientRefLink, cascade="all, delete-orphan"
|
||||||
)
|
)
|
||||||
|
model_config = ConfigDict(
|
||||||
class Config:
|
exclude={
|
||||||
exclude = {
|
|
||||||
"id",
|
"id",
|
||||||
"ingredient_references",
|
"ingredient_references",
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, ingredient_references, session, **_) -> None:
|
def __init__(self, ingredient_references, session, **_) -> None:
|
||||||
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import sqlalchemy.orm as orm
|
import sqlalchemy.orm as orm
|
||||||
|
from pydantic import ConfigDict
|
||||||
from sqlalchemy import event
|
from sqlalchemy import event
|
||||||
from sqlalchemy.ext.orderinglist import ordering_list
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, validates
|
from sqlalchemy.orm import Mapped, mapped_column, validates
|
||||||
@ -134,10 +135,9 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||||
name_normalized: Mapped[str] = mapped_column(sa.String, nullable=False, index=True)
|
name_normalized: Mapped[str] = mapped_column(sa.String, nullable=False, index=True)
|
||||||
description_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
description_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||||
|
model_config = ConfigDict(
|
||||||
class Config:
|
get_attr="slug",
|
||||||
get_attr = "slug"
|
exclude={
|
||||||
exclude = {
|
|
||||||
"assets",
|
"assets",
|
||||||
"notes",
|
"notes",
|
||||||
"nutrition",
|
"nutrition",
|
||||||
@ -146,7 +146,8 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
"settings",
|
"settings",
|
||||||
"comments",
|
"comments",
|
||||||
"timeline_events",
|
"timeline_events",
|
||||||
}
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@validates("name")
|
@validates("name")
|
||||||
def validate_name(self, _, name):
|
def validate_name(self, _, name):
|
||||||
|
@ -2,6 +2,7 @@ import enum
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from pydantic import ConfigDict
|
||||||
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, orm
|
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, orm
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
@ -84,9 +85,8 @@ class User(SqlAlchemyBase, BaseMixins):
|
|||||||
favorite_recipes: Mapped[list["RecipeModel"]] = orm.relationship(
|
favorite_recipes: Mapped[list["RecipeModel"]] = orm.relationship(
|
||||||
"RecipeModel", secondary=users_to_favorites, back_populates="favorited_by"
|
"RecipeModel", secondary=users_to_favorites, back_populates="favorited_by"
|
||||||
)
|
)
|
||||||
|
model_config = ConfigDict(
|
||||||
class Config:
|
exclude={
|
||||||
exclude = {
|
|
||||||
"password",
|
"password",
|
||||||
"admin",
|
"admin",
|
||||||
"can_manage",
|
"can_manage",
|
||||||
@ -94,6 +94,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
|||||||
"can_organize",
|
"can_organize",
|
||||||
"group",
|
"group",
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@hybrid_property
|
@hybrid_property
|
||||||
def group_slug(self) -> str:
|
def group_slug(self) -> str:
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} is opgedateer, {url}",
|
"generic-updated-with-url": "{name} is opgedateer, {url}",
|
||||||
"generic-duplicated": "{name} is gekopieer",
|
"generic-duplicated": "{name} is gekopieer",
|
||||||
"generic-deleted": "{name} is verwyder"
|
"generic-deleted": "{name} is verwyder"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} تم تحديثه، {url}",
|
"generic-updated-with-url": "{name} تم تحديثه، {url}",
|
||||||
"generic-duplicated": "تم تكرار {name}",
|
"generic-duplicated": "تم تكرار {name}",
|
||||||
"generic-deleted": "تم حذف {name}"
|
"generic-deleted": "تم حذف {name}"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} беше актуализирано, {url}",
|
"generic-updated-with-url": "{name} беше актуализирано, {url}",
|
||||||
"generic-duplicated": "{name} е дублицирано",
|
"generic-duplicated": "{name} е дублицирано",
|
||||||
"generic-deleted": "{name} беше изтрито"
|
"generic-deleted": "{name} беше изтрито"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "година|години",
|
||||||
|
"day": "ден|дни",
|
||||||
|
"hour": "час|часове",
|
||||||
|
"minute": "минута|минути",
|
||||||
|
"second": "секунда|секунди",
|
||||||
|
"millisecond": "милисекунда|милисекунди",
|
||||||
|
"microsecond": "микросекунда|микросекунди"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} ha estat actualitzat, {url}",
|
"generic-updated-with-url": "{name} ha estat actualitzat, {url}",
|
||||||
"generic-duplicated": "S'ha duplicat {name}",
|
"generic-duplicated": "S'ha duplicat {name}",
|
||||||
"generic-deleted": "{name} ha estat eliminat"
|
"generic-deleted": "{name} ha estat eliminat"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} byl aktualizován, {url}",
|
"generic-updated-with-url": "{name} byl aktualizován, {url}",
|
||||||
"generic-duplicated": "{name} byl duplikován",
|
"generic-duplicated": "{name} byl duplikován",
|
||||||
"generic-deleted": "{name} byl odstraněn"
|
"generic-deleted": "{name} byl odstraněn"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} er blevet opdateret, {url}",
|
"generic-updated-with-url": "{name} er blevet opdateret, {url}",
|
||||||
"generic-duplicated": "{name} er blevet dublikeret",
|
"generic-duplicated": "{name} er blevet dublikeret",
|
||||||
"generic-deleted": "{name} er blevet slettet"
|
"generic-deleted": "{name} er blevet slettet"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "år|år",
|
||||||
|
"day": "dag|dage",
|
||||||
|
"hour": "time|timer",
|
||||||
|
"minute": "minut|minutter",
|
||||||
|
"second": "sekund|sekunder",
|
||||||
|
"millisecond": "millisekund|millisekunder",
|
||||||
|
"microsecond": "mikrosekund|mikrosekunder"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} wurde aktualisiert, {url}",
|
"generic-updated-with-url": "{name} wurde aktualisiert, {url}",
|
||||||
"generic-duplicated": "{name} wurde dupliziert",
|
"generic-duplicated": "{name} wurde dupliziert",
|
||||||
"generic-deleted": "{name} wurde gelöscht"
|
"generic-deleted": "{name} wurde gelöscht"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "jahr|jahre",
|
||||||
|
"day": "tag|tage",
|
||||||
|
"hour": "stunde|stunden",
|
||||||
|
"minute": "minute|minuten",
|
||||||
|
"second": "sekunde|sekunden",
|
||||||
|
"millisecond": "millisekunde|millisekunden",
|
||||||
|
"microsecond": "mikrosekunde|mikrosekunden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "Se ha actualizado {name}, {url}",
|
"generic-updated-with-url": "Se ha actualizado {name}, {url}",
|
||||||
"generic-duplicated": "Se ha duplicado {name}",
|
"generic-duplicated": "Se ha duplicado {name}",
|
||||||
"generic-deleted": "Se ha eliminado {name}"
|
"generic-deleted": "Se ha eliminado {name}"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "año|años",
|
||||||
|
"day": "día|días",
|
||||||
|
"hour": "hora|horas",
|
||||||
|
"minute": "minuto|minutos",
|
||||||
|
"second": "segundo|segundos",
|
||||||
|
"millisecond": "milisegundo|milisegundos",
|
||||||
|
"microsecond": "microsegundo|microsegundos"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} on päivitetty, {url}",
|
"generic-updated-with-url": "{name} on päivitetty, {url}",
|
||||||
"generic-duplicated": "{name} on kahdennettu",
|
"generic-duplicated": "{name} on kahdennettu",
|
||||||
"generic-deleted": "{name} on poistettu"
|
"generic-deleted": "{name} on poistettu"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} a été mis à jour, {url}",
|
"generic-updated-with-url": "{name} a été mis à jour, {url}",
|
||||||
"generic-duplicated": "{name} a été dupliqué",
|
"generic-duplicated": "{name} a été dupliqué",
|
||||||
"generic-deleted": "{name} a été supprimé"
|
"generic-deleted": "{name} a été supprimé"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} a été mis à jour, {url}",
|
"generic-updated-with-url": "{name} a été mis à jour, {url}",
|
||||||
"generic-duplicated": "{name} a été dupliqué",
|
"generic-duplicated": "{name} a été dupliqué",
|
||||||
"generic-deleted": "{name} a été supprimé"
|
"generic-deleted": "{name} a été supprimé"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "année|années",
|
||||||
|
"day": "jour|jours",
|
||||||
|
"hour": "heure|heures",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "seconde|secondes",
|
||||||
|
"millisecond": "milliseconde|millisecondes",
|
||||||
|
"microsecond": "microseconde|microsecondes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} עודכן, {url}",
|
"generic-updated-with-url": "{name} עודכן, {url}",
|
||||||
"generic-duplicated": "{name} שוכפל",
|
"generic-duplicated": "{name} שוכפל",
|
||||||
"generic-deleted": "{name} נמחק"
|
"generic-deleted": "{name} נמחק"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} je ažuriran, {url}",
|
"generic-updated-with-url": "{name} je ažuriran, {url}",
|
||||||
"generic-duplicated": "{name} je dupliciran",
|
"generic-duplicated": "{name} je dupliciran",
|
||||||
"generic-deleted": "{name} je obrisan"
|
"generic-deleted": "{name} je obrisan"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} frissítve, {url}",
|
"generic-updated-with-url": "{name} frissítve, {url}",
|
||||||
"generic-duplicated": "{name} duplikálva",
|
"generic-duplicated": "{name} duplikálva",
|
||||||
"generic-deleted": "{name} törölve lett"
|
"generic-deleted": "{name} törölve lett"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "év",
|
||||||
|
"day": "nap",
|
||||||
|
"hour": "óra",
|
||||||
|
"minute": "perc",
|
||||||
|
"second": "másodperc",
|
||||||
|
"millisecond": "ezredmásodperc",
|
||||||
|
"microsecond": "mikroszekundum"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} è stato aggiornato, {url}",
|
"generic-updated-with-url": "{name} è stato aggiornato, {url}",
|
||||||
"generic-duplicated": "{name} è stato duplicato",
|
"generic-duplicated": "{name} è stato duplicato",
|
||||||
"generic-deleted": "{name} è stato eliminato"
|
"generic-deleted": "{name} è stato eliminato"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "anno|anni",
|
||||||
|
"day": "giorno|giorni",
|
||||||
|
"hour": "ora|ore",
|
||||||
|
"minute": "minuto|minuti",
|
||||||
|
"second": "secondo|secondi",
|
||||||
|
"millisecond": "millisecondo|millisecondi",
|
||||||
|
"microsecond": "microsecondo|microsecondi"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} atnaujintas, {url}",
|
"generic-updated-with-url": "{name} atnaujintas, {url}",
|
||||||
"generic-duplicated": "{name} buvo nukopijuotas",
|
"generic-duplicated": "{name} buvo nukopijuotas",
|
||||||
"generic-deleted": "{name} ištrintas"
|
"generic-deleted": "{name} ištrintas"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} is bijgewerkt, {url}",
|
"generic-updated-with-url": "{name} is bijgewerkt, {url}",
|
||||||
"generic-duplicated": "(naam) is gekopieerd",
|
"generic-duplicated": "(naam) is gekopieerd",
|
||||||
"generic-deleted": "{name} is verwijderd"
|
"generic-deleted": "{name} is verwijderd"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "jaar|jaren",
|
||||||
|
"day": "dag|dagen",
|
||||||
|
"hour": "uur|uren",
|
||||||
|
"minute": "minuut|minuten",
|
||||||
|
"second": "seconde|seconden",
|
||||||
|
"millisecond": "milliseconde milliseconden",
|
||||||
|
"microsecond": "microseconde microseconden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} har blitt oppdatert, {url}",
|
"generic-updated-with-url": "{name} har blitt oppdatert, {url}",
|
||||||
"generic-duplicated": "{name} har blitt duplisert",
|
"generic-duplicated": "{name} har blitt duplisert",
|
||||||
"generic-deleted": "{name} har blitt slettet"
|
"generic-deleted": "{name} har blitt slettet"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} został zaktualizowany. {url}",
|
"generic-updated-with-url": "{name} został zaktualizowany. {url}",
|
||||||
"generic-duplicated": "{name} został zduplikowany",
|
"generic-duplicated": "{name} został zduplikowany",
|
||||||
"generic-deleted": "{name} został usunięty"
|
"generic-deleted": "{name} został usunięty"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} foi atualizado, {url}",
|
"generic-updated-with-url": "{name} foi atualizado, {url}",
|
||||||
"generic-duplicated": "{name} foi duplicada",
|
"generic-duplicated": "{name} foi duplicada",
|
||||||
"generic-deleted": "{name} foi excluído"
|
"generic-deleted": "{name} foi excluído"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} foi atualizado, {url}",
|
"generic-updated-with-url": "{name} foi atualizado, {url}",
|
||||||
"generic-duplicated": "{name} foi duplicado",
|
"generic-duplicated": "{name} foi duplicado",
|
||||||
"generic-deleted": "{name} foi removido"
|
"generic-deleted": "{name} foi removido"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "ano|anos",
|
||||||
|
"day": "dia|dias",
|
||||||
|
"hour": "hora|horas",
|
||||||
|
"minute": "minuto|minutos",
|
||||||
|
"second": "segundo|segundos",
|
||||||
|
"millisecond": "milissegundo|milissegundos",
|
||||||
|
"microsecond": "microssegundo|microssegundos"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} a fost actualizat, {url}",
|
"generic-updated-with-url": "{name} a fost actualizat, {url}",
|
||||||
"generic-duplicated": "{name} a fost duplicat",
|
"generic-duplicated": "{name} a fost duplicat",
|
||||||
"generic-deleted": "{name} a fost șters"
|
"generic-deleted": "{name} a fost șters"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} был обновлен, {url}",
|
"generic-updated-with-url": "{name} был обновлен, {url}",
|
||||||
"generic-duplicated": "Копия {name} была создана",
|
"generic-duplicated": "Копия {name} была создана",
|
||||||
"generic-deleted": "{name} был удален"
|
"generic-deleted": "{name} был удален"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} bol aktualizovaný, {url}",
|
"generic-updated-with-url": "{name} bol aktualizovaný, {url}",
|
||||||
"generic-duplicated": "{name} bol duplikovaný",
|
"generic-duplicated": "{name} bol duplikovaný",
|
||||||
"generic-deleted": "{name} bol vymazaný"
|
"generic-deleted": "{name} bol vymazaný"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} je bil posodobljen, {url}",
|
"generic-updated-with-url": "{name} je bil posodobljen, {url}",
|
||||||
"generic-duplicated": "{name} je bilo podvojeno",
|
"generic-duplicated": "{name} je bilo podvojeno",
|
||||||
"generic-deleted": "{name} je bil izbrisan"
|
"generic-deleted": "{name} je bil izbrisan"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} је ажурирано, {url}",
|
"generic-updated-with-url": "{name} је ажурирано, {url}",
|
||||||
"generic-duplicated": "{name} је дуплиран",
|
"generic-duplicated": "{name} је дуплиран",
|
||||||
"generic-deleted": "{name} је обрисан"
|
"generic-deleted": "{name} је обрисан"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} har uppdaterats, {url}",
|
"generic-updated-with-url": "{name} har uppdaterats, {url}",
|
||||||
"generic-duplicated": "{name} har duplicerats",
|
"generic-duplicated": "{name} har duplicerats",
|
||||||
"generic-deleted": "{name} har tagits bort"
|
"generic-deleted": "{name} har tagits bort"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "år|år",
|
||||||
|
"day": "dag|dagar",
|
||||||
|
"hour": "timme|timmar",
|
||||||
|
"minute": "minut|minuter",
|
||||||
|
"second": "sekund|sekunder",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} güncellendi, {url}",
|
"generic-updated-with-url": "{name} güncellendi, {url}",
|
||||||
"generic-duplicated": "{name} yinelendi",
|
"generic-duplicated": "{name} yinelendi",
|
||||||
"generic-deleted": "{name} silindi"
|
"generic-deleted": "{name} silindi"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "yıl|yıllar",
|
||||||
|
"day": "gün|günler",
|
||||||
|
"hour": "saat|saatler",
|
||||||
|
"minute": "dakika|dakikalar",
|
||||||
|
"second": "saniye|saniyeler",
|
||||||
|
"millisecond": "milisaniye|milisaniyeler",
|
||||||
|
"microsecond": "mikrosaniye|mikrosaniyeler"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} оновлено, {url}",
|
"generic-updated-with-url": "{name} оновлено, {url}",
|
||||||
"generic-duplicated": "{name} дубльовано",
|
"generic-duplicated": "{name} дубльовано",
|
||||||
"generic-deleted": "{name} видалено"
|
"generic-deleted": "{name} видалено"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "рік|роки",
|
||||||
|
"day": "день|дні",
|
||||||
|
"hour": "година|години",
|
||||||
|
"minute": "хвилина|хвилини",
|
||||||
|
"second": "секунда|секунди",
|
||||||
|
"millisecond": "мілісекунда|мілісекунди",
|
||||||
|
"microsecond": "мікросекунда|мікросекунди"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,14 @@
|
|||||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
"generic-duplicated": "{name} has been duplicated",
|
"generic-duplicated": "{name} has been duplicated",
|
||||||
"generic-deleted": "{name} has been deleted"
|
"generic-deleted": "{name} has been deleted"
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"year": "year|years",
|
||||||
|
"day": "day|days",
|
||||||
|
"hour": "hour|hours",
|
||||||
|
"minute": "minute|minutes",
|
||||||
|
"second": "second|seconds",
|
||||||
|
"millisecond": "millisecond|milliseconds",
|
||||||
|
"microsecond": "microsecond|microseconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user