diff --git a/accounts/forms.py b/accounts/forms.py index 1e71180..022e3c5 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -10,15 +10,15 @@ class CustomUserCreationForm(UserCreationForm): fields = ('username', 'email', 'password1', 'password2') class CustomUserChangeForm(UserChangeForm): - password = None # Passwort-Änderung über extra Formular + password = None # Password change via separate form class Meta: model = User fields = ('email',) widgets = { - 'email': forms.EmailInput(attrs={'class': 'text-input', 'placeholder': 'E-Mail-Adresse'}), + 'email': forms.EmailInput(attrs={'class': 'text-input', 'placeholder': 'Email address'}), } class JellyfinLoginForm(forms.Form): - username = forms.CharField(label='Benutzername', widget=forms.TextInput(attrs={'class': 'form-control'})) - password = forms.CharField(label='Passwort', widget=forms.PasswordInput(attrs={'class': 'form-control'})) + username = forms.CharField(label='Username', widget=forms.TextInput(attrs={'class': 'form-control'})) + password = forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class': 'form-control'})) diff --git a/accounts/models.py b/accounts/models.py index f80eb9a..5233221 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -4,9 +4,9 @@ from django.utils.translation import gettext_lazy as _ class User(AbstractUser): """ - Custom User Model mit zusätzlichen Feldern und Berechtigungen. - Normale User können nur ihre eigenen Daten bearbeiten. - Admin-User können alles. + Custom User Model with additional fields and permissions. + Regular users can only edit their own data. + Admin users can edit everything. """ email = models.EmailField(_("email address"), unique=True) bio = models.TextField(max_length=500, blank=True) @@ -26,7 +26,7 @@ class User(AbstractUser): client = JellyfinClient() return client.is_admin(self.jellyfin_user_id, self.jellyfin_token) except: - # Im Fehlerfall den lokalen Status verwenden + # On error, fall back to local status return self.is_admin @property diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html index 996abd9..830af11 100644 --- a/accounts/templates/accounts/login.html +++ b/accounts/templates/accounts/login.html @@ -2,14 +2,14 @@ {% block content %}
-

Anmelden

+

Sign in

{% csrf_token %} {{ form.as_p }} - +
{% endblock %} \ No newline at end of file diff --git a/accounts/templates/accounts/password_change.html b/accounts/templates/accounts/password_change.html index 3901230..9a1c5c0 100644 --- a/accounts/templates/accounts/password_change.html +++ b/accounts/templates/accounts/password_change.html @@ -2,11 +2,11 @@ {% block content %}
-

Passwort ändern

+

Change password

{% csrf_token %} {{ form.as_p }} - +
{% endblock %} \ No newline at end of file diff --git a/accounts/templates/accounts/password_change_done.html b/accounts/templates/accounts/password_change_done.html index 7d3137a..b7ebb97 100644 --- a/accounts/templates/accounts/password_change_done.html +++ b/accounts/templates/accounts/password_change_done.html @@ -2,8 +2,8 @@ {% block content %}
-

Passwort geändert

-

Ihr Passwort wurde erfolgreich geändert.

-

Zurück zum Profil

+

Password changed

+

Your password has been changed successfully.

+

Back to profile

{% endblock %} \ No newline at end of file diff --git a/accounts/templates/accounts/profile.html b/accounts/templates/accounts/profile.html index 6bf9950..d693c61 100644 --- a/accounts/templates/accounts/profile.html +++ b/accounts/templates/accounts/profile.html @@ -7,7 +7,7 @@ {% block content %}
-

Hallo, {{ user.username }}

+

Hello, {{ user.username }}

{% if messages %}
@@ -18,24 +18,24 @@ {% endif %}
-

E-Mail-Adresse

+

Email address

{% csrf_token %}
- + {{ form.email }}
- +
{% if user.jellyfin_server %}
-

Jellyfin-Verbindung

+

Jellyfin connection

Server: {{ user.jellyfin_server }}
- Status: {% if user.jellyfin_token %}Verbunden{% else %}Nicht verbunden{% endif %}
+ Status: {% if user.jellyfin_token %}Connected{% else %}Not connected{% endif %}
{% if user.is_jellyfin_admin %} - Jellyfin Administrator + Jellyfin administrator {% endif %}

@@ -43,9 +43,9 @@
-

Meine Abonnements

+

My subscriptions

-

Serien

+

Series

{% if series_subs %}
{% for sub in series_subs %} @@ -53,11 +53,11 @@ {% if sub.series_poster %} {{ sub.series_title }} {% else %} - + {% endif %}
{{ sub.series_title }}
-
Abonniert am {{ sub.created_at|date:"d.m.Y" }}
+
Subscribed on {{ sub.created_at|date:"d.m.Y" }}
{% if sub.series_overview %}
{{ sub.series_overview|truncatechars:100 }}
{% endif %} @@ -66,10 +66,10 @@ {% endfor %}
{% else %} -

Keine Serien abonniert.

+

No series subscribed.

{% endif %} -

Filme

+

Movies

{% if movie_subs %}
{% for sub in movie_subs %} @@ -77,11 +77,11 @@ {% if sub.poster %} {{ sub.title }} {% else %} - + {% endif %}
{{ sub.title }}
-
Abonniert am {{ sub.created_at|date:"d.m.Y" }}
+
Subscribed on {{ sub.created_at|date:"d.m.Y" }}
{% if sub.overview %}
{{ sub.overview|truncatechars:100 }}
{% endif %} @@ -90,7 +90,7 @@ {% endfor %}
{% else %} -

Keine Filme abonniert.

+

No movies subscribed.

{% endif %}
diff --git a/accounts/templates/accounts/register.html b/accounts/templates/accounts/register.html index 2216734..3f9a48a 100644 --- a/accounts/templates/accounts/register.html +++ b/accounts/templates/accounts/register.html @@ -2,14 +2,14 @@ {% block content %}
-

Registrieren

+

Register

{% csrf_token %} {{ form.as_p }} - +
{% endblock %} \ No newline at end of file diff --git a/accounts/utils.py b/accounts/utils.py index a7c7dab..92ae664 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -7,7 +7,7 @@ from django.contrib import messages class JellyfinClient: def __init__(self): - # Basis-Einstellungen aus den Django-Settings + # Base settings from Django settings self.client = settings.JELLYFIN_CLIENT self.version = settings.JELLYFIN_VERSION self.device = settings.JELLYFIN_DEVICE @@ -18,13 +18,13 @@ class JellyfinClient: def authenticate(self, username, password): """Authenticate with Jellyfin and return user info if successful""" if not self.server_url: - raise ValueError("Keine Server-URL angegeben") + raise ValueError("No server URL provided") - # Stelle sicher, dass die URL ein Protokoll hat + # Ensure the URL has a protocol if not self.server_url.startswith(('http://', 'https://')): self.server_url = f'http://{self.server_url}' - # Entferne trailing slashes + # Remove trailing slashes self.server_url = self.server_url.rstrip('/') headers = { @@ -57,13 +57,13 @@ class JellyfinClient: 'is_admin': data['User'].get('Policy', {}).get('IsAdministrator', False) } except requests.exceptions.ConnectionError: - raise ValueError("Verbindung zum Server nicht möglich. Bitte überprüfen Sie die Server-URL.") + raise ValueError("Unable to connect to the server. Please check the server URL.") except requests.exceptions.Timeout: - raise ValueError("Zeitüberschreitung bei der Verbindung zum Server.") + raise ValueError("Connection to the server timed out.") except requests.exceptions.HTTPError as e: if e.response.status_code == 401: return None # Authentifizierung fehlgeschlagen - raise ValueError(f"HTTP-Fehler: {e.response.status_code}") + raise ValueError(f"HTTP error: {e.response.status_code}") except Exception as e: return None @@ -71,7 +71,7 @@ class JellyfinClient: """Check if user is admin in Jellyfin""" cache_key = f'jellyfin_admin_{user_id}' - # Check cache first + # Check cache first cached = cache.get(cache_key) if cached is not None: return cached @@ -106,11 +106,11 @@ def jellyfin_admin_required(view_func): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): if not request.user.is_authenticated: - messages.error(request, 'Sie müssen angemeldet sein, um diese Seite zu sehen.') + messages.error(request, 'You must be logged in to view this page.') return redirect('accounts:login') if not request.user.is_jellyfin_admin: - messages.error(request, 'Sie benötigen Admin-Rechte, um diese Seite zu sehen.') + messages.error(request, 'You need admin rights to view this page.') return redirect('index') return view_func(request, *args, **kwargs) diff --git a/accounts/views.py b/accounts/views.py index ba5515d..8ab32b1 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -17,7 +17,7 @@ class RegisterView(CreateView): def form_valid(self, form): response = super().form_valid(form) - messages.success(self.request, 'Registrierung erfolgreich! Sie können sich jetzt anmelden.') + messages.success(self.request, 'Registration successful! You can now sign in.') return response @login_required @@ -26,12 +26,12 @@ def profile(request): form = CustomUserChangeForm(request.POST, instance=request.user) if form.is_valid(): form.save() - messages.success(request, 'E-Mail gespeichert.') + messages.success(request, 'Email saved.') return redirect('accounts:profile') else: form = CustomUserChangeForm(instance=request.user) - # Lade Abonnements + # Load subscriptions series_subs = request.user.series_subscriptions.all() movie_subs = request.user.movie_subscriptions.all() @@ -40,7 +40,7 @@ def profile(request): from settingspanel.models import AppSettings from arr_api.services import sonarr_get_series, radarr_lookup_movie_by_title cfg = AppSettings.current() - # Serien + # Series for sub in series_subs: if not sub.series_poster and sub.series_id: details = sonarr_get_series(sub.series_id, base_url=cfg.sonarr_url, api_key=cfg.sonarr_api_key) @@ -51,7 +51,7 @@ def profile(request): if not sub.series_genres: sub.series_genres = details.get('series_genres') or [] sub.save(update_fields=['series_poster', 'series_overview', 'series_genres']) - # Filme + # Movies for sub in movie_subs: if not sub.poster: details = radarr_lookup_movie_by_title(sub.title, base_url=cfg.radarr_url, api_key=cfg.radarr_api_key) @@ -84,7 +84,7 @@ def jellyfin_login(request): app_settings = AppSettings.current() server_url = app_settings.get_jellyfin_url() if not server_url: - messages.error(request, 'Jellyfin Server ist nicht konfiguriert. Bitte Setup abschließen.') + messages.error(request, 'Jellyfin server is not configured. Please complete setup.') return render(request, 'accounts/login.html', {'form': form}) try: @@ -93,7 +93,7 @@ def jellyfin_login(request): auth_result = client.authenticate(username, password) if not auth_result: - messages.error(request, 'Anmeldung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldedaten.') + messages.error(request, 'Sign in failed. Please check your credentials.') return render(request, 'accounts/login.html', {'form': form}) # Existierenden User finden oder neu erstellen @@ -116,13 +116,13 @@ def jellyfin_login(request): user.save() login(request, user) - messages.success(request, f'Willkommen, {username}!') + messages.success(request, f'Welcome, {username}!') return redirect('arr_api:index') except ValueError as e: messages.error(request, str(e)) except Exception as e: - messages.error(request, f'Verbindungsfehler: {str(e)}') + messages.error(request, f'Connection error: {str(e)}') # invalid form or error path return render(request, 'accounts/login.html', {'form': form}) diff --git a/arr_api/management/commands/check_new_media.py b/arr_api/management/commands/check_new_media.py index c3eca81..ad9ae90 100644 --- a/arr_api/management/commands/check_new_media.py +++ b/arr_api/management/commands/check_new_media.py @@ -3,12 +3,12 @@ from django.utils import timezone from arr_api.notifications import check_and_notify_users class Command(BaseCommand): - help = 'Prüft neue Medien und sendet Benachrichtigungen' + help = 'Checks for new media and sends notifications' def handle(self, *args, **kwargs): - self.stdout.write(f'[{timezone.now()}] Starte Medien-Check...') + self.stdout.write(f'[{timezone.now()}] Starting media check...') try: check_and_notify_users() - self.stdout.write(self.style.SUCCESS(f'[{timezone.now()}] Medien-Check erfolgreich beendet')) + self.stdout.write(self.style.SUCCESS(f'[{timezone.now()}] Media check finished successfully')) except Exception as e: - self.stdout.write(self.style.ERROR(f'[{timezone.now()}] Fehler beim Medien-Check: {str(e)}')) + self.stdout.write(self.style.ERROR(f'[{timezone.now()}] Error during media check: {str(e)}')) diff --git a/arr_api/models.py b/arr_api/models.py index 8d731cb..d0a567c 100644 --- a/arr_api/models.py +++ b/arr_api/models.py @@ -12,7 +12,7 @@ class SeriesSubscription(models.Model): updated_at = models.DateTimeField(auto_now=True) class Meta: - unique_together = ['user', 'series_id'] # Ein User kann eine Serie nur einmal abonnieren + unique_together = ['user', 'series_id'] # A user can subscribe to a series only once def __str__(self): return self.series_title @@ -29,18 +29,16 @@ class MovieSubscription(models.Model): updated_at = models.DateTimeField(auto_now=True) class Meta: - unique_together = ['user', 'movie_id'] # Ein User kann einen Film nur einmal abonnieren + unique_together = ['user', 'movie_id'] # A user can subscribe to a movie only once def __str__(self): return self.title class SentNotification(models.Model): - """ - Speichert gesendete Benachrichtigungen um Duplikate zu vermeiden - """ + """Store sent notifications to avoid duplicates""" user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) media_id = models.IntegerField() - media_type = models.CharField(max_length=10) # 'series' oder 'movie' + media_type = models.CharField(max_length=10) # 'series' or 'movie' media_title = models.CharField(max_length=255) air_date = models.DateField() sent_at = models.DateTimeField(auto_now_add=True) diff --git a/arr_api/notifications.py b/arr_api/notifications.py index 4c90b08..45ff7ce 100644 --- a/arr_api/notifications.py +++ b/arr_api/notifications.py @@ -68,7 +68,7 @@ def send_notification_email( release_type=None, ): """ - Sendet eine Benachrichtigungs-E-Mail an einen User mit erweiterten Details + Sends a notification email to a user with extended details """ eff = _set_runtime_email_settings() logger.info( @@ -94,7 +94,7 @@ def send_notification_email( context = { 'username': user.username, 'title': media_title, - 'type': 'Serie' if media_type == 'series' else 'Film', + 'type': 'Series' if media_type == 'series' else 'Movie', 'overview': overview, 'poster_url': poster_url, 'episode_title': episode_title, @@ -105,7 +105,7 @@ def send_notification_email( 'release_type': release_type, } - subject = f"Neue {context['type']} verfügbar: {media_title}" + subject = f"New {context['type']} available: {media_title}" message = render_to_string('arr_api/email/new_media_notification.html', context) send_mail( @@ -209,8 +209,8 @@ def get_todays_radarr_calendar(): def check_jellyfin_availability(user, media_id, media_type): """ - Ersetzt: Wir prüfen Verfügbarkeit über Sonarr/Radarr (hasFile), - was zuverlässig ist, wenn Jellyfin dieselben Ordner scannt. + Replaced: We check availability via Sonarr/Radarr (hasFile), + which is reliable if Jellyfin scans the same folders. """ # user is unused here; kept for backward compatibility if media_type == 'series': diff --git a/arr_api/templates/arr_api/calendar.html b/arr_api/templates/arr_api/calendar.html new file mode 100644 index 0000000..ae51bba --- /dev/null +++ b/arr_api/templates/arr_api/calendar.html @@ -0,0 +1,301 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Calendar – Subscribarr{% endblock %} + +{% block extra_style %} + + + +{% endblock %} + +{% block content %} +
+

Calendar

+
+
+ + + + +{% csrf_token %} + + +{% endblock %} diff --git a/arr_api/templates/arr_api/index.html b/arr_api/templates/arr_api/index.html index 2404439..4911b5c 100644 --- a/arr_api/templates/arr_api/index.html +++ b/arr_api/templates/arr_api/index.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% load static %} -{% block title %}Subscribarr – Übersicht{% endblock %} +{% block title %}Subscribarr – Overview{% endblock %} {% block extra_style %} @@ -20,26 +20,30 @@
- - - + + +
-
{% if show_series %}
-

Laufende Serien

+

Ongoing series

{% for s in series_grouped %}
{% empty %} -
Keine kommenden Episoden.
+
No upcoming episodes.
{% endfor %}
@@ -72,7 +76,7 @@ {% endwith %}
{% empty %} -

Keine Serien gefunden.

+

No series found.

{% endfor %}
@@ -80,7 +84,7 @@ {% if show_movies %}
-

Anstehende Filme

+

Upcoming movies

{% for m in movies %}
{{ m.title }}{% if m.year %} ({{ m.year }}){% endif %}
- {% if m.inCinemas %}Kino: {% endif %} + {% if m.inCinemas %}In theaters: {% endif %} {% if m.digitalRelease %}
Digital: {% endif %} - {% if m.physicalRelease %}
Disc: {% endif %} + {% if m.physicalRelease %}
Physical: {% endif %}
{% empty %} -

Keine Filme gefunden.

+

No movies found.

{% endfor %}
@@ -118,17 +122,17 @@
- +