## File: README.md

<h1 align="center">
  
  [](https://tandoor.dev)
  
  Tandoor Recipes
  
</h1>

<h4 align="center">The recipe manager that allows you to manage your ever growing collection of digital recipes.</h4>







## Core Features

- 🥗 **Manage your recipes** - Manage your ever growing recipe collection
- 📆 **Plan** - multiple meals for each day
- 🛒 **Shopping lists** - via the meal plan or straight from recipes
- 🪄 **use AI** to recognize images, sort recipe steps, find nutrition facts and more
- 📚 **Cookbooks** - collect recipes into books
- 👪 **Share and collaborate** on recipes with friends and family

## Made by and for power users

- 🔍 Powerful & customizable **search** with fulltext support and [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
- 🏷️ Create and search for **tags**, assign them in batch to all files matching certain filters
- ↔️ Quickly merge and rename ingredients, tags and units
- 📥️ **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
- ➗ Support for **fractions** or decimals
- 🐳 Easy setup with **Docker** and included examples for **Kubernetes**, **Unraid** and **Synology**
- 🎨 Customize your interface with **themes**
- 📦 **Sync** files with Dropbox and Nextcloud

## All the must haves

- 📱Optimized for use on **mobile** devices
- 🌍 localized in many languages thanks to the awesome community
- 📥️ **Import your collection** from many other [recipe managers](https://docs.tandoor.dev/features/import_export/)
- ➕ Many more like recipe scaling, image compression, printing views and supermarkets

This application is meant for people with a collection of recipes they want to share with family and friends or simply
store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as
a public page.

## Docs

Documentation can be found [here](https://docs.tandoor.dev/).

## ❤️ Support our work ❤️
Tandoor is developed by volunteers in their free time just because its fun. That said earning
some money with the project allows us to spend more time on it and thus make improvements we otherwise couldn't.
Because of that there are several ways you can support us

- **GitHub Sponsors** You can sponsor contributors of this project on GitHub: [vabene1111](https://github.com/sponsors/vabene1111)
- **Patron** You can sponsor contributors of this project on Patron: [vabene111](https://www.patreon.com/cw/vabene1111)
- **Host at Hetzner** We have been very happy customers of Hetzner for multiple years for all of our projects. If you want to get into self-hosting or are tired of the expensive big providers, their cloud servers are a great place to get started. When you sign up via our [referral link](https://hetzner.cloud/?ref=ISdlrLmr9kGj) you will get 20€ worth of cloud credits and we get a small kickback too.
- **Let us host for you** We are offering a [hosted version](https://app.tandoor.dev) where all profits support us and the development of tandoor (currently only available in germany).

## Contributing
Contributions are welcome but please read [this](https://docs.tandoor.dev/contribute/guidelines/) **BEFORE** contributing anything!

## Your Feedback

Share some information on how you use Tandoor to help me improve the application [Google Survey](https://forms.gle/qNfLK2tWTeWHe9Qd7)

## Get in touch



| [Community](https://community.tandoor.dev) | Get support, share best practices, discuss feature ideas, and meet other Tandoor users. |
| --- | --- |
| [Discord](https://discord.gg/RhzBrfWgtp) | We have a public Discord server that anyone can join. This is where all our developers and contributors hang out and where we make announcements |



## License

Beginning with version 0.10.0 the code in this repository is licensed under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.de.html) license with a
[common clause](https://commonsclause.com/) selling exception. See [LICENSE.md](https://github.com/vabene1111/recipes/blob/develop/LICENSE.md) for details.

> NOTE: There appears to be a whole range of legal issues with licensing anything other than the standard completely open licenses.
> I am in the process of getting some professional legal advice to sort out these issues. 
> Please also see [Issue 238](https://github.com/vabene1111/recipes/issues/238) for some discussion and **reasoning** regarding the topic.

**Reasoning**
**This software and *all* its features are and will always be free for everyone to use and enjoy.**

The reason for the selling exception is that a significant amount of time was spend over multiple years to develop this software.
A paid hosted version which will be identical in features and code base to the software offered in this repository will
likely be released in the future (including all features needed to sell a hosted version as they might also be useful for personal use).
This will not only benefit me personally but also everyone who self-hosts this software as any profits made through selling the hosted option
allow me to spend more time developing and improving the software for everyone. Selling exceptions are [approved by Richard Stallman](http://www.gnu.org/philosophy/selling-exceptions.en.html) and the
common clause license is very permissive (see the [FAQ](https://commonsclause.com/)).

---

## File: docs/contribute/feature_contrib/featureguides.md

If there is a not a guide below for the type of feature you wish to implement then please add a request on [github](https://github.com/TandoorRecipes/recipes/issues) for the type of feature you wish to contribute. 

Alternatively please document the process of adding a feature type if you have already contributed a feature.

# Feature Contribution Guides

- [Import / Export Integrations Guide](/contribute/feature_contrib/integration.md)

---

## File: docs/contribute/feature_contrib/Integration.md

# Import / Export Integration Contribution Guide

# Setup
There are 5 files you need to configure in order create a new integration
1. Create a new integration class in `/cookbook/integration/`
2. Include integration in `/cookbook/forms.py` 
3. Add the new integration class import and include in the 'if' chain in `/cookbook/views/import_export.py`
4. Add your integration to the documentation at `/docs/features/import_export.md`
5. Include integration and docs link in `/vue3/src/utils/integration_utils.ts`


## 1. Creating a New Integration Class
Your integration class should be named after what you are integrating with and should inherit from the `Integration` class. Use the template below to setup your class.

`/cookbook/integration/yourintegration.py`
```
/* Detailed source-code truncated for AI context efficiency. */
```

## 2. including in Forms.py
In the `/cookbook/forms.py` find the `ImportExportBase` class and add to it the following amoung the others:
```python
YOURINTEGRATION = "YOURINTEGRATION"
```
Then you will find under that the following declaration:

`
type = forms.ChoiceField(choices=())
`
the choices will have a long list of tuples. Add to the list of tuples the following:

```python
(YOURINTEGRATION, 'Your Integration'),
```

## 3. Including in Views
In the `/cookbook/views/import_export.py` import your integration

```python
from cookbook.integration.yourintegration import YourIntegration
```

Then add the following code to the long `if` chain:

```python
if export_type == ImportExportBase.YOURINTEGRATION:
    return YourIntegration(request, export_type)
```

be careful to use the exact all caps name of your integration that you used in the `cookbook/forms.py` or else it won't recognize it as a type. the snake case is the class that you defined in `/cookbook/integration`

## 4. Add to the Documentation
If nothing else, you at least have to add one slugline about your integration in the `/docs/features/import_export.md` because the Vue pages require a link to send the send the user to if they hit the information button on the import/export form.

Go to the bottom of the doc and add:
```markdown
## YourIntegration
a little blurb about how it works or anything users should know about how the data needs to be formated.
```

Additionally add your integration to the table at the top of the document, marking the state of your integration, or wait until it is integrated and tested before adding to the table.

## 5. Add to Vue Integration Utils
In the `/vue3/src/utils/integration_utils.ts` find `export const INTEGRATIONS: Array` and in the long list add:
```typo3_typoscript
{id: 'YOURINTEGRATION', name: "Your Integration", import: true, export: false, helpUrl: 'https://docs.tandoor.dev/features/import_export/#yourintegration'},
```
be sure to change 'true' or 'false' value for the import and export options to the correct values for your integration. 'true' indicates that it should be listed in the menu for imports or exports respectively.

### Be sure to update vue using `yarn` after to be sure the html files get updated

---

# Integration Test Setup
## File Structure and Files
Tandoor uses Pytest to implement its testing features. To add tests and test documents navigate to 
```
cookbook/tests/other/
``` 
There you can create a test file `test_yourintegration.py`. It is very important that it starts with "test_" as that is how pytest knows to run it as a test.

For the text files that you will want to parse and test for your integration you can put them at:

```
cookbook/test/other/test_data/your_integration/
```
making your own folder for your test inputs there. When directing your tests to pull the files from that folder make sure to include the whole path starting at `cookbook/`

## Creating an Integration Test with a Request object
Like the filename, inside the file `test_yourintegration.py` pytest looks specifically for the functions that start with the string `test_` any function that does not have that prefex won't be run. This is useful if you want to define helper methods to your tests.

Since your integration is a child of the `Integration` class you must pass your integration the required arguments: `request` and `export_type`. Unless your test has a specific export_type you are trying to test, it is not consequential what you put for `export_type`, so long as it is a string. I generally just use "export" in my test. For request though we need to generate one

You can accomplish this with the following code:
```python
from io import BytesIO

from django.contrib import auth
from django.test import RequestFactory
from django_scopes import scope

from cookbook.integration.cooklang import YourIntegrationClass

def test_yourintegration(u1_s1):
    user = auth.get_user(u1_s1)
    space = user.userspace_set.first().space
    request = RequestFactory()
    request.user = user
    request.space = space
    
    with scope(space=space):
        integration_object = YourIntegrationClass(request, "export")
        with open("cookbook/test/other/test_data/your_integration/recipe_file.txt", "rb") as file:
            recipe_bytes = file.read()
            recipe_name = file.name
        buffer = BytesIO(recipe_bytes)
        buffer.name = recipe_name
        recipe_object = integration_object.get_recipe_from_file(buffer)
        # all of your test function logic inside this with clause 
```
though it is not important for you to understand, the u1_s1 is a pytest fixture that can be passed into your tests. By adding it as an argument for the test, pytest will fill that fixture in and you can use it to get a test user using the `auth.get_user()` method

with that you can add your assertions and test it using pytest.

---
# Integration Class Logic
Now that the setup is complete you need to implement the logic on the new Integration class you created.

## get_recipe_from_file method
This function is called by the `Integration.do_import()` class method when a file is imported through the web portal. The `get_recipe_from_file(self, file)` takes only a file as an argument, which is passed from the `Integration.do_import()` as an `IOByte` binary object containing only the binary characters of the contents of the file, as well as a property called `name` that has a "utf-8" string of what the file name was.

### Reading the File
To get the string values of the contents of the file, you must call the following methods:

```python
file_text = file.getvalue().decode("utf-8")
```
Then you can parse the file_text however you see fit for your integration. no need to do a `with open()` statement. The `Integration` parent class handles the opening and the closing of the file.

If your recipe name is dependent on the filename you can access the file name with:
```python
filename = file.name
```

### Managing the Tandoor 'Recipe' Object
The `Recipe` class has many properties, and it is recommended to view all of them [here](https://github.com/TandoorRecipes/recipes/blob/develop/cookbook/models.py) in order to determine specifically how your integration will organize its data into the `Recipe` object data structure. The important ones to know are:
```python
name = models.CharField(max_length=128)
description = models.CharField(max_length=512, blank=True, null=True)
servings = models.IntegerField(default=1)
servings_text = models.CharField(default='', blank=True, max_length=32)
image = models.ImageField(upload_to='recipes/', blank=True, null=True)
keywords = models.ManyToManyField(Keyword, blank=True)
steps = models.ManyToManyField(Step, blank=True)
internal = models.BooleanField(default=False)

created_by = models.ForeignKey(User, on_delete=models.PROTECT)

space = models.ForeignKey(Space, on_delete=models.CASCADE)
```
You can define as many of these variables as you like at the time of creating the Recipe object but the bare minimum should be:

```python
from cookbook.models import Recipe

def get_recipe_from_file(self, file)-> Recipe:
    #opening the file and parsing it
    
    recipe_object = Recipe.objects.create(
        name = your_recipe_name,
        created_by = self.request.user,
        internal = True,
        space = self.request.space,
    )
    
    #translating the parsed file into th recipe object
    
    return recipe_object
```
Both `created_by` and `space` require the request object from the webpage or API that initiated the request, the `Integration` parent class handles both of those and they can be retrieved through the `self.request` property. The `name` allows the recipe to be identifiable from the other recipes, making it uniquely identifiable from other newly instantiated `Recipe` objects. lasty `True` is the default value for `internal` only change it to false if that is what you intend.

After the `Recipe` object is created you can change or add data to its properties, these changes will show up in tests and work as expected until the program drops it from memory. Then all those changes will be lost unless you use the `.save()` method. It is recommended not to use the `.save()` after every change, but instead to make all your changes and call `.save()` once at the end of the `get_recipe_from_file` method before the object gets returned back to the `Integration.do_import()` method.

```python
from cookbook.models import Recipe

def get_recipe_from_file(self, file)-> Recipe:
    #opening the file and parsing it
    
    recipe_object = Recipe.objects.create(
        name = your_recipe_name,
        created_by = self.request.user,
        internal = True,
        space = self.request.space,
    )
    
    # Integration logic, making many changes to the recipe_object
    
    recipe_object.description = "A yummy treat for any day!"
    recipe_object.save()
    return recipe_object
```

### Other Models You Need To Know
The 3 main models other than `Recipe` you will need to use are the `Step`, `Ingredient`(also `Food` and `Unit`- They all go hand in hand), and `Keyword`. The rough structure of these object in a `Recipe` are:

(Note that the `Recipe` object is not a `Json` object, the below is just a representation of the data)
```json
{Recipe: 
    [{"steps": 
        [{Step: [
            {"ingredients": [
                {Ingredient: 
                  {"food": 
                    {Food:
                      [{"name": "food name"},
                        {Unit: {"name": "unit name"}},
                        {"amount": int}
                    ]}}}]},
            {"instruction": "recipe step text"}
        ]}]},
    {"keywords": [
        {Keyword: 
          {"name": "keyword name"}
        }]}]}
```
Below are their bare-bones implementations in regard to creating a `Recipe` object integration.

```python
from cookbook.models import Recipe, Keyword, Step, Ingredient, Food, Unit

def get_recipe_from_file(self, file)-> Recipe:
    #opening the file and parsing it
    
    recipe_object = Recipe.objects.create(
        name = your_recipe_name,
        created_by = self.request.user,
        internal = True,
        space = self.request.space,)
    
    #logic for creating a list of keywords

    for keyword in parsed_file.keywords:
        recipe_object.keywords.add(
            Keyword.objects.get_or_create(
                space=self.request.space, 
                name=keyword)[0])
    
    i = 0
    for line in parsed_file:
        #logic for creating a list of ingredients
        #logic for instructions
        step = Step.objects.create(
            instruction=line.instruction_string, 
            order=i, 
            space=self.request.space, 
            show_ingredients_table=self.request.user.userpreference.show_step_ingredients)
        for ingredient in line.ingredients:
            step.ingredients.add(
                Ingredient.objects.create(
                    food=Food.objects.get_or_create(name=ingredient.name, space=self.request.space)[0],
                    unit=Unit.objects.get_or_create(name=ingredient.quantity.unit, space=self.request.space)[0],
                    amount=ingredient.quantity.amount,
                    space=self.request.space, ))
        recipe_object.steps.add(step)
        i+=1
    recipe_object.save()
    return recipe_object
```

## import_file_name_filter method
Documentation to come.

## get_file_from_recipe method
Documentation to come.

## get_files_from_recipes method
Documentation to come.

---

## File: docs/contribute/contribute.md

If you like this application and want it to give back, there are many ways to contribute.


!!! success "Contribution List"
     If you help bring this project forward you deserve to be credited for it.
     Feel free to add yourself to `CONTRIBUTERS.md` or message me to add you if you have contributed anything.


## Translations

If you know any foreign languages you can:
Improve the translations for any of the existing languages.

See [here](/contribute/translations/) for further information on how to contribute translation to Tandoor.

## Issues and Feature Requests

The most basic but also crucial way of contributing is reporting issues and commenting on ideas and feature requests
over at [GitHub issues](https://github.com/vabene1111/recipes/issues).

Without feedback, improvement can't happen, so don't hesitate to say what you want to say.

## Documentation

Helping improve the documentation for Tandoor is one of the easiest ways to give back and doesn't even require deep technical knowledge.
You can write guides on how to install and configure Tandoor expanding our repository of non-standard configuations.
Or you can write how-to guides using some of Tandoor's advanced features such as authentication or automation.

See [here](/contribute/documentation/) for more information on how to add documentation to Tandoor.


## Contributing Code

For the truly ambitious, you can help write code to fix issues, add additional features, or write your own scripts using
Tandoor's extensive API and share your work with the community.

Guides for contributing specific types of features can be found [here](/contribute/feature_contrib/featureguides/)

Before writing any code, please make sure that you review [contribution guidelines](/contribute/guidelines/) and
[VSCode](/contribute/vscode) or [PyCharm](/contribute/pycharm) specific configurations.

---

## File: docs/contribute/documentation.md

The documentation is built from the markdown files in the [docs](https://github.com/vabene1111/recipes/tree/develop/docs)
folder of the GitHub repository.

In order to contribute to the documentation, there are a couple of methods that you can use.

## Directly on GitHub

You can fork the develop repository and edit the markdown files directly on the GitHub website.

## With an IDE

You can fork the develop repository and edit the markdown files from your favorite IDE such as VSCode or PyCharm.
One advantage of using as IDE is that you can preview your changes by:

### Installing mkdocs

Now install mkdocs and dependencies: `pip install mkdocs-material mkdocs-include-markdown-plugin`.

### Serving Documetation

If you want to test the documentation, locally run `mkdocs serve` from the project root.

## Super Low Tech

Create your documentation in any work processor (or even create a video!) and [open a feature request](https://github.com/vabene1111/recipes/issues)
attaching your document and requesting that someone add the documentation to Tandoor.

---

## File: docs/contribute/guidelines.md

If you want to contribute bug fixes or small tweaks then your pull requests are always welcome!


!!! danger "Discuss First!"
     If you want to contribute larger features that introduce more complexity to the project please
     make sure to **first submit a technical description** outlining what and how you want to do it.
     This allows me and the community to give feedback and manage the complexity of the overall
     application. If you don't do this please don't be mad if I reject your PR.


## License

Contributing to Tandoor requires signing a Contributor License Agreement. You can review the CLA [here](https://cla-assistant.io/TandoorRecipes/recipes).

## Linting & Formatting

Tandoor uses a number of libraries to maintain style and formatting consistency.
To contribute to the project you are required to use the following packages with the project defined configurations:

- flake8
- yapf
- isort
- prettier


!!! tip "Manual Formatting"
     It is possible to run formatting manually, but it is recommended to setup your IDE to format on save.
     ``` bash
     flake8 file.py --ignore=E501 | isort -q file.py | yapf -i file.py
     prettier --write file.vue
    ```

## Testing

Django uses pytest-django to implement a full suite of testing. If you make any functional changes, please implement the appropriate
tests.

Tandoor is also actively soliciting contributors willing to setup vue3 testing. If you have knowledge in this area it would be greatly appreciated.

## API Client


!!! note "JAVA required"
     The OpenAPI Generator is a Java project. You must have the java binary executable available on your PATH for this to work.

Tandoor uses [django-rest-framework](https://www.django-rest-framework.org/) for API implementation. Making contributions that impact the API requires an understanding of
ViewSets and Serializers.

The API Client is generated automatically from the OpenAPI interface provided by the Django REST framework.
For this [openapi-generator](https://github.com/OpenAPITools/openapi-generator) is used.

Install it using your desired setup method. (For example, using `npm install @openapitools/openapi-generator-cli -g`.)

### Vue

Generate the schema using the `generate_api_client.py` script in the main directory. 

## Install and Configuration

Instructions for [VSCode](/contribute/vscode)
Instructions for [PyCharm](/contribute/pycharm)

---

## File: docs/contribute/installation.md

!!! info "Development Setup"
    The dev setup is a little messy as this application combines the best (at least in my opinion) of both Django and Vue.js.

### Devcontainer Setup

There is a [devcontainer](https://containers.dev) set up to ease development. It is optimized for VSCode, but should be able to
be used by other editors as well. Once the container is running, you can do things like start a Django dev server, start a Vue.js
dev server, run python tests, etc. by either using the VSCode tasks below, or manually running commands described in the individual
technology sections below.

In VSCode, simply check out the git repository, and then via the command palette, choose `Dev Containers: Reopen in container`.

If you need to change python dependencies (requierments.txt) or OS packages, you will need to rebuild the container. If you are
changing OS package requirements, you will need to update both the main `Dockerfile` and the `.devcontainer/Dockerfile`.

### Django

This application is developed using the Django framework for Python. They have excellent
[documentation](https://www.djangoproject.com/start/) on how to get started, so I will only give you the basics here.

1. Clone this repository wherever you like and install the Python language for your OS (I recommend using version 3.10 or above).
2. Open it in your favorite editor/IDE (e.g. PyCharm).
   a. If you want, create a virtual environment for all your packages.
3. Install all required packages: `pip install -r requirements.txt`.
4. Run the migrations: `python manage.py migrate`.
5. Start the development server: `python manage.py runserver`.

There is **no** need to set any environment variables. By default, a simple SQLite database is used and all settings are
populated from default values.

### Vue.js

!!! danger "Development Setup"
    The vite dev server **must** be started before the django runserver command is run or else django will **not** recognize it and try to fallback to the build files. 

The frontend is build using [Vue.js](https://vuejs.org/).

In order to work on these pages, you will have to install a Javascript package manager of your choice. The following examples use yarn.

1. go to the `vue3` and run `yarn install` to install the dependencies  
2. run `yarn serve` to start the dev server that allows hot reloading and easy and quick development

If you do not wish to work on those pages, but instead want the application to work properly during development, run `yarn build` to build the frontend pages once. After that you 
might need to run `python manage.py collectstatic` to setup the static files.


### Building Docker Image from Source
Similar to the Vue.js procedure, if you wish to build a docker image from source run you must build the vue3 files first.

1. in your project files navigate to `vue3` and run `yarn install` to install the dependencies
2. run `yarn build` to build the static files for django to use.
3. navigate back to the root directory and run `docker build -t ${tag}:${version} .`

Django's `collectstatic` command is not necessary in this instance as the `Dockerfile`'s entrypoint will collect the static files upon startup on `docker run`.

---

## File: docs/contribute/pycharm.md

PyCharm can be configured to format and lint on save. Doing so requires some manual configuration as outlined below.

## Setup File Watchers

1. Navigate to File -> Settings -> Plugins
2. Download and install [File Watchers](https://plugins.jetbrains.com/plugin/7177-file-watchers)
3. Navigate to File -> Settings -> Tools -> Black
4. Confirm 'Use Black Formatter' is unchecked for both 'On code reformat' and 'On save'

## Setup flake8 Watcher

1. Navigate to File -> Settings -> Tools -> File Watchers
2. Click the '+' to add a new watcher.
3. Configure the watcher as below.

   

4. Navigate to File -> Settings -> Editor -> Inspections -> File watcher problems
5. Under Severity select 'Edit Severities'
6. Click the '+' to add a severity calling it 'Linting Error'
7. Configure a background and effect as below.

   

## Setup isort

1. Navigate to File -> Settings -> Tools -> File Watchers
2. Click the '+' to add a new watcher.
3. Configure the watcher as below.

   

## Setup yapf

1. Navigate to File -> Settings -> Tools -> File Watchers
2. Click the '+' to add a new watcher.
3. Configure the watcher as below.

   


!!! hint
    Adding a comma at the end of a list will trigger yapf to put each element of the list on a new line


!!! note
     In order to debug vue yarn and vite servers must be started before starting the django server.

## Setup prettier

1. Navigate to File -> Settings -> Tools -> File Watchers
2. Click the '+' to add a new watcher.
3. Change 'File Type' to 'Any'.
4. Click the three dots next to 'Scope' to create a custom scope.
5. Click '+' to add a new scope

- Name: prettier
- Pattern: `file:vue/src//*||file:vue3/src//*||file:docs//*`

6. Configure the watcher as below.

   

- Arguments: `--cwd $ProjectFileDir$\vue prettier -w --config $ProjectFileDir$\.prettierrc $FilePath$`

---

## File: docs/contribute/related.md

## Recipe Scraper

While not directly related to Tandoor, we make extensive use of the brilliant [recipe-scrapers](https://github.com/hhursev/recipe-scrapers)
package by hhursev.

If you have the skills to add new sites or help resolve issues you are indirectly helping Tandoor.

## Unofficial mobile apps

### kitshn

Unofficial Tandoor recipes client

Maintained by [Aimo](https://github.com/aimok04/kitshn)

- Website: [https://kitshn.app/](https://kitshn.app/)
- Appstores: [Apple](https://apps.apple.com/us/app/kitshn-for-tandoor/id6740168361), [Android](https://play.google.com/store/apps/details?id=de.kitshn.android)

### Untare (discontinued)

Maintained by [phantomate](https://github.com/phantomate/Untare)

[iPhone](https://apps.apple.com/nl/app/untare/id6448643329?l=en&platform=iphone)
[Android](https://play.google.com/store/apps/details?id=unofficial.tandoor.recipes)

## GPT Recipe

Maintained by [John Pedrie](https://github.com/jdpedrie/gpt-recipe)

Convert pictures of recipes to a structure that can be imported to Tandoor with ChatGPT.

## Tandoor Menu Generator

Maintained by [smilerz](https://github.com/smilerz/tandoor-menu-generator)

Generate a meal plan based on complex criteria and optionally insert it into an SVG menu template.

## Morsl

Maintained by [featurecreep-cron](https://github.com/featurecreep-cron/morsl)

Automate meal planning for Tandoor. Set up profiles with filter rules — rating, books, tags, recently cooked — and generate meal plans that sync directly to Tandoor. Supports automatic generation and a shareable menu for household members.

---

## File: docs/contribute/translations.md

Translations are managed on [translate.tandoor.dev](https://translate.tandoor.dev/), a self hosted instance of [Weblate](https://weblate.org/de/).

You can simply register an account and then follow these steps to add translations:

1. After registering, you are asked to select your languages. This is optional but allows weblate to only show you relevant translations.
2. In the navigation click on `Projects` and then `Browse all projects`.
3. Select Tandoor and on the top-right hand corner, select `Watch project Tandoor` (click on `Not watching`).
4. Go back to the dashboard. It now shows you the relevant translations for your languages. Click on the pencil icon to get started.


!!! info "Creating a new language"
     To create a new language you must first select Tandoor (the project) and then a component.
     Here you will have the option to add the language. Afterwards you can also simply add it to the other components as well.
     Once a new language is (partially) finished let me know on GitHub so I can add it to the language-switcher in Tandoor itself.

There is also [a lot of documentation](https://docs.weblate.org/en/latest/user/translating.html) available from Weblate directly.



It is also possible to provide the translations directly by creating a new language
using `manage.py makemessages -l <language_code> -i venv`. Once finished, simply open a PR with the changed files. This sometimes causes issues merging
with weblate, so I would prefer the use of weblate.