From c7f7bc801e80bce1e9f86150badad13a93fd8ec1 Mon Sep 17 00:00:00 2001 From: rucki Date: Sun, 23 Aug 2026 17:50:21 +0200 Subject: [PATCH] Improve teams settings invites and due tasks --- core/admin.py | 3 +- core/context_processors.py | 24 + core/forms.py | 42 +- core/middleware.py | 24 + .../0012_userpreference_language.py | 18 + core/migrations/0013_userinvite.py | 30 + .../migrations/0014_alter_userinvite_email.py | 18 + .../0015_userpreference_due_first.py | 18 + core/models.py | 30 + core/templates/core/_edit_form.html | 2 +- core/templates/core/_item.html | 4 +- core/templates/core/_tags.html | 6 +- core/templates/core/accept_user_invite.html | 23 + core/templates/core/base.html | 5 +- core/templates/core/index.html | 21 +- core/templates/core/item_editor.html | 2 +- core/templates/core/journal.html | 6 +- core/templates/core/settings.html | 103 ++-- core/templates/core/team_detail.html | 2 +- core/templates/core/team_list.html | 56 ++ core/urls.py | 4 + core/views.py | 176 +++++- locale/de/LC_MESSAGES/django.po | 529 +++++++++++------- locale/en/LC_MESSAGES/django.po | 471 ++++++++++------ my2dos/settings.py | 2 + 25 files changed, 1165 insertions(+), 454 deletions(-) create mode 100644 core/context_processors.py create mode 100644 core/middleware.py create mode 100644 core/migrations/0012_userpreference_language.py create mode 100644 core/migrations/0013_userinvite.py create mode 100644 core/migrations/0014_alter_userinvite_email.py create mode 100644 core/migrations/0015_userpreference_due_first.py create mode 100644 core/templates/core/accept_user_invite.html create mode 100644 core/templates/core/team_list.html diff --git a/core/admin.py b/core/admin.py index 615ca03..d8867b2 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import ApiKey, Attachment, Item, Kanban, Tag, Team, TeamInvite, TeamMembership, UserPreference +from .models import ApiKey, Attachment, Item, Kanban, Tag, Team, TeamInvite, TeamMembership, UserInvite, UserPreference class AttachmentInline(admin.TabularInline): @@ -22,6 +22,7 @@ class ApiKeyAdmin(admin.ModelAdmin): admin.site.register(Tag) +admin.site.register(UserInvite) admin.site.register(Team) admin.site.register(TeamMembership) admin.site.register(TeamInvite) diff --git a/core/context_processors.py b/core/context_processors.py new file mode 100644 index 0000000..3c883b5 --- /dev/null +++ b/core/context_processors.py @@ -0,0 +1,24 @@ +from django.urls import reverse +from django.utils.translation import gettext as _ + +from .models import UserPreference + + +def overview_navigation(request): + if not request.user.is_authenticated: + return {'nav_overview_labels': [_('Notes'), _('Tasks'), _('Links')], 'nav_hidden_links': []} + prefs, _created = UserPreference.objects.get_or_create(user=request.user) + overview = [] + hidden = [] + task_shown = prefs.show_open_todos or prefs.show_done_todos + items = [ + (task_shown, _('Tasks'), f"{reverse('index')}?kind=todo"), + (prefs.show_notes, _('Notes'), f"{reverse('index')}?kind=note"), + (prefs.show_links, _('Links'), f"{reverse('index')}?kind=link"), + ] + for shown, label, url in items: + if shown: + overview.append(label) + else: + hidden.append({'label': label, 'url': url}) + return {'nav_overview_labels': overview or [_('Overview')], 'nav_hidden_links': hidden} diff --git a/core/forms.py b/core/forms.py index 283eb90..8d9db24 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,7 +1,8 @@ from django import forms +from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django.utils.translation import gettext_lazy as _ -from .models import ApiKey, Attachment, Item, Kanban, Tag, Team, TeamInvite, UserPreference +from .models import ApiKey, Attachment, Item, Kanban, Tag, Team, TeamInvite, UserInvite, UserPreference class MultipleFileInput(forms.ClearableFileInput): @@ -38,17 +39,12 @@ class EditItemForm(forms.ModelForm): class Meta: model = Item - fields = ['content', 'comment', 'visibility', 'due_at', 'url'] + fields = ['content', 'comment', 'visibility', 'url'] widgets = { 'comment': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), - 'due_at': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'), 'url': forms.URLInput(attrs={'class': 'form-control', 'placeholder': 'URL'}), } - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['due_at'].input_formats = ['%Y-%m-%dT%H:%M'] - class UserSettingsForm(forms.ModelForm): class Meta: @@ -60,16 +56,42 @@ class UserSettingsForm(forms.ModelForm): class UserPreferenceForm(forms.ModelForm): class Meta: model = UserPreference - fields = ['show_notes', 'show_open_todos', 'show_done_todos', 'show_links', 'show_journal', 'overview_lines'] + fields = ['language', 'show_notes', 'show_open_todos', 'show_done_todos', 'show_links', 'due_first', 'overview_lines'] labels = { + 'language': _('Language'), 'show_notes': _('Show notes'), 'show_open_todos': _('Show open tasks'), 'show_done_todos': _('Show completed tasks'), 'show_links': _('Show links'), - 'show_journal': _('Show journal'), + 'due_first': _('Always show due items first'), 'overview_lines': _('Lines in overview'), } - widgets = {'overview_lines': forms.NumberInput(attrs={'class': 'form-control', 'min': 1, 'max': 50})} + widgets = { + 'language': forms.Select(attrs={'class': 'form-select'}), + 'overview_lines': forms.NumberInput(attrs={'class': 'form-control', 'min': 1, 'max': 50}), + } + + +class UserInviteForm(forms.ModelForm): + email = forms.EmailField(required=True, label=_('Email'), widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'person@example.com', 'required': True})) + + class Meta: + model = UserInvite + fields = ['email'] + + +class InviteRegistrationForm(UserCreationForm): + email = forms.EmailField(required=False, label=_('Email'), widget=forms.EmailInput(attrs={'class': 'form-control'})) + + class Meta: + model = User + fields = ['username', 'email', 'password1', 'password2'] + widgets = {'username': forms.TextInput(attrs={'class': 'form-control'})} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for name in ['password1', 'password2']: + self.fields[name].widget.attrs.update({'class': 'form-control'}) class TeamForm(forms.ModelForm): diff --git a/core/middleware.py b/core/middleware.py new file mode 100644 index 0000000..2df8c6b --- /dev/null +++ b/core/middleware.py @@ -0,0 +1,24 @@ +from django.utils import translation + + +class UserLanguageMiddleware: + """Prefer the authenticated user's saved language over browser/cookie language.""" + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + user = getattr(request, 'user', None) + language = '' + if user and user.is_authenticated: + try: + language = user.preferences.language + except Exception: + language = '' + if language: + translation.activate(language) + request.LANGUAGE_CODE = language + response = self.get_response(request) + if language: + response.setdefault('Content-Language', language) + return response diff --git a/core/migrations/0012_userpreference_language.py b/core/migrations/0012_userpreference_language.py new file mode 100644 index 0000000..53c6c50 --- /dev/null +++ b/core/migrations/0012_userpreference_language.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.17 on 2026-08-23 13:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0011_alter_item_kanban_status_alter_item_kind_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='userpreference', + name='language', + field=models.CharField(blank=True, choices=[('', 'Browser default'), ('de', 'Deutsch'), ('en', 'English')], default='', max_length=10), + ), + ] diff --git a/core/migrations/0013_userinvite.py b/core/migrations/0013_userinvite.py new file mode 100644 index 0000000..10ff4f0 --- /dev/null +++ b/core/migrations/0013_userinvite.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.17 on 2026-08-23 13:53 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0012_userpreference_language'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserInvite', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(blank=True, max_length=254)), + ('token', models.CharField(editable=False, max_length=80, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('accepted_at', models.DateTimeField(blank=True, null=True)), + ('invited_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_user_invites', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/core/migrations/0014_alter_userinvite_email.py b/core/migrations/0014_alter_userinvite_email.py new file mode 100644 index 0000000..8254ad6 --- /dev/null +++ b/core/migrations/0014_alter_userinvite_email.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.17 on 2026-08-23 14:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0013_userinvite'), + ] + + operations = [ + migrations.AlterField( + model_name='userinvite', + name='email', + field=models.EmailField(max_length=254), + ), + ] diff --git a/core/migrations/0015_userpreference_due_first.py b/core/migrations/0015_userpreference_due_first.py new file mode 100644 index 0000000..baa0563 --- /dev/null +++ b/core/migrations/0015_userpreference_due_first.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.17 on 2026-08-23 14:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0014_alter_userinvite_email'), + ] + + operations = [ + migrations.AddField( + model_name='userpreference', + name='due_first', + field=models.BooleanField(default=True), + ), + ] diff --git a/core/models.py b/core/models.py index 07b77c2..628cf9f 100644 --- a/core/models.py +++ b/core/models.py @@ -3,6 +3,7 @@ import secrets from django.conf import settings from django.db import models from django.urls import reverse +from django.utils import timezone from django.utils.translation import gettext_lazy as _ TAG_RE = re.compile(r'(?{% trans "Private" %} - {% if item.kind == 'todo' %}
{% endif %} + {% if item.kind == 'todo' %}
{% endif %}
{% if item.kind == 'link' %}{% endif %} diff --git a/core/templates/core/_item.html b/core/templates/core/_item.html index e5fc7c3..f267785 100644 --- a/core/templates/core/_item.html +++ b/core/templates/core/_item.html @@ -1,5 +1,5 @@ {% load markdown_extras i18n %} -
+
@@ -14,7 +14,7 @@ {% if overview_lines %}{% endif %}
{% if item.comment %}
{{ item.comment|markdown }}
{% endif %} - {% if item.due_at %}
{% trans "Due:" %} {{ item.due_at|date:'d.m.Y H:i' }}
{% endif %} + {% if item.due_at %}
{% if item.is_overdue %}{% trans "Overdue" %}{% elif item.is_due_today %}{% trans "Due today" %}{% endif %}{% trans "Due:" %} {{ item.due_at|date:'d.m.Y H:i' }}
{% endif %} {% if item.url %}{{ item.url }}{% endif %} {% if item.linked_items.all %}
{% trans "Links:" %} {% for linked in item.linked_items.all %}#{{ linked.id }} {% endfor %}
{% endif %} {% if item.attachments.all %} diff --git a/core/templates/core/_tags.html b/core/templates/core/_tags.html index b3f98dd..771d41b 100644 --- a/core/templates/core/_tags.html +++ b/core/templates/core/_tags.html @@ -1,10 +1,10 @@ {% load querystring i18n %}
{% for tag in tags %} - {% if active_tag == tag.name %} - #{{ tag.name }} × + {% if tag.name in active_tags %} + #{{ tag.name }} × {% else %} - #{{ tag.name }} + #{{ tag.name }} {% endif %} {% empty %}{% trans "No tags yet" %}{% endfor %}
diff --git a/core/templates/core/accept_user_invite.html b/core/templates/core/accept_user_invite.html new file mode 100644 index 0000000..3efa9b6 --- /dev/null +++ b/core/templates/core/accept_user_invite.html @@ -0,0 +1,23 @@ +{% extends 'core/base.html' %} +{% load i18n %} +{% block content %} +
+
+

{% trans "Create account" %}

+

{% trans "You were invited to my2dos. Create your account to continue." %}

+
+ {% csrf_token %} + {% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% for error in field.errors %}
{{ error }}
{% endfor %} +
+ {% endfor %} + +
+
+
+{% endblock %} diff --git a/core/templates/core/base.html b/core/templates/core/base.html index 6967eff..cb00f6d 100644 --- a/core/templates/core/base.html +++ b/core/templates/core/base.html @@ -17,11 +17,11 @@ - +
{% for message in messages %}
{{ message }}
{% endfor %}{% block content %}{% endblock %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 042570a..d13a9c1 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -9,17 +9,19 @@
diff --git a/core/templates/core/item_editor.html b/core/templates/core/item_editor.html index af8e249..2e548d6 100644 --- a/core/templates/core/item_editor.html +++ b/core/templates/core/item_editor.html @@ -23,7 +23,7 @@
- {% if item.kind == 'todo' %}
{% endif %} + {% if item.kind == 'todo' %}
{% endif %}
{% if item.kind == 'link' %}{% endif %} diff --git a/core/templates/core/journal.html b/core/templates/core/journal.html index cc50e48..8511170 100644 --- a/core/templates/core/journal.html +++ b/core/templates/core/journal.html @@ -3,18 +3,18 @@ {% block content %}

Journal {{ selected_day }}

diff --git a/core/templates/core/settings.html b/core/templates/core/settings.html index cb40508..a7b5ef4 100644 --- a/core/templates/core/settings.html +++ b/core/templates/core/settings.html @@ -2,60 +2,87 @@ {% load i18n %} {% block content %} ← {% trans "back" %} -
-
-
-

{% trans "User settings" %}

- +
+ + +
+
+

{% trans "Profile" %}

+

{% trans "Manage your personal user data." %}

+ {% csrf_token %} -
{{ profile_form.first_name }}
-
{{ profile_form.last_name }}
-
{{ profile_form.email }}
- +
{{ profile_form.first_name }}
+
{{ profile_form.last_name }}
+
{{ profile_form.email }}
+
-
-
-
-

{% trans "Default view" %}

-
+ +
+

{% trans "View & language" %}

+

{% trans "Choose your language and configure the default overview." %}

+ {% csrf_token %} - {% for field in preference_form %} - {% if field.field.widget.input_type == 'checkbox' %} - - {% else %} -
{{ field }}
- {% endif %} - {% endfor %} - +
+ {% for field in preference_form %} + {% if field.name == 'language' or field.name == 'overview_lines' %} +
{{ field }}
+ {% endif %} + {% endfor %} +
+
{% trans "Sorting" %}
+ +
+
+
+
{% trans "Show in overview" %}
+
+ {% for field in preference_form %} + {% if field.field.widget.input_type == 'checkbox' and field.name != 'due_first' %} +
+ {% endif %} + {% endfor %} +
+
+
-
-

{% trans "Teams" %}

-
- {% csrf_token %} -
{{ team_form.name }}
-
{% trans "This tag automatically turns items into team items, e.g. #my2dos." %}
{{ team_form.slug }}
- + +
+

{% trans "User invitations" %}

+

{% trans "Invite new users during the test phase. Only people with an invitation link can register." %}

+ + {% csrf_token %} +
{{ user_invite_form.email }}
+
- {% for team in teams %}{{ team.name }} #{{ team.slug }}{% empty %}{% trans "No teams yet." %}{% endfor %} + {% for invite in user_invites %}
{{ invite.email }}
{% csrf_token %}
{{ request.scheme }}://{{ request.get_host }}{% url 'accept_user_invite' invite.token %}
{% empty %}{% trans "No open invitations." %}{% endfor %}
-
-

{% trans "Create API key" %}

-
+ +
+

{% trans "API keys" %}

+

{% trans "Create API keys for integrations and optionally restrict them to tags." %}

+ {% csrf_token %}
{{ key_form.name }}
{% trans "No selection = access to all own items." %}
{{ key_form.tags }}
- + -
-
-

{% trans "API keys" %}

{% for key in api_keys %} -
+
{{ key.name }}
{% csrf_token %}
{{ key.token }}
{% trans "Tags" %}: {% for tag in key.tags.all %}#{{ tag.name }} {% empty %}{% trans "all" %}{% endfor %}
diff --git a/core/templates/core/team_detail.html b/core/templates/core/team_detail.html index 2fdeb66..87c3740 100644 --- a/core/templates/core/team_detail.html +++ b/core/templates/core/team_detail.html @@ -1,7 +1,7 @@ {% extends 'core/base.html' %} {% load i18n %} {% block content %} -← {% trans "Settings" %} +← {% trans "Teams" %}

{% trans "Team" %} {{ team.name }}

diff --git a/core/templates/core/team_list.html b/core/templates/core/team_list.html new file mode 100644 index 0000000..cc944eb --- /dev/null +++ b/core/templates/core/team_list.html @@ -0,0 +1,56 @@ +{% extends 'core/base.html' %} +{% load i18n %} +{% block content %} +
+
+

{% trans "Teams" %}

+
{% trans "All teams you belong to." %}
+
+ ← {% trans "back" %} +
+ +
+
+
+

{% trans "Create team" %}

+
+ {% csrf_token %} +
{{ team_form.name }}
+
{{ team_form.slug }}
{% trans "This tag automatically turns items into team items, e.g. #my2dos." %}
+ +
+
+
+ +
+
+ {% for s in stats %} + + {% empty %} +
{% trans "You are not a member of any team yet." %}
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index f9e5702..698caa1 100644 --- a/core/urls.py +++ b/core/urls.py @@ -6,13 +6,17 @@ urlpatterns = [ path('', views.index, name='index'), path('login/', auth_views.LoginView.as_view(template_name='core/login.html'), name='login'), path('logout/', auth_views.LogoutView.as_view(), name='logout'), + path('invite//', views.accept_user_invite, name='accept_user_invite'), path('new/', views.new_item, name='new_item'), path('journal/', views.journal, name='journal'), path('kanban/', views.kanban_list, name='kanban_list'), path('kanban//', views.kanban_detail, name='kanban_detail'), path('kanban//move//', views.kanban_move, name='kanban_move'), + path('language/', views.set_user_language, name='set_user_language'), path('settings/', views.settings_view, name='settings'), path('settings/api-keys//delete/', views.delete_api_key, name='delete_api_key'), + path('settings/invites//delete/', views.delete_user_invite, name='delete_user_invite'), + path('teams/', views.team_list, name='team_list'), path('teams//', views.team_detail, name='team_detail'), path('teams/invite//', views.team_accept_invite, name='team_accept_invite'), path('items//', views.item_detail, name='item_detail'), diff --git a/core/views.py b/core/views.py index 1662df4..9019b6c 100644 --- a/core/views.py +++ b/core/views.py @@ -2,21 +2,24 @@ import json import re from functools import wraps from calendar import Calendar -from datetime import date +from datetime import date, datetime, time from html import unescape from urllib.request import Request, urlopen +from django.conf import settings from django.contrib import messages +from django.contrib.auth import login from django.contrib.auth.decorators import login_required -from django.db.models import Q +from django.db.models import Case, IntegerField, Q, Value, When from django.http import HttpResponse, JsonResponse from django.urls import reverse from django.utils import timezone +from django.utils import translation from django.utils.translation import gettext as _ from django.shortcuts import get_object_or_404, redirect, render from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods, require_POST -from .forms import ApiKeyForm, EditItemForm, KanbanForm, QuickItemForm, TeamForm, TeamInviteForm, UserPreferenceForm, UserSettingsForm -from .models import ApiKey, Attachment, Item, Kanban, Tag, Team, TeamInvite, TeamMembership, UserPreference +from .forms import ApiKeyForm, EditItemForm, InviteRegistrationForm, KanbanForm, QuickItemForm, TeamForm, TeamInviteForm, UserInviteForm, UserPreferenceForm, UserSettingsForm +from .models import ApiKey, Attachment, Item, Kanban, Tag, Team, TeamInvite, TeamMembership, UserInvite, UserPreference COMMAND_RE = re.compile(r'/(todo|note|link|journal|public|private)\b', re.I) URL_RE = re.compile(r'https?://\S+') @@ -51,6 +54,19 @@ def parse_quick_content(raw): return kind, visibility, content, url +def parse_due_at_from_post(post): + due_date = (post.get('due_date') or '').strip() + due_time = (post.get('due_time') or '').strip() + if not due_date: + return None + try: + parsed_date = date.fromisoformat(due_date) + parsed_time = time.fromisoformat(due_time) if due_time else time(23, 59) + return timezone.make_aware(datetime.combine(parsed_date, parsed_time)) + except ValueError: + return None + + def attach_files_and_replace_tokens(item, files): replacements = {} for file in files: @@ -154,7 +170,9 @@ def index(request): response['HX-Trigger'] = 'tagsChanged' return response return redirect('index') - tag = request.GET.get('tag') + active_tags = [t.strip().lower() for t in request.GET.getlist('tag') if t.strip()] + active_tag_mode = 'all' if request.GET.get('tag_mode') == 'all' else 'any' + tag = active_tags[0] if active_tags else None kind = request.GET.get('kind') status = request.GET.get('status') day = request.GET.get('day') @@ -165,14 +183,18 @@ def index(request): query.pop('q', None) return redirect(f'{request.path}?{query.urlencode()}') items = visible_items(request.user) + prefs, created = UserPreference.objects.get_or_create(user=request.user) applied_preferences = False - if tag: - items = items.filter(tags__name=tag) + if active_tags: + if active_tag_mode == 'all': + for tag_name in active_tags: + items = items.filter(tags__name=tag_name) + else: + items = items.filter(tags__name__in=active_tags).distinct() if kind in dict(Item.Kind.choices): items = items.filter(kind=kind) elif not any([tag, status, q, day]): applied_preferences = True - prefs, _ = UserPreference.objects.get_or_create(user=request.user) type_filter = Q() if prefs.show_notes: type_filter |= Q(kind=Item.Kind.NOTE) @@ -189,15 +211,24 @@ def index(request): items = items.filter(created_at__date=day) if status == 'open': items = items.filter(kind=Item.Kind.TODO, is_done=False) + elif status == 'due': + today_end = timezone.make_aware(datetime.combine(timezone.localdate(), time(23, 59, 59))) + items = items.filter(kind=Item.Kind.TODO, is_done=False, due_at__lte=today_end).order_by('due_at', '-created_at') elif status == 'archive': items = items.filter(kind=Item.Kind.TODO, is_done=True) elif not applied_preferences: items = items.exclude(kind=Item.Kind.TODO, is_done=True) if q: items = items.filter(Q(content__icontains=q) | Q(url__icontains=q) | Q(tags__name__icontains=q)).distinct() - prefs, _ = UserPreference.objects.get_or_create(user=request.user) + today_end = timezone.make_aware(datetime.combine(timezone.localdate(), time(23, 59, 59))) + due_count = items.filter(kind=Item.Kind.TODO, is_done=False, due_at__lte=today_end).count() + if prefs.due_first and status != 'archive': + items = items.annotate(due_rank=Case( + When(kind=Item.Kind.TODO, is_done=False, due_at__lte=today_end, then=Value(0)), + default=Value(1), output_field=IntegerField(), + )).order_by('due_rank', 'due_at', '-created_at') return render(request, 'core/index.html', { - 'form': QuickItemForm(), 'items': items, 'tags': Tag.objects.all(), 'active_tag': tag, 'active_kind': kind, 'active_status': status, 'active_day': day, 'q': q, 'overview_lines': prefs.overview_lines, + 'form': QuickItemForm(), 'items': items, 'tags': Tag.objects.all(), 'active_tag': tag, 'active_tags': active_tags, 'active_tag_mode': active_tag_mode, 'active_kind': kind, 'active_status': status, 'active_day': day, 'q': q, 'due_count': due_count, 'overview_lines': prefs.overview_lines, }) @@ -254,13 +285,32 @@ def journal(request): weeks = [[{'day': d, 'date': date(year, month, d).isoformat() if d else '', 'has_entries': d and date(year, month, d) in activity_dates} for d in week] for week in Calendar(firstweekday=0).monthdayscalendar(year, month)] prev_month, prev_year = (12, year - 1) if month == 1 else (month - 1, year) next_month, next_year = (1, year + 1) if month == 12 else (month + 1, year) - prefs, _ = UserPreference.objects.get_or_create(user=request.user) + prefs, created = UserPreference.objects.get_or_create(user=request.user) return render(request, 'core/journal.html', { 'form': QuickItemForm(), 'items': items, 'tags': Tag.objects.all(), 'weeks': weeks, 'year': year, 'month': month, 'selected_day': selected_day, 'prev_year': prev_year, 'prev_month': prev_month, 'next_year': next_year, 'next_month': next_month, 'overview_lines': prefs.overview_lines, }) +@require_POST +def set_user_language(request): + language = request.POST.get('language') + next_url = request.POST.get('next') or '/' + if language not in {'de', 'en', ''}: + language = '' + if request.user.is_authenticated: + prefs, created = UserPreference.objects.get_or_create(user=request.user) + prefs.language = language + prefs.save(update_fields=['language']) + response = redirect(next_url) + if language: + translation.activate(language) + response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) + else: + response.delete_cookie(settings.LANGUAGE_COOKIE_NAME) + return response + + @login_required def settings_view(request): if request.method == 'POST': @@ -271,7 +321,7 @@ def settings_view(request): messages.success(request, _('Settings saved.')) return redirect('settings') elif request.POST.get('action') == 'preferences': - prefs, _ = UserPreference.objects.get_or_create(user=request.user) + prefs, created = UserPreference.objects.get_or_create(user=request.user) pref_form = UserPreferenceForm(request.POST, instance=prefs) if pref_form.is_valid(): pref_form.save() @@ -286,24 +336,22 @@ def settings_view(request): key_form.save_m2m() messages.success(request, _('API key created: %(token)s') % {'token': api_key.token}) return redirect('settings') - elif request.POST.get('action') == 'team': - team_form = TeamForm(request.POST) - if team_form.is_valid(): - team = team_form.save(commit=False) - team.owner = request.user - team.save() - TeamMembership.objects.create(team=team, user=request.user, role=TeamMembership.Role.OWNER) - Tag.objects.get_or_create(name=team.slug) - messages.success(request, _('Team %(team)s created. Use #%(slug)s for team items.') % {'team': team.name, 'slug': team.slug}) - return redirect('team_detail', pk=team.pk) - prefs, _ = UserPreference.objects.get_or_create(user=request.user) + elif request.POST.get('action') == 'userinvite': + invite_form = UserInviteForm(request.POST) + if invite_form.is_valid(): + invite = invite_form.save(commit=False) + invite.invited_by = request.user + invite.save() + messages.success(request, _('Invitation link created: %(url)s') % {'url': request.build_absolute_uri(reverse("accept_user_invite", args=[invite.token]))}) + return redirect('settings') + prefs, created = UserPreference.objects.get_or_create(user=request.user) return render(request, 'core/settings.html', { 'profile_form': UserSettingsForm(instance=request.user), 'preference_form': UserPreferenceForm(instance=prefs), 'key_form': ApiKeyForm(), - 'team_form': TeamForm(), + 'user_invite_form': UserInviteForm(), + 'user_invites': request.user.sent_user_invites.filter(accepted_at__isnull=True), 'api_keys': request.user.api_keys.prefetch_related('tags'), - 'teams': Team.objects.filter(memberships__user=request.user).distinct(), }) @@ -315,6 +363,68 @@ def delete_api_key(request, pk): return redirect('settings') +@login_required +@require_POST +def delete_user_invite(request, pk): + invite = get_object_or_404(UserInvite, pk=pk, invited_by=request.user, accepted_at__isnull=True) + invite.delete() + if request.headers.get('HX-Request'): + return HttpResponse('') + messages.success(request, _('Invitation deleted.')) + return redirect('settings') + + +def accept_user_invite(request, token): + invite = get_object_or_404(UserInvite, token=token, accepted_at__isnull=True) + if request.user.is_authenticated: + invite.accepted_at = timezone.now() + invite.save(update_fields=['accepted_at']) + messages.success(request, _('Invitation accepted.')) + return redirect('index') + if request.method == 'POST': + form = InviteRegistrationForm(request.POST) + if form.is_valid(): + user = form.save(commit=False) + if invite.email and not user.email: + user.email = invite.email + user.save() + invite.accepted_at = timezone.now() + invite.save(update_fields=['accepted_at']) + login(request, user) + messages.success(request, _('Welcome to my2dos.')) + return redirect('index') + else: + form = InviteRegistrationForm(initial={'email': invite.email}) + return render(request, 'core/accept_user_invite.html', {'invite': invite, 'form': form}) + + +@login_required +def team_list(request): + if request.method == 'POST': + team_form = TeamForm(request.POST) + if team_form.is_valid(): + team = team_form.save(commit=False) + team.owner = request.user + team.save() + TeamMembership.objects.create(team=team, user=request.user, role=TeamMembership.Role.OWNER) + Tag.objects.get_or_create(name=team.slug) + messages.success(request, _('Team %(team)s created. Use #%(slug)s for team items.') % {'team': team.name, 'slug': team.slug}) + return redirect('team_detail', pk=team.pk) + teams = Team.objects.filter(memberships__user=request.user).distinct().prefetch_related('memberships__user') + stats = [] + base_items = visible_items(request.user) + for team in teams: + team_items = base_items.filter(team=team) + stats.append({ + 'team': team, + 'members_count': team.memberships.count(), + 'items_count': team_items.count(), + 'open_todos_count': team_items.filter(kind=Item.Kind.TODO, is_done=False).count(), + 'recent_items': team_items[:3], + }) + return render(request, 'core/team_list.html', {'stats': stats, 'team_form': TeamForm(), 'overview_lines': request.user.preferences.overview_lines if hasattr(request.user, 'preferences') else 4}) + + @login_required def team_detail(request, pk): membership = get_object_or_404(TeamMembership.objects.select_related('team'), team_id=pk, user=request.user) @@ -384,7 +494,11 @@ def edit_item(request, pk): if request.method == 'POST': form = EditItemForm(request.POST, request.FILES, instance=item) if form.is_valid(): - item = form.save() + item = form.save(commit=False) + if item.kind == Item.Kind.TODO: + item.due_at = parse_due_at_from_post(request.POST) + item.save() + form.save_m2m() attach_files_and_replace_tokens(item, request.FILES.getlist('files')) item.sync_metadata() apply_team_from_tags(item, request.user, force_private=item.visibility == Item.Visibility.PRIVATE) @@ -403,7 +517,11 @@ def item_editor(request, pk): if request.method == 'POST': form = EditItemForm(request.POST, request.FILES, instance=item) if form.is_valid(): - item = form.save() + item = form.save(commit=False) + if item.kind == Item.Kind.TODO: + item.due_at = parse_due_at_from_post(request.POST) + item.save() + form.save_m2m() attach_files_and_replace_tokens(item, request.FILES.getlist('files')) item.sync_metadata() apply_team_from_tags(item, request.user, force_private=item.visibility == Item.Visibility.PRIVATE) @@ -468,7 +586,9 @@ def kanban_move(request, pk, item_pk): @login_required def tag_list(request): - return render(request, 'core/_tags.html', {'tags': Tag.objects.all(), 'active_tag': request.GET.get('tag')}) + active_tags = [t.strip().lower() for t in request.GET.getlist('tag') if t.strip()] + active_tag_mode = 'all' if request.GET.get('tag_mode') == 'all' else 'any' + return render(request, 'core/_tags.html', {'tags': Tag.objects.all(), 'active_tag': active_tags[0] if active_tags else None, 'active_tags': active_tags, 'active_tag_mode': active_tag_mode}) @login_required diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 694b1b4..5b9b2be 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-23 14:50+0200\n" +"POT-Creation-Date: 2026-08-23 16:56+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,109 +17,143 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: core/forms.py:28 -msgid "Write something … /todo /link /public #tag [[note:1]]" -msgstr "Schreibe etwas … /todo /link /public #tag [[note:1]]" +#: core/context_processors.py:9 core/context_processors.py:16 +#: core/templates/core/index.html:16 +msgid "Notes" +msgstr "Notizen" -#: core/forms.py:65 -msgid "Show notes" -msgstr "Notizen anzeigen" +#: core/context_processors.py:9 core/context_processors.py:15 +#| msgid "Task" +msgid "Tasks" +msgstr "Aufgaben" -#: core/forms.py:66 -msgid "Show open tasks" -msgstr "Offene Aufgaben anzeigen" +#: core/context_processors.py:9 core/context_processors.py:17 +#| msgid "Links:" +msgid "Links" +msgstr "Links" -#: core/forms.py:67 -msgid "Show completed tasks" -msgstr "Erledigte Aufgaben anzeigen" - -#: core/forms.py:68 -msgid "Show links" -msgstr "Links anzeigen" - -#: core/forms.py:69 -msgid "Show journal" -msgstr "Journal anzeigen" - -#: core/forms.py:70 -msgid "Lines in overview" -msgstr "Zeilen in der Übersicht" - -#: core/forms.py:79 core/templates/core/settings.html:37 -msgid "Team name" -msgstr "Teamname" - -#: core/forms.py:79 core/templates/core/settings.html:38 -msgid "Team tag" -msgstr "Team-Tag" - -#: core/forms.py:93 core/templates/core/settings.html:13 -msgid "Email" -msgstr "E-Mail" - -#: core/forms.py:98 -msgid "Tag" -msgstr "Tag" - -#: core/forms.py:103 -msgid "Kanban name" -msgstr "Name des Kanbans" - -#: core/forms.py:117 -msgid "API key name" -msgstr "Name des API Keys" - -#: core/models.py:75 core/templates/core/index.html:5 -#: core/templates/core/index.html:53 core/templates/core/journal.html:25 -msgid "Task" -msgstr "Aufgabe" - -#: core/models.py:76 core/templates/core/index.html:6 -#: core/templates/core/index.html:54 core/templates/core/journal.html:26 -msgid "Note" -msgstr "Notiz" - -#: core/models.py:77 -msgid "Link" -msgstr "Link" - -#: core/models.py:78 core/templates/core/base.html:24 +#: core/context_processors.py:18 core/models.py:98 +#: core/templates/core/base.html:24 msgid "Journal" msgstr "Journal" -#: core/models.py:81 core/templates/core/_edit_form.html:20 +#: core/context_processors.py:25 +#| msgid "Overdue" +msgid "Overview" +msgstr "Überfällig" + +#: core/forms.py:29 +msgid "Write something … /todo /link /public #tag [[note:1]]" +msgstr "Schreibe etwas … /todo /link /public #tag [[note:1]]" + +#: core/forms.py:61 +msgid "Language" +msgstr "Sprache" + +#: core/forms.py:62 +msgid "Show notes" +msgstr "Notizen anzeigen" + +#: core/forms.py:63 +msgid "Show open tasks" +msgstr "Offene Aufgaben anzeigen" + +#: core/forms.py:64 +msgid "Show completed tasks" +msgstr "Erledigte Aufgaben anzeigen" + +#: core/forms.py:65 +msgid "Show links" +msgstr "Links anzeigen" + +#: core/forms.py:66 +msgid "Show journal" +msgstr "Journal anzeigen" + +#: core/forms.py:67 +msgid "Always show due items first" +msgstr "Fällige immer oben" + +#: core/forms.py:68 +msgid "Lines in overview" +msgstr "Zeilen in der Übersicht" + +#: core/forms.py:77 core/forms.py:85 core/forms.py:116 +#: core/templates/core/settings.html:26 core/templates/core/settings.html:66 +msgid "Email" +msgstr "E-Mail" + +#: core/forms.py:102 core/templates/core/team_list.html:18 +msgid "Team name" +msgstr "Teamname" + +#: core/forms.py:102 core/templates/core/team_list.html:19 +msgid "Team tag" +msgstr "Team-Tag" + +#: core/forms.py:121 +msgid "Tag" +msgstr "Tag" + +#: core/forms.py:126 +msgid "Kanban name" +msgstr "Name des Kanbans" + +#: core/forms.py:140 +msgid "API key name" +msgstr "Name des API Keys" + +#: core/models.py:95 core/templates/core/index.html:5 +#: core/templates/core/index.html:64 core/templates/core/journal.html:25 +msgid "Task" +msgstr "Aufgabe" + +#: core/models.py:96 core/templates/core/index.html:6 +#: core/templates/core/index.html:65 core/templates/core/journal.html:26 +msgid "Note" +msgstr "Notiz" + +#: core/models.py:97 +msgid "Link" +msgstr "Link" + +#: core/models.py:101 core/templates/core/_edit_form.html:20 #: core/templates/core/item_editor.html:25 msgid "Private" msgstr "Privat" -#: core/models.py:82 core/templates/core/team_detail.html:7 +#: core/models.py:102 core/templates/core/team_detail.html:7 msgid "Team" msgstr "Team" -#: core/models.py:83 core/templates/core/_edit_form.html:21 +#: core/models.py:103 core/templates/core/_edit_form.html:21 #: core/templates/core/item_editor.html:25 msgid "Public" msgstr "Öffentlich" -#: core/models.py:92 core/templates/core/kanban_detail.html:7 +#: core/models.py:112 core/templates/core/kanban_detail.html:7 msgid "Inbox" msgstr "Eingang" -#: core/models.py:94 +#: core/models.py:114 msgid "In Progress" msgstr "In Bearbeitung" -#: core/models.py:95 core/templates/core/kanban_detail.html:10 +#: core/models.py:115 core/templates/core/kanban_detail.html:10 #: core/templates/core/kanban_list.html:7 msgid "Done" msgstr "Erledigt" +#: core/models.py:167 +msgid "Browser default" +msgstr "Browser-Standard" + #: core/templates/core/_edit_form.html:5 #: core/templates/core/item_editor.html:11 msgid "Type:" msgstr "Typ:" -#: core/templates/core/_edit_form.html:7 core/templates/core/index.html:58 +#: core/templates/core/_edit_form.html:7 core/templates/core/index.html:69 #: core/templates/core/item_editor.html:13 core/templates/core/journal.html:29 #: core/templates/core/new_item.html:13 msgid "Add tags" @@ -135,18 +169,23 @@ msgid "Comment, e.g. same task already captured [[12]]" msgstr "Kommentar, z.B. gleiche Aufgabe bereits erfasst [[12]]" #: core/templates/core/_edit_form.html:23 -msgid "Date/time for todo" -msgstr "Datum/Zeit für Todo" +#: core/templates/core/item_editor.html:26 +msgid "Due date" +msgstr "Fälligkeitsdatum" -#: core/templates/core/_edit_form.html:27 core/templates/core/index.html:69 +#: core/templates/core/_edit_form.html:23 +#: core/templates/core/item_editor.html:26 +msgid "Time optional" +msgstr "Uhrzeit optional" + +#: core/templates/core/_edit_form.html:27 core/templates/core/index.html:80 msgid "Image from clipboard:" msgstr "Bild aus Zwischenablage:" -#: core/templates/core/_edit_form.html:29 core/templates/core/index.html:67 +#: core/templates/core/_edit_form.html:29 core/templates/core/index.html:78 #: core/templates/core/item_editor.html:9 #: core/templates/core/item_editor.html:31 core/templates/core/journal.html:32 #: core/templates/core/new_item.html:10 core/templates/core/new_item.html:30 -#: core/templates/core/settings.html:14 msgid "Save" msgstr "Speichern" @@ -168,6 +207,14 @@ msgstr "weniger anzeigen" msgid "show more" msgstr "mehr anzeigen" +#: core/templates/core/_item.html:17 +msgid "Overdue" +msgstr "Überfällig" + +#: core/templates/core/_item.html:17 +msgid "Due today" +msgstr "Heute fällig" + #: core/templates/core/_item.html:17 msgid "Due:" msgstr "Fällig:" @@ -180,23 +227,30 @@ msgstr "Links:" msgid "Attachments:" msgstr "Anhänge:" -#: core/templates/core/_tags.html:9 core/templates/core/index.html:44 +#: core/templates/core/_tags.html:9 core/templates/core/index.html:55 msgid "No tags yet" msgstr "Noch keine Tags" -#: core/templates/core/base.html:24 -msgid "Notes · Tasks · Links" -msgstr "Notizen · Aufgaben · Links" +#: core/templates/core/accept_user_invite.html:6 +#: core/templates/core/accept_user_invite.html:19 +msgid "Create account" +msgstr "Account erstellen" + +#: core/templates/core/accept_user_invite.html:7 +msgid "You were invited to my2dos. Create your account to continue." +msgstr "" +"Du wurdest zu my2dos eingeladen. Erstelle deinen Account, um fortzufahren." #: core/templates/core/base.html:24 msgid "Kanban" msgstr "Kanban" -#: core/templates/core/base.html:24 core/templates/core/settings.html:34 +#: core/templates/core/base.html:24 core/templates/core/team_detail.html:4 +#: core/templates/core/team_list.html:6 msgid "Teams" msgstr "Teams" -#: core/templates/core/base.html:24 core/templates/core/team_detail.html:4 +#: core/templates/core/base.html:24 core/templates/core/settings.html:8 msgid "Settings" msgstr "Einstellungen" @@ -212,57 +266,45 @@ msgstr "Logout" msgid "Login" msgstr "Login" -#: core/templates/core/base.html:32 core/templates/core/index.html:99 +#: core/templates/core/base.html:32 core/templates/core/index.html:110 #: core/templates/core/new_item.html:9 msgid "Create task" msgstr "Aufgabe erstellen" -#: core/templates/core/base.html:32 core/templates/core/index.html:100 -#, fuzzy -#| msgid "Create link" +#: core/templates/core/base.html:32 core/templates/core/index.html:111 msgid "Save link" msgstr "Link erstellen" -#: core/templates/core/base.html:32 core/templates/core/index.html:101 -#, fuzzy -#| msgid "Create journal entry" +#: core/templates/core/base.html:32 core/templates/core/index.html:112 msgid "Journal entry" msgstr "Journal-Eintrag erstellen" -#: core/templates/core/base.html:32 core/templates/core/index.html:102 -#, fuzzy -#| msgid "Public" +#: core/templates/core/base.html:32 core/templates/core/index.html:113 msgid "public" msgstr "Öffentlich" -#: core/templates/core/base.html:32 core/templates/core/index.html:103 -#, fuzzy -#| msgid "Private" +#: core/templates/core/base.html:32 core/templates/core/index.html:114 msgid "private" msgstr "Privat" -#: core/templates/core/base.html:32 core/templates/core/index.html:104 +#: core/templates/core/base.html:32 core/templates/core/index.html:115 msgid "Insert Markdown code block" msgstr "Markdown Codeblock einfügen" -#: core/templates/core/base.html:32 core/templates/core/index.html:105 +#: core/templates/core/base.html:32 core/templates/core/index.html:116 msgid "Search internal task/note" msgstr "interne Aufgabe/Notiz suchen" -#: core/templates/core/base.html:32 core/templates/core/index.html:106 -#, fuzzy -#| msgid "to insert images into the text." +#: core/templates/core/base.html:32 core/templates/core/index.html:117 msgid "Insert image from attachments" msgstr "können Bilder im Text eingefügt werden." -#: core/templates/core/base.html:33 core/templates/core/index.html:113 -#, fuzzy -#| msgid "Image preview:" +#: core/templates/core/base.html:33 core/templates/core/index.html:124 msgid "Image:" msgstr "Bildvorschau:" #: core/templates/core/detail.html:4 core/templates/core/kanban_list.html:4 -#: core/templates/core/settings.html:4 +#: core/templates/core/settings.html:4 core/templates/core/team_list.html:9 msgid "back" msgstr "zurück" @@ -270,50 +312,63 @@ msgstr "zurück" msgid "Filter" msgstr "Filter" -#: core/templates/core/index.html:16 -msgid "Notes" -msgstr "Notizen" - #: core/templates/core/index.html:18 core/templates/core/kanban_list.html:7 msgid "Open" msgstr "Offen" #: core/templates/core/index.html:19 +msgid "Due" +msgstr "Fällig:" + +#: core/templates/core/index.html:20 msgid "Archive" msgstr "Archiv" -#: core/templates/core/index.html:26 +#: core/templates/core/index.html:28 msgid "Search (Ctrl+F, # for tags)" msgstr "Suche (Ctrl+F, # für Tags)" -#: core/templates/core/index.html:27 +#: core/templates/core/index.html:29 msgid "Clear search" msgstr "Suche löschen" -#: core/templates/core/index.html:34 core/templates/core/journal.html:15 -#: core/templates/core/settings.html:61 +#: core/templates/core/index.html:37 core/templates/core/journal.html:15 +#: core/templates/core/settings.html:88 msgid "Tags" msgstr "Tags" -#: core/templates/core/index.html:40 +#: core/templates/core/index.html:39 +msgid "Tag mode" +msgstr "Tag-Modus" + +#: core/templates/core/index.html:40 core/templates/core/index.html:51 +msgid "any" +msgstr "beliebig" + +#: core/templates/core/index.html:41 core/templates/core/index.html:51 +#: core/templates/core/settings.html:88 +msgid "all" +msgstr "alle" + +#: core/templates/core/index.html:50 msgid "Show tags" msgstr "Tags anzeigen" -#: core/templates/core/index.html:65 core/templates/core/journal.html:32 +#: core/templates/core/index.html:76 core/templates/core/journal.html:32 #: core/templates/core/new_item.html:26 msgid "Gallery" msgstr "Galerie" -#: core/templates/core/index.html:65 core/templates/core/journal.html:32 +#: core/templates/core/index.html:76 core/templates/core/journal.html:32 #: core/templates/core/new_item.html:26 msgid "Photo" msgstr "Foto" -#: core/templates/core/index.html:66 +#: core/templates/core/index.html:77 msgid "Editor" msgstr "Editor" -#: core/templates/core/index.html:72 +#: core/templates/core/index.html:83 msgid "Nothing here yet." msgstr "Noch nichts vorhanden." @@ -382,71 +437,104 @@ msgstr "Mit" msgid "to insert images into the text." msgstr "können Bilder im Text eingefügt werden." -#: core/templates/core/settings.html:8 -msgid "User settings" -msgstr "Benutzereinstellungen" +#: core/templates/core/settings.html:10 core/templates/core/settings.html:20 +msgid "Profile" +msgstr "Benutzer" -#: core/templates/core/settings.html:11 -msgid "First name" -msgstr "Vorname" +#: core/templates/core/settings.html:11 core/templates/core/settings.html:32 +msgid "View & language" +msgstr "Ansicht & Sprache" #: core/templates/core/settings.html:12 -msgid "Last name" -msgstr "Name" +msgid "Invitations" +msgstr "Offene Einladungen:" -#: core/templates/core/settings.html:20 -msgid "Default view" -msgstr "Standardansicht" - -#: core/templates/core/settings.html:30 -msgid "Save default view" -msgstr "Standardansicht speichern" - -#: core/templates/core/settings.html:38 -msgid "This tag automatically turns items into team items, e.g. #my2dos." -msgstr "Dieser Tag macht Einträge automatisch zu Team-Einträgen, z.B. #my2dos." - -#: core/templates/core/settings.html:39 -msgid "Create team" -msgstr "Team erstellen" - -#: core/templates/core/settings.html:42 -msgid "No teams yet." -msgstr "Noch keine Teams." - -#: core/templates/core/settings.html:46 -msgid "Create API key" -msgstr "API Key erzeugen" - -#: core/templates/core/settings.html:49 -msgid "Name" -msgstr "Name" - -#: core/templates/core/settings.html:50 -msgid "Restrict to tags" -msgstr "Auf Tags beschränken" - -#: core/templates/core/settings.html:50 -msgid "No selection = access to all own items." -msgstr "Keine Auswahl = Zugriff auf alle eigenen Einträge." - -#: core/templates/core/settings.html:51 -msgid "Generate API key" -msgstr "API Key generieren" - -#: core/templates/core/settings.html:55 +#: core/templates/core/settings.html:13 core/templates/core/settings.html:75 msgid "API keys" msgstr "API Keys" -#: core/templates/core/settings.html:59 +#: core/templates/core/settings.html:21 +msgid "Manage your personal user data." +msgstr "Verwalte deine persönlichen Benutzerdaten." + +#: core/templates/core/settings.html:24 +msgid "First name" +msgstr "Vorname" + +#: core/templates/core/settings.html:25 +msgid "Last name" +msgstr "Name" + +#: core/templates/core/settings.html:27 +msgid "Save profile" +msgstr "Benutzer speichern" + +#: core/templates/core/settings.html:33 +msgid "Choose your language and configure the default overview." +msgstr "Wähle deine Sprache und konfiguriere die Standardübersicht." + +#: core/templates/core/settings.html:43 +msgid "Sorting" +msgstr "Einstellungen" + +#: core/templates/core/settings.html:44 +msgid "Overdue and due-today tasks are sorted to the top of the overview." +msgstr "" +"Überfällige und heute fällige Aufgaben werden in der Übersicht nach oben " +"sortiert." + +#: core/templates/core/settings.html:48 +msgid "Show in overview" +msgstr "Zeilen in der Übersicht" + +#: core/templates/core/settings.html:57 +msgid "Save view settings" +msgstr "Ansicht speichern" + +#: core/templates/core/settings.html:62 +msgid "User invitations" +msgstr "Offene Einladungen:" + +#: core/templates/core/settings.html:63 +msgid "" +"Invite new users during the test phase. Only people with an invitation link " +"can register." +msgstr "" + +#: core/templates/core/settings.html:67 core/templates/core/team_detail.html:26 +msgid "Create invitation link" +msgstr "Einladungslink erzeugen" + +#: core/templates/core/settings.html:70 core/templates/core/settings.html:86 msgid "Delete" msgstr "Löschen" -#: core/templates/core/settings.html:61 -msgid "all" -msgstr "alle" +#: core/templates/core/settings.html:70 core/templates/core/team_detail.html:29 +msgid "No open invitations." +msgstr "Keine offenen Einladungen." -#: core/templates/core/settings.html:63 +#: core/templates/core/settings.html:76 +msgid "Create API keys for integrations and optionally restrict them to tags." +msgstr "" +"Erzeuge API Keys für Integrationen und beschränke sie optional auf Tags." + +#: core/templates/core/settings.html:79 +msgid "Name" +msgstr "Name" + +#: core/templates/core/settings.html:80 +msgid "Restrict to tags" +msgstr "Auf Tags beschränken" + +#: core/templates/core/settings.html:80 +msgid "No selection = access to all own items." +msgstr "Keine Auswahl = Zugriff auf alle eigenen Einträge." + +#: core/templates/core/settings.html:81 +msgid "Generate API key" +msgstr "API Key generieren" + +#: core/templates/core/settings.html:90 msgid "No API keys yet." msgstr "Noch keine API Keys." @@ -467,18 +555,10 @@ msgstr "Mitglieder" msgid "Invite" msgstr "Einladen" -#: core/templates/core/team_detail.html:26 -msgid "Create invitation link" -msgstr "Einladungslink erzeugen" - #: core/templates/core/team_detail.html:28 msgid "Open invitations:" msgstr "Offene Einladungen:" -#: core/templates/core/team_detail.html:29 -msgid "No open invitations." -msgstr "Keine offenen Einladungen." - #: core/templates/core/team_detail.html:34 msgid "Team items" msgstr "Team-Einträge" @@ -488,30 +568,99 @@ msgstr "Team-Einträge" msgid "No team items yet. Create something with #%(slug)s." msgstr "Noch keine Team-Einträge. Erstelle etwas mit #%(slug)s." -#: core/views.py:271 +#: core/templates/core/team_list.html:7 +msgid "All teams you belong to." +msgstr "Alle Teams, zu denen du gehörst." + +#: core/templates/core/team_list.html:15 core/templates/core/team_list.html:20 +msgid "Create team" +msgstr "Team erstellen" + +#: core/templates/core/team_list.html:19 +msgid "This tag automatically turns items into team items, e.g. #my2dos." +msgstr "Dieser Tag macht Einträge automatisch zu Team-Einträgen, z.B. #my2dos." + +#: core/templates/core/team_list.html:32 +msgid "members" +msgstr "Mitglieder" + +#: core/templates/core/team_list.html:34 +msgid "Open team" +msgstr "Offen" + +#: core/templates/core/team_list.html:37 +msgid "items" +msgstr "Team-Einträge" + +#: core/templates/core/team_list.html:38 +msgid "open tasks" +msgstr "Offene Aufgaben anzeigen" + +#: core/templates/core/team_list.html:46 +msgid "No team items yet." +msgstr "Noch keine Teams." + +#: core/templates/core/team_list.html:51 +msgid "You are not a member of any team yet." +msgstr "Du bist jetzt Mitglied im Team %(team)s." + +#: core/views.py:321 msgid "Settings saved." msgstr "Einstellungen gespeichert." -#: core/views.py:278 +#: core/views.py:328 msgid "Default view saved." msgstr "Standardansicht gespeichert." -#: core/views.py:287 +#: core/views.py:337 #, python-format msgid "API key created: %(token)s" msgstr "API Key erzeugt: %(token)s" -#: core/views.py:297 -#, python-format -msgid "Team %(team)s created. Use #%(slug)s for team items." -msgstr "Team %(team)s erstellt. Nutze #%(slug)s für Team-Einträge." - -#: core/views.py:329 +#: core/views.py:345 core/views.py:439 #, python-format msgid "Invitation link created: %(url)s" msgstr "Einladungslink erzeugt: %(url)s" -#: core/views.py:349 +#: core/views.py:373 +msgid "Invitation deleted." +msgstr "Einladungslink erzeugt: %(url)s" + +#: core/views.py:382 +msgid "Invitation accepted." +msgstr "Einladungslink erzeugt: %(url)s" + +#: core/views.py:394 +msgid "Welcome to my2dos." +msgstr "Willkommen bei my2dos." + +#: core/views.py:411 +#, python-format +msgid "Team %(team)s created. Use #%(slug)s for team items." +msgstr "Team %(team)s erstellt. Nutze #%(slug)s für Team-Einträge." + +#: core/views.py:459 #, python-format msgid "You are now a member of team %(team)s." msgstr "Du bist jetzt Mitglied im Team %(team)s." + +#~ msgid "Notes · Tasks · Links" +#~ msgstr "Notizen · Aufgaben · Links" + +#~ msgid "Create teams and use their tag to share items." +#~ msgstr "Erstelle Teams und nutze deren Tag, um Einträge zu teilen." + +#~ msgid "No teams yet." +#~ msgstr "Noch keine Teams." + +#~ msgid "Date/time for todo" +#~ msgstr "Datum/Zeit für Todo" + +#~ msgid "Default view" +#~ msgstr "Standardansicht" + +#~ msgid "Save default view" +#~ msgstr "Standardansicht speichern" + +#~ msgid "Create API key" +#~ msgstr "API Key erzeugen" diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index baf9684..cf80e58 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-23 14:50+0200\n" +"POT-Creation-Date: 2026-08-23 16:56+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,109 +18,140 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: core/forms.py:28 -msgid "Write something … /todo /link /public #tag [[note:1]]" +#: core/context_processors.py:9 core/context_processors.py:16 +#: core/templates/core/index.html:16 +msgid "Notes" msgstr "" -#: core/forms.py:65 -msgid "Show notes" +#: core/context_processors.py:9 core/context_processors.py:15 +msgid "Tasks" msgstr "" -#: core/forms.py:66 -msgid "Show open tasks" +#: core/context_processors.py:9 core/context_processors.py:17 +msgid "Links" msgstr "" -#: core/forms.py:67 -msgid "Show completed tasks" -msgstr "" - -#: core/forms.py:68 -msgid "Show links" -msgstr "" - -#: core/forms.py:69 -msgid "Show journal" -msgstr "" - -#: core/forms.py:70 -msgid "Lines in overview" -msgstr "" - -#: core/forms.py:79 core/templates/core/settings.html:37 -msgid "Team name" -msgstr "" - -#: core/forms.py:79 core/templates/core/settings.html:38 -msgid "Team tag" -msgstr "" - -#: core/forms.py:93 core/templates/core/settings.html:13 -msgid "Email" -msgstr "" - -#: core/forms.py:98 -msgid "Tag" -msgstr "" - -#: core/forms.py:103 -msgid "Kanban name" -msgstr "" - -#: core/forms.py:117 -msgid "API key name" -msgstr "" - -#: core/models.py:75 core/templates/core/index.html:5 -#: core/templates/core/index.html:53 core/templates/core/journal.html:25 -msgid "Task" -msgstr "" - -#: core/models.py:76 core/templates/core/index.html:6 -#: core/templates/core/index.html:54 core/templates/core/journal.html:26 -msgid "Note" -msgstr "" - -#: core/models.py:77 -msgid "Link" -msgstr "" - -#: core/models.py:78 core/templates/core/base.html:24 +#: core/context_processors.py:18 core/models.py:98 +#: core/templates/core/base.html:24 msgid "Journal" msgstr "" -#: core/models.py:81 core/templates/core/_edit_form.html:20 +#: core/context_processors.py:25 +msgid "Overview" +msgstr "" + +#: core/forms.py:29 +msgid "Write something … /todo /link /public #tag [[note:1]]" +msgstr "" + +#: core/forms.py:61 +msgid "Language" +msgstr "" + +#: core/forms.py:62 +msgid "Show notes" +msgstr "" + +#: core/forms.py:63 +msgid "Show open tasks" +msgstr "" + +#: core/forms.py:64 +msgid "Show completed tasks" +msgstr "" + +#: core/forms.py:65 +msgid "Show links" +msgstr "" + +#: core/forms.py:66 +msgid "Show journal" +msgstr "" + +#: core/forms.py:67 +msgid "Always show due items first" +msgstr "" + +#: core/forms.py:68 +msgid "Lines in overview" +msgstr "" + +#: core/forms.py:77 core/forms.py:85 core/forms.py:116 +#: core/templates/core/settings.html:26 core/templates/core/settings.html:66 +msgid "Email" +msgstr "" + +#: core/forms.py:102 core/templates/core/team_list.html:18 +msgid "Team name" +msgstr "" + +#: core/forms.py:102 core/templates/core/team_list.html:19 +msgid "Team tag" +msgstr "" + +#: core/forms.py:121 +msgid "Tag" +msgstr "" + +#: core/forms.py:126 +msgid "Kanban name" +msgstr "" + +#: core/forms.py:140 +msgid "API key name" +msgstr "" + +#: core/models.py:95 core/templates/core/index.html:5 +#: core/templates/core/index.html:64 core/templates/core/journal.html:25 +msgid "Task" +msgstr "" + +#: core/models.py:96 core/templates/core/index.html:6 +#: core/templates/core/index.html:65 core/templates/core/journal.html:26 +msgid "Note" +msgstr "" + +#: core/models.py:97 +msgid "Link" +msgstr "" + +#: core/models.py:101 core/templates/core/_edit_form.html:20 #: core/templates/core/item_editor.html:25 msgid "Private" msgstr "" -#: core/models.py:82 core/templates/core/team_detail.html:7 +#: core/models.py:102 core/templates/core/team_detail.html:7 msgid "Team" msgstr "" -#: core/models.py:83 core/templates/core/_edit_form.html:21 +#: core/models.py:103 core/templates/core/_edit_form.html:21 #: core/templates/core/item_editor.html:25 msgid "Public" msgstr "" -#: core/models.py:92 core/templates/core/kanban_detail.html:7 +#: core/models.py:112 core/templates/core/kanban_detail.html:7 msgid "Inbox" msgstr "" -#: core/models.py:94 +#: core/models.py:114 msgid "In Progress" msgstr "" -#: core/models.py:95 core/templates/core/kanban_detail.html:10 +#: core/models.py:115 core/templates/core/kanban_detail.html:10 #: core/templates/core/kanban_list.html:7 msgid "Done" msgstr "" +#: core/models.py:167 +msgid "Browser default" +msgstr "" + #: core/templates/core/_edit_form.html:5 #: core/templates/core/item_editor.html:11 msgid "Type:" msgstr "" -#: core/templates/core/_edit_form.html:7 core/templates/core/index.html:58 +#: core/templates/core/_edit_form.html:7 core/templates/core/index.html:69 #: core/templates/core/item_editor.html:13 core/templates/core/journal.html:29 #: core/templates/core/new_item.html:13 msgid "Add tags" @@ -136,18 +167,23 @@ msgid "Comment, e.g. same task already captured [[12]]" msgstr "" #: core/templates/core/_edit_form.html:23 -msgid "Date/time for todo" +#: core/templates/core/item_editor.html:26 +msgid "Due date" msgstr "" -#: core/templates/core/_edit_form.html:27 core/templates/core/index.html:69 +#: core/templates/core/_edit_form.html:23 +#: core/templates/core/item_editor.html:26 +msgid "Time optional" +msgstr "" + +#: core/templates/core/_edit_form.html:27 core/templates/core/index.html:80 msgid "Image from clipboard:" msgstr "" -#: core/templates/core/_edit_form.html:29 core/templates/core/index.html:67 +#: core/templates/core/_edit_form.html:29 core/templates/core/index.html:78 #: core/templates/core/item_editor.html:9 #: core/templates/core/item_editor.html:31 core/templates/core/journal.html:32 #: core/templates/core/new_item.html:10 core/templates/core/new_item.html:30 -#: core/templates/core/settings.html:14 msgid "Save" msgstr "" @@ -169,6 +205,14 @@ msgstr "" msgid "show more" msgstr "" +#: core/templates/core/_item.html:17 +msgid "Overdue" +msgstr "" + +#: core/templates/core/_item.html:17 +msgid "Due today" +msgstr "" + #: core/templates/core/_item.html:17 msgid "Due:" msgstr "" @@ -181,23 +225,29 @@ msgstr "" msgid "Attachments:" msgstr "" -#: core/templates/core/_tags.html:9 core/templates/core/index.html:44 +#: core/templates/core/_tags.html:9 core/templates/core/index.html:55 msgid "No tags yet" msgstr "" -#: core/templates/core/base.html:24 -msgid "Notes · Tasks · Links" +#: core/templates/core/accept_user_invite.html:6 +#: core/templates/core/accept_user_invite.html:19 +msgid "Create account" +msgstr "" + +#: core/templates/core/accept_user_invite.html:7 +msgid "You were invited to my2dos. Create your account to continue." msgstr "" #: core/templates/core/base.html:24 msgid "Kanban" msgstr "" -#: core/templates/core/base.html:24 core/templates/core/settings.html:34 +#: core/templates/core/base.html:24 core/templates/core/team_detail.html:4 +#: core/templates/core/team_list.html:6 msgid "Teams" msgstr "" -#: core/templates/core/base.html:24 core/templates/core/team_detail.html:4 +#: core/templates/core/base.html:24 core/templates/core/settings.html:8 msgid "Settings" msgstr "" @@ -213,45 +263,45 @@ msgstr "" msgid "Login" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:99 +#: core/templates/core/base.html:32 core/templates/core/index.html:110 #: core/templates/core/new_item.html:9 msgid "Create task" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:100 +#: core/templates/core/base.html:32 core/templates/core/index.html:111 msgid "Save link" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:101 +#: core/templates/core/base.html:32 core/templates/core/index.html:112 msgid "Journal entry" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:102 +#: core/templates/core/base.html:32 core/templates/core/index.html:113 msgid "public" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:103 +#: core/templates/core/base.html:32 core/templates/core/index.html:114 msgid "private" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:104 +#: core/templates/core/base.html:32 core/templates/core/index.html:115 msgid "Insert Markdown code block" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:105 +#: core/templates/core/base.html:32 core/templates/core/index.html:116 msgid "Search internal task/note" msgstr "" -#: core/templates/core/base.html:32 core/templates/core/index.html:106 +#: core/templates/core/base.html:32 core/templates/core/index.html:117 msgid "Insert image from attachments" msgstr "" -#: core/templates/core/base.html:33 core/templates/core/index.html:113 +#: core/templates/core/base.html:33 core/templates/core/index.html:124 msgid "Image:" msgstr "" #: core/templates/core/detail.html:4 core/templates/core/kanban_list.html:4 -#: core/templates/core/settings.html:4 +#: core/templates/core/settings.html:4 core/templates/core/team_list.html:9 msgid "back" msgstr "" @@ -259,50 +309,63 @@ msgstr "" msgid "Filter" msgstr "" -#: core/templates/core/index.html:16 -msgid "Notes" -msgstr "" - #: core/templates/core/index.html:18 core/templates/core/kanban_list.html:7 msgid "Open" msgstr "" #: core/templates/core/index.html:19 +msgid "Due" +msgstr "" + +#: core/templates/core/index.html:20 msgid "Archive" msgstr "" -#: core/templates/core/index.html:26 +#: core/templates/core/index.html:28 msgid "Search (Ctrl+F, # for tags)" msgstr "" -#: core/templates/core/index.html:27 +#: core/templates/core/index.html:29 msgid "Clear search" msgstr "" -#: core/templates/core/index.html:34 core/templates/core/journal.html:15 -#: core/templates/core/settings.html:61 +#: core/templates/core/index.html:37 core/templates/core/journal.html:15 +#: core/templates/core/settings.html:88 msgid "Tags" msgstr "" -#: core/templates/core/index.html:40 +#: core/templates/core/index.html:39 +msgid "Tag mode" +msgstr "" + +#: core/templates/core/index.html:40 core/templates/core/index.html:51 +msgid "any" +msgstr "" + +#: core/templates/core/index.html:41 core/templates/core/index.html:51 +#: core/templates/core/settings.html:88 +msgid "all" +msgstr "" + +#: core/templates/core/index.html:50 msgid "Show tags" msgstr "" -#: core/templates/core/index.html:65 core/templates/core/journal.html:32 +#: core/templates/core/index.html:76 core/templates/core/journal.html:32 #: core/templates/core/new_item.html:26 msgid "Gallery" msgstr "" -#: core/templates/core/index.html:65 core/templates/core/journal.html:32 +#: core/templates/core/index.html:76 core/templates/core/journal.html:32 #: core/templates/core/new_item.html:26 msgid "Photo" msgstr "" -#: core/templates/core/index.html:66 +#: core/templates/core/index.html:77 msgid "Editor" msgstr "" -#: core/templates/core/index.html:72 +#: core/templates/core/index.html:83 msgid "Nothing here yet." msgstr "" @@ -371,71 +434,101 @@ msgstr "" msgid "to insert images into the text." msgstr "" -#: core/templates/core/settings.html:8 -msgid "User settings" +#: core/templates/core/settings.html:10 core/templates/core/settings.html:20 +msgid "Profile" msgstr "" -#: core/templates/core/settings.html:11 -msgid "First name" +#: core/templates/core/settings.html:11 core/templates/core/settings.html:32 +msgid "View & language" msgstr "" #: core/templates/core/settings.html:12 -msgid "Last name" +msgid "Invitations" msgstr "" -#: core/templates/core/settings.html:20 -msgid "Default view" -msgstr "" - -#: core/templates/core/settings.html:30 -msgid "Save default view" -msgstr "" - -#: core/templates/core/settings.html:38 -msgid "This tag automatically turns items into team items, e.g. #my2dos." -msgstr "" - -#: core/templates/core/settings.html:39 -msgid "Create team" -msgstr "" - -#: core/templates/core/settings.html:42 -msgid "No teams yet." -msgstr "" - -#: core/templates/core/settings.html:46 -msgid "Create API key" -msgstr "" - -#: core/templates/core/settings.html:49 -msgid "Name" -msgstr "" - -#: core/templates/core/settings.html:50 -msgid "Restrict to tags" -msgstr "" - -#: core/templates/core/settings.html:50 -msgid "No selection = access to all own items." -msgstr "" - -#: core/templates/core/settings.html:51 -msgid "Generate API key" -msgstr "" - -#: core/templates/core/settings.html:55 +#: core/templates/core/settings.html:13 core/templates/core/settings.html:75 msgid "API keys" msgstr "" -#: core/templates/core/settings.html:59 -msgid "Delete" +#: core/templates/core/settings.html:21 +msgid "Manage your personal user data." msgstr "" -#: core/templates/core/settings.html:61 -msgid "all" +#: core/templates/core/settings.html:24 +msgid "First name" +msgstr "" + +#: core/templates/core/settings.html:25 +msgid "Last name" +msgstr "" + +#: core/templates/core/settings.html:27 +msgid "Save profile" +msgstr "" + +#: core/templates/core/settings.html:33 +msgid "Choose your language and configure the default overview." +msgstr "" + +#: core/templates/core/settings.html:43 +msgid "Sorting" +msgstr "" + +#: core/templates/core/settings.html:44 +msgid "Overdue and due-today tasks are sorted to the top of the overview." +msgstr "" + +#: core/templates/core/settings.html:48 +msgid "Show in overview" +msgstr "" + +#: core/templates/core/settings.html:57 +msgid "Save view settings" +msgstr "" + +#: core/templates/core/settings.html:62 +msgid "User invitations" msgstr "" #: core/templates/core/settings.html:63 +msgid "" +"Invite new users during the test phase. Only people with an invitation link " +"can register." +msgstr "" + +#: core/templates/core/settings.html:67 core/templates/core/team_detail.html:26 +msgid "Create invitation link" +msgstr "" + +#: core/templates/core/settings.html:70 core/templates/core/settings.html:86 +msgid "Delete" +msgstr "" + +#: core/templates/core/settings.html:70 core/templates/core/team_detail.html:29 +msgid "No open invitations." +msgstr "" + +#: core/templates/core/settings.html:76 +msgid "Create API keys for integrations and optionally restrict them to tags." +msgstr "" + +#: core/templates/core/settings.html:79 +msgid "Name" +msgstr "" + +#: core/templates/core/settings.html:80 +msgid "Restrict to tags" +msgstr "" + +#: core/templates/core/settings.html:80 +msgid "No selection = access to all own items." +msgstr "" + +#: core/templates/core/settings.html:81 +msgid "Generate API key" +msgstr "" + +#: core/templates/core/settings.html:90 msgid "No API keys yet." msgstr "" @@ -456,18 +549,10 @@ msgstr "" msgid "Invite" msgstr "" -#: core/templates/core/team_detail.html:26 -msgid "Create invitation link" -msgstr "" - #: core/templates/core/team_detail.html:28 msgid "Open invitations:" msgstr "" -#: core/templates/core/team_detail.html:29 -msgid "No open invitations." -msgstr "" - #: core/templates/core/team_detail.html:34 msgid "Team items" msgstr "" @@ -477,30 +562,78 @@ msgstr "" msgid "No team items yet. Create something with #%(slug)s." msgstr "" -#: core/views.py:271 +#: core/templates/core/team_list.html:7 +msgid "All teams you belong to." +msgstr "" + +#: core/templates/core/team_list.html:15 core/templates/core/team_list.html:20 +msgid "Create team" +msgstr "" + +#: core/templates/core/team_list.html:19 +msgid "This tag automatically turns items into team items, e.g. #my2dos." +msgstr "" + +#: core/templates/core/team_list.html:32 +msgid "members" +msgstr "" + +#: core/templates/core/team_list.html:34 +msgid "Open team" +msgstr "" + +#: core/templates/core/team_list.html:37 +msgid "items" +msgstr "" + +#: core/templates/core/team_list.html:38 +msgid "open tasks" +msgstr "" + +#: core/templates/core/team_list.html:46 +msgid "No team items yet." +msgstr "" + +#: core/templates/core/team_list.html:51 +msgid "You are not a member of any team yet." +msgstr "" + +#: core/views.py:321 msgid "Settings saved." msgstr "" -#: core/views.py:278 +#: core/views.py:328 msgid "Default view saved." msgstr "" -#: core/views.py:287 +#: core/views.py:337 #, python-format msgid "API key created: %(token)s" msgstr "" -#: core/views.py:297 -#, python-format -msgid "Team %(team)s created. Use #%(slug)s for team items." -msgstr "" - -#: core/views.py:329 +#: core/views.py:345 core/views.py:439 #, python-format msgid "Invitation link created: %(url)s" msgstr "" -#: core/views.py:349 +#: core/views.py:373 +msgid "Invitation deleted." +msgstr "" + +#: core/views.py:382 +msgid "Invitation accepted." +msgstr "" + +#: core/views.py:394 +msgid "Welcome to my2dos." +msgstr "" + +#: core/views.py:411 +#, python-format +msgid "Team %(team)s created. Use #%(slug)s for team items." +msgstr "" + +#: core/views.py:459 #, python-format msgid "You are now a member of team %(team)s." msgstr "" diff --git a/my2dos/settings.py b/my2dos/settings.py index 0041d32..2254124 100644 --- a/my2dos/settings.py +++ b/my2dos/settings.py @@ -28,6 +28,7 @@ MIDDLEWARE = [ 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'core.middleware.UserLanguageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] @@ -41,6 +42,7 @@ TEMPLATES = [{ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'core.context_processors.overview_navigation', ]}, }] WSGI_APPLICATION = 'my2dos.wsgi.application'