Compare commits

..

79 Commits

Author SHA1 Message Date
Hayden
77d01adf15 fix: add --no-project flags to prevent mealie installation 2025-12-20 20:54:39 -06:00
Hayden
b0d85bc406 ci(docs): isolate docs dependencies to avoid python-ldap build
Add dedicated docs dependency group with only mkdocs-material to avoid
installing python-ldap during docs builds. python-ldap requires OpenLDAP
dev headers (libldap2-dev) which aren't available on standard CI runners.
2025-12-20 20:31:33 -06:00
Hayden
efb9dae681 docs: add GitHub Actions workflow for docs deployment (#6752) 2025-12-20 20:26:14 -06:00
renovate[bot]
cee93d2a87 fix(deps): update dependency fastapi to v0.126.0 (#6750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-20 16:06:03 -06:00
Arsène Reymond
0d4a8654c1 fix: PWA maskable android icons & enctype shared_target (#6731)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-19 05:06:27 +00:00
Hayden
95b1be07bb chore(l10n): New Crowdin updates (#6744)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-19 04:53:09 +00:00
mealie-commit-bot[bot]
a6fc98fc82 chore: bump version to v3.8.0 2025-12-19 01:37:16 +00:00
Michael Genson
6f03010f6c fix: Security Patches (#6743) 2025-12-18 22:54:16 +00:00
renovate[bot]
69397c91b8 fix(deps): update dependency openai to v2.13.0 (#6726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-18 21:45:42 +00:00
Hayden
798792dcdc chore(l10n): New Crowdin updates (#6736)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-18 15:32:22 -06:00
renovate[bot]
cc32dd9fa6 chore(deps): update dependency ruff to v0.14.10 (#6742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-18 15:32:00 -06:00
renovate[bot]
0c64eb29f9 fix(deps): update dependency fastapi to v0.125.0 (#6740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 17:37:43 -06:00
renovate[bot]
8baa5cc315 chore(deps): update dependency pre-commit to v4.5.1 (#6734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 00:07:49 +00:00
Hayden
6f3a5c6c8f chore(l10n): New Crowdin updates (#6733) 2025-12-16 17:42:13 +00:00
Hayden
778078590b chore(l10n): New Crowdin updates (#6729) 2025-12-15 22:54:37 -06:00
github-actions[bot]
53c82e5491 chore(auto): Update pre-commit hooks (#6724) 2025-12-15 18:16:59 +00:00
Hayden
fef114d97f chore(l10n): New Crowdin updates (#6725) 2025-12-15 08:55:48 -06:00
Hayden
e80cbfad7f chore(l10n): New Crowdin updates (#6722)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-14 23:31:48 -06:00
renovate[bot]
99527ce738 chore(deps): update dependency mypy to v1.19.1 (#6723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-14 23:31:34 -06:00
Arsène Reymond
08ccced734 fix: localize text validators message (#6719) 2025-12-14 09:56:11 -06:00
github-actions[bot]
43c2c9552b chore(l10n): Crowdin locale sync (#6716)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-13 22:10:43 -06:00
Hayden
db5741c7ee chore(l10n): New Crowdin updates (#6710) 2025-12-13 22:10:23 -06:00
renovate[bot]
a1e394cf36 fix(deps): update dependency tzdata to v2025.3 (#6713) 2025-12-13 15:21:42 -06:00
Michael Genson
bdbef1ab9e fix: More lenient postgres override parsing (#6712) 2025-12-13 14:21:54 -06:00
Michael Genson
e5276f6c20 fix: Put tooltips behind app bar (#6711) 2025-12-13 10:56:18 -06:00
Michael Genson
20a6e71b31 feat: Optionally include URL when importing via HTML/JSON (#6709) 2025-12-12 23:20:26 -06:00
Michael Genson
24c111af7b chore: Miscellaneous cleanup (#6708) 2025-12-12 22:48:49 -06:00
davidschinkel
ab4559319e fix: Improved bulk deletion by reducing refreshs (#6634)
Co-authored-by: David Schinkel <david@zollsoft.de>
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2025-12-13 04:08:04 +00:00
Michael Genson
2f8625ac44 fix: Disable submit on enter when editing timeline events (#6707) 2025-12-12 21:56:36 -06:00
Hayden
dd146afa57 chore(l10n): New Crowdin updates (#6706) 2025-12-12 21:20:34 -06:00
renovate[bot]
91d15f671e fix(deps): update dependency authlib to v1.6.6 (#6700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 01:54:41 +00:00
renovate[bot]
7008b13246 fix(deps): update dependency fastapi to v0.124.4 (#6702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 19:43:35 -06:00
mealie-commit-bot[bot]
1a1798cd88 chore: bump version to v3.7.0 2025-12-13 01:21:07 +00:00
Michael Genson
64f47c1589 fix: Reprocess script UUID handling for postgres (#6705) 2025-12-12 19:18:52 -06:00
Michael Genson
326bb1eb8e feat: Reprocess image user script (#6704) 2025-12-12 18:30:49 -06:00
Hayden
80dc2ecfb7 chore(l10n): New Crowdin updates (#6701) 2025-12-12 13:53:06 +00:00
renovate[bot]
b72082663f chore(deps): update node.js to 20988bc (#6698) 2025-12-12 05:24:05 +00:00
renovate[bot]
f46ae423d3 chore(deps): update dependency ruff to v0.14.9 (#6699) 2025-12-11 23:13:36 -06:00
Hayden
05cdff8ae7 chore(l10n): New Crowdin updates (#6697) 2025-12-12 03:57:19 +01:00
Hayden
0facdf73be chore(l10n): New Crowdin updates (#6694) 2025-12-11 21:38:11 +00:00
renovate[bot]
cbad569134 fix(deps): update dependency openai to v2.11.0 (#6696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 15:27:38 -06:00
Hayden
1063433aa9 chore(l10n): New Crowdin updates (#6693) 2025-12-10 19:47:30 -06:00
renovate[bot]
0ba22c81e7 fix(deps): update dependency fastapi to v0.124.2 (#6688) 2025-12-10 18:59:49 +00:00
renovate[bot]
0667177a2e fix(deps): update dependency recipe-scrapers to v15.11.0 (#6691) 2025-12-10 18:48:05 +00:00
Hayden
6fcf22869b chore(l10n): New Crowdin updates (#6689) 2025-12-10 12:37:02 -06:00
renovate[bot]
20b45e57e0 fix(deps): update dependency sqlalchemy to v2.0.45 (#6687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 20:53:09 -06:00
Hayden
7a38a52158 chore(l10n): New Crowdin updates (#6686) 2025-12-09 19:45:24 -06:00
renovate[bot]
e27eca5571 chore(deps): update node.js to 9a2ed90 (#6684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-09 12:53:03 -06:00
renovate[bot]
a90b2ccafd chore(deps): update dependency coverage to v7.13.0 (#6683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 12:52:45 -06:00
Michael Genson
e0d8104643 feat: Suggest HTML importer on URL importer failure (#6685) 2025-12-09 11:05:34 -06:00
Hayden
53ee64828b chore(l10n): New Crowdin updates (#6678)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-09 10:27:21 -06:00
Arsène Reymond
6f7fba5ac1 feat: Add user QueryFilter and improve UI on mobile (#6235)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
2025-12-09 09:49:12 -06:00
github-actions[bot]
89aed15905 chore(auto): Update pre-commit hooks (#6680)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2025-12-08 14:55:38 +00:00
renovate[bot]
aac48287a4 fix(deps): update dependency apprise to v1.9.6 (#6677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 08:45:21 -06:00
Hayden
34daaa0476 chore(l10n): New Crowdin updates (#6675) 2025-12-07 09:43:47 -06:00
Henri Cook
af56a3e69d fix: improve password manager autofill compatibility on login page (#6662)
Co-authored-by: Henri Cook <henri.cook@linklaters.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-06 23:35:16 -06:00
github-actions[bot]
0908812b47 chore(l10n): Crowdin locale sync (#6672)
Co-authored-by: GitHub Action <action@github.com>
2025-12-06 22:58:22 -06:00
renovate[bot]
d910fbafe8 chore(deps): update dependency pytest to v9.0.2 (#6670) 2025-12-07 00:15:15 +00:00
renovate[bot]
c7692426d5 fix(deps): update dependency orjson to v3.11.5 (#6667) 2025-12-07 00:04:01 +00:00
renovate[bot]
b7a615add9 fix(deps): update dependency fastapi to v0.124.0 (#6664) 2025-12-06 17:52:57 -06:00
Hayden
3167e23b6b chore(l10n): New Crowdin updates (#6671) 2025-12-06 17:21:05 -06:00
Hayden
8b582f8682 feat: autofill default credentials on first login (#6666) 2025-12-06 17:47:33 +00:00
Hayden
05f648d7fb fix: clear cached store data on logout to prevent user data leakage (#6665) 2025-12-06 11:36:39 -06:00
Nathan Winspear
1f19133870 docs: add theming examples to backend configuration guide (#6443)
Co-authored-by: Michael Genson <genson.michael@gmail.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-05 19:35:33 -06:00
Hayden
98273da16e chore(l10n): New Crowdin updates (#6661)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-05 16:29:47 -06:00
miah
f857ca18da feat: Improve startup workflow UI (#6342)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-05 22:18:32 +00:00
renovate[bot]
22a0e6d608 fix(deps): update dependency fastapi to v0.123.10 (#6660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 15:58:01 -06:00
Tempest
ed806b9fec feat: Improve Image Minification Logic and Efficiency (#5883)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
2025-12-05 15:45:53 -06:00
Michael Genson
ae8b489f97 dev: Add copilot-instructions.md (#6659) 2025-12-05 13:00:45 -06:00
Noneangel
71732d4766 feat: frontend autocomplete is diacritics/ligatures insensitive (#6169)
Co-authored-by: Pierre <pierre@debian.zabi.ovh>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2025-12-05 12:44:37 -06:00
Cash Prokop-Weaver
6695314588 feat: Add snack, drink, and dessert (#6149)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
2025-12-05 11:54:00 -06:00
Nico Hirsch
c115e6d83f feat: Put calendar directly in the date picker dialogs (#6110)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
2025-12-05 10:05:47 -06:00
renovate[bot]
e3e970213c fix(deps): update dependency fastapi to v0.123.9 (#6657)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 23:29:52 -06:00
Hayden
7fe358e5e7 chore(l10n): New Crowdin updates (#6653) 2025-12-04 21:54:37 +00:00
renovate[bot]
c7f3334479 fix(deps): update dependency openai to v2.9.0 (#6656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 15:43:47 -06:00
renovate[bot]
d4467f65fb fix(deps): update dependency fastapi to v0.123.8 (#6640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 21:23:23 +00:00
renovate[bot]
27e61ec6b1 chore(deps): update dependency ruff to v0.14.8 (#6655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 15:12:20 -06:00
Hayden
6c6dc8103d chore(l10n): New Crowdin updates (#6649) 2025-12-03 10:25:28 +01:00
Hayden
35963dad2e fix: change log rotation size from 10kb to 10mb (#6648) 2025-12-02 19:06:51 -06:00
170 changed files with 4323 additions and 2620 deletions

View File

@@ -13,6 +13,7 @@ RUN echo "export PROMPT_COMMAND='history -a'" >> /home/vscode/.bashrc \
&& chown vscode:vscode -R /home/vscode/
RUN npm install -g @go-task/cli
RUN npm install -g json-schema-to-typescript
# Install additional OS packages
RUN apt-get update \

View File

@@ -23,7 +23,6 @@
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"mypy.runUsingActiveInterpreter": true
},
@@ -31,10 +30,10 @@
"charliermarsh.ruff",
"dbaeumer.vscode-eslint",
"matangover.mypy",
"ms-python.black-formatter",
"ms-python.pylint",
"ms-python.python",
"ms-python.vscode-pylance",
"streetsidesoftware.code-spell-checker-cspell-bundled-dictionaries",
"Vue.volar"
]
}
@@ -42,6 +41,7 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
3000,
8000, // used by mkdocs
9000,
9091, // used by docker production
24678 // used by nuxt when hot-reloading using polling

240
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,240 @@
# Mealie Development Guide for AI Agents
## Project Overview
Mealie is a self-hosted recipe manager, meal planner, and shopping list application with a FastAPI backend (Python 3.12) and Nuxt 3 frontend (Vue 3 + TypeScript). It uses SQLAlchemy ORM with support for SQLite and PostgreSQL databases.
**Development vs Production:**
- **Development:** Frontend (port 3000) and backend (port 9000) run as separate processes
- **Production:** Frontend is statically generated and served via FastAPI's SPA module (`mealie/routes/spa/`) in a single container
## Architecture & Key Patterns
### Backend Architecture (mealie/)
**Repository-Service-Controller Pattern:**
- **Controllers** (`mealie/routes/**/controller_*.py`): Inherit from `BaseUserController` or `BaseAdminController`, handle HTTP concerns, delegate to services
- **Services** (`mealie/services/`): Business logic layer, inherit from `BaseService`, coordinate repos and external dependencies
- **Repositories** (`mealie/repos/`): Data access layer using SQLAlchemy, accessed via `AllRepositories` factory
- Get repos via dependency injection: `repos: AllRepositories = Depends(get_repositories)`
- All repos scoped to group/household context automatically
**Route Organization:**
- Routes in `mealie/routes/` organized by domain (auth, recipe, groups, households, admin)
- Use `APIRouter` with FastAPI dependency injection
- Apply `@router.get/post/put/delete` decorators with Pydantic response models
- Route controllers use `HttpRepo` mixin for common CRUD operations (see `mealie/routes/_base/mixins.py`)
**Schemas & Type Generation:**
- Pydantic schemas in `mealie/schema/` with strict separation: `*In`, `*Out`, `*Create`, `*Update` suffixes
- Auto-exported from submodules via `__init__.py` files (generated by `task dev:generate`)
- TypeScript types auto-generated from Pydantic schemas - **never manually edit** `frontend/lib/api/types/`
**Database & Sessions:**
- Session management via `Depends(generate_session)` in FastAPI routes
- Use `session_context()` context manager in services/scripts
- SQLAlchemy models in `mealie/db/models/`, migrations in `mealie/alembic/`
- Create migrations: `task py:migrate -- "description"`
### Frontend Architecture (frontend/)
**Component Organization (strict naming conventions):**
- **Domain Components** (`components/Domain/`): Feature-specific, prefix with domain (e.g., `AdminDashboard`)
- **Global Components** (`components/global/`): Reusable primitives, prefix with `Base` (e.g., `BaseButton`)
- **Layout Components** (`components/Layout/`): Layout-only, prefix with `App` if props or `The` if singleton
- **Page Components** (`components/` with page prefix): Last resort for breaking up complex pages
**API Client Pattern:**
- API clients in `frontend/lib/api/` extend `BaseAPI`, `BaseCRUDAPI`, or `BaseCRUDAPIReadOnly`
- Types imported from auto-generated `frontend/lib/api/types/` (DO NOT EDIT MANUALLY)
- Composables in `frontend/composables/` for shared state and API logic (e.g., `use-mealie-auth.ts`)
- Use `useAuthBackend()` for authentication state, `useMealieAuth()` for user management
**State Management:**
- Nuxt 3 composables for state (no Vuex)
- Auth state via `use-mealie-auth.ts` composable
- Prefer composables over global state stores
## Essential Commands (via Task/Taskfile.yml)
**Development workflow:**
```bash
task setup # Install all dependencies (Python + Node)
task dev:services # Start Postgres & Mailpit containers
task py # Start FastAPI backend (port 9000)
task ui # Start Nuxt frontend (port 3000)
task docs # Start MkDocs documentation server
```
**Code generation (REQUIRED after schema changes):**
```bash
task dev:generate # Generate TypeScript types, schema exports, test helpers
```
**Testing & Quality:**
```bash
task py:test # Run pytest (supports args: task py:test -- -k test_name)
task py:check # Format + lint + type-check + test (full validation)
task py:format # Ruff format
task py:lint # Ruff check
task py:mypy # Type checking
task ui:test # Vitest frontend tests
task ui:check # Frontend lint + test
```
**Database:**
```bash
task py:migrate -- "description" # Generate Alembic migration
task py:postgres # Run backend with PostgreSQL config
```
**Docker:**
```bash
task docker:prod # Build and run production Docker compose
```
## Critical Development Practices
### Python Backend
1. **Always use `uv` for Python commands** (not `python` or `pip`):
```bash
uv run python mealie/app.py
uv run pytest tests/
```
2. **Type hints are mandatory:** Use mypy-compatible annotations, handle Optional types explicitly
3. **Dependency injection pattern:**
```python
from fastapi import Depends
from mealie.repos.all_repositories import get_repositories, AllRepositories
def my_route(
repos: AllRepositories = Depends(get_repositories),
user: PrivateUser = Depends(get_current_user)
):
recipe = repos.recipes.get_one(recipe_id)
```
4. **Settings & Configuration:**
- Get settings: `settings = get_app_settings()` (cached singleton)
- Get directories: `dirs = get_app_dirs()`
- Never instantiate `AppSettings()` directly
5. **Testing:**
- Fixtures in `tests/fixtures/`
- Use `api_client` fixture for integration tests
- Follow existing patterns in `tests/integration_tests/` and `tests/unit_tests/`
### Frontend
1. **Run code generation after backend schema changes:** `task dev:generate`
2. **TypeScript strict mode:** All code must pass type checking
3. **Component naming:** Follow strict conventions (see Architecture section above)
4. **API calls pattern:**
```typescript
const api = useUserApi();
const recipe = await api.recipes.getOne(recipeId);
```
5. **Composables for shared logic:** Prefer composables in `composables/` over inline code duplication
6. **Translations:** Only modify `en-US` locale files when adding new translation strings - other locales are managed via Crowdin and **must never be modified** (PRs modifying non-English locales will be rejected)
### Cross-Cutting Concerns
1. **Code generation is source of truth:** After Pydantic schema changes, run `task dev:generate` to update:
- TypeScript types (`frontend/lib/api/types/`)
- Schema exports (`mealie/schema/*/__init__.py`)
- Test data paths and routes
2. **Multi-tenancy:** All data scoped to **groups** and **households**:
- Groups contain multiple households
- Households contain recipes, meal plans, shopping lists
- Repositories automatically filter by group/household context
3. **Pre-commit hooks:** Install via `task setup:py`, enforces Ruff formatting/linting
4. **Testing before PRs:** Run `task py:check` and `task ui:check` before submitting PRs
## Pull Request Best Practices
### Before Submitting a PR
1. **Draft PRs are optional:** Create a draft PR early if you want feedback while working, or open directly as ready when complete
2. **Verify code generation:** If you modified Pydantic schemas, ensure `task dev:generate` was run
3. **Follow Conventional Commits:** Title your PR according to the conventional commits format (see PR template)
4. **Add release notes:** Include user-facing changes in the PR description
### What to Review
**Architecture & Patterns:**
- Does the code follow the repository-service-controller pattern?
- Are controllers delegating business logic to services?
- Are services coordinating repositories, not accessing the database directly?
- Is dependency injection used properly (`Depends(get_repositories)`, `Depends(get_current_user)`)?
**Data Scoping:**
- Are repositories correctly scoped to group/household context?
- Do route handlers properly validate group/household ownership before operations?
- Are multi-tenant boundaries enforced (users can't access other groups' data)?
**Type Safety:**
- Are type hints present on all functions and methods?
- Are Pydantic schemas using correct suffixes (`*In`, `*Out`, `*Create`, `*Update`)?
- For frontend, does TypeScript code pass strict type checking?
**Generated Files:**
- Verify `frontend/lib/api/types/` files weren't manually edited (they're auto-generated)
- Check that `mealie/schema/*/__init__.py` exports match actual schema files (auto-generated)
- If schemas changed, confirm generated files were updated via `task dev:generate`
**Code Quality:**
- Is the code readable and well-organized?
- Are complex operations documented with clear comments?
- Do component names follow the strict naming conventions (Domain/Global/Layout/Page prefixes)?
- Are composables used for shared frontend logic instead of duplication?
**Translations:**
- Were only `en-US` locale files modified for new translation strings?
- Verify no other locale files (managed by Crowdin) were touched
**Database Changes:**
- Are Alembic migrations included for schema changes?
- Are migrations tested against both SQLite and PostgreSQL?
### Review Etiquette
- Be constructive and specific in feedback
- Suggest code examples when proposing changes
- Focus on architecture and logic - formatting/linting is handled by CI
- Use "Approve" when ready to merge, "Request Changes" for blocking issues, "Comment" for non-blocking suggestions
## Common Gotchas
- **Don't manually edit generated files:** `frontend/lib/api/types/`, schema `__init__.py` files
- **Repository context:** Repos are group/household-scoped - passing wrong IDs causes 404s
- **Session handling:** Don't create sessions manually, use dependency injection or `session_context()`
- **Schema changes require codegen:** After changing Pydantic models, run `task dev:generate`
- **Translation files:** Only modify `en-US` locale files - all other locales are managed by Crowdin
- **Dev containers:** This project uses VS Code dev containers - leverage the pre-configured environment
- **Task commands:** Use `task` commands instead of direct tool invocation for consistency
## Key Files to Reference
- `Taskfile.yml` - All development commands and workflows
- `mealie/routes/_base/base_controllers.py` - Controller base classes and patterns
- `mealie/repos/repository_factory.py` - Repository factory and available repos
- `frontend/lib/api/base/base-clients.ts` - API client base classes
- `tests/conftest.py` - Test fixtures and setup
- `dev/code-generation/main.py` - Code generation entry point
## Additional Resources
- [Documentation](https://docs.mealie.io/)
- [Contributors Guide](https://nightly.mealie.io/contributors/developers-guide/code-contributions/)
- [Discord](https://discord.gg/QuStdQGSGK)

50
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Deploy Documentation
on:
push:
branches: [mealie-next]
paths:
- 'docs/**'
- '.github/workflows/docs.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: uv sync --only-group docs --no-install-project
- name: Build docs
run: uv run --no-project mkdocs build -d site
working-directory: docs
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/site
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ dev/data/backups/*
dev/data/debug/*
dev/data/img/*
dev/data/migration/*
dev/data/templates/*
dev/data/users/*
dev/data/groups/*

View File

@@ -12,7 +12,7 @@ repos:
exclude: ^tests/data/
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.7
rev: v0.14.9
hooks:
- id: ruff
- id: ruff-format

View File

@@ -1,7 +1,7 @@
###############################################
# Frontend Build
###############################################
FROM node:24@sha256:aa648b387728c25f81ff811799bbf8de39df66d7e2d9b3ab55cc6300cb9175d9 \
FROM node:24@sha256:20988bcdc6dc76690023eb2505dd273bdeefddcd0bde4bfd1efe4ebf8707f747 \
AS frontend-builder
WORKDIR /frontend

View File

@@ -145,22 +145,95 @@ Setting the following environmental variables will change the theme of the front
If using YAML sequence syntax, don't include any quotes:<br>`THEME_LIGHT_PRIMARY=#E58325` or `THEME_LIGHT_PRIMARY=E58325`
| Variables | Default | Description |
| --------------------- | :-----: | --------------------------- |
| THEME_LIGHT_PRIMARY | #E58325 | Light Theme Config Variable |
| THEME_LIGHT_ACCENT | #007A99 | Light Theme Config Variable |
| THEME_LIGHT_SECONDARY | #973542 | Light Theme Config Variable |
| THEME_LIGHT_SUCCESS | #43A047 | Light Theme Config Variable |
| THEME_LIGHT_INFO | #1976D2 | Light Theme Config Variable |
| THEME_LIGHT_WARNING | #FF6D00 | Light Theme Config Variable |
| THEME_LIGHT_ERROR | #EF5350 | Light Theme Config Variable |
| THEME_DARK_PRIMARY | #E58325 | Dark Theme Config Variable |
| THEME_DARK_ACCENT | #007A99 | Dark Theme Config Variable |
| THEME_DARK_SECONDARY | #973542 | Dark Theme Config Variable |
| THEME_DARK_SUCCESS | #43A047 | Dark Theme Config Variable |
| THEME_DARK_INFO | #1976D2 | Dark Theme Config Variable |
| THEME_DARK_WARNING | #FF6D00 | Dark Theme Config Variable |
| THEME_DARK_ERROR | #EF5350 | Dark Theme Config Variable |
| Variables | Default | Description |
| --------------------- | :-----: | ---------------------------------- |
| THEME_LIGHT_PRIMARY | #E58325 | Main brand color and headers |
| THEME_LIGHT_ACCENT | #007A99 | Buttons and interactive elements |
| THEME_LIGHT_SECONDARY | #973542 | Navigation and sidebar backgrounds |
| THEME_LIGHT_SUCCESS | #43A047 | Success messages and confirmations |
| THEME_LIGHT_INFO | #1976D2 | Information alerts and tooltips |
| THEME_LIGHT_WARNING | #FF6D00 | Warning notifications |
| THEME_LIGHT_ERROR | #EF5350 | Error messages and alerts |
| THEME_DARK_PRIMARY | #E58325 | Main brand color and headers |
| THEME_DARK_ACCENT | #007A99 | Buttons and interactive elements |
| THEME_DARK_SECONDARY | #973542 | Navigation and sidebar backgrounds |
| THEME_DARK_SUCCESS | #43A047 | Success messages and confirmations |
| THEME_DARK_INFO | #1976D2 | Information alerts and tooltips |
| THEME_DARK_WARNING | #FF6D00 | Warning notifications |
| THEME_DARK_ERROR | #EF5350 | Error messages and alerts |
#### Theming Examples
The examples below provide copy-ready Docker Compose environment configurations for three different color palettes. Copy and paste the desired theme into your `docker-compose.yml` file's environment section.
!!! info
These themes are functional and ready to use, but they are provided primarily as examples. The color palettes can be adjusted or refined to better suit your preferences.
=== "Blue Theme"
```yaml
environment:
# Light mode colors
THEME_LIGHT_PRIMARY: '#5E9BD1'
THEME_LIGHT_ACCENT: '#A3C9E8'
THEME_LIGHT_SECONDARY: '#4F89BA'
THEME_LIGHT_SUCCESS: '#4CAF50'
THEME_LIGHT_INFO: '#4A9ED8'
THEME_LIGHT_WARNING: '#EAC46B'
THEME_LIGHT_ERROR: '#E57373'
# Dark mode colors
THEME_DARK_PRIMARY: '#5A8FBF'
THEME_DARK_ACCENT: '#90B8D9'
THEME_DARK_SECONDARY: '#406D96'
THEME_DARK_SUCCESS: '#81C784'
THEME_DARK_INFO: '#78B2C0'
THEME_DARK_WARNING: '#EBC86E'
THEME_DARK_ERROR: '#E57373'
```
=== "Green Theme"
```yaml
environment:
# Light mode colors
THEME_LIGHT_PRIMARY: '#75A86C'
THEME_LIGHT_ACCENT: '#A8D0A6'
THEME_LIGHT_SECONDARY: '#638E5E'
THEME_LIGHT_SUCCESS: '#4CAF50'
THEME_LIGHT_INFO: '#4A9ED8'
THEME_LIGHT_WARNING: '#EAC46B'
THEME_LIGHT_ERROR: '#E57373'
# Dark mode colors
THEME_DARK_PRIMARY: '#739B7A'
THEME_DARK_ACCENT: '#9FBE9D'
THEME_DARK_SECONDARY: '#56775E'
THEME_DARK_SUCCESS: '#81C784'
THEME_DARK_INFO: '#78B2C0'
THEME_DARK_WARNING: '#EBC86E'
THEME_DARK_ERROR: '#E57373'
```
=== "Pink Theme"
```yaml
environment:
# Light mode colors
THEME_LIGHT_PRIMARY: '#D97C96'
THEME_LIGHT_ACCENT: '#E891A7'
THEME_LIGHT_SECONDARY: '#C86C88'
THEME_LIGHT_SUCCESS: '#4CAF50'
THEME_LIGHT_INFO: '#2196F3'
THEME_LIGHT_WARNING: '#FFC107'
THEME_LIGHT_ERROR: '#E57373'
# Dark mode colors
THEME_DARK_PRIMARY: '#C2185B'
THEME_DARK_ACCENT: '#FF80AB'
THEME_DARK_SECONDARY: '#AD1457'
THEME_DARK_SUCCESS: '#81C784'
THEME_DARK_INFO: '#64B5F6'
THEME_DARK_WARNING: '#FFD54F'
THEME_DARK_ERROR: '#E57373'
```
### Docker Secrets

View File

@@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
1. Take a backup just in case!
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.6.1`
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.8.0`
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
4. Restart the container

View File

@@ -10,7 +10,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
```yaml
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:v3.6.1 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.8.0 # (3)
container_name: mealie
restart: always
ports:

View File

@@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
```yaml
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:v3.6.1 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.8.0 # (3)
container_name: mealie
restart: always
ports:

View File

@@ -9,6 +9,23 @@
- Create a Backup and Download from the UI
- Upgrade
!!! info "Improved Image Processing"
Starting with :octicons-tag-24: v3.7.0, we updated our image processing algorithm to improve image quality and compression. New image processing can be up to 40%-50% smaller on disk while providing higher resolution thumbnails. To take advantage of these improvements on older recipes, you can run our image-processing script:
```shell
docker exec -it mealie bash
python /opt/mealie/lib64/python3.12/site-packages/mealie/scripts/reprocess_images.py
```
### Options
- `--workers N`: Number of worker threads (default: 2, safe for low-powered devices)
- `--force-all`: Reprocess all recipes regardless of current image state
### Example
```shell
python /opt/mealie/lib64/python3.12/site-packages/mealie/scripts/reprocess_images.py --workers 8
```
## Upgrading to Mealie v1 or later
If you are upgrading from pre-v1.0.0 to v1.0.0 or later (v2.0.0, etc.), make sure you read [Migrating to Mealie v1](./migrating-to-mealie-v1.md)!

File diff suppressed because one or more lines are too long

View File

@@ -32,8 +32,8 @@ theme:
markdown_extensions:
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- def_list
- pymdownx.highlight
- pymdownx.superfences

View File

@@ -0,0 +1,121 @@
<template>
<v-container max-width="880" class="end-page-content">
<div class="d-flex flex-column ga-6">
<div>
<v-card-title class="text-h4 justify-center">
{{ $t('admin.setup.setup-complete') }}
</v-card-title>
<v-card-subtitle class="justify-center">
{{ $t('admin.setup.here-are-a-few-things-to-help-you-get-started') }}
</v-card-subtitle>
</div>
<div
v-for="section, idx in sections"
:key="idx"
class="d-flex flex-column ga-3"
>
<v-card-title class="text-h6 pl-0">
{{ section.title }}
</v-card-title>
<div class="sections d-flex flex-column ga-2">
<v-card
v-for="link, linkIdx in section.links"
:key="linkIdx"
clas="link-card"
:href="link.to"
:title="link.text"
:subtitle="link.description"
:append-icon="$globals.icons.chevronRight"
>
<template #prepend>
<v-avatar :icon="link.icon || undefined" variant="tonal" :color="section.color" />
</template>
</v-card>
</div>
</div>
</div>
</v-container>
</template>
<script lang="ts">
export default defineNuxtComponent({
setup() {
const i18n = useI18n();
const $auth = useMealieAuth();
const groupSlug = computed(() => $auth.user.value?.groupSlug);
const { $globals } = useNuxtApp();
const sections = ref([
{
title: i18n.t("profile.data-migrations"),
color: "info",
links: [
{
icon: $globals.icons.backupRestore,
to: "/admin/backups",
text: i18n.t("settings.backup.backup-restore"),
description: i18n.t("admin.setup.restore-from-v1-backup"),
},
{
icon: $globals.icons.import,
to: "/group/migrations",
text: i18n.t("migration.recipe-migration"),
description: i18n.t("migration.coming-from-another-application-or-an-even-older-version-of-mealie"),
},
],
},
{
title: i18n.t("recipe.create-recipes"),
color: "success",
links: [
{
icon: $globals.icons.createAlt,
to: computed(() => `/g/${groupSlug.value || ""}/r/create/new`),
text: i18n.t("recipe.create-recipe"),
description: i18n.t("recipe.create-recipe-description"),
},
{
icon: $globals.icons.link,
to: computed(() => `/g/${groupSlug.value || ""}/r/create/url`),
text: i18n.t("recipe.import-with-url"),
description: i18n.t("recipe.scrape-recipe-description"),
},
],
},
{
title: i18n.t("user.manage-users"),
color: "primary",
links: [
{
icon: $globals.icons.group,
to: "/admin/manage/users",
text: i18n.t("user.manage-users"),
description: i18n.t("user.manage-users-description"),
},
{
icon: $globals.icons.user,
to: "/user/profile",
text: i18n.t("profile.manage-user-profile"),
description: i18n.t("admin.setup.manage-profile-or-get-invite-link"),
},
],
},
]);
return { sections };
},
});
</script>
<style>
.v-container {
.v-card-title,
.v-card-subtitle {
padding: 0;
white-space: unset;
}
.v-card-item {
gap: 0.5rem;
}
}
</style>

View File

@@ -83,6 +83,11 @@ const fieldDefs: FieldDefinition[] = [
label: i18n.t("household.households"),
type: Organizer.Household,
},
{
name: "user_id",
label: i18n.t("user.users"),
type: Organizer.User,
},
{
name: "created_at",
label: i18n.t("general.date-created"),

View File

@@ -14,7 +14,7 @@
<BaseButton
download
size="small"
:download-url="`/api/recipes/bulk-actions/export/download?path=${item.path}`"
:download-url="`/api/recipes/bulk-actions/export/${item.id}/download`"
/>
</template>
</v-data-table>

View File

@@ -58,6 +58,9 @@ const MEAL_TYPE_OPTIONS = [
{ title: i18n.t("meal-plan.lunch"), value: "lunch" },
{ title: i18n.t("meal-plan.dinner"), value: "dinner" },
{ title: i18n.t("meal-plan.side"), value: "side" },
{ title: i18n.t("meal-plan.snack"), value: "snack" },
{ title: i18n.t("meal-plan.drink"), value: "drink" },
{ title: i18n.t("meal-plan.dessert"), value: "dessert" },
{ title: i18n.t("meal-plan.type-any"), value: "unset" },
];
@@ -103,6 +106,11 @@ const fieldDefs: FieldDefinition[] = [
label: i18n.t("household.households"),
type: Organizer.Household,
},
{
name: "user_id",
label: i18n.t("user.users"),
type: Organizer.User,
},
{
name: "last_made",
label: i18n.t("general.last-made"),

View File

@@ -1,283 +1,297 @@
<template>
<v-card class="ma-0" style="overflow-x: auto;">
<v-card class="ma-0" flat fluid>
<v-card-text class="ma-0 pa-0">
<v-container fluid class="ma-0 pa-0">
<VueDraggable
v-model="fields"
handle=".handle"
:delay="250"
:delay-on-touch-only="true"
v-bind="{
animation: 200,
group: 'recipe-instructions',
ghostClass: 'ghost',
}"
@start="drag = true"
@end="onDragEnd"
<VueDraggable
v-model="fields"
handle=".handle"
:delay="250"
:delay-on-touch-only="true"
v-bind="{
animation: 200,
group: 'recipe-instructions',
ghostClass: 'ghost',
}"
@start="drag = true"
@end="onDragEnd"
>
<v-row
v-for="(field, index) in fields"
:key="field.id"
class="d-flex flex-row flex-wrap mx-auto pb-2"
:class="$vuetify.display.xs ? (Math.floor(index / 1) % 2 === 0 ? 'bg-dark' : 'bg-light') : ''"
style="max-width: 100%;"
>
<v-row
v-for="(field, index) in fields"
:key="field.id"
class="d-flex flex-nowrap"
style="max-width: 100%;"
<!-- drag handle -->
<v-col
:cols="config.items.icon.cols(index)"
:sm="config.items.icon.sm(index)"
:class="$vuetify.display.smAndDown ? 'd-flex pa-0' : 'd-flex justify-end pr-6'"
>
<!-- drag handle -->
<v-col
:cols="config.items.icon.cols"
:class="config.col.class"
:style="config.items.icon.style"
<v-icon class="handle my-auto" :size="28" style="cursor: move;">
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-col>
<!-- and / or -->
<v-col
v-if="index != 0 || $vuetify.display.smAndUp"
:cols="config.items.logicalOperator.cols(index)"
:sm="config.items.logicalOperator.sm(index)"
:class="config.col.class"
>
<v-select
v-if="index"
:model-value="field.logicalOperator"
:items="[logOps.AND, logOps.OR]"
item-title="label"
item-value="value"
variant="underlined"
@update:model-value="setLogicalOperatorValue(field, index, $event as unknown as LogicalOperator)"
>
<v-icon
class="handle"
:size="24"
style="cursor: move;margin: auto;"
>
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-col>
<!-- and / or -->
<v-col
:cols="config.items.logicalOperator.cols"
:class="config.col.class"
:style="config.items.logicalOperator.style"
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- left parenthesis -->
<v-col
v-if="showAdvanced"
:cols="config.items.leftParens.cols(index)"
:sm="config.items.leftParens.sm(index)"
:class="config.col.class"
>
<v-select
:model-value="field.leftParenthesis"
:items="['', '(', '((', '(((']"
variant="underlined"
@update:model-value="setLeftParenthesisValue(field, index, $event)"
>
<v-select
v-if="index"
:model-value="field.logicalOperator"
:items="[logOps.AND, logOps.OR]"
item-title="label"
item-value="value"
variant="underlined"
@update:model-value="setLogicalOperatorValue(field, index, $event as unknown as LogicalOperator)"
>
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- left parenthesis -->
<v-col
v-if="showAdvanced"
:cols="config.items.leftParens.cols"
:class="config.col.class"
:style="config.items.leftParens.style"
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw }}
</span>
</template>
</v-select>
</v-col>
<!-- field name -->
<v-col
:cols="config.items.fieldName.cols(index)"
:sm="config.items.fieldName.sm(index)"
:class="config.col.class"
>
<v-select
chips
:model-value="field.label"
:items="fieldDefs"
variant="underlined"
item-title="label"
@update:model-value="setField(index, $event)"
>
<v-select
:model-value="field.leftParenthesis"
:items="['', '(', '((', '(((']"
variant="underlined"
@update:model-value="setLeftParenthesisValue(field, index, $event)"
>
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw }}
</span>
</template>
</v-select>
</v-col>
<!-- field name -->
<v-col
:cols="config.items.fieldName.cols"
:class="config.col.class"
:style="config.items.fieldName.style"
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- relational operator -->
<v-col
:cols="config.items.relationalOperator.cols(index)"
:sm="config.items.relationalOperator.sm(index)"
:class="config.col.class"
>
<v-select
v-if="field.type !== 'boolean'"
:model-value="field.relationalOperatorValue"
:items="field.relationalOperatorOptions"
item-title="label"
item-value="value"
variant="underlined"
@update:model-value="setRelationalOperatorValue(field, index, $event as unknown as RelationalKeyword | RelationalOperator)"
>
<v-select
chips
:model-value="field.label"
:items="fieldDefs"
variant="underlined"
item-title="label"
@update:model-value="setField(index, $event)"
>
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- relational operator -->
<v-col
:cols="config.items.relationalOperator.cols"
:class="config.col.class"
:style="config.items.relationalOperator.style"
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- field value -->
<v-col
:cols="config.items.fieldValue.cols(index)"
:sm="config.items.fieldValue.sm(index)"
:class="config.col.class"
>
<v-select
v-if="field.fieldOptions"
:model-value="field.values"
:items="field.fieldOptions"
item-title="label"
item-value="value"
multiple
variant="underlined"
@update:model-value="setFieldValues(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'string'"
:model-value="field.value"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'number'"
:model-value="field.value"
type="number"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-checkbox
v-else-if="field.type === 'boolean'"
:model-value="field.value"
@update:model-value="setFieldValue(field, index, $event!)"
/>
<v-menu
v-else-if="field.type === 'date'"
v-model="datePickers[index]"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto"
>
<v-select
v-if="field.type !== 'boolean'"
:model-value="field.relationalOperatorValue"
:items="field.relationalOperatorOptions"
item-title="label"
item-value="value"
variant="underlined"
@update:model-value="setRelationalOperatorValue(field, index, $event as unknown as RelationalKeyword | RelationalOperator)"
>
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- field value -->
<v-col
:cols="config.items.fieldValue.cols"
:class="config.col.class"
:style="config.items.fieldValue.style"
>
<v-select
v-if="field.fieldOptions"
:model-value="field.values"
:items="field.fieldOptions"
item-title="label"
item-value="value"
multiple
variant="underlined"
@update:model-value="setFieldValues(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'string'"
:model-value="field.value"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'number'"
:model-value="field.value"
type="number"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-checkbox
v-else-if="field.type === 'boolean'"
:model-value="field.value"
@update:model-value="setFieldValue(field, index, $event!)"
/>
<v-menu
v-else-if="field.type === 'date'"
v-model="datePickers[index]"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto"
>
<template #activator="{ props: activatorProps }">
<v-text-field
:model-value="field.value ? $d(new Date(field.value + 'T00:00:00')) : null"
persistent-hint
:prepend-icon="$globals.icons.calendar"
variant="underlined"
color="primary"
v-bind="activatorProps"
readonly
/>
</template>
<v-date-picker
:model-value="field.value ? new Date(field.value + 'T00:00:00') : null"
hide-header
:first-day-of-week="firstDayOfWeek"
:local="$i18n.locale"
@update:model-value="val => setFieldValue(field, index, val ? val.toISOString().slice(0, 10) : '')"
<template #activator="{ props: activatorProps }">
<v-text-field
:model-value="field.value ? $d(new Date(field.value + 'T00:00:00')) : null"
persistent-hint
:prepend-icon="$globals.icons.calendar"
variant="underlined"
color="primary"
v-bind="activatorProps"
readonly
/>
</v-menu>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Category"
v-model="field.organizers"
:selector-type="Organizer.Category"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="setFieldOrganizers(field, index, $event)"
</template>
<v-date-picker
:model-value="field.value ? new Date(field.value + 'T00:00:00') : null"
hide-header
:first-day-of-week="firstDayOfWeek"
:local="$i18n.locale"
@update:model-value="val => setFieldValue(field, index, val ? val.toISOString().slice(0, 10) : '')"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tag"
v-model="field.organizers"
:selector-type="Organizer.Tag"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="setFieldOrganizers(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tool"
v-model="field.organizers"
:selector-type="Organizer.Tool"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="setFieldOrganizers(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Food"
v-model="field.organizers"
:selector-type="Organizer.Food"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="setFieldOrganizers(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Household"
v-model="field.organizers"
:selector-type="Organizer.Household"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="setFieldOrganizers(field, index, $event)"
/>
</v-col>
<!-- right parenthesis -->
<v-col
v-if="showAdvanced"
:cols="config.items.rightParens.cols"
:class="config.col.class"
:style="config.items.rightParens.style"
</v-menu>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Category"
v-model="field.organizers"
:selector-type="Organizer.Category"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tag"
v-model="field.organizers"
:selector-type="Organizer.Tag"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tool"
v-model="field.organizers"
:selector-type="Organizer.Tool"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Food"
v-model="field.organizers"
:selector-type="Organizer.Food"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Household"
v-model="field.organizers"
:selector-type="Organizer.Household"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.User"
v-model="field.organizers"
:selector-type="Organizer.User"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
</v-col>
<!-- right parenthesis -->
<v-col
v-if="showAdvanced"
:cols="config.items.rightParens.cols(index)"
:sm="config.items.rightParens.sm(index)"
:class="config.col.class"
>
<v-select
:model-value="field.rightParenthesis"
:items="['', ')', '))', ')))']"
variant="underlined"
@update:model-value="setRightParenthesisValue(field, index, $event)"
>
<v-select
:model-value="field.rightParenthesis"
:items="['', ')', '))', ')))']"
variant="underlined"
@update:model-value="setRightParenthesisValue(field, index, $event)"
>
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw }}
</span>
</template>
</v-select>
</v-col>
<!-- field actions -->
<v-col
:cols="config.items.fieldActions.cols"
:class="config.col.class"
:style="config.items.fieldActions.style"
>
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.delete,
text: $t('general.delete'),
event: 'delete',
disabled: fields.length === 1,
},
]"
class="my-auto"
@delete="removeField(index)"
/>
</v-col>
</v-row>
</VueDraggable>
</v-container>
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw }}
</span>
</template>
</v-select>
</v-col>
<!-- field actions -->
<v-col
v-if="!$vuetify.display.smAndDown || index === fields.length - 1"
:cols="config.items.fieldActions.cols(index)"
:sm="config.items.fieldActions.sm(index)"
:class="config.col.class"
>
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.delete,
text: $t('general.delete'),
event: 'delete',
disabled: fields.length === 1,
},
]"
class="my-auto"
@delete="removeField(index)"
/>
</v-col>
</v-row>
</VueDraggable>
</v-card-text>
<v-card-actions>
<v-row fluid class="d-flex justify-end pa-0 mx-2">
<v-row fluid class="d-flex justify-end ma-2">
<v-spacer />
<v-checkbox
v-model="showAdvanced"
@@ -305,6 +319,7 @@ import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerS
import { Organizer } from "~/lib/api/types/non-generated";
import type { LogicalOperator, QueryFilterJSON, QueryFilterJSONPart, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response";
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store";
import { useUserStore } from "~/composables/store/use-user-store";
import { type Field, type FieldDefinition, type FieldValue, type OrganizerBase, useQueryFilterBuilder } from "~/composables/use-query-filter-builder";
const props = defineProps({
@@ -344,6 +359,7 @@ const storeMap = {
[Organizer.Tool]: useToolStore(),
[Organizer.Food]: useFoodStore(),
[Organizer.Household]: useHouseholdStore(),
[Organizer.User]: useUserStore(),
};
function onDragEnd(event: any) {
@@ -602,46 +618,56 @@ function buildQueryFilterJSON(): QueryFilterJSON {
}
const config = computed(() => {
const baseColMaxWidth = 55;
const multiple = fields.value.length > 1;
const adv = state.showAdvanced;
return {
col: {
class: "d-flex justify-center align-end field-col pa-1",
class: "d-flex justify-center align-end py-0",
},
select: {
textClass: "d-flex justify-center text-center",
},
items: {
icon: {
cols: 1,
cols: (_index: number) => 2,
sm: (_index: number) => 1,
style: "width: fit-content;",
},
leftParens: {
cols: state.showAdvanced ? 1 : 0,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`,
cols: (index: number) => (adv ? (index === 0 ? 2 : 0) : 0),
sm: (_index: number) => (adv ? 1 : 0),
},
logicalOperator: {
cols: 1,
style: `min-width: ${baseColMaxWidth}px;`,
cols: (_index: number) => 0,
sm: (_index: number) => (multiple ? 1 : 0),
},
fieldName: {
cols: state.showAdvanced ? 2 : 3,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`,
cols: (index: number) => {
if (adv) return index === 0 ? 8 : 12;
return index === 0 ? 10 : 12;
},
sm: (_index: number) => (adv ? 2 : 3),
},
relationalOperator: {
cols: 2,
style: `min-width: ${baseColMaxWidth * 2}px;`,
cols: (_index: number) => 12,
sm: (_index: number) => 2,
},
fieldValue: {
cols: state.showAdvanced ? 3 : 4,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`,
cols: (index: number) => {
const last = index === fields.value.length - 1;
if (adv) return last ? 8 : 10;
return last ? 10 : 12;
},
sm: (_index: number) => (adv ? 3 : 4),
},
rightParens: {
cols: state.showAdvanced ? 1 : 0,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`,
cols: (index: number) => (adv ? (index === fields.value.length - 1 ? 2 : 0) : 0),
sm: (_index: number) => (adv ? 1 : 0),
},
fieldActions: {
cols: 1,
style: `min-width: ${baseColMaxWidth}px;`,
cols: (index: number) => (index === fields.value.length - 1 ? 2 : 0),
sm: (_index: number) => 1,
},
},
};
@@ -651,5 +677,14 @@ const config = computed(() => {
<style scoped>
* {
font-size: 1em;
--bg-opactity: calc(var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
}
.bg-dark {
background-color: rgba(0, 0, 0, var(--bg-opactity));
}
.bg-light {
background-color: rgba(255, 255, 255, var(--bg-opactity));
}
</style>

View File

@@ -15,11 +15,11 @@
@click.self="$emit('click')"
>
<RecipeCardImage
small
:icon-size="imageHeight"
:height="imageHeight"
:slug="slug"
:recipe-id="recipeId"
size="small"
:image-version="image"
>
<v-expand-transition v-if="description">

View File

@@ -19,10 +19,10 @@
cover
>
<RecipeCardImage
tiny
:icon-size="100"
:slug="slug"
:recipe-id="recipeId"
size="small"
:image-version="image"
:height="height"
/>
@@ -41,11 +41,11 @@
name="avatar"
>
<RecipeCardImage
tiny
:icon-size="100"
:slug="slug"
:recipe-id="recipeId"
:image-version="image"
size="small"
width="125"
:height="height"
/>

View File

@@ -45,31 +45,15 @@
@confirm="addRecipeToPlan()"
>
<v-card-text>
<v-menu
v-model="pickerMenu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto"
>
<template #activator="{ props: activatorProps }">
<v-text-field
:model-value="$d(newMealdate)"
:label="$t('general.date')"
:prepend-icon="$globals.icons.calendar"
v-bind="activatorProps"
readonly
/>
</template>
<v-date-picker
v-model="newMealdate"
hide-header
:first-day-of-week="firstDayOfWeek"
:local="$i18n.locale"
@update:model-value="pickerMenu = false"
/>
</v-menu>
<v-date-picker
v-model="newMealdate"
class="mx-auto mb-3"
hide-header
show-adjacent-months
color="primary"
:first-day-of-week="firstDayOfWeek"
:local="$i18n.locale"
/>
<v-select
v-model="newMealType"
:return-object="false"
@@ -207,7 +191,6 @@ const loading = ref(false);
const menuItems = ref<ContextMenuItem[]>([]);
const newMealdate = ref(new Date());
const newMealType = ref<PlanEntryType>("dinner");
const pickerMenu = ref(false);
const newMealdateString = computed(() => {
// Format the date to YYYY-MM-DD in the same timezone as newMealdate

View File

@@ -56,6 +56,7 @@
variant="solo"
return-object
:items="units || []"
:custom-filter="normalizeFilter"
item-title="name"
class="mx-1"
:placeholder="$t('recipe.choose-unit')"
@@ -114,6 +115,7 @@
variant="solo"
return-object
:items="foods || []"
:custom-filter="normalizeFilter"
item-title="name"
class="mx-1 py-0"
:placeholder="$t('recipe.choose-food')"
@@ -171,6 +173,7 @@
variant="solo"
return-object
:items="search.data.value || []"
:custom-filter="normalizeFilter"
item-title="name"
class="mx-1 py-0"
:placeholder="$t('search.type-to-search')"
@@ -225,6 +228,7 @@ import { ref, computed, reactive, toRefs } from "vue";
import { useDisplay } from "vuetify";
import { useI18n } from "vue-i18n";
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
import { normalizeFilter } from "~/composables/use-utils";
import { useNuxtApp } from "#app";
import type { RecipeIngredient } from "~/lib/api/types/recipe";
import { usePublicExploreApi, useUserApi } from "~/composables/api";

View File

@@ -4,17 +4,19 @@
v-bind="inputAttrs"
v-model:search="searchInput"
:items="items"
:custom-filter="normalizeFilter"
:label="label"
chips
closable-chips
item-title="name"
:item-title="itemTitle"
item-value="name"
multiple
:variant="variant"
:prepend-inner-icon="icon"
:append-icon="showAdd ? $globals.icons.create : undefined"
return-object
auto-select-first
class="pa-0"
class="pa-0 ma-0"
@update:model-value="resetSearchInput"
@click:append="dialog = true"
>
@@ -32,7 +34,6 @@
{{ item.value }}
</v-chip>
</template>
<template
v-if="showAdd"
#append
@@ -52,11 +53,13 @@ import type { RecipeTool } from "~/lib/api/types/admin";
import { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated";
import type { HouseholdSummary } from "~/lib/api/types/household";
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store";
import { useUserStore } from "~/composables/store/use-user-store";
import { normalizeFilter } from "~/composables/use-utils";
import type { UserSummary } from "~/lib/api/types/user";
interface Props {
selectorType: RecipeOrganizer;
inputAttrs?: Record<string, any>;
returnObject?: boolean;
showAdd?: boolean;
showLabel?: boolean;
showIcon?: boolean;
@@ -65,7 +68,6 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
inputAttrs: () => ({}),
returnObject: true,
showAdd: true,
showLabel: true,
showIcon: true,
@@ -78,7 +80,7 @@ const selected = defineModel<(
| RecipeCategory
| RecipeTool
| IngredientFood
| string
| UserSummary
)[] | undefined>({ required: true });
onMounted(() => {
@@ -106,6 +108,8 @@ const label = computed(() => {
return i18n.t("general.foods");
case Organizer.Household:
return i18n.t("household.households");
case Organizer.User:
return i18n.t("user.users");
default:
return i18n.t("general.organizer");
}
@@ -127,11 +131,19 @@ const icon = computed(() => {
return $globals.icons.foods;
case Organizer.Household:
return $globals.icons.household;
case Organizer.User:
return $globals.icons.user;
default:
return $globals.icons.tags;
}
});
const itemTitle = computed(() =>
props.selectorType === Organizer.User
? (i: any) => i?.fullName ?? i?.name ?? ""
: "name",
);
// ===========================================================================
// Store & Items Setup
@@ -141,28 +153,19 @@ const storeMap = {
[Organizer.Tool]: useToolStore(),
[Organizer.Food]: useFoodStore(),
[Organizer.Household]: useHouseholdStore(),
[Organizer.User]: useUserStore(),
};
const store = computed(() => {
const activeStore = computed(() => {
const { store } = storeMap[props.selectorType];
return store.value;
});
const items = computed(() => {
if (!props.returnObject) {
return store.value.map(item => item.name);
}
return store.value;
const items = computed<any[]>(() => {
const list = (activeStore.value as unknown as any[]) ?? [];
return list;
});
function removeByIndex(index: number) {
if (selected.value === undefined) {
return;
}
const newSelected = selected.value.filter((_, i) => i !== index);
selected.value = [...newSelected];
}
function appendCreated(item: any) {
if (selected.value === undefined) {
return;

View File

@@ -28,7 +28,7 @@ const props = withDefaults(defineProps<Props>(), {
});
const display = useDisplay();
const { recipeImage } = useStaticRoutes();
const { recipeImage, recipeSmallImage } = useStaticRoutes();
const { imageKey } = usePageState(props.recipe.slug);
const { user } = usePageUser();
@@ -46,7 +46,9 @@ const imageHeight = computed(() => {
});
const recipeImageUrl = computed(() => {
return recipeImage(props.recipe.id, props.recipe.image, imageKey.value);
return display.smAndDown.value
? recipeSmallImage(props.recipe.id, props.recipe.image, imageKey.value)
: recipeImage(props.recipe.id, props.recipe.image, imageKey.value);
});
watch(

View File

@@ -5,6 +5,7 @@
:title="$t('recipe.edit-timeline-event')"
:icon="$globals.icons.edit"
can-submit
disable-submit-on-enter
:submit-text="$t('general.save')"
@submit="submitEdit"
>

View File

@@ -119,7 +119,7 @@ defineEmits<{
const { $globals } = useNuxtApp();
const display = useDisplay();
const { recipeTimelineEventImage } = useStaticRoutes();
const { recipeTimelineEventSmallImage } = useStaticRoutes();
const { eventTypeOptions } = useTimelineEventTypes();
const { user: currentUser } = useMealieAuth();
@@ -173,7 +173,7 @@ const eventImageUrl = computed<string>(() => {
return "";
}
return recipeTimelineEventImage(props.event.recipeId, props.event.id);
return recipeTimelineEventSmallImage(props.event.recipeId, props.event.id);
});
</script>

View File

@@ -149,6 +149,6 @@ export default defineNuxtComponent({
<style scoped>
.v-toolbar {
z-index: 1010 !important;
z-index: 2010 !important;
}
</style>

View File

@@ -7,6 +7,7 @@
item-title="name"
return-object
:items="items"
:custom-filter="normalizeFilter"
:prepend-icon="icon || $globals.icons.tags"
auto-select-first
clearable
@@ -52,6 +53,7 @@
import type { MultiPurposeLabelSummary } from "~/lib/api/types/labels";
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
import { normalizeFilter } from "~/composables/use-utils";
export default defineNuxtComponent({
props: {
@@ -122,6 +124,7 @@ export default defineNuxtComponent({
itemIdVal,
searchInput,
emitCreate,
normalizeFilter,
};
},
});

View File

@@ -9,6 +9,7 @@
<v-autocomplete
v-model="selectedLocale"
:items="locales"
:custom-filter="normalizeFilter"
item-title="name"
item-value="value"
class="my-3"
@@ -44,6 +45,7 @@
<script lang="ts">
import { useLocales } from "~/composables/use-locales";
import { normalizeFilter } from "~/composables/use-utils";
export default defineNuxtComponent({
props: {
@@ -83,6 +85,7 @@ export default defineNuxtComponent({
locale,
selectedLocale,
onLocaleSelect,
normalizeFilter,
};
},
});

View File

@@ -29,7 +29,7 @@ export default defineNuxtComponent({
"ul", "ol", "li", "dl", "dt", "dd", "abbr", "a", "img", "blockquote", "iframe",
"del", "ins", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "colgroup",
],
ADD_ATTR: [
ALLOWED_ATTR: [
"href", "src", "alt", "height", "width", "class", "allow", "title", "allowfullscreen", "frameborder",
"scrolling", "cite", "datetime", "name", "abbr", "target", "border",
],

View File

@@ -0,0 +1,58 @@
import { describe, expect, test, vi } from "vitest";
import { ref } from "vue";
import { useStoreActions } from "./use-actions-factory";
import type { BaseCRUDAPI } from "~/lib/api/base/base-clients";
describe("useStoreActions", () => {
const mockApi = {
getAll: vi.fn(),
createOne: vi.fn(),
updateOne: vi.fn(),
deleteOne: vi.fn(),
} as unknown as BaseCRUDAPI<unknown, unknown, unknown>;
const mockStore = ref([]);
const mockLoading = ref(false);
test("deleteMany calls deleteOne for each ID and refreshes once", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
mockApi.deleteOne = vi.fn().mockResolvedValue({ response: { data: {} } });
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
const ids = ["1", "2", "3"];
await actions.deleteMany(ids);
expect(mockApi.deleteOne).toHaveBeenCalledTimes(3);
expect(mockApi.deleteOne).toHaveBeenCalledWith("1");
expect(mockApi.deleteOne).toHaveBeenCalledWith("2");
expect(mockApi.deleteOne).toHaveBeenCalledWith("3");
expect(mockApi.getAll).toHaveBeenCalledTimes(1);
});
test("deleteMany handles empty array", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
mockApi.deleteOne = vi.fn();
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
await actions.deleteMany([]);
expect(mockApi.deleteOne).not.toHaveBeenCalled();
expect(mockApi.getAll).toHaveBeenCalledTimes(1);
});
test("deleteMany sets loading state", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
mockApi.deleteOne = vi.fn().mockResolvedValue({});
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
const promise = actions.deleteMany(["1"]);
expect(mockLoading.value).toBe(true);
await promise;
expect(mockLoading.value).toBe(false);
});
});

View File

@@ -12,6 +12,7 @@ interface StoreActions<T extends BoundT> extends ReadOnlyStoreActions<T> {
createOne(createData: T): Promise<T | null>;
updateOne(updateData: T): Promise<T | null>;
deleteOne(id: string | number): Promise<T | null>;
deleteMany(ids: (string | number)[]): Promise<void>;
}
/**
@@ -165,11 +166,23 @@ export function useStoreActions<T extends BoundT>(
return response?.data || null;
}
async function deleteMany(ids: (string | number)[]) {
loading.value = true;
for (const id of ids) {
await api.deleteOne(id);
}
if (allRef?.value) {
await refresh();
}
loading.value = false;
}
return {
getAll,
refresh,
createOne,
updateOne,
deleteOne,
deleteMany,
};
}

View File

@@ -52,7 +52,7 @@ export const useStore = function <T extends BoundT>(
return await storeActions.refresh(1, -1, params);
},
flushStore() {
store = ref([]);
store.value = [];
},
};

View File

@@ -1,7 +1,31 @@
export { useCategoryStore, usePublicCategoryStore, useCategoryData } from "./use-category-store";
export { useFoodStore, usePublicFoodStore, useFoodData } from "./use-food-store";
export { useHouseholdStore, usePublicHouseholdStore } from "./use-household-store";
export { useLabelStore, useLabelData } from "./use-label-store";
export { useTagStore, usePublicTagStore, useTagData } from "./use-tag-store";
export { useToolStore, usePublicToolStore, useToolData } from "./use-tool-store";
export { useUnitStore, useUnitData } from "./use-unit-store";
import { resetCategoryStore } from "./use-category-store";
import { resetFoodStore } from "./use-food-store";
import { resetHouseholdStore } from "./use-household-store";
import { resetLabelStore } from "./use-label-store";
import { resetTagStore } from "./use-tag-store";
import { resetToolStore } from "./use-tool-store";
import { resetUnitStore } from "./use-unit-store";
import { resetCookbookStore } from "./use-cookbook-store";
import { resetUserStore } from "./use-user-store";
export { useCategoryStore, usePublicCategoryStore, useCategoryData, resetCategoryStore } from "./use-category-store";
export { useFoodStore, usePublicFoodStore, useFoodData, resetFoodStore } from "./use-food-store";
export { useHouseholdStore, usePublicHouseholdStore, resetHouseholdStore } from "./use-household-store";
export { useLabelStore, useLabelData, resetLabelStore } from "./use-label-store";
export { useTagStore, usePublicTagStore, useTagData, resetTagStore } from "./use-tag-store";
export { useToolStore, usePublicToolStore, useToolData, resetToolStore } from "./use-tool-store";
export { useUnitStore, useUnitData, resetUnitStore } from "./use-unit-store";
export { useCookbookStore, usePublicCookbookStore, resetCookbookStore } from "./use-cookbook-store";
export { useUserStore, resetUserStore } from "./use-user-store";
export function clearAllStores() {
resetCategoryStore();
resetFoodStore();
resetHouseholdStore();
resetLabelStore();
resetTagStore();
resetToolStore();
resetUnitStore();
resetCookbookStore();
resetUserStore();
}

View File

@@ -7,6 +7,12 @@ const store: Ref<RecipeCategory[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export function resetCategoryStore() {
store.value = [];
loading.value = false;
publicLoading.value = false;
}
export const useCategoryData = function () {
return useData<RecipeCategory>({
id: "",

View File

@@ -7,6 +7,12 @@ const cookbooks: Ref<ReadCookBook[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export function resetCookbookStore() {
cookbooks.value = [];
loading.value = false;
publicLoading.value = false;
}
export const useCookbookStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
const store = useStore<ReadCookBook>("cookbook", cookbooks, loading, api.cookbooks);

View File

@@ -7,6 +7,12 @@ const store: Ref<IngredientFood[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export function resetFoodStore() {
store.value = [];
loading.value = false;
publicLoading.value = false;
}
export const useFoodData = function () {
return useData<IngredientFood>({
id: "",

View File

@@ -7,6 +7,12 @@ const store: Ref<HouseholdSummary[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export function resetHouseholdStore() {
store.value = [];
loading.value = false;
publicLoading.value = false;
}
export const useHouseholdStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useReadOnlyStore<HouseholdSummary>("household", store, loading, api.households);

View File

@@ -6,6 +6,11 @@ import { useUserApi } from "~/composables/api";
const store: Ref<MultiPurposeLabelOut[]> = ref([]);
const loading = ref(false);
export function resetLabelStore() {
store.value = [];
loading.value = false;
}
export const useLabelData = function () {
return useData<MultiPurposeLabelOut>({
groupId: "",

View File

@@ -7,6 +7,12 @@ const store: Ref<RecipeTag[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export function resetTagStore() {
store.value = [];
loading.value = false;
publicLoading.value = false;
}
export const useTagData = function () {
return useData<RecipeTag>({
id: "",

View File

@@ -11,6 +11,12 @@ const store: Ref<RecipeTool[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export function resetToolStore() {
store.value = [];
loading.value = false;
publicLoading.value = false;
}
export const useToolData = function () {
return useData<RecipeToolWithOnHand>({
id: "",

View File

@@ -6,6 +6,11 @@ import { useUserApi } from "~/composables/api";
const store: Ref<IngredientUnit[]> = ref([]);
const loading = ref(false);
export function resetUnitStore() {
store.value = [];
loading.value = false;
}
export const useUnitData = function () {
return useData<IngredientUnit>({
id: "",

View File

@@ -7,6 +7,11 @@ import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
const store: Ref<UserSummary[]> = ref([]);
const loading = ref(false);
export function resetUserStore() {
store.value = [];
loading.value = false;
}
class GroupUserAPIReadOnly extends BaseCRUDAPIReadOnly<UserSummary> {
baseRoute = "/api/groups/members";
itemRoute = (idOrUsername: string | number) => `/groups/members/${idOrUsername}`;

View File

@@ -1,5 +1,6 @@
import { ref, computed } from "vue";
import type { UserOut } from "~/lib/api/types/user";
import { clearAllStores } from "~/composables/store";
interface AuthData {
value: UserOut | null;
@@ -101,6 +102,13 @@ export const useAuthBackend = function (): AuthState {
setToken(null);
authUser.value = null;
authStatus.value = "unauthenticated";
// Clear all cached store data to prevent data leakage between users
clearAllStores();
// Clear Nuxt's useAsyncData cache
clearNuxtData();
await router.push(callbackUrl || "/login");
}
}

View File

@@ -15,6 +15,9 @@ export function usePlanTypeOptions() {
{ text: i18n.t("meal-plan.lunch"), value: "lunch" },
{ text: i18n.t("meal-plan.dinner"), value: "dinner" },
{ text: i18n.t("meal-plan.side"), value: "side" },
{ text: i18n.t("meal-plan.snack"), value: "snack" },
{ text: i18n.t("meal-plan.drink"), value: "drink" },
{ text: i18n.t("meal-plan.dessert"), value: "dessert" },
] as PlanOption[];
}

View File

@@ -21,19 +21,19 @@ export const LOCALES = [
{
name: "Українська (Ukrainian)",
value: "uk-UA",
progress: 93,
progress: 99,
dir: "ltr",
},
{
name: "Türkçe (Turkish)",
value: "tr-TR",
progress: 35,
progress: 39,
dir: "ltr",
},
{
name: "Svenska (Swedish)",
value: "sv-SE",
progress: 67,
progress: 68,
dir: "ltr",
},
{
@@ -69,7 +69,7 @@ export const LOCALES = [
{
name: "Português (Portuguese)",
value: "pt-PT",
progress: 40,
progress: 39,
dir: "ltr",
},
{
@@ -81,7 +81,7 @@ export const LOCALES = [
{
name: "Polski (Polish)",
value: "pl-PL",
progress: 46,
progress: 52,
dir: "ltr",
},
{
@@ -93,7 +93,7 @@ export const LOCALES = [
{
name: "Nederlands (Dutch)",
value: "nl-NL",
progress: 54,
progress: 55,
dir: "ltr",
},
{
@@ -147,7 +147,7 @@ export const LOCALES = [
{
name: "עברית (Hebrew)",
value: "he-IL",
progress: 73,
progress: 72,
dir: "rtl",
},
{
@@ -165,7 +165,7 @@ export const LOCALES = [
{
name: "Français canadien (Canadian French)",
value: "fr-CA",
progress: 100,
progress: 99,
dir: "ltr",
},
{
@@ -201,13 +201,13 @@ export const LOCALES = [
{
name: "British English",
value: "en-GB",
progress: 44,
progress: 43,
dir: "ltr",
},
{
name: "Ελληνικά (Greek)",
value: "el-GR",
progress: 41,
progress: 42,
dir: "ltr",
},
{
@@ -219,7 +219,7 @@ export const LOCALES = [
{
name: "Dansk (Danish)",
value: "da-DK",
progress: 47,
progress: 52,
dir: "ltr",
},
{
@@ -231,13 +231,13 @@ export const LOCALES = [
{
name: "Català (Catalan)",
value: "ca-ES",
progress: 38,
progress: 39,
dir: "ltr",
},
{
name: "Български (Bulgarian)",
value: "bg-BG",
progress: 47,
progress: 51,
dir: "ltr",
},
{

View File

@@ -168,6 +168,7 @@ export function useQueryFilterBuilder() {
|| type === Organizer.Tool
|| type === Organizer.Food
|| type === Organizer.Household
|| type === Organizer.User
);
};

View File

@@ -0,0 +1,34 @@
import { describe, expect, test } from "vitest";
import { normalize, normalizeFilter } from "./use-utils";
describe("test normalize", () => {
test("base case", () => {
expect(normalize("banana")).not.toEqual(normalize("Potatoes"));
});
test("diacritics", () => {
expect(normalize("Rátàtôuile")).toEqual("ratatouile");
});
test("ligatures", () => {
expect(normalize("IJ")).toEqual("ij");
expect(normalize("æ")).toEqual("ae");
expect(normalize("œ")).toEqual("oe");
expect(normalize("ff")).toEqual("ff");
expect(normalize("fi")).toEqual("fi");
expect(normalize("st")).toEqual("st");
});
});
describe("test normalize filter", () => {
test("base case", () => {
const patternA = "Escargots persillés";
const patternB = "persillés";
expect(normalizeFilter(patternA, patternB)).toBeTruthy();
expect(normalizeFilter(patternB, patternA)).toBeFalsy();
});
test("normalize", () => {
const value = "Cœur de bœuf";
const query = "coeur";
expect(normalizeFilter(value, query)).toBeTruthy();
});
});

View File

@@ -1,4 +1,5 @@
import { useDark, useToggle } from "@vueuse/core";
import type { FilterFunction } from "vuetify";
export const useToggleDarkMode = () => {
const isDark = useDark();
@@ -18,6 +19,38 @@ export const titleCase = function (str: string) {
.join(" ");
};
const replaceAllBuilder = (map: Map<string, string>): ((str: string) => string) => {
const re = new RegExp(Array.from(map.keys()).join("|"), "gi");
return str => str.replace(re, matched => map.get(matched)!);
};
const normalizeLigatures = replaceAllBuilder(new Map([
["œ", "oe"],
["æ", "ae"],
["ij", "ij"],
["ff", "ff"],
["fi", "fi"],
["fl", "fl"],
["st", "st"],
]));
export const normalize = (str: string) => {
if (!str) {
return "";
}
let normalized = str.normalize("NFKD").toLowerCase();
normalized = normalized.replace(/\p{Diacritic}/gu, "");
normalized = normalizeLigatures(normalized);
return normalized;
};
export const normalizeFilter: FilterFunction = (value: string, query: string) => {
const normalizedValue = normalize(value);
const normalizeQuery = normalize(query);
return normalizedValue.includes(normalizeQuery);
};
export function uuid4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(parseInt(c) ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (parseInt(c) / 4)))).toString(16),

View File

@@ -1,12 +1,13 @@
import type { RequestResponse } from "~/lib/api/types/non-generated";
import type { ValidationResponse } from "~/lib/api/types/response";
import { required, email, whitespace, url, minLength, maxLength } from "~/lib/validators";
import { required, email, whitespace, url, urlOptional, minLength, maxLength } from "~/lib/validators";
export const validators = {
required,
email,
whitespace,
url,
urlOptional,
minLength,
maxLength,
};

View File

@@ -4,10 +4,10 @@
"about-mealie": "Meer oor Mealie",
"api-docs": "API Dokumentasie",
"api-port": "API Poort",
"application-mode": "Applikasie modues",
"application-mode": "Applikasie Modus",
"database-type": "Databasis Tipe",
"database-url": "Databasis URL",
"default-group": "Standaard groep",
"default-group": "Standaard Groep",
"default-household": "Default Household",
"demo": "Demonstrasie",
"demo-status": "Demonstrasie Status",
@@ -65,7 +65,7 @@
"something-went-wrong": "Iets het verkeerd geloop!",
"subscribed-events": "Ingetekende Gebeure",
"test-message-sent": "Toets Boodskap Gestuur",
"message-sent": "Message Sent",
"message-sent": "Boodskap Gestuur",
"new-notification": "Nuwe kennisgewing",
"event-notifiers": "Gebeurteniskennisgewers",
"apprise-url-skipped-if-blank": "Apprise URL (oorgeslaan indien leeg)",
@@ -84,7 +84,7 @@
"label-events": "Label Events"
},
"general": {
"add": "Add",
"add": "Voeg by",
"cancel": "Kanselleer",
"clear": "Maak skoon",
"close": "Maak toe",
@@ -134,7 +134,7 @@
"no-recipe-found": "Geen resep gevind nie",
"ok": "OK",
"options": "Opsies:",
"plural-name": "Plural Name",
"plural-name": "Meervoudsnaam",
"print": "Druk",
"print-preferences": "Drukvoorkeure",
"random": "Willekeurig",
@@ -148,23 +148,23 @@
"save": "Stoor",
"settings": "Verstellings",
"share": "Deel",
"show-all": "Show All",
"show-all": "Wys Alles",
"shuffle": "Skommel",
"sort": "Sorteer",
"sort-ascending": "Sort Ascending",
"sort-descending": "Sort Descending",
"sort-ascending": "Sorteer Oplopend",
"sort-descending": "Sorteer Aflopend",
"sort-alphabetically": "Alfabeties",
"status": "Status",
"subject": "Onderwerp",
"submit": "Dien in",
"success-count": "Sukses: {count}",
"sunday": "Sondag",
"system": "System",
"system": "Sisteem",
"templates": "Sjablone:",
"test": "Toets",
"themes": "Temas",
"thursday": "Donderdag",
"title": "Title",
"title": "Titel",
"token": "Token",
"tuesday": "Dinsdag",
"type": "Tipe",
@@ -179,12 +179,12 @@
"units": "Eenhede",
"back": "Terug",
"next": "Volgende",
"start": "Start",
"start": "Begin",
"toggle-view": "Wissel aansig",
"date": "Datum",
"id": "Id",
"owner": "Eienaar",
"change-owner": "Change Owner",
"change-owner": "Verander Eienaar",
"date-added": "Datum bygevoeg",
"none": "Geen",
"run": "Hardloop",
@@ -212,13 +212,13 @@
"upload-file": "Laai dokument op",
"created-on-date": "Geskep op: {0}",
"unsaved-changes": "You have unsaved changes. Do you want to save before leaving? Okay to save, Cancel to discard changes.",
"clipboard-copy-failure": "Failed to copy to the clipboard.",
"confirm-delete-generic-items": "Are you sure you want to delete the following items?",
"organizers": "Organizers",
"clipboard-copy-failure": "Kon nie kopieer na die knipbord toe nie.",
"confirm-delete-generic-items": "Is jy seker jy wil die volgende items verwyder?",
"organizers": "Organiseerders",
"caution": "Caution",
"show-advanced": "Show Advanced",
"add-field": "Add Field",
"date-created": "Date Created",
"date-created": "Datum Geskep",
"date-updated": "Date Updated"
},
"group": {
@@ -342,6 +342,9 @@
"breakfast": "Ontbyt",
"lunch": "Middagete",
"dinner": "Aandete",
"snack": "Snack",
"drink": "Drank",
"dessert": "Nagereg",
"type-any": "Enige",
"day-any": "Enige",
"editor": "Editor",
@@ -438,10 +441,11 @@
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Plak jou resepdata in. Elke reël sal as 'n item in 'n lys hanteer word",
"recipe-markup-specification": "Resep formaat spesifikasie",
"recipe-url": "Resep URL",
"recipe-html-or-json": "Recipe HTML or JSON",
"recipe-html-or-json": "Resep HTML of JSON",
"upload-a-recipe": "Laai 'n resep op",
"upload-individual-zip-file": "Laai 'n .zip-lêer op wat vanaf 'n ander Mealie-instansie uitgevoer is.",
"url-form-hint": "Kopieer en plak 'n skakel vanaf jou gunstelingresepwebwerf",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Bekyk opgespoorde data",
"trim-whitespace-description": "Knip voorste en agterste witspasie sowel as leë reëls",
"trim-prefix-description": "Knip die eerste karakter van elke reël af",
@@ -450,7 +454,7 @@
"create-manually": "Skep 'n resep met die hand",
"make-recipe-image": "Maak dit die prentjie vir hierdie resep",
"add-food": "Add Food",
"add-recipe": "Add Recipe"
"add-recipe": "Voeg Resep By"
},
"page": {
"404-page-not-found": "404 Bladsy nie gevind nie",
@@ -517,8 +521,8 @@
"recipe-deleted": "Resep uitgevee",
"recipe-image": "Resep foto",
"recipe-image-updated": "Resep foto is opgedateer",
"delete-image": "Delete Recipe Image",
"delete-image-confirmation": "Are you sure you want to delete this recipe image?",
"delete-image": "Verwyder Resep Beeld",
"delete-image-confirmation": "Is jy seker jy wil dié beeld van die resep verwyder?",
"recipe-image-deleted": "Recipe image deleted",
"recipe-name": "Resepnaam",
"recipe-settings": "Resep verstellings",
@@ -552,7 +556,7 @@
"add-to-plan": "Voeg by plan",
"add-to-timeline": "Voeg by tydlyn",
"recipe-added-to-list": "Resep by lys gevoeg",
"recipes-added-to-list": "Recipes added to list",
"recipes-added-to-list": "Resepte toegevoeg tot lys",
"successfully-added-to-list": "Successfully added to list",
"recipe-added-to-mealplan": "Resep is by die maaltydplan gevoeg",
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
@@ -565,7 +569,7 @@
"choose-unit": "Kies 'n eenheid",
"press-enter-to-create": "Druk Enter om te skep",
"choose-food": "Keuse van kos",
"choose-recipe": "Choose Recipe",
"choose-recipe": "Kies Resep",
"notes": "Notas",
"toggle-section": "Wissel afdeling",
"see-original-text": "Sien oorspronklike teks",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Voer oorspronklike sleutelwoorde as merkers in",
"stay-in-edit-mode": "Bly in redigeer modus",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -654,7 +660,7 @@
"report-deletion-failed": "Kon nie verslag uitvee nie",
"recipe-debugger": "Resep debugger",
"recipe-debugger-description": "Gryp die URL van die resep wat jy wil debug en plak dit hier. Die URL sal deur die resepskraper geskraap word en die resultate sal vertoon word. As jy nie enige data terugstuur sien nie, word die webwerf wat jy probeer skraap nie deur Mealie of sy skraperbiblioteek ondersteun nie.",
"use-openai": "Use OpenAI",
"use-openai": "Gebruik OpenAI",
"recipe-debugger-use-openai-description": "Use OpenAI to parse the results instead of relying on the scraper library. When creating a recipe via URL, this is done automatically if the scraper library fails, but you may test it manually here.",
"debug": "Debug",
"tree-view": "Boomstruktuur",
@@ -665,8 +671,8 @@
"upload-image": "Laai prent",
"screen-awake": "Hou die skerm aan",
"remove-image": "Verwyder prent",
"nextStep": "Next step",
"recipe-actions": "Recipe Actions",
"nextStep": "Volgende stap",
"recipe-actions": "Resep Aksies",
"parser": {
"ingredient-parser": "Ingredient Parser",
"explanation": "To use the ingredient parser, click the 'Parse All' button to start the process. Once the processed ingredients are available, you can review the items and verify that they were parsed correctly. The model's confidence score is displayed on the right of the item title. This score is an average of all the individual scores and may not always be completely accurate.",
@@ -676,7 +682,7 @@
"brute-parser": "Brute Parser",
"openai-parser": "OpenAI Parser",
"parse-all": "Parse All",
"no-unit": "No unit",
"no-unit": "Geen eenheid",
"missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
@@ -687,12 +693,12 @@
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
"delete-item": "Delete Item"
"delete-item": "Verwyder Item"
},
"reset-servings-count": "Reset Servings Count",
"not-linked-ingredients": "Additional Ingredients",
"not-linked-ingredients": "Bykomende Bestanddele",
"upload-another-image": "Upload another image",
"upload-images": "Upload images",
"upload-images": "Laai beelde op",
"upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image",
@@ -884,7 +890,7 @@
"oidc-ready": "OIDC Klar",
"oidc-ready-error-text": "Ikke alle OIDC værdier er konfigureret. Dette kan ignoreres hvis du ikke bruger OIDC godkendelse.",
"oidc-ready-success-text": "Krævede OIDC variabler er udfyldt.",
"openai-ready": "OpenAI Ready",
"openai-ready": "OpenAI Gereed",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set."
},
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "الإفطار",
"lunch": "الغداء",
"dinner": "العشاء",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "أي",
"day-any": "أي",
"editor": "المحرر",
@@ -442,6 +445,7 @@
"upload-a-recipe": "تحميل وصفة",
"upload-individual-zip-file": "تحميل مِلَفّ zip فردي تم تصديره من مثيل Malie آخر.",
"url-form-hint": "نسخ ولصق رابط من موقعك المفضل للوصفة",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "عرض البيانات المحللة",
"trim-whitespace-description": "قص المسافات البيضاء البادئة واللاحقة وكذلك الأسطر الفارغة",
"trim-prefix-description": "قص الحرف الأول من كل سطر",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "جرب الإضافة بالجملة",
"scrape-recipe-have-raw-html-or-json-data": "هل لديك بيانات HTML أو JSON خام؟",
"scrape-recipe-you-can-import-from-raw-data-directly": "يمكنك الإضافة مباشرة باستخدام بيانات خام",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "استيراد الكلمات المفتاحية الأصلية كوسوم",
"stay-in-edit-mode": "البقاء في وضع التعديل",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "هو مثل",
"is-not-like": "ليس مثل"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Закуска",
"lunch": "Обяд",
"dinner": "Вечеря",
"snack": "Закуска",
"drink": "Питие",
"dessert": "Десерт",
"type-any": "Всички",
"day-any": "Всички",
"editor": "Редактор",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Качи рецепта",
"upload-individual-zip-file": "Качи като индивидуален .zip файлов формат от друга инстанция на Mealie.",
"url-form-hint": "Копирай и постави линк от твоя любим сайт за рецепти",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Виж събраните данни",
"trim-whitespace-description": "Премахни интервалите в началото и края на текста, също така и празните редове",
"trim-prefix-description": "Премахни първия символ от всеки ред",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Пробвайте масовото импорторане",
"scrape-recipe-have-raw-html-or-json-data": "Имате ли сурови HTML или JSON данни?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Можете да импортирате директно от сурови данни",
"scrape-recipe-website-being-blocked": "Блокиран ли е уебсайтът?",
"scrape-recipe-try-importing-raw-html-instead": "Опитайте вместо това да импортирате суровия HTML код.",
"import-original-keywords-as-tags": "Добави оригиналните ключови думи като етикети",
"stay-in-edit-mode": "Остани в режим на редакция",
"parse-recipe-ingredients-after-import": "Анализиране на съставките на рецептата след импортиране",
@@ -698,7 +704,7 @@
"cover-image": "Изображение на корицата",
"include-linked-recipes": "Влючване на свързаните рецепти",
"include-linked-recipe-ingredients": "Включване на съставките от свързаните рецепти",
"toggle-recipe": "Превключване на рецептата"
"toggle-recipe": "Вмъкни рецепта"
},
"recipe-finder": {
"recipe-finder": "Търсачка на рецепти",
@@ -1422,5 +1428,13 @@
"is-like": "е като",
"is-not-like": "не е като"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Esmorzar",
"lunch": "Dinar",
"dinner": "Sopar",
"snack": "Piscolabis",
"drink": "Beguda",
"dessert": "Postres",
"type-any": "Qualsevol",
"day-any": "Qualsevol",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Puja una recepta",
"upload-individual-zip-file": "Puja només un arxiu zip, exportat d'altre Mealie.",
"url-form-hint": "Copia i enganxa l'enllaç del teu lloc web de receptes preferit",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Visualitza les dades recuperades",
"trim-whitespace-description": "Elimina els espais a principi i final; i elimina les línies buides",
"trim-prefix-description": "Elimina el primer caràcter de cada línia",
@@ -449,8 +453,8 @@
"import-by-url": "Importa per URL",
"create-manually": "Crea una recepta manualment",
"make-recipe-image": "Fes-la la imatge de la recepta",
"add-food": "Add Food",
"add-recipe": "Add Recipe"
"add-food": "Afegeix Aliment",
"add-recipe": "Afegeix Recepta"
},
"page": {
"404-page-not-found": "404 - Pàgina no trobada",
@@ -480,7 +484,7 @@
"comment": "Comentari",
"comments": "Comentaris",
"delete-confirmation": "Estàs segur que vols suprimir-la?",
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?",
"admin-delete-confirmation": "Estàs a punt d'eliminar una recepta que no és teva utilitzant permisos d'administrador. N'estàs segur?",
"delete-recipe": "Suprimeix la recepta",
"description": "Descripció",
"disable-amount": "Oculta les quantitats",
@@ -517,9 +521,9 @@
"recipe-deleted": "S'ha suprimit la recepta",
"recipe-image": "Imatge de la recepta",
"recipe-image-updated": "S'ha actualitzat la imatge de la recepta",
"delete-image": "Delete Recipe Image",
"delete-image-confirmation": "Are you sure you want to delete this recipe image?",
"recipe-image-deleted": "Recipe image deleted",
"delete-image": "Suprimir la imatge de la recepta",
"delete-image-confirmation": "Estàs segur que vols suprimir la imatge d'aquesta recepta?",
"recipe-image-deleted": "S'ha suprimit la imatge de la recepta",
"recipe-name": "Nom de la recepta",
"recipe-settings": "Opcions de la recepta",
"recipe-update-failed": "S'ha produït un error a l'actualitzar la recepta",
@@ -565,7 +569,7 @@
"choose-unit": "Tria el tipus d'unitat",
"press-enter-to-create": "Premeu enter per a crear-lo",
"choose-food": "Tria un aliment",
"choose-recipe": "Choose Recipe",
"choose-recipe": "Tria la recepta",
"notes": "Notes",
"toggle-section": "Nova secció",
"see-original-text": "Mostra el text original",
@@ -597,11 +601,11 @@
"added-to-timeline": "Added to timeline",
"failed-to-add-to-timeline": "Failed to add to timeline",
"failed-to-update-recipe": "Failed to update recipe",
"added-to-timeline-but-failed-to-add-image": "Added to timeline, but failed to add image",
"added-to-timeline-but-failed-to-add-image": "S'ha afegit a la línia de temps, però no s'ha pogut afegir la imatge",
"api-extras-description": "Els extres de receptes són una funcionalitat clau de l'API de Mealie. Permeten crear parells clau/valor JSON personalitzats dins una recepta, per referenciar-los des d'aplicacions de tercers. Pots emprar aquestes claus per proveir informació, per exemple per a desencadenar automatitzacions o missatges personlitzats per a propagar al teu dispositiu desitjat.",
"message-key": "Clau del missatge",
"parse": "Analitzar",
"ingredients-not-parsed-description": "It looks like your ingredients aren't parsed yet. Click the \"{parse}\" button below to parse your ingredients into structured foods.",
"ingredients-not-parsed-description": "Sembla que els teus ingredients encara no s'han analitzat. Feu clic al botó \"{parse}\" de sota per transformar els vostres ingredients en aliments estructurats.",
"attach-images-hint": "Afegeix imatges arrossegant i deixant anar la imatge a l'editor",
"drop-image": "Deixa anar la imatge",
"enable-ingredient-amounts-to-use-this-feature": "Habilita les quantitats d'ingredients per a poder fer servir aquesta característica",
@@ -619,10 +623,10 @@
"create-recipe-from-an-image": "Crear una recepta a partir d'una imatge",
"create-recipe-from-an-image-description": "Crear una recepta pujant una imatge d'ella. Mealie intentarà extreure el text de la imatge mitjançant IA i crear-ne la recepta.",
"crop-and-rotate-the-image": "Retalla i rota la imatge, per tal que només el text sigui visible, i estigui orientat correctament.",
"create-from-images": "Create from Images",
"create-from-images": "Crear una recepta a partir d'una imatge",
"should-translate-description": "Tradueix la recepta a la meva llengua",
"please-wait-image-procesing": "Si us plau, esperi, la imatge s'està processant. Això pot tardar un temps.",
"please-wait-images-processing": "Please wait, the images are processing. This may take some time.",
"please-wait-images-processing": "Espereu, les imatges s'estan processant. Això pot trigar una estona.",
"bulk-url-import": "Importació d'URL en massa",
"debug-scraper": "Rastrejador de depuració",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Crea la recepta proporcionant-ne un nom. Totes les receptes han de tenir un nom únic.",
@@ -633,9 +637,11 @@
"scrape-recipe-suggest-bulk-importer": "Prova l'importador a granel",
"scrape-recipe-have-raw-html-or-json-data": "Teniu dades HTML o JSON pla?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Podeu importar directament des de les dades planes",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importa les paraules clau originals com a tags",
"stay-in-edit-mode": "Segueix en el mode d'edició",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
"parse-recipe-ingredients-after-import": "Analitza els ingredients de la recepta després d'importar",
"import-from-zip": "Importa des d'un ZIP",
"import-from-zip-description": "Importa una sola recepta que ha estat importada d'una altra instància de Mealie.",
"import-from-html-or-json": "Importar des d'un HTML o JSON",
@@ -679,26 +685,26 @@
"no-unit": "Sense unitat",
"missing-unit": "Crear unitat que manca: {unit}",
"missing-food": "Crear menjar que manca: {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
"this-unit-could-not-be-parsed-automatically": "Aquesta unitat no s'ha pogut analitzar automàticament",
"this-food-could-not-be-parsed-automatically": "Aquest aliment no s'ha pogut analitzar automàticament",
"no-food": "Sense menjar",
"review-parsed-ingredients": "Review parsed ingredients",
"confidence-score": "Confidence Score",
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
"delete-item": "Delete Item"
"review-parsed-ingredients": "Revisió d'ingredients analitzats",
"confidence-score": "Puntuació de confiança",
"ingredient-parser-description": "Els teus ingredients s'han analitzat correctament. Si us plau, revisa els ingredients dels quals no estem segurs.",
"ingredient-parser-final-review-description": "Un cop revisats tots els ingredients, tindràs una oportunitat més de revisar tots els ingredients abans d'aplicar els canvis a la teva recepta.",
"add-text-as-alias-for-item": "Afegeix \"{text}\" com a àlies de {item}",
"delete-item": "Eliminar element"
},
"reset-servings-count": "Reiniciar racions servides",
"not-linked-ingredients": "Ingredients addicionals",
"upload-another-image": "Upload another image",
"upload-images": "Upload images",
"upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image",
"include-linked-recipes": "Include Linked Recipes",
"include-linked-recipe-ingredients": "Include Linked Recipe Ingredients",
"toggle-recipe": "Toggle Recipe"
"upload-another-image": "Puja una altra imatge",
"upload-images": "Puja imatges",
"upload-more-images": "Puja més imatges",
"set-as-cover-image": "Estableix com a imatge de portada de recepta",
"cover-image": "Imatge de portada",
"include-linked-recipes": "Inclou les receptes enllaçades",
"include-linked-recipe-ingredients": "Inclou els ingredients de la recepta enllaçada",
"toggle-recipe": "Alternar recepta"
},
"recipe-finder": {
"recipe-finder": "Cercador de receptes",
@@ -736,7 +742,7 @@
"advanced": "Avançat",
"auto-search": "Cerca automàtica",
"no-results": "No s'han trobat resultats",
"type-to-search": "Type to search..."
"type-to-search": "Escriviu per cercar..."
},
"settings": {
"add-a-new-theme": "Afegiu un nou tema",
@@ -1075,8 +1081,8 @@
"forgot-password": "Contrasenya oblidada",
"forgot-password-text": "Introdueix siusplau la teva adreça de correu electrònic i t'enviarem un enllaç per restablir la teva contrassenya.",
"changes-reflected-immediately": "Els canvis en aquest usuari s'actualitzaran immediatament.",
"default-activity": "Default Activity",
"default-activity-hint": "Select which page you'd like to navigate to upon logging in from this device"
"default-activity": "Activitat per defecte",
"default-activity-hint": "Seleccioneu a quina pàgina voleu navegar en iniciar sessió des d'aquest dispositiu"
},
"language-dialog": {
"translated": "traduït",
@@ -1194,7 +1200,7 @@
"group-details": "Detalls del grup",
"group-details-description": "Abans de crear un compte heu de crear un grup. Al grup només hi serà vostè, però després podeu convidar d'altres. Els membres d'un grup poden compartir menús, llistes de la compra, receptes i molt més!",
"use-seed-data": "Afegiu dades predeterminades",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"use-seed-data-description": "Mealie disposa d'una col·lecció d'aliments, unitats i etiquetes que es poden utilitzar per omplir el vostre grup amb dades útils per organitzar les vostres receptes. Aquests es tradueixen a l'idioma que heu seleccionat actualment. Sempre podeu afegir o modificar aquestes dades més endavant.",
"account-details": "Detalls del compte"
},
"validation": {
@@ -1422,5 +1428,13 @@
"is-like": "és com",
"is-not-like": "no és com"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Snídaně",
"lunch": "Oběd",
"dinner": "Večeře",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Libovolné",
"day-any": "Libovolný",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Nahrát recept",
"upload-individual-zip-file": "Nahrát individuální .zip soubor exportovaný z jiné instance Mealie.",
"url-form-hint": "Zkopírujte a vložte odkaz z vaší oblíbené stránky s recepty",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Zobrazit scrapovaná data",
"trim-whitespace-description": "Oříznout počáteční a koncové mezery stejně jako prázdné řádky",
"trim-prefix-description": "Oříznout první znak z každé řádky",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Vyzkoušejte hromadný import",
"scrape-recipe-have-raw-html-or-json-data": "Máte surová data HTML nebo JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Můžete importovat přímo ze surových dat",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importovat původní klíčová slova jako štítky",
"stay-in-edit-mode": "Zůstat v režimu úprav",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "je jako",
"is-not-like": "není jako"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Morgenmad",
"lunch": "Frokost",
"dinner": "Aftensmad",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Alle",
"day-any": "Alle",
"editor": "Redigeringsværktøj",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Upload en opskrift",
"upload-individual-zip-file": "Upload en individuel .zip-fil, eksporteret fra en anden Mealie-instans.",
"url-form-hint": "Kopiér og indsæt et link fra din foretrukne opskrifts hjemmeside",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Vis dataudtræk",
"trim-whitespace-description": "Fjern indledende og efterfølgende mellemrum samt blanke linjer",
"trim-prefix-description": "Beskær første tegn fra hver linje",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Prøv masseimport",
"scrape-recipe-have-raw-html-or-json-data": "Har rå HTML- eller JSON-data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kan importere direkte fra rå data",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importér originale nøgleord som mærker",
"stay-in-edit-mode": "Bliv i redigeringstilstand",
"parse-recipe-ingredients-after-import": "Fortolk opskrift ingredienser efter import",
@@ -1422,5 +1428,13 @@
"is-like": "er ligesom",
"is-not-like": "er ikke som"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -51,7 +51,7 @@
"category": "Kategorie"
},
"events": {
"apprise-url": "Apprise-URL",
"apprise-url": "Apprise URL",
"database": "Datenbank",
"delete-event": "Ereignis löschen",
"event-delete-confirmation": "Bist du dir sicher, dass du dieses Ereignis löschen möchtest?",
@@ -191,7 +191,7 @@
"menu": "Menü",
"a-name-is-required": "Ein Name wird benötigt",
"delete-with-name": "{name} löschen",
"confirm-delete-generic-with-name": "Bist du dir sicher, dass du dies löschen möchtest?",
"confirm-delete-generic-with-name": "Bist du dir sicher, dass du {name} löschen möchtest?",
"confirm-delete-own-admin-account": "Bitte beachte, dass du versuchst, dein eigenes Administrator-Konto zu löschen! Diese Aktion kann nicht rückgängig gemacht werden und wird dein Konto dauerhaft löschen?",
"organizer": "Organisator",
"transfer": "Übertragen",
@@ -342,6 +342,9 @@
"breakfast": "Frühstück",
"lunch": "Mittagessen",
"dinner": "Abendessen",
"snack": "Zwischenmahlzeit ",
"drink": "Getränk",
"dessert": "Nachspeise",
"type-any": "Alle",
"day-any": "Alle",
"editor": "Bearbeiten",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Rezept hochladen",
"upload-individual-zip-file": "Lade eine individuelle .zip-Datei hoch, die von einer anderen Mealie-Instanz exportiert wird.",
"url-form-hint": "Kopiere einen Link von deiner Lieblingsrezept-Website und füge ihn ein",
"copy-and-paste-the-source-url-of-your-data-optional": "Kopieren und fügen Sie die Quell-URL Ihrer Daten ein (optional)",
"view-scraped-data": "Gesammelte Daten anzeigen",
"trim-whitespace-description": "Leerzeichen am Anfang und Ende sowie leere Zeilen entfernen",
"trim-prefix-description": "Erste Zeichen aus jeder Zeile entfernen",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Probiere den Massenimporter aus",
"scrape-recipe-have-raw-html-or-json-data": "Hast du Roh-HTML oder JSON Daten?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kannst direkt von Rohdaten importieren",
"scrape-recipe-website-being-blocked": "Die Website wird blockiert?",
"scrape-recipe-try-importing-raw-html-instead": "Versuchen Sie stattdessen das reine HTML zu importieren.",
"import-original-keywords-as-tags": "Importiere ursprüngliche Stichwörter als Schlagwörter",
"stay-in-edit-mode": "Im Bearbeitungsmodus bleiben",
"parse-recipe-ingredients-after-import": "Zutaten nach dem Import parsen",
@@ -1422,5 +1428,13 @@
"is-like": "ist wie",
"is-not-like": "ist nicht wie"
}
},
"validators": {
"required": "Dieses Feld ist erforderlich",
"invalid-email": "E-Mail muss gültig sein",
"invalid-url": "Muss eine gültige URL sein",
"no-whitespace": "Kein Leerzeichen erlaubt",
"min-length": "Muss mindestens {min} Zeichen haben",
"max-length": "Darf mindestens {max} Zeichen haben"
}
}

View File

@@ -334,7 +334,7 @@
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Μόνο συνταγές με αυτές τις κατηγορίες θα χρησιμοποιηθούν στα προγράμματα γευμάτων",
"planner": "Προγραμματισμός",
"quick-week": "Γρήγορη προβολή",
"side": "Πλευρά",
"side": "Συνοδευτικό",
"sides": "Πλευρές",
"start-date": "Ημερομηνία έναρξης",
"rule-day": "Ημέρα/ες Κανόνα",
@@ -342,6 +342,9 @@
"breakfast": "Πρωινό",
"lunch": "Μεσημεριανό",
"dinner": "Βραδινό",
"snack": "Σνακ",
"drink": "Ποτό",
"dessert": "Επιδόρπιο",
"type-any": "Οτιδήποτε",
"day-any": "Οποιαδήποτε",
"editor": "Επεξεργαστής κειμένου",
@@ -359,7 +362,7 @@
"for-type-meal-types": "για γεύματα τύπου {0}",
"meal-plan-rules": "Κανόνες Προγράμματος Γευμάτων",
"new-rule": "Νέος κανόνας",
"meal-plan-rules-description": "Μπορείτε να δημιουργήσετε κανόνες για την αυτόματη επιλογή συνταγών για τα προγράμματα γευμάτων. Αυτοί οι κανόνες χρησιμοποιούνται από το διακομιστή για τον προσδιορισμό της τυχαίας δεξαμενής συνταγών από τις οποίες μπορείτε να επιλέξετε κατά τη δημιουργία προγραμμάτων γευμάτων. Σημειώστε ότι αν οι κανόνες έχουν τους ίδιους περιορισμούς ημέρας/τύπου τότε τα φίλτρα κανόνων θα συγχωνευθούν. Στην πράξη, είναι περιττή η δημιουργία διπλότυπων κανόνων, είναι όμως εφικτή.",
"meal-plan-rules-description": "Μπορείτε να δημιουργήσετε κανόνες για την αυτόματη επιλογή συνταγών για τα προγράμματα γευμάτων. Αυτοί οι κανόνες χρησιμοποιούνται από το διακομιστή για τον προσδιορισμό της δεξαμενής τυχαίας επιλογής συνταγής, κατά τη δημιουργία προγραμμάτων γευμάτων. Σημειώστε ότι αν οι κανόνες έχουν τους ίδιους περιορισμούς ημέρας/τύπου τότε τα φίλτρα κανόνων θα συγχωνευθούν. Στην πράξη, είναι περιττή η δημιουργία διπλότυπων κανόνων, είναι όμως εφικτή.",
"new-rule-description": "Κατά τη δημιουργία ενός νέου κανόνα για ένα σχέδιο γεύματος, μπορείτε να περιορίσετε τον κανόνα ώστε να ισχύει για μια συγκεκριμένη ημέρα της εβδομάδας ή/και ένα συγκεκριμένο τύπο γεύματος. Για να εφαρμόσετε έναν κανόνα σε όλες τις ημέρες ή σε όλους τους τύπους γεύματος μπορείτε να ορίσετε τον κανόνα σε \"Ολα\" που θα τον εφαρμόσει σε όλες τις πιθανές τιμές για την ημέρα ή/και τον τύπο γεύματος.",
"recipe-rules": "Κανόνες Συνταγής",
"applies-to-all-days": "Εφαρμόζεται για όλες τις ημέρες",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Ανεβάστε μια συνταγή",
"upload-individual-zip-file": "Ανεβάστε ένα μεμονωμένο αρχείο .zip που εξάγεται από μια άλλη περίπτωση Mealie.",
"url-form-hint": "Αντιγράψτε και επικολλήστε έναν σύνδεσμο από την αγαπημένη σας ιστοσελίδα συνταγών",
"copy-and-paste-the-source-url-of-your-data-optional": "Αντιγράψτε και επικολλήστε το πηγαίο URL των δεδομένων σας (προαιρετικό)",
"view-scraped-data": "Προβολή Παραγόμενων Δεδομένων",
"trim-whitespace-description": "Περικοπή κενών στην αρχή και το τέλος καθώς και των κενών γραμμών",
"trim-prefix-description": "Περικοπή πρώτου χαρακτήρα από κάθε γραμμή",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Δοκιμάστε τον μαζικό εισαγωγέα συνταγών μας",
"scrape-recipe-have-raw-html-or-json-data": "Εχουν ακατέργαστα δεδομένα HTML ή JSON;",
"scrape-recipe-you-can-import-from-raw-data-directly": "Μπορείτε να κάνετε εισαγωγή απευθείας από ακατέργαστα δεδομένα",
"scrape-recipe-website-being-blocked": "Η ιστοσελίδα μπλοκάρεται;",
"scrape-recipe-try-importing-raw-html-instead": "Δοκιμάστε να εισάγετε τον ακατέργαστο κώδικα HTML.",
"import-original-keywords-as-tags": "Εισαγωγή αρχικών λέξεων-κλειδιών ως ετικέτες",
"stay-in-edit-mode": "Παραμονή σε λειτουργία επεξεργασίας",
"parse-recipe-ingredients-after-import": "Ανάλυση συστατικών συνταγής μετά την εισαγωγή",
@@ -873,9 +879,9 @@
"secure-site": "Ασφαλής Ιστοσελίδα",
"secure-site-error-text": "Παροχή μέσω localhost ή ασφάλεια με https. Το πρόχειρο και τα πρόσθετα API προγράμματος περιήγησης μπορεί να μην λειτουργούν.",
"secure-site-success-text": "Ο ιστότοπος έχει πρόσβαση από localhost ή https",
"server-side-base-url": "Βασική Διεύθυνση URL Πλευράς Διακομιστή",
"server-side-base-url": "Βασική διεύθυνση URL πλευράς διακομιστή",
"server-side-base-url-error-text": "Το `BASE_URL` εξακολουθεί να είναι η προεπιλεγμένη τιμή στο διακομιστή API. Αυτό θα προκαλέσει προβλήματα με τις συνδέσεις ειδοποιήσεων που δημιουργούνται στο διακομιστή για email, κλπ.",
"server-side-base-url-success-text": "Το URL Πλευράς Διακομιστή δεν ταιριάζει με την προεπιλογή",
"server-side-base-url-success-text": "Η διεύθυνση URL πλευράς διακομιστή δεν ταιριάζει με την προεπιλεγμένη",
"ldap-ready": "Ετοιμο για LDAP",
"ldap-ready-error-text": "Δεν έχουν ρυθμιστεί όλες οι τιμές LDAP. Αυτό μπορεί να αγνοηθεί αν δεν χρησιμοποιείτε έλεγχο ταυτότητας LDAP.",
"ldap-ready-success-text": "Ολες οι απαιτούμενες μεταβλητές LDAP έχουν οριστεί.",
@@ -1422,5 +1428,13 @@
"is-like": "είναι όμοιο με",
"is-not-like": "δεν είναι όμοιο με"
}
},
"validators": {
"required": "Αυτό το πεδίο είναι υποχρεωτικό",
"invalid-email": "Το e-mail πρέπει να είναι έγκυρο",
"invalid-url": "Πρέπει να είναι μια έγκυρη διεύθυνση URL",
"no-whitespace": "Δεν επιτρέπονται κενοί χαρακτήρες",
"min-length": "Πρέπει να αποτελείται από τουλάχιστον {min} χαρακτήρες",
"max-length": "Πρέπει να αποτελείται το πολύ από {max} χαρακτήρες"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Breakfast",
"lunch": "Lunch",
"dinner": "Dinner",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Any",
"day-any": "Any",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Upload a Recipe",
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.",
"url-form-hint": "Copy and paste a link from your favourite recipe website",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "View Scraped Data",
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
"trim-prefix-description": "Trim first character from each line",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Import original keywords as tags",
"stay-in-edit-mode": "Stay in Edit mode",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Breakfast",
"lunch": "Lunch",
"dinner": "Dinner",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Any",
"day-any": "Any",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Upload a Recipe",
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.",
"url-form-hint": "Copy and paste a link from your favorite recipe website",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "View Scraped Data",
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
"trim-prefix-description": "Trim first character from each line",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Import original keywords as tags",
"stay-in-edit-mode": "Stay in Edit mode",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Desayuno",
"lunch": "Comida principal",
"dinner": "Cena",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Cualquiera",
"day-any": "Cualquier",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Subir una receta",
"upload-individual-zip-file": "Sube un archivo .zip individual exportado desde otra instancia de Mealie.",
"url-form-hint": "Copia y pega un enlace desde tu página web favorita",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Ver información recuperada",
"trim-whitespace-description": "Eliminar espacios en blanco iniciales y finales así como líneas en blanco",
"trim-prefix-description": "Eliminar el primer carácter de cada línea",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Prueba el importador masivo",
"scrape-recipe-have-raw-html-or-json-data": "¿Tiene datos HTML o JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Puede importar directamente desde datos brutos",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importar palabras clave originales como etiquetas",
"stay-in-edit-mode": "Permanecer en modo edición",
"parse-recipe-ingredients-after-import": "Analizar los ingredientes de la receta después de importarla",
@@ -1422,5 +1428,13 @@
"is-like": "es como",
"is-not-like": "no es como"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Hommikusöök",
"lunch": "Lõuna",
"dinner": "Õhtusöök",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Kõik",
"day-any": "Kõik",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Retsepti üleslaadimne",
"upload-individual-zip-file": "Lae üles üksik .zip fail, mis eksporditi teisest Mealie ekspemplarist.",
"url-form-hint": "Kopeeri ja kleebi link oma lemmikust retsepti leheküljest",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Kuva omandatud andmed",
"trim-whitespace-description": "Eemalda alguses ning lõpus olevad tühikud ning tühjad read",
"trim-prefix-description": "Eemalda esimene tähemärk igast reast",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Proovi hulgiimportimist.",
"scrape-recipe-have-raw-html-or-json-data": "Sul on töötlemata HTMLi või JSONi andmed?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Sa võid otse importida töötlemata andmetest",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Impordi originaal võtmesõnad siltidena",
"stay-in-edit-mode": "Püsige redigeerimisrežiimis",
"parse-recipe-ingredients-after-import": "Tuvasta retsepti koostisosad pärast importimist",
@@ -1422,5 +1428,13 @@
"is-like": "on nagu",
"is-not-like": "ei ole nagu"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Aamiainen",
"lunch": "Lounas",
"dinner": "Päivällinen",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Mikä tahansa",
"day-any": "Koska tahansa",
"editor": "Editori",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Lataa resepti",
"upload-individual-zip-file": "Tuo yksittäinen pakattu kansio toisesta Mealie instanssista.",
"url-form-hint": "Liitä linkki lempireseptiverkkosivultasi",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Näytä hankittu data",
"trim-whitespace-description": "Leikkaa alussa ja lopussa olevat välilyönnit sekä tyhjät rivit",
"trim-prefix-description": "Poista joka rivin ensimmäinen merkki",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Kokeile massasiirtotyökalua",
"scrape-recipe-have-raw-html-or-json-data": "Onko sinulla raakaa HTML- tai JSON-dataa?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Voit tuoda raakadatan suoraan",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Tuo alkuperäiset avainsanat tunnisteiksi",
"stay-in-edit-mode": "Pysy muokkaustilassa",
"parse-recipe-ingredients-after-import": "Jäsennä reseptin ainesosat tuonnin jälkeen",
@@ -1422,5 +1428,13 @@
"is-like": "on kuin",
"is-not-like": "ei ole kuin"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Petit-déjeuner",
"lunch": "Déjeuner",
"dinner": "Souper",
"snack": "Goûter",
"drink": "Boissons",
"dessert": "Dessert",
"type-any": "Tous",
"day-any": "Tous",
"editor": "Éditeur",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Télécharger une recette",
"upload-individual-zip-file": "Chargez un fichier .zip exporté depuis une autre instance Mealie.",
"url-form-hint": "Copiez et collez un lien depuis votre site de recettes favori",
"copy-and-paste-the-source-url-of-your-data-optional": "Copiez et collez l'URL source de vos données (facultatif)",
"view-scraped-data": "Voir les données récupérées",
"trim-whitespace-description": "Ajuster les espaces de début et de fin ainsi que les lignes vides",
"trim-prefix-description": "Couper le premier caractère de chaque ligne",
@@ -565,7 +569,7 @@
"choose-unit": "Choisissez une unité",
"press-enter-to-create": "Clique sur Entrer pour créer",
"choose-food": "Choisissez un aliment",
"choose-recipe": "Choose Recipe",
"choose-recipe": "Choisissez la recette",
"notes": "Notes",
"toggle-section": "Activer/Désactiver la section",
"see-original-text": "Afficher le texte original",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Essayez limportateur de masse",
"scrape-recipe-have-raw-html-or-json-data": "Vous avez des données brutes en HTML ou JSON ?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Vous pouvez directement importer des données brutes",
"scrape-recipe-website-being-blocked": "Le site web est bloqué ?",
"scrape-recipe-try-importing-raw-html-instead": "Essayez plutôt d'importer le code HTML brut.",
"import-original-keywords-as-tags": "Importer les mots-clés d'origine en tant que tags",
"stay-in-edit-mode": "Rester en mode édition",
"parse-recipe-ingredients-after-import": "Analyser les ingrédients de la recette après l'import",
@@ -736,7 +742,7 @@
"advanced": "Avancé",
"auto-search": "Recherche automatique",
"no-results": "Aucun résultat trouvé",
"type-to-search": "Type to search..."
"type-to-search": "Tapez pour chercher..."
},
"settings": {
"add-a-new-theme": "Ajouter un nouveau thème",
@@ -1422,5 +1428,13 @@
"is-like": "est comme",
"is-not-like": "n'est pas similaire à"
}
},
"validators": {
"required": "Ce champ est obligatoire",
"invalid-email": "Le-mail doit être valide",
"invalid-url": "Doit être une URL valide",
"no-whitespace": "Aucun espace n'est autorisé",
"min-length": "Doit contenir au moins {min} caractères",
"max-length": "Doit contenir au maximum {max} caractères"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Petit déjeuner",
"lunch": "Dîner",
"dinner": "Souper",
"snack": "Goûter",
"drink": "Boissons",
"dessert": "Dessert",
"type-any": "Tous",
"day-any": "Tous",
"editor": "Éditeur",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Télécharger une recette",
"upload-individual-zip-file": "Téléverser un fichier .zip exporté depuis une autre instance Mealie.",
"url-form-hint": "Copiez et collez un lien depuis votre site de recettes favori",
"copy-and-paste-the-source-url-of-your-data-optional": "Copiez et collez l'URL source de vos données (facultatif)",
"view-scraped-data": "Voir les données récupérées",
"trim-whitespace-description": "Ajuster les espaces de début et de fin ainsi que les lignes vides",
"trim-prefix-description": "Couper le premier caractère de chaque ligne",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Essayez limportateur de masse",
"scrape-recipe-have-raw-html-or-json-data": "Vous avez des données brutes en HTML ou JSON ?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Vous pouvez directement importer des données brutes",
"scrape-recipe-website-being-blocked": "Le site web est bloqué ?",
"scrape-recipe-try-importing-raw-html-instead": "Essayez plutôt d'importer le code HTML brut.",
"import-original-keywords-as-tags": "Importer les mots-clés d'origine en tant que tags",
"stay-in-edit-mode": "Rester en mode édition",
"parse-recipe-ingredients-after-import": "Analyser les ingrédients de la recette après l'import",
@@ -1422,5 +1428,13 @@
"is-like": "est similaire à",
"is-not-like": "n'est pas similaire à"
}
},
"validators": {
"required": "Ce champ est obligatoire",
"invalid-email": "Le-mail doit être valide",
"invalid-url": "Doit être une URL valide",
"no-whitespace": "Aucun espace n'est autorisé",
"min-length": "Doit contenir au moins {min} caractères",
"max-length": "Doit contenir au maximum {max} caractères"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Petit-déjeuner",
"lunch": "Déjeuner",
"dinner": "Dîner",
"snack": "Goûter",
"drink": "Boissons",
"dessert": "Dessert",
"type-any": "Tous",
"day-any": "Tous",
"editor": "Éditeur",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Télécharger une recette",
"upload-individual-zip-file": "Chargez un fichier .zip exporté depuis une autre instance Mealie.",
"url-form-hint": "Copiez et collez un lien depuis votre site de recettes favori",
"copy-and-paste-the-source-url-of-your-data-optional": "Copiez et collez l'URL source de vos données (facultatif)",
"view-scraped-data": "Voir les données récupérées",
"trim-whitespace-description": "Ajuster les espaces de début et de fin ainsi que les lignes vides",
"trim-prefix-description": "Couper le premier caractère de chaque ligne",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Essayez limportateur de masse",
"scrape-recipe-have-raw-html-or-json-data": "Vous avez des données brutes en HTML ou JSON ?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Vous pouvez directement importer des données brutes",
"scrape-recipe-website-being-blocked": "Le site web est bloqué ?",
"scrape-recipe-try-importing-raw-html-instead": "Essayez plutôt d'importer le code HTML brut.",
"import-original-keywords-as-tags": "Importer les mots-clés d'origine en tant que tags",
"stay-in-edit-mode": "Rester en mode édition",
"parse-recipe-ingredients-after-import": "Analyser les ingrédients de la recette après l'import",
@@ -1422,5 +1428,13 @@
"is-like": "est comme",
"is-not-like": "n'est pas similaire à"
}
},
"validators": {
"required": "Ce champ est obligatoire",
"invalid-email": "Le-mail doit être valide",
"invalid-url": "Doit être une URL valide",
"no-whitespace": "Aucun espace n'est autorisé",
"min-length": "Doit contenir au moins {min} caractères",
"max-length": "Doit contenir au maximum {max} caractères"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Almorzo",
"lunch": "Xantar",
"dinner": "Cea",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Calquera",
"day-any": "Calquera",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Cargar unha Receita",
"upload-individual-zip-file": "Cargar un ficheiro .zip individual, exportado de outra instancia do Mealie.",
"url-form-hint": "Copie e pegue un link do seu site de receitas favorito",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Ver datos recollidos",
"trim-whitespace-description": "Eliminar os espazos en branco no início e no fin, asi como as liñas en branco",
"trim-prefix-description": "Eliminar o primeiro caracter de cada liña",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Prove o importador en masa",
"scrape-recipe-have-raw-html-or-json-data": "Ten datos HTML ou JSON en bruto?",
"scrape-recipe-you-can-import-from-raw-data-directly": "É posível importar diretamente a partir de datos en bruto",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importar palavras-chave orixinais como etiquetas",
"stay-in-edit-mode": "Permanecer no modo de edición",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "é como",
"is-not-like": "non é como"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "ארוחת בוקר",
"lunch": "ארוחת צהריים",
"dinner": "ארוחת ערב",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "הכל",
"day-any": "הכל",
"editor": "עורך",
@@ -442,6 +445,7 @@
"upload-a-recipe": "העלאת מתכון",
"upload-individual-zip-file": "העלאת קובץ זיפ שיוצא ממילי אחר.",
"url-form-hint": "העתק והדבק קישור מאתר המתכונים המועדף עליך",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "צפייה במידע שנאסף",
"trim-whitespace-description": "הסר רווחים מתחילת / סוף שורה ושורות ריקות",
"trim-prefix-description": "חתוך תו ראשון מכל שורה",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "נסה את יכולת קריאת רשימה",
"scrape-recipe-have-raw-html-or-json-data": "יש לך מידע גולמי ב-HTML או JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "ניתן לייבא ישירות ממידע גולמי",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "ייבוא שמות מפתח מקוריות כתגיות",
"stay-in-edit-mode": "השאר במצב עריכה",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "דומה ל-",
"is-not-like": "לא דומה לא-"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Doručak",
"lunch": "Ručak",
"dinner": "Večera",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Bilo koji",
"day-any": "Bilo koji",
"editor": "Urednik",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Učitaj Recept",
"upload-individual-zip-file": "Prenesite pojedinačnu .zip datoteku koja je izvezena iz druge instance Mealie aplikacije.",
"url-form-hint": "Kopirajte i zalijepite poveznicu s vaše omiljene web stranice za recepte",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Prikaz Prikupljenih Podataka",
"trim-whitespace-description": "Ukloni vodeće i slijedeće praznine, kao i prazne linije",
"trim-prefix-description": "Ukloni prvi znak sa svake linije",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Isprobajte masovni uvoz",
"scrape-recipe-have-raw-html-or-json-data": "Imate neobrađene HTML ili JSON podatke?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Možete uvesti iz neobrađenih podataka izravno",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Uvezi originalne ključne riječi kao oznake",
"stay-in-edit-mode": "Ostanite u načinu uređivanja",
"parse-recipe-ingredients-after-import": "Parsiranje sastojaka recepta nakon uvoza",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Reggeli",
"lunch": "Ebéd",
"dinner": "Vacsora",
"snack": "Rágcsa",
"drink": "Ital",
"dessert": "Desszert",
"type-any": "Bármely",
"day-any": "Bármely",
"editor": "Szerkesztő",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Recept feltöltése",
"upload-individual-zip-file": "Tölts fel egy .zíp archívumot, ami egy másik Mealie példányból lett exportálva.",
"url-form-hint": "Másold be a linket a kedvenc recept weboldaladról",
"copy-and-paste-the-source-url-of-your-data-optional": "Másolja és illessze be az adatok forrás URL-jét (opcionális)",
"view-scraped-data": "Letöltött adat megtekintése",
"trim-whitespace-description": "Vágja le a kezdő és a záró fehérjeleket, valamint az üres sorokat",
"trim-prefix-description": "Minden sor első karakterének levágása",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Próbálja ki a tömeges importálót",
"scrape-recipe-have-raw-html-or-json-data": "Nyers HTML vagy JSON adatai vannak?",
"scrape-recipe-you-can-import-from-raw-data-directly": "A nyers adatokból közvetlenül is importálhat",
"scrape-recipe-website-being-blocked": "A weboldal blokkolva van?",
"scrape-recipe-try-importing-raw-html-instead": "Próbálja meg inkább a nyers HTML-t importálni.",
"import-original-keywords-as-tags": "Eredeti kulcsszavak importálása címkeként",
"stay-in-edit-mode": "Maradjon Szerkesztés módban",
"parse-recipe-ingredients-after-import": "Recept összetevőinek elemzése importálás után",
@@ -1422,5 +1428,13 @@
"is-like": "hasonló",
"is-not-like": "nem hasonló"
}
},
"validators": {
"required": "Ez kötelező mező",
"invalid-email": "E-mail-nek érvényesnek kell lennie",
"invalid-url": "Érvényes URL-nek kell lennie",
"no-whitespace": "Szóközt nem tartalmazhat",
"min-length": "Legalább {min} karakter legyen",
"max-length": "Legfeljebb {max} karakter legyen"
}
}

View File

@@ -69,7 +69,7 @@
"new-notification": "Ný tilkynning",
"event-notifiers": "Viðburðar tilkynningar",
"apprise-url-skipped-if-blank": "Apprise URL (sleppt ef tómt)",
"apprise-url-is-left-intentionally-blank": "Þar sem \"Apprise\" slóðir innihalda yfirleitt viðkvæmar upplýsingar, er þessum reit viljandi skilið eftir auðum við breytingar. Ef þú vilt uppfæra slóðina skaltu slá inn þá nýju hér, annars skaltu skilja reitinn eftir auðan til að halda núverandi slóð.",
"apprise-url-is-left-intentionally-blank": "Þar sem \"Apprise\" slóðir innihalda yfirleitt viðkvæmar upplýsingar, er þessi reitur viljandi skilinn eftir auður. Ef þú vilt uppfæra slóðina skaltu slá inn hana inn hér, annars skaltu skilja reitinn eftir auðan til að halda núverandi slóð.",
"enable-notifier": "Virkja tilkynningar",
"what-events": "Hvaða viðburði ætti þessi tilkynnir að vera áskrifandi að?",
"user-events": "Notenda viðburðir",
@@ -342,6 +342,9 @@
"breakfast": "Morgunverður",
"lunch": "Hádegisverður",
"dinner": "Kvöldverður",
"snack": "Snarl",
"drink": "Drykkur",
"dessert": "Eftirréttur",
"type-any": "Allir",
"day-any": "Alla",
"editor": "Ritill",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Hlaða inn uppskrift",
"upload-individual-zip-file": "Hlaða inn .zip skrá sem er flutt úr annarri Mealie uppsetningu.",
"url-form-hint": "Afritaðu og límdu tengil frá uppáhalds uppskriftar síðunni þinni",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Skoða unnin gögn",
"trim-whitespace-description": "Fjarlægja bil fremst og aftast í texta sem og auðum línum",
"trim-prefix-description": "Eyða fyrsta staf úr hverri línu",
@@ -616,8 +620,8 @@
"create-recipe-description": "Stofna nýja uppskrift frá grunni.",
"create-recipes": "Stofna uppskriftir",
"import-with-zip": "Hlaða inn með .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"create-recipe-from-an-image": "Stofna uppskrift út frá mynd",
"create-recipe-from-an-image-description": "Stofna uppskrift með því hlaða inn myndum af uppskriftartextanum. Mealie mun reyna að vinna texta úr myndunum með gervigreind og stofna nýja uppskrift út frá textanum.",
"crop-and-rotate-the-image": "Sníða og snúa mynd svo bara textinn sé sýnilegur og að myndin snúi rétt.",
"create-from-images": "Stofna uppskrift frá mynd",
"should-translate-description": "Þýða uppskrift á mitt tungumál",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Prófaðu að setja inn margar uppskriftir í einu",
"scrape-recipe-have-raw-html-or-json-data": "Ertu með hrá HTML eða JSON gögn?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Það er hægt að hlaða inn hráum gögnum beint",
"scrape-recipe-website-being-blocked": "Er vefsíðan lokuð?",
"scrape-recipe-try-importing-raw-html-instead": "Reyndu að flytja inn HTML kóðann í staðinn.",
"import-original-keywords-as-tags": "Nota upprunanleg merki",
"stay-in-edit-mode": "Vera í breytingarham",
"parse-recipe-ingredients-after-import": "Greina innhald uppskriftar eftir að búið er að hlaða inn uppskrift",
@@ -655,7 +661,7 @@
"recipe-debugger": "Yfirfara uppskrift",
"recipe-debugger-description": "Náðu í slóðina af uppskriftinni sem þú villt yfirfara og límdu hana hér. Síðan með uppskriftinni verður greind með greiningarverkfærinu og þú munnt sjá niðurstöðuna. Ef þú sérð að engin gögn skila sér þá er slóðin sem þú ert að greina ekki studd af Mealie eða greiningarverkfærinu.",
"use-openai": "Nota OpenAI",
"recipe-debugger-use-openai-description": "Nota OpenAI til að greina í staðinn fyrir að treysta á greiningar verkfærið. Þegar er fengin af slóð þá gerist þetta sjálfkrafa ef almenn greining mistekst, en þú getur prófað þ hér.",
"recipe-debugger-use-openai-description": "Nota OpenAI til að greina í staðinn fyrir að treysta á greiningar verkfærið. Ef greiningar verkfærinu mistekst að greina uppskrift af vefslóð þá gerist það sjálfvirkt að OpenAI greinir uppskriftina en þú getur prófað þetta sjálfur hér.",
"debug": "Villuleit",
"tree-view": "Tré sýn",
"recipe-servings": "Fjöldi skammta",
@@ -1217,7 +1223,7 @@
"recipe-link-copied-message": "Tengill fyrir uppskriftina afritaður í klippispjald"
},
"banner-experimental": {
"title": "Experimental Feature",
"title": "Tilraunareiginleiki",
"description": "This page contains experimental or still-baking features. Please excuse the mess.",
"issue-link-text": "Track our progress here"
},
@@ -1333,8 +1339,8 @@
"household-delete-note": "Heimili með notendum er ekki hægt að eyða"
},
"profile": {
"welcome-user": "👋 Velkomin/Velkominn/Velkomið, {0}!",
"description": "Umsjá með prófíl, uppskriftum og hóp stillingum.",
"welcome-user": "👋 Halló, {0}",
"description": "Umsjá með prófíl, uppskriftum og hópstillingum.",
"invite-link": "Boð tengill",
"get-invite-link": "Fá boð tengil",
"get-public-link": "Fá almennan tengil",
@@ -1388,7 +1394,7 @@
"hide-cookbooks-from-other-households": "Fela uppskriftarbækur frá öðrum heimilum",
"hide-cookbooks-from-other-households-description": "Þegar þetta er valið þá munu aðeins uppskriftarbækur úr þínu heimili birtast á hliðarstikunni",
"public-cookbook": "Opin uppskriftarbók",
"public-cookbook-description": "Opnar uppskriftarbækur er hægt að deila með þeim sem ekki eru notendur í mealie kerfinu og eru þær sýnilegar á hópsíðunni þinni.",
"public-cookbook-description": "Opnar uppskriftabækur er hægt að deila með þeim sem ekki eru notendur í Mealie kerfinu og eru þær sýnilegar á hópsíðunni þinni.",
"filter-options": "Filter Options",
"filter-options-description": "When require all is selected the cookbook will only include recipes that have all of the items selected. This applies to each subset of selectors and not a cross section of the selected items.",
"require-all-categories": "Þarf alla flokka",
@@ -1396,7 +1402,7 @@
"require-all-tools": "Require All Tools",
"cookbook-name": "Nafn á uppskriftabók",
"cookbook-with-name": "Uppskriftabók {0}",
"household-cookbook-name": "{0} Uppskriftabók {1}",
"household-cookbook-name": "Uppskriftabók {0} {1}",
"create-a-cookbook": "Stofna uppskrifabók",
"cookbook": "Uppskriftabók"
},
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "Þessi reitur er nauðsynlegur",
"invalid-email": "Verður að vera gilt netfang",
"invalid-url": "Verður að vera gild vefslóð",
"no-whitespace": "Engin bil leyfð",
"min-length": "Verður að vera að lágmarki {min} stafir",
"max-length": "Má vera að hámarki {max} stafir"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Colazione",
"lunch": "Pranzo",
"dinner": "Cena",
"snack": "Snack",
"drink": "Bevanda",
"dessert": "Dessert",
"type-any": "Qualsiasi",
"day-any": "Qualsiasi",
"editor": "Modifica",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Carica una Ricetta",
"upload-individual-zip-file": "Carica un singolo file .zip esportato da un'altra istanza di Mealie.",
"url-form-hint": "Copia e incolla un link dal tuo sito di ricette preferito",
"copy-and-paste-the-source-url-of-your-data-optional": "Copia e incolla lo URL della fonte dei dati (opzionale)",
"view-scraped-data": "Visualizza Dati Ottenuti dallo Scraping",
"trim-whitespace-description": "Tagliare lo spazio bianco iniziale e finale così come le linee vuote",
"trim-prefix-description": "Taglia il primo carattere da ogni riga",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Prova l'importatore massivo",
"scrape-recipe-have-raw-html-or-json-data": "Hai dei dati grezzi HTML o JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "È possibile importare direttamente dai dati grezzi",
"scrape-recipe-website-being-blocked": "Il sito viene bloccato?",
"scrape-recipe-try-importing-raw-html-instead": "Prova a importare l'HTML puro.",
"import-original-keywords-as-tags": "Importa parole chiave originali come tag",
"stay-in-edit-mode": "Rimani in modalità Modifica",
"parse-recipe-ingredients-after-import": "Analizza gli ingredienti della ricetta dopo l'importazione",
@@ -1422,5 +1428,13 @@
"is-like": "è simile",
"is-not-like": "non è come"
}
},
"validators": {
"required": "Questo Campo è Obbligatorio",
"invalid-email": "L'Email Deve Essere Valida",
"invalid-url": "Lo URL Deve Essere Valido",
"no-whitespace": "Gli Spazi Non Sono Ammessi",
"min-length": "Deve Essere Almeno {min} Caratteri",
"max-length": "Deve Essere Al Massimo {max} Caratteri"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "朝食",
"lunch": "昼食",
"dinner": "夕食",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "すべて",
"day-any": "すべて",
"editor": "エディタ",
@@ -442,6 +445,7 @@
"upload-a-recipe": "レシピのアップロード",
"upload-individual-zip-file": "別のMealieインスタンスからエクスポートされた個別の.zipファイルをアップロードします。",
"url-form-hint": "お気に入りのレシピサイトからリンクをコピーして貼り付け",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "スクライピングされたデータの表示",
"trim-whitespace-description": "先頭と末尾の空白、空白行をトリミングします。",
"trim-prefix-description": "各行の最初の文字をトリミングする",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "一括インポートを試す",
"scrape-recipe-have-raw-html-or-json-data": "生の HTML または JSON データをお持ちですか?",
"scrape-recipe-you-can-import-from-raw-data-directly": "生データから直接インポートできます",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "元のキーワードをタグとしてインポート",
"stay-in-edit-mode": "編集モードを維持",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -692,11 +698,11 @@
"reset-servings-count": "サービング数をリセット",
"not-linked-ingredients": "追加の材料",
"upload-another-image": "Upload another image",
"upload-images": "Upload images",
"upload-images": "画像のアップロード",
"upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image",
"include-linked-recipes": "Include Linked Recipes",
"cover-image": "カバー画像",
"include-linked-recipes": "リンクされたレシピを含める",
"include-linked-recipe-ingredients": "Include Linked Recipe Ingredients",
"toggle-recipe": "Toggle Recipe"
},
@@ -1422,5 +1428,13 @@
"is-like": "次のようなものです",
"is-not-like": "というわけではありません"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "조식",
"lunch": "점심",
"dinner": "저녁 식사",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "모두",
"day-any": "모두",
"editor": "편집기",
@@ -442,6 +445,7 @@
"upload-a-recipe": "레시피 업로드",
"upload-individual-zip-file": "다른 Mealie 인스턴스에서 내보낸 개별 .zip 파일을 업로드합니다.",
"url-form-hint": "좋아하는 레시피 웹사이트에서 링크를 복사하여 붙여넣으세요",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "스크랩된 데이터 보기",
"trim-whitespace-description": "앞뒤 공백과 빈 줄을 잘라냅니다.",
"trim-prefix-description": "Trim first character from each line",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Import original keywords as tags",
"stay-in-edit-mode": "Stay in Edit mode",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Pusryčiai",
"lunch": "Pietūs",
"dinner": "Vakarienė",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Bet kas",
"day-any": "Bet kas",
"editor": "Redagavimas",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Įkelti receptą",
"upload-individual-zip-file": "Įkelkite .zip failą, eksportuotą iš kitos \"Mealie\" sistemos.",
"url-form-hint": "Nukopijuokite ir įklijuokite nuorodą iš mėgstamų receptų svetainės",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Peržiūrėti nuskaitytus duomenis",
"trim-whitespace-description": "Pašalinti tarpus bei tuščias eilutes pradžioje ir pabaigoje",
"trim-prefix-description": "Pašalinti kiekvienos eilutės pirmąjį ženklą",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Įkelti pradinius raktažodžius kaip žymas",
"stay-in-edit-mode": "Toliau redaguoti",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Brokastis",
"lunch": "pusdienas",
"dinner": "Vakariņas",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Jebkurš",
"day-any": "Jebkurš",
"editor": "Redaktors",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Augšupielādējiet recepti",
"upload-individual-zip-file": "Augšupielādējiet atsevišķu.zip failu, kas eksportēts no citas Mealie instances.",
"url-form-hint": "Kopējiet un ielīmējiet saiti no savas iecienītākās receptes vietnes",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Skatīt nokasītos datus",
"trim-whitespace-description": "Apgrieziet priekšējo un aizmugurējo atstarpi, kā arī tukšas rindas",
"trim-prefix-description": "Izgrieziet pirmo rakstzīmi no katras rindas",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Izmēģiniet lielapjoma importētāju",
"scrape-recipe-have-raw-html-or-json-data": "Vai jums ir neapstrādāti HTML vai JSON dati?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Jūs varat importēt no neapstrādātiem datiem tieši",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importējiet oriģinālos atslēgvārdus kā tagus",
"stay-in-edit-mode": "Palieciet rediģēšanas režīmā",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "ir kā",
"is-not-like": "nav tāds, kā"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Ontbijt",
"lunch": "Lunch",
"dinner": "Diner",
"snack": "Tussendoortje",
"drink": "Drankje",
"dessert": "Toetje",
"type-any": "Alle",
"day-any": "Elke",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Upload een recept",
"upload-individual-zip-file": "Upload een .zip-bestand dat uit een andere Mealie-instantie is geëxporteerd.",
"url-form-hint": "Kopieer en plak een link vanuit jouw favoriete receptenwebsite",
"copy-and-paste-the-source-url-of-your-data-optional": "Kopieer en plak de bron URL van uw gegevens (optioneel)",
"view-scraped-data": "Bekijk opgehaalde data",
"trim-whitespace-description": "Haal witruimtes en witregels aan het begin en einde weg",
"trim-prefix-description": "Verwijder het eerste teken van elke regel",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Probeer importeren in bulk",
"scrape-recipe-have-raw-html-or-json-data": "Heb je onbewerkte HTML of JSON gegevens?",
"scrape-recipe-you-can-import-from-raw-data-directly": "U kunt direct importeren uit onbewerkte gegevens",
"scrape-recipe-website-being-blocked": "Wordt de website geblokkeerd?",
"scrape-recipe-try-importing-raw-html-instead": "Probeer de HTML broncode te importeren.",
"import-original-keywords-as-tags": "Importeer oorspronkelijke trefwoorden als labels",
"stay-in-edit-mode": "Blijf in bewerkingsmodus",
"parse-recipe-ingredients-after-import": "Ontleed de ingrediënten van het recept na importeren",
@@ -1422,5 +1428,13 @@
"is-like": "is zoals",
"is-not-like": "is niet zoals"
}
},
"validators": {
"required": "Dit is een verplicht veld",
"invalid-email": "E-mailadres moet geldig zijn",
"invalid-url": "Moet een geldige URL zijn",
"no-whitespace": "Geen spaties toegestaan",
"min-length": "Moet minimaal {min} tekens bevatten",
"max-length": "Zorg dat je {max} tekens gebruikt"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Frokost",
"lunch": "Lunsj",
"dinner": "Middag",
"snack": "Snacks",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Enhver",
"day-any": "Enhver",
"editor": "Redigeringsverktøy",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Last opp oppskrift",
"upload-individual-zip-file": "Last opp en individuell .zip-fil eksportert fra en annen Mealie-instans.",
"url-form-hint": "Kopier og lim inn en lenke fra nettstedet med favorittoppskriftene dine",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Vis skrapte data",
"trim-whitespace-description": "Fjern innledende og etterfølgende mellomrom i tillegg til tomme linjer",
"trim-prefix-description": "Fjern første tegn fra hver linje",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Prøv masseimportering",
"scrape-recipe-have-raw-html-or-json-data": "Har du HTML- eller JSON-rådata?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kan importere fra rådata direkte",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importer originale søkeord som emneord",
"stay-in-edit-mode": "Forbli i redigeringsmodus",
"parse-recipe-ingredients-after-import": "Analyser oppskriftens ingredienser etter at importen er fullført",
@@ -1422,5 +1428,13 @@
"is-like": "er som",
"is-not-like": "er ikke som"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Śniadanie",
"lunch": "Obiad",
"dinner": "Kolacja",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Dowolny",
"day-any": "Dowolny",
"editor": "Edytor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Wrzuć przepis",
"upload-individual-zip-file": "Prześlij pojedynczy plik .zip wyeksportowany z innej instancji Mealie.",
"url-form-hint": "Skopiuj i wklej link ze swojej ulubionej strony z przepisami",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Wyświetl zebrane dane",
"trim-whitespace-description": "Przytnij pustą przestrzeń przed i po zawartości oraz puste linie",
"trim-prefix-description": "Przytnij pierwszy znak z każdej linii",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Wypróbuj importer zbiorczy",
"scrape-recipe-have-raw-html-or-json-data": "Masz dane HTML bądź JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Możesz zaimportować bezpośrednio z surowych danych",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importuj oryginalne słowa kluczowe jako tagi",
"stay-in-edit-mode": "Pozostań w trybie edycji",
"parse-recipe-ingredients-after-import": "Analizuj składniki receptury po zaimportowaniu",
@@ -1422,5 +1428,13 @@
"is-like": "jest jak",
"is-not-like": "nie jest jak"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Café da manhã",
"lunch": "Almoço",
"dinner": "Jantar",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Qualquer",
"day-any": "Qualquer",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Enviar uma Receita",
"upload-individual-zip-file": "Enviar um arquivo .zip individual exportado a partir de outra instância do Mealie.",
"url-form-hint": "Copie e cole um link do seu site de receita favorito",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Visualizar Dados Rastreados",
"trim-whitespace-description": "Aparar o espaço em branco e à direita, bem como linhas em branco",
"trim-prefix-description": "Aparar primeiro caractere de cada linha",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Tente o importador em massa",
"scrape-recipe-have-raw-html-or-json-data": "Tem dados HTML ou JSON brutos?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Você pode importar diretamente de dados brutos",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importar palavras-chave originais como marcadores",
"stay-in-edit-mode": "Permanecer no modo de edição",
"parse-recipe-ingredients-after-import": "Interpretar os ingredientes da receita após importar",
@@ -1422,5 +1428,13 @@
"is-like": "é como",
"is-not-like": "não é como"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Pequeno-almoço",
"lunch": "Almoço",
"dinner": "Jantar",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Qualquer",
"day-any": "Qualquer",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Enviar uma Receita",
"upload-individual-zip-file": "Carregar um ficheiro .zip individual, exportado de outra instância do Mealie.",
"url-form-hint": "Copie e cole um link do seu site de receitas favorito",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Ver dados recolhidos",
"trim-whitespace-description": "Eliminar os espaços em branco no início e no fim, bem como as linhas em branco",
"trim-prefix-description": "Apagar o primeiro caractere de cada linha",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Experimente o importador em massa",
"scrape-recipe-have-raw-html-or-json-data": "Tem dados HTML ou JSON em bruto?",
"scrape-recipe-you-can-import-from-raw-data-directly": "É possível importar diretamente a partir de dados em bruto",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importar palavras-chave originais como etiquetas",
"stay-in-edit-mode": "Permanecer no modo de edição",
"parse-recipe-ingredients-after-import": "Analisar ingredientes da receita após a importação",
@@ -1422,5 +1428,13 @@
"is-like": "é como",
"is-not-like": "não é como"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Mic dejun",
"lunch": "Prânz",
"dinner": "Cină",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Oricare",
"day-any": "Oricare",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Încarcă o rețetă",
"upload-individual-zip-file": "Încărcaţi un fişier individual .zip exportat dintr-o altă instanţă de Mealie.",
"url-form-hint": "Copiază și lipește un link de pe site-ul tău web preferat de rețete",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Vezi datele colectate",
"trim-whitespace-description": "Elimină spațiile albe de la început și sfârșit precum și liniile goale",
"trim-prefix-description": "Elimină primul caracter din fiecare linie",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Încearcă importatorul în bulk",
"scrape-recipe-have-raw-html-or-json-data": "Ai date de tip HTML sau JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Poți importa datele direct",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importă cuvintele cheie originale ca tag-uri",
"stay-in-edit-mode": "Rămâi în modul Editare",
"parse-recipe-ingredients-after-import": "Analizează ingredientele rețetei după import",
@@ -1422,5 +1428,13 @@
"is-like": "este similar",
"is-not-like": "nu este similar"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -19,10 +19,10 @@
"log-lines": "Строки журнала",
"not-demo": "Не демо",
"portfolio": "Портфолио",
"production": "Production",
"production": "Продуктивная среда",
"support": "Поддержка",
"version": "Версия",
"unknown-version": "неизвестно",
"unknown-version": "Неизвестная версия",
"sponsor": "Спонсор"
},
"asset": {
@@ -342,6 +342,9 @@
"breakfast": "Завтрак",
"lunch": "Обед",
"dinner": "Ужин",
"snack": "Закуска",
"drink": "Напиток",
"dessert": "Десерт",
"type-any": "Любой",
"day-any": "Любой",
"editor": "Редактор",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Загрузить рецепт",
"upload-individual-zip-file": "Загрузить отдельный .zip файл, экспортированный из другой Mealie.",
"url-form-hint": "Скопируйте и вставьте ссылку из вашего любимого сайта рецептов",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Просмотр отсканированных данных",
"trim-whitespace-description": "Обрезать ведущие и конечные пробелы, а также пустые строки",
"trim-prefix-description": "Обрезать первый символ из каждой строки",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Воспользуйтесь массовым импортом",
"scrape-recipe-have-raw-html-or-json-data": "У Вас есть данные HTML или JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Вы можете импортировать напрямую из необработанных данных",
"scrape-recipe-website-being-blocked": "Сайт заблокирован?",
"scrape-recipe-try-importing-raw-html-instead": "Попробуйте импортировать необработанный HTML файл.",
"import-original-keywords-as-tags": "Импортировать исходные ключевые слова как теги",
"stay-in-edit-mode": "Остаться в режиме редактирования",
"parse-recipe-ingredients-after-import": "Распознавание ингредиентов рецепта после импорта",
@@ -1422,5 +1428,13 @@
"is-like": "содержит",
"is-not-like": "не содержит"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Raňajky",
"lunch": "Obed",
"dinner": "Večera",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Ľubovoľný",
"day-any": "Ľubovoľný",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Nahrať recept",
"upload-individual-zip-file": "Nahrať súbor .zip exportovaný z inej Mealie inštalácie.",
"url-form-hint": "Okopírujte a zložte odkaz z vašej obľúbenej webstránky",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Náhľad získaných údajov",
"trim-whitespace-description": "Vymazať medzery a prázdne riadky na začiatku a na konci",
"trim-prefix-description": "Vymazať prvé písmeno z každého riadku",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Vyskúšajte hromadný importér",
"scrape-recipe-have-raw-html-or-json-data": "Máte surové údaje HTML alebo JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Môžete importovať priamo nespracované údaje",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Importovať pôvodné kľúčové slová ako štítky",
"stay-in-edit-mode": "Zostať v režime editovania",
"parse-recipe-ingredients-after-import": "Analyzovať ingrediencie po importe",
@@ -1422,5 +1428,13 @@
"is-like": "je ako",
"is-not-like": "nie je ako"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Zajtrk",
"lunch": "Kosilo",
"dinner": "Večerja",
"snack": "Prigrizek",
"drink": "Pijača",
"dessert": "Sladica",
"type-any": "Katerikoli",
"day-any": "Katerikoli",
"editor": "Urednik",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Naloži recept",
"upload-individual-zip-file": "Naloži posamezno .zip datoteko, izvoženo iz druge Mealie namestitve.",
"url-form-hint": "Kopiraj in prilepi povezavo iz vaše priljubljene strani z recepti",
"copy-and-paste-the-source-url-of-your-data-optional": "Kopirajte in prilepite izvorni URL svojih podatkov (neobvezno)",
"view-scraped-data": "Poglej postrgane podatke",
"trim-whitespace-description": "Poreži začetne in končne presledke, kot tudi prazne vrstice",
"trim-prefix-description": "Poreži prvi znak v vsaki vrstici",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Preizkusi masovni uvoz",
"scrape-recipe-have-raw-html-or-json-data": "Imaš surove HTML ali JSON podatke?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Uvoziš lahko neposredno iz surovih podatkov",
"scrape-recipe-website-being-blocked": "Je spletna stran blokirana?",
"scrape-recipe-try-importing-raw-html-instead": "Poskusite namesto tega uvoziti surovi HTML.",
"import-original-keywords-as-tags": "Uvozi izvorne ključne besede kot značke",
"stay-in-edit-mode": "Urejaj naprej",
"parse-recipe-ingredients-after-import": "Razčlenitev sestavin recepta po uvozu",
@@ -1422,5 +1428,13 @@
"is-like": "je kot",
"is-not-like": "ni kot"
}
},
"validators": {
"required": "To polje je obvezno",
"invalid-email": "E-pošta mora biti veljavna",
"invalid-url": "URL mora biti veljaven",
"no-whitespace": "Presledki niso dovoljeni",
"min-length": "Mora vsebovati vsaj {min} znakov",
"max-length": "Lahko je največ {max} znakov"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Доручак",
"lunch": "Ручак",
"dinner": "Вечера",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Било који",
"day-any": "Било који",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Upload a Recipe",
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.",
"url-form-hint": "Копирајте и налепите везу са вашег омиљеног сајта за рецепте",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "View Scraped Data",
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
"trim-prefix-description": "Trim first character from each line",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Увези оригиналне кључне речи као ознаке",
"stay-in-edit-mode": "Stay in Edit mode",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Frukost",
"lunch": "Lunch",
"dinner": "Middag",
"snack": "Snacks",
"drink": "Drinkar",
"dessert": "Dessert",
"type-any": "Alla",
"day-any": "Alla",
"editor": "Redigerare",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Ladda upp ett recept",
"upload-individual-zip-file": "Ladda upp en individuell .zip-fil som exporteras från en annan Mealie-instans.",
"url-form-hint": "Kopiera och klistra in en länk från din favorit recept webbplats",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Visa skrapade data",
"trim-whitespace-description": "Ta bort inledande och avslutande blanksteg samt tomma rader",
"trim-prefix-description": "Ta bort första tecknet från varje rad",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Testa massimportören",
"scrape-recipe-have-raw-html-or-json-data": "Har rå HTML eller JSON-data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Du kan importera från rådata direkt",
"scrape-recipe-website-being-blocked": "Är websidan blockerad?",
"scrape-recipe-try-importing-raw-html-instead": "Prova att importera HTML istället.",
"import-original-keywords-as-tags": "Importera ursprungliga sökord som taggar",
"stay-in-edit-mode": "Stanna kvar i redigeringsläge",
"parse-recipe-ingredients-after-import": "Tolka receptingredienser efter import",
@@ -1422,5 +1428,13 @@
"is-like": "är som",
"is-not-like": "är inte som"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Kahvaltı",
"lunch": "Öğle Yemeği",
"dinner": "Akşam Yemeği",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Herhangi",
"day-any": "Herhangi",
"editor": "Editör",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Tarif Yükle",
"upload-individual-zip-file": "Başka bir Mealie örneğinden dışa aktarılan ayrı bir .zip dosyası yükleyin.",
"url-form-hint": "Favori tarif sitenizden bir bağlantıyı kopyalayıp yapıştırın",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Kazınmış Verileri Görüntüle",
"trim-whitespace-description": "Baştaki ve sondaki boşlukların yanı sıra boş satırları da kırpın",
"trim-prefix-description": "Her satırın ilk karakterini kırpın",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Toplu ithalatçıyı deneyin",
"scrape-recipe-have-raw-html-or-json-data": "Ham HTML veya JSON verileriniz mi var?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Ham veriden doğrudan içe aktarabilirsiniz",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Orijinal anahtar kelimeleri etiket olarak içe aktar",
"stay-in-edit-mode": "Düzenleme modunda kalın",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Сніданок",
"lunch": "Обід",
"dinner": "Вечеря",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Будь-який",
"day-any": "Будь-який",
"editor": "Редактор",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Завантажити рецепт",
"upload-individual-zip-file": "Завантажити окремий .zip файл, експортований з іншого Mealie.",
"url-form-hint": "Скопіюйте та вставте посилання з вашого улюбленого кулінарного веб-сайту",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "Переглянути зібрані дані",
"trim-whitespace-description": "Обрізати початкові та кінцеву пробілів і порожні лінії",
"trim-prefix-description": "Обрізати перший символ з кожного рядка",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Спробуйте масовий розпізнавач",
"scrape-recipe-have-raw-html-or-json-data": "Маєте необроблені дані HTML або JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Ви можете імпортувати необроблені дані",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Імпортувати оригінальні ключові слова як теги",
"stay-in-edit-mode": "Залишитися в режимі редактора",
"parse-recipe-ingredients-after-import": "Розпізнавання інгредієнтів рецепту після імпорту",
@@ -1422,5 +1428,13 @@
"is-like": "схожий",
"is-not-like": "не схожий"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Breakfast",
"lunch": "Lunch",
"dinner": "Dinner",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Any",
"day-any": "Any",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "Upload a Recipe",
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.",
"url-form-hint": "Copy and paste a link from your favorite recipe website",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "View Scraped Data",
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
"trim-prefix-description": "Trim first character from each line",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Import original keywords as tags",
"stay-in-edit-mode": "Stay in Edit mode",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "早餐",
"lunch": "午餐",
"dinner": "晚餐",
"snack": "Snack",
"drink": "Drink",
"dessert": "甜点",
"type-any": "任意",
"day-any": "任意",
"editor": "编辑器",
@@ -442,6 +445,7 @@
"upload-a-recipe": "上传食谱",
"upload-individual-zip-file": "上传从Mealie导出的.zip文件。",
"url-form-hint": "从您最喜爱的食谱网站复制并粘贴链接",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "查看爬取的数据",
"trim-whitespace-description": "删除开头和结尾的空格和空行",
"trim-prefix-description": "删除每行的首个字符",
@@ -449,8 +453,8 @@
"import-by-url": "通过网址导入食谱",
"create-manually": "手动创建食谱",
"make-recipe-image": "将此设为食谱图片",
"add-food": "Add Food",
"add-recipe": "Add Recipe"
"add-food": "添加食物",
"add-recipe": "添加食谱"
},
"page": {
"404-page-not-found": "404-页面未找到",
@@ -517,9 +521,9 @@
"recipe-deleted": "食谱已删除",
"recipe-image": "食谱图片",
"recipe-image-updated": "食谱图片已更新",
"delete-image": "Delete Recipe Image",
"delete-image-confirmation": "Are you sure you want to delete this recipe image?",
"recipe-image-deleted": "Recipe image deleted",
"delete-image": "删除食谱图像",
"delete-image-confirmation": "您确定要删除此食谱图像吗?",
"recipe-image-deleted": "食谱图片已删除",
"recipe-name": "食谱名称",
"recipe-settings": "食谱设置",
"recipe-update-failed": "食谱更新失败",
@@ -571,7 +575,7 @@
"see-original-text": "查看原文",
"original-text-with-value": "原文: {originalText}",
"ingredient-linker": "食材关联器",
"unlinked": "Not linked yet",
"unlinked": "未链接",
"linked-to-other-step": "已关联到其他步骤",
"auto": "自动",
"cook-mode": "烹饪模式",
@@ -633,9 +637,11 @@
"scrape-recipe-suggest-bulk-importer": "试试批量导入器",
"scrape-recipe-have-raw-html-or-json-data": "有原始 HTML 或 JSON 数据?",
"scrape-recipe-you-can-import-from-raw-data-directly": "您可以直接从原始数据导入",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "导入原始关键字作为标签",
"stay-in-edit-mode": "留在编辑模式",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
"parse-recipe-ingredients-after-import": "导入后解析食材",
"import-from-zip": "从Zip压缩包导入",
"import-from-zip-description": "导入从另一个Mealie应用导出的单个食谱。",
"import-from-html-or-json": "从 HTML 或 JSON 导入",
@@ -683,10 +689,10 @@
"this-food-could-not-be-parsed-automatically": "这种食物不能被自动解析",
"no-food": "没有食物",
"review-parsed-ingredients": "Review parsed ingredients",
"confidence-score": "Confidence Score",
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
"confidence-score": "置信度",
"ingredient-parser-description": "您的食材已成功解析。请检查我们不确定的食材。",
"ingredient-parser-final-review-description": "一旦所有食材都检查完毕,在将更改应用到您的菜谱之前,您还有最后一次机会检查所有食材。",
"add-text-as-alias-for-item": "将“{text}”添加为“{item}”的别名",
"delete-item": "Delete Item"
},
"reset-servings-count": "重置份量数量",
@@ -696,7 +702,7 @@
"upload-more-images": "上传更多图片",
"set-as-cover-image": "设置为食谱封面图片",
"cover-image": "封面图片",
"include-linked-recipes": "Include Linked Recipes",
"include-linked-recipes": "包含已链接的菜谱",
"include-linked-recipe-ingredients": "Include Linked Recipe Ingredients",
"toggle-recipe": "Toggle Recipe"
},
@@ -736,7 +742,7 @@
"advanced": "高级",
"auto-search": "自动搜索",
"no-results": "未找到任何结果",
"type-to-search": "Type to search..."
"type-to-search": "键入以搜索……"
},
"settings": {
"add-a-new-theme": "新增布景主题",
@@ -879,7 +885,7 @@
"ldap-ready": "LDAP 已就绪",
"ldap-ready-error-text": "某些LDAP环境变量尚未配置。如果你不使用LDAP验证可以忽略该报错",
"ldap-ready-success-text": "LDAP所需的环境变量均已配置。",
"build": "生成",
"build": "构建",
"recipe-scraper-version": "食谱刮削器版本",
"oidc-ready": "OIDC 已就绪",
"oidc-ready-error-text": "某些OIDC环境变量尚未配置。如果你不使用OIDC验证可以忽略该报错",
@@ -1422,5 +1428,13 @@
"is-like": "匹配",
"is-not-like": "不匹配"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -342,6 +342,9 @@
"breakfast": "Breakfast",
"lunch": "午餐",
"dinner": "晚餐",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"type-any": "Any",
"day-any": "Any",
"editor": "Editor",
@@ -442,6 +445,7 @@
"upload-a-recipe": "上傳食譜",
"upload-individual-zip-file": "上傳從另一個Mealie匯出的zip壓縮檔",
"url-form-hint": "複製您最喜歡的食譜網站的網址並在此貼上",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"view-scraped-data": "查看網頁擷取資料",
"trim-whitespace-description": "Trim leading and trailing whitespace as well as blank lines",
"trim-prefix-description": "Trim first character from each line",
@@ -633,6 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer",
"scrape-recipe-have-raw-html-or-json-data": "Have raw HTML or JSON data?",
"scrape-recipe-you-can-import-from-raw-data-directly": "You can import from raw data directly",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"import-original-keywords-as-tags": "Import original keywords as tags",
"stay-in-edit-mode": "Stay in Edit mode",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
@@ -1422,5 +1428,13 @@
"is-like": "is like",
"is-not-like": "is not like"
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
}
}

View File

@@ -110,80 +110,6 @@ export interface CreateBackup {
options: BackupOptions;
templates?: string[] | null;
}
export interface CustomPageBase {
name: string;
slug: string | null;
position: number;
categories?: RecipeCategoryResponse[];
}
export interface RecipeCategoryResponse {
name: string;
id: string;
groupId?: string | null;
slug: string;
recipes?: RecipeSummary[];
}
export interface RecipeSummary {
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeServings?: number;
recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
cookTime?: string | null;
performTime?: string | null;
description?: string | null;
recipeCategory?: RecipeCategory[] | null;
tags?: RecipeTag[] | null;
tools?: RecipeTool[];
rating?: number | null;
orgURL?: string | null;
dateAdded?: string | null;
dateUpdated?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
lastMade?: string | null;
}
export interface RecipeCategory {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
groupId?: string | null;
name: string;
slug: string;
householdsWithTool?: string[];
[k: string]: unknown;
}
export interface CustomPageImport {
name: string;
status: boolean;
exception?: string | null;
}
export interface CustomPageOut {
name: string;
slug: string | null;
position: number;
categories?: RecipeCategoryResponse[];
id: number;
}
export interface DebugResponse {
success: boolean;
response?: string | null;
@@ -248,11 +174,6 @@ export interface Migrations {
type: string;
files?: MigrationFile[];
}
export interface NotificationImport {
name: string;
status: boolean;
exception?: string | null;
}
export interface RecipeImport {
name: string;
status: boolean;

View File

@@ -44,7 +44,6 @@ export interface QueryFilterJSONPart {
attributeName?: string | null;
relationalOperator?: RelationalKeyword | RelationalOperator | null;
value?: string | string[] | null;
[k: string]: unknown;
}
export interface SaveCookBook {
name: string;

View File

@@ -334,7 +334,6 @@ export interface IngredientUnit {
}
export interface IngredientUnitAlias {
name: string;
[k: string]: unknown;
}
export interface CreateIngredientUnit {
id?: string | null;
@@ -349,11 +348,9 @@ export interface CreateIngredientUnit {
pluralAbbreviation?: string | null;
useAbbreviation?: boolean;
aliases?: CreateIngredientUnitAlias[];
[k: string]: unknown;
}
export interface CreateIngredientUnitAlias {
name: string;
[k: string]: unknown;
}
export interface IngredientFood {
id: string;
@@ -372,7 +369,6 @@ export interface IngredientFood {
}
export interface IngredientFoodAlias {
name: string;
[k: string]: unknown;
}
export interface MultiPurposeLabelSummary {
name: string;
@@ -391,11 +387,9 @@ export interface CreateIngredientFood {
labelId?: string | null;
aliases?: CreateIngredientFoodAlias[];
householdsWithIngredientFood?: string[];
[k: string]: unknown;
}
export interface CreateIngredientFoodAlias {
name: string;
[k: string]: unknown;
}
export interface Recipe {
id?: string | null;
@@ -433,21 +427,18 @@ export interface Recipe {
[k: string]: unknown;
} | null;
comments?: RecipeCommentOut[] | null;
[k: string]: unknown;
}
export interface RecipeCategory {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
@@ -455,7 +446,6 @@ export interface RecipeTool {
name: string;
slug: string;
householdsWithTool?: string[];
[k: string]: unknown;
}
export interface RecipeStep {
id?: string | null;
@@ -463,11 +453,9 @@ export interface RecipeStep {
summary?: string | null;
text: string;
ingredientReferences?: IngredientReferences[];
[k: string]: unknown;
}
export interface IngredientReferences {
referenceId?: string | null;
[k: string]: unknown;
}
export interface Nutrition {
calories?: string | null;
@@ -481,7 +469,6 @@ export interface Nutrition {
sugarContent?: string | null;
transFatContent?: string | null;
unsaturatedFatContent?: string | null;
[k: string]: unknown;
}
export interface RecipeSettings {
public?: boolean;
@@ -490,18 +477,15 @@ export interface RecipeSettings {
landscapeView?: boolean;
disableComments?: boolean;
locked?: boolean;
[k: string]: unknown;
}
export interface RecipeAsset {
name: string;
icon: string;
fileName?: string | null;
[k: string]: unknown;
}
export interface RecipeNote {
title: string;
text: string;
[k: string]: unknown;
}
export interface RecipeCommentOut {
recipeId: string;
@@ -511,14 +495,12 @@ export interface RecipeCommentOut {
updatedAt: string;
userId: string;
user: UserBase;
[k: string]: unknown;
}
export interface UserBase {
id: string;
username?: string | null;
admin: boolean;
fullName?: string | null;
[k: string]: unknown;
}
export interface ShoppingListAddRecipeParamsBulk {
recipeIncrementQuantity?: number;

View File

@@ -5,9 +5,9 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side";
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side" | "snack" | "drink" | "dessert";
export type PlanRulesDay = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | "unset";
export type PlanRulesType = "breakfast" | "lunch" | "dinner" | "side" | "unset";
export type PlanRulesType = "breakfast" | "lunch" | "dinner" | "side" | "snack" | "drink" | "dessert" | "unset";
export type LogicalOperator = "AND" | "OR";
export type RelationalKeyword = "IS" | "IS NOT" | "IN" | "NOT IN" | "CONTAINS ALL" | "LIKE" | "NOT LIKE";
export type RelationalOperator = "=" | "<>" | ">" | "<" | ">=" | "<=";
@@ -53,7 +53,6 @@ export interface QueryFilterJSONPart {
attributeName?: string | null;
relationalOperator?: RelationalKeyword | RelationalOperator | null;
value?: string | string[] | null;
[k: string]: unknown;
}
export interface PlanRulesSave {
day?: PlanRulesDay;
@@ -106,14 +105,12 @@ export interface RecipeCategory {
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
@@ -121,7 +118,6 @@ export interface RecipeTool {
name: string;
slug: string;
householdsWithTool?: string[];
[k: string]: unknown;
}
export interface SavePlanEntry {
date: string;

View File

@@ -29,7 +29,8 @@ export type RecipeOrganizer
| "tags"
| "tools"
| "foods"
| "households";
| "households"
| "users";
export enum Organizer {
Category = "categories",
@@ -37,4 +38,5 @@ export enum Organizer {
Tool = "tools",
Food = "foods",
Household = "households",
User = "users",
}

View File

@@ -510,6 +510,7 @@ export interface ScrapeRecipeBase {
export interface ScrapeRecipeData {
includeTags?: boolean;
data: string;
url?: string | null;
}
export interface ScrapeRecipeTest {
url: string;

View File

@@ -146,8 +146,8 @@ export class RecipeAPI extends BaseCRUDAPI<CreateRecipe, Recipe, Recipe> {
return await this.requests.post<Recipe | null>(routes.recipesTestScrapeUrl, { url, useOpenAI });
}
async createOneByHtmlOrJson(data: string, includeTags: boolean) {
return await this.requests.post<string>(routes.recipesCreateFromHtmlOrJson, { data, includeTags });
async createOneByHtmlOrJson(data: string, includeTags: boolean, url: string | null = null) {
return await this.requests.post<string>(routes.recipesCreateFromHtmlOrJson, { data, includeTags, url });
}
async createOneByUrl(url: string, includeTags: boolean) {

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