diff --git a/AGENTS.md b/AGENTS.md index 92b10b2..1926d2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,12 @@ Bygg ett webbaserat system för hantering av utlägg (”claims”) åt en organ 11. Offentliga sidor ska använda Tailwind-baserade komponenter (CDN är okej) med minimalistisk layout. Claim-formuläret ska erbjuda klient-side kontroll för antal rader (plus/minus) utan sidladdning och återanvända formsetets tomma form som mall. 12. Adminvyn för claims ska spegla samma designprinciper (kort per claim, statuschippar, loggtimeline och inlinebeslut). 13. Hantering av utbetalningar i UI är bakom flaggan `CLAIMS_ENABLE_INTERNAL_PAYMENTS` och kan även togglas via Django admin (modell `SystemSetting`). När den är på ska godkända claims få en summeringssektion med tydlig info (namn, belopp, kontonr) och en "Betala"-knapp som markerar posten som betald (med logg och markerad betalstatus). När flaggan är av saknas knappen och admins instrueras att hantera betalning externt. -14. När ett utlägg markerats som betalt ska beslut/status vara låst (ingen uppdatering av kommentar eller status i UI eller Django admin). +14. När ett utlägg markerats som betalt ska beslut/status vara låst (ingen uppdatering av kommentar eller status i UI eller Django admin). Reset av betalstatus sker endast via admin-knappen. +15. Filuppladdningar ska alltid få säkra, unika namn (UUID i `receipts/`), och originalnamn ska inte exponeras. +16. För e-postaviseringar: använd `CLAIMS_EMAIL_ENABLED` (default false) och miljövariablerna `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USE_TLS`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD`, `CLAIMS_EMAIL_FROM`, `CLAIMS_ADMIN_NOTIFICATION_EMAIL`. När flaggan är av ska koden inte försöka skicka mejl (men gärna logga att aviseringar är inaktiverade). +17. Systemet ska vara tvåspråkigt (sv/en). Alla nya strängar måste wraps i `gettext`/`{% trans %}` och översättningar läggs till via `makemessages`/`compilemessages`. Navbaren innehåller alltid språkväljare och `` ska sätta `lang` enligt vald session. +18. Efter inskick ska användaren hamna på en Tailwind-baserad bekräftelsesida med knappar för att logga in eller skicka nytt utlägg. +19. Ett management-kommando `reset_claims` ska alltid finnas uppdaterat för att rensa claims men lämna konton (använd `uv run python manage.py reset_claims`). ## Säkerhet och drift - Skydda admin-flöden bakom inloggning. diff --git a/README.md b/README.md index db95da9..c94d580 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,91 @@ ## claims-system -### Kom igång -```bash -uv sync -uv run python manage.py migrate -uv run python manage.py createsuperuser -uv run python manage.py runserver +Modern Django/Tailwind-baserad portal för att ta emot, granska och betala utlägg. + +--- + +### 1. Kom igång +1. `uv sync` +2. `uv run python manage.py migrate` +3. `uv run python manage.py createsuperuser` +4. `uv run python manage.py runserver` + +Nyckel-URLer (default): +- Offentligt formulär `GET /claims/new/` +- Bekräftelsesida `GET /claims/submitted/` +- Adminlista `GET /claims/admin/` +- Mina utlägg `GET /claims/mine/` +- Användarhantering `GET /claims/users/` +- Export-placeholder `GET /claims/export/` +- Auth `GET /accounts/login|logout/` + +--- + +### 2. Kärnfunktioner +- **Multi-rad formulär:** Offentligt formulär stödjer upp till 5 rader. Lägg till `?forms=n` eller använd +/−-knapparna (lägger till rader utan reload). +- **Auto-prefill:** Inloggade användare får namn, e-post och senaste kontonummer förifyllt. +- **Valuta & projekt:** Varje rad har dold valutaväljare (SEK default) och projektreferens. Projekt listas från Django admin > Projekt. +- **Kvitton:** Filuppladdningar sparas med slumpat UUID-baserat namn under `receipts/` för säkerhet och unika namn. +- **Adminlista:** Kortlayout med statuschippar, loggtimeline, kvittolänkar och inline-formulär för godkänn/avslag. +- **Betalspårning:** När intern betalning är på får godkända claims en "Betala"-knapp. När ett claim markeras som betalt låses status/kommentar tills reset görs. +- **Mina utlägg:** Inloggade ser sina egna claims i samma Tailwind-layout med kvitto-länk och logg. +- **Användarhantering:** Tailwind-sida där personal kan skapa konton, tilldela `claims.view_claim`/`claims.change_claim`, markera staff och ta bort användare. + +--- + +### 3. Språk & UI +- Django i18n är aktiverat (`LANGUAGES = [('sv','Swedish'), ('en','English')]`, LocaleMiddleware, språkväljare i navbaren). +- Alla mallar/formulär använder `{% trans %}`/`gettext`. Engelska översättningar ligger i `locale/en/LC_MESSAGES/django.po` (kompileras till `.mo`). +- Uppdatera översättningar: + ```bash + uv run django-admin makemessages -l en + uv run django-admin compilemessages -l en + ``` +- `` sätts automatiskt, och språkväljaren lagrar valet i session/cookie. + +--- + +### 4. Viktiga inställningar +| Variabel | Default | Beskrivning | +| --- | --- | --- | +| `CLAIMS_ENABLE_INTERNAL_PAYMENTS` | `true` | Styr “Betala”-flödet. Kan också togglas i Django admin > Systeminställningar. | +| `CLAIMS_EMAIL_ENABLED` | `false` | Slår på e-postaviseringar. Låt vara `false` i testläge. | +| `CLAIMS_EMAIL_FROM` | `no-reply@claims.local` | Avsändare för utskick. | +| `CLAIMS_ADMIN_NOTIFICATION_EMAIL` | tom | Om satt skickas notifiering vid nytt claim (när e-post är aktiverad). | +| `EMAIL_BACKEND` | `django.core.mail.backends.console.EmailBackend` | Byt till SMTP i prod (se nedan). | +| `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USE_TLS`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD` | tom | Standard Django SMTP-inställningar. | +| `LANGUAGE_CODE` | `sv` | Standardspråk. | +| `LOCALE_PATHS` | `BASE_DIR/locale` | Katalog för `.po/.mo`. | +| `LOGIN_REDIRECT_URL` | `/claims/admin/` | Efter inloggning. | +| `LOGOUT_REDIRECT_URL` | `/accounts/login/` | Efter utloggning. | + +SMTP-exempel: +```env +CLAIMS_EMAIL_ENABLED=true +CLAIMS_EMAIL_FROM=claims@example.com +CLAIMS_ADMIN_NOTIFICATION_EMAIL=finance@example.com +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +EMAIL_HOST=smtp.example.com +EMAIL_PORT=587 +EMAIL_USE_TLS=true +EMAIL_HOST_USER=apikey +EMAIL_HOST_PASSWORD=secret ``` -- Offentligt formulär: `http://localhost:8000/claims/new/` -- Sidan börjar med ett block där användaren skriver sina uppgifter (för inloggade fylls namn/e‑post + senaste kontonummer i automatiskt). Själva utläggsraderna kan fyllas i flera åt gången via formset (lägg till `?forms=n` för fler rader, max 5). -- Varje rad har en dold valuta-väljare. Standard är SEK men EUR/USD/GBP går att välja vid behov. -- Välj även vilket projekt/evenemang utlägget hör till (valen hämtas från Django admin > Projekt). -- Adminlista (kräver `claims.view_claim`, uppdateringar kräver `claims.change_claim`): `http://localhost:8000/claims/admin/` -- Adminlistan visar kvittolänk, vem som skickade in (och om det var en inloggad användare) samt en logg över alla statusändringar. -- När ett utlägg markerats som betalat låses beslutskommentar/status i hela systemet (både listvyn och Django admin). -- Export-meny (placeholder för framtida integrationer): `http://localhost:8000/claims/export/` -- Inloggade användare kan följa sina egna claim via `http://localhost:8000/claims/mine/`. -- Behörighets- och kontohantering (visa kräver `auth.view_user`, skapa/uppdatera/ta bort kräver respektive `auth.add_user`/`auth.change_user`/`auth.delete_user`): `http://localhost:8000/claims/users/` -- Django auth-vyer (login/logout) exponeras under `/accounts/`. -- Använd Django admin (`/admin/`) för att skapa konton, lägga användare i grupper, lägga upp projekt/evenemang samt tilldela behörigheterna `claims.view_claim` och `claims.change_claim`. Superusers har full kontroll per default. -- Intern betalningshantering styrs av miljövariabeln `CLAIMS_ENABLE_INTERNAL_PAYMENTS` (default `true`) och kan dessutom togglas i Django admin under **Systeminställningar**. När funktionen är på får godkända claims en "Betala"-knapp som loggar vem som markerade posten som betald. +--- + +### 5. Underhåll & verktyg +- **Reset claims:** `uv run python manage.py reset_claims` (bekräfta med `ja` eller använd `--noinput`). Tar bort alla claims/loggar/kvitton men lämnar användarkonton. +- **Django admin:** `/admin/` används för projekt, grupper, superusers, SystemSetting mm. Staff-användare har automatiskt åtkomst. +- **Testa konfiguration:** `uv run python manage.py check`. +- **Språkreset:** Rensa cookies om språkväljaren inte byter språk (Django använder `django_language` cookien). + +--- + +### 6. Länkar och roller +- Offentligt formulär: alla (även utan konto). +- Mina utlägg / Adminlista / Export / Betalningar: kräver `claims.view_claim` (och `claims.change_claim` för beslut). +- Användarhantering: `auth.view_user` + respektive add/change/delete. +- Språkväljare och logout finns i nav-menyn på varje sida. + +Se även `AGENTS.md` för utvecklingsriktlinjer, betalflöden och e-postpolicy. EOF diff --git a/claims/email_utils.py b/claims/email_utils.py new file mode 100644 index 0000000..ecf5eca --- /dev/null +++ b/claims/email_utils.py @@ -0,0 +1,53 @@ +import logging + +from django.conf import settings +from django.core.mail import send_mail +from django.template.loader import render_to_string +from django.utils.html import strip_tags + +logger = logging.getLogger(__name__) + + +def emails_enabled(): + return getattr(settings, "CLAIMS_EMAIL_ENABLED", False) + + +def _send(subject: str, template: str, context: dict, recipient: str): + if not emails_enabled(): + logger.info("Email disabled - skipping send to %s", recipient) + return + html_message = render_to_string(template, context) + plain_message = strip_tags(html_message) + send_mail( + subject=subject, + message=plain_message, + from_email=settings.CLAIMS_EMAIL_FROM, + recipient_list=[recipient], + html_message=html_message, + fail_silently=False, + ) + + +def send_claimant_confirmation_email(claim): + if not claim.email: + return + context = {"claim": claim} + _send( + subject="Din utläggsbegäran är mottagen", + template="emails/claim_submitted_claimant.html", + context=context, + recipient=claim.email, + ) + + +def notify_admin_of_claim(claim): + recipient = settings.CLAIMS_ADMIN_NOTIFICATION_EMAIL + if not recipient: + return + context = {"claim": claim} + _send( + subject="Nytt utlägg inskickat", + template="emails/claim_submitted_admin.html", + context=context, + recipient=recipient, + ) diff --git a/claims/forms.py b/claims/forms.py index d1fff02..283fa99 100644 --- a/claims/forms.py +++ b/claims/forms.py @@ -1,6 +1,7 @@ from django import forms from django.contrib.auth import get_user_model, password_validation from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ from .models import Claim, Project @@ -15,16 +16,16 @@ FILE_CLASSES = "mt-1 block w-full text-sm text-gray-700 file:mr-4 file:rounded-m class ClaimantForm(forms.Form): full_name = forms.CharField( max_length=255, - label="Namn", + label=_("Namn"), widget=forms.TextInput(attrs={"class": INPUT_CLASSES}), ) email = forms.EmailField( - label="E-post", + label=_("E-post"), widget=forms.EmailInput(attrs={"class": INPUT_CLASSES}), ) account_number = forms.CharField( max_length=50, - label="Kontonummer", + label=_("Kontonummer"), widget=forms.TextInput(attrs={"class": INPUT_CLASSES}), ) @@ -45,11 +46,11 @@ class ClaimLineForm(forms.ModelForm): model = Claim fields = ["description", "amount", "currency", "project", "receipt"] labels = { - "description": "Beskrivning", - "amount": "Belopp", - "currency": "Valuta", - "project": "Evenemang/Projekt", - "receipt": "Kvitto", + "description": _("Beskrivning"), + "amount": _("Belopp"), + "currency": _("Valuta"), + "project": _("Evenemang/Projekt"), + "receipt": _("Kvitto"), } widgets = { "description": forms.Textarea(attrs={"rows": 3, "class": TEXTAREA_CLASSES}), @@ -60,15 +61,15 @@ class ClaimDecisionForm(forms.Form): ACTION_APPROVE = "approve" ACTION_REJECT = "reject" ACTION_CHOICES = ( - (ACTION_APPROVE, "Godkänn"), - (ACTION_REJECT, "Neka"), + (ACTION_APPROVE, _("Godkänn")), + (ACTION_REJECT, _("Neka")), ) claim_id = forms.IntegerField(widget=forms.HiddenInput) action = forms.ChoiceField(choices=ACTION_CHOICES) decision_note = forms.CharField( required=False, - widget=forms.Textarea(attrs={"rows": 2, "placeholder": "Kommentar"}), + widget=forms.Textarea(attrs={"rows": 2, "placeholder": _("Kommentar")}), ) def clean(self): @@ -76,31 +77,31 @@ class ClaimDecisionForm(forms.Form): action = cleaned.get("action") note = cleaned.get("decision_note", "").strip() if action == self.ACTION_REJECT and not note: - self.add_error("decision_note", "Kommentar krävs när du nekar ett utlägg.") + self.add_error("decision_note", _("Kommentar krävs när du nekar ett utlägg.")) return cleaned class UserManagementForm(forms.Form): - username = forms.CharField(max_length=150, label="Användarnamn") - email = forms.EmailField(required=False, label="E-post") - first_name = forms.CharField(max_length=150, required=False, label="Förnamn") - last_name = forms.CharField(max_length=150, required=False, label="Efternamn") - password1 = forms.CharField(widget=forms.PasswordInput, label="Lösenord") - password2 = forms.CharField(widget=forms.PasswordInput, label="Bekräfta lösenord") - is_staff = forms.BooleanField(required=False, initial=True, label="Administratör (staff)") - grant_view = forms.BooleanField(required=False, initial=True, label="Ge behörighet att se utlägg") - grant_change = forms.BooleanField(required=False, initial=True, label="Ge behörighet att besluta utlägg") + username = forms.CharField(max_length=150, label=_("Användarnamn")) + email = forms.EmailField(required=False, label=_("E-post")) + first_name = forms.CharField(max_length=150, required=False, label=_("Förnamn")) + last_name = forms.CharField(max_length=150, required=False, label=_("Efternamn")) + password1 = forms.CharField(widget=forms.PasswordInput, label=_("Lösenord")) + password2 = forms.CharField(widget=forms.PasswordInput, label=_("Bekräfta lösenord")) + is_staff = forms.BooleanField(required=False, initial=True, label=_("Administratör (staff)")) + grant_view = forms.BooleanField(required=False, initial=True, label=_("Ge behörighet att se utlägg")) + grant_change = forms.BooleanField(required=False, initial=True, label=_("Ge behörighet att besluta utlägg")) def clean_username(self): username = self.cleaned_data["username"] if User.objects.filter(username=username).exists(): - raise forms.ValidationError("Användarnamnet är upptaget.") + raise forms.ValidationError(_("Användarnamnet är upptaget.")) return username def clean(self): cleaned = super().clean() if cleaned.get("password1") != cleaned.get("password2"): - self.add_error("password2", "Lösenorden matchar inte.") + self.add_error("password2", _("Lösenorden matchar inte.")) password = cleaned.get("password1") if password: temp_user = User( @@ -118,9 +119,9 @@ class UserManagementForm(forms.Form): class UserPermissionForm(forms.Form): user_id = forms.IntegerField(widget=forms.HiddenInput) - is_staff = forms.BooleanField(required=False, label="Admin/staff") - grant_view = forms.BooleanField(required=False, label="Får se utlägg") - grant_change = forms.BooleanField(required=False, label="Får besluta utlägg") + is_staff = forms.BooleanField(required=False, label=_("Admin/staff")) + grant_view = forms.BooleanField(required=False, label=_("Får se utlägg")) + grant_change = forms.BooleanField(required=False, label=_("Får besluta utlägg")) class DeleteUserForm(forms.Form): diff --git a/claims/management/__init__.py b/claims/management/__init__.py new file mode 100644 index 0000000..0e632e1 --- /dev/null +++ b/claims/management/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/claims/management/commands/__init__.py b/claims/management/commands/__init__.py new file mode 100644 index 0000000..0e632e1 --- /dev/null +++ b/claims/management/commands/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/claims/management/commands/reset_claims.py b/claims/management/commands/reset_claims.py new file mode 100644 index 0000000..74a4948 --- /dev/null +++ b/claims/management/commands/reset_claims.py @@ -0,0 +1,35 @@ +from django.core.management.base import BaseCommand +from django.db import transaction + +from claims.models import Claim + + +class Command(BaseCommand): + help = "Tar bort samtliga utlägg (claims) men lämnar användarkonton orörda." + + def add_arguments(self, parser): + parser.add_argument( + "--noinput", + action="store_true", + help="Utför rensningen utan interaktiv bekräftelse.", + ) + + def handle(self, *args, **options): + if not options["noinput"]: + confirm = input( + "Detta tar bort alla claims inklusive loggar och kvitton men lämnar konton. Fortsätt? (skriv 'ja'): " + ) + if confirm.lower() != "ja": + self.stdout.write(self.style.WARNING("Avbrutet.")) + return + + count = Claim.objects.count() + with transaction.atomic(): + for claim in Claim.objects.iterator(): + if claim.receipt: + claim.receipt.delete(save=False) + Claim.objects.all().delete() + + self.stdout.write( + self.style.SUCCESS(f"Raderade {count} claims och tillhörande loggar/kvitton.") + ) diff --git a/claims/models.py b/claims/models.py index d1f855f..b59bccf 100644 --- a/claims/models.py +++ b/claims/models.py @@ -1,4 +1,7 @@ +import uuid + from django.conf import settings +from django.core.files.storage import default_storage from django.db import models from django.utils.translation import gettext_lazy as _ @@ -90,6 +93,22 @@ class Claim(models.Model): performed_by=performed_by, ) + def save(self, *args, **kwargs): + if self.receipt and not kwargs.pop("skip_receipt_rename", False): + original_name = self.receipt.name or "" + ext = original_name.rsplit(".", 1)[-1] if "." in original_name else "dat" + new_name = self._generate_unique_receipt_name(ext) + self.receipt.name = new_name + super().save(*args, **kwargs) + + def _generate_unique_receipt_name(self, ext): + ext = ext.lower() + for _ in range(10): + candidate = f"receipts/{uuid.uuid4().hex}.{ext}" + if not default_storage.exists(candidate): + return candidate + return f"receipts/{uuid.uuid4().hex}.{ext}" + class ClaimLog(models.Model): class Action(models.TextChoices): diff --git a/claims/templates/claims/admin_list.html b/claims/templates/claims/admin_list.html index 9b819c5..ca66d28 100644 --- a/claims/templates/claims/admin_list.html +++ b/claims/templates/claims/admin_list.html @@ -1,25 +1,20 @@ {% extends "claims/base.html" %} +{% load i18n %} -{% block title %}Admin – Utlägg{% endblock %} +{% block title %}{% trans "Admin – Utlägg" %}{% endblock %} {% block content %}
-

Översikt

-

Inkomna utlägg

-

Filtrera på status, granska kvitton och uppdatera beslut direkt i listan.

+

{% trans "Översikt" %}

+

{% trans "Inkomna utlägg" %}

+

{% trans "Filtrera på status, granska kvitton och uppdatera beslut direkt i listan." %}

- {% with selected=status_filter %} - {% with filters="all"|add:"," %} - {% endwith %} - {% with statuses=status_choices %} - {% endwith %} - {% endwith %} - Alla + {% trans "Alla" %} {% for value, label in status_choices %} {% endif %} - Skapad {{ claim.created_at|date:"Y-m-d H:i" }} + {% trans "Skapad" %} {{ claim.created_at|date:"Y-m-d H:i" }}
-

Person

+

{% trans "Person" %}

{{ claim.full_name }}

- {{ claim.email }} · Konto: {{ claim.account_number }}
+ {{ claim.email }} · {% trans "Konto" %}: {{ claim.account_number }}
{% if claim.submitted_by %} - Inloggad användare: {{ claim.submitted_by.get_username }} + {% trans "Inloggad användare" %}: {{ claim.submitted_by.get_username }} {% else %} - Inskickad av gäst + {% trans "Inskickad av gäst" %} {% endif %}

@@ -65,16 +60,16 @@ {{ claim.get_status_display }} {% if claim.decision_note %} -

Kommentar: {{ claim.decision_note }}

+

{% trans "Kommentar" %}: {{ claim.decision_note }}

{% endif %} {% if payments_enabled and claim.status == 'approved' %} {% if claim.is_paid %} - Betald {{ claim.paid_at|date:"Y-m-d H:i" }} - {% if claim.paid_by %}av {{ claim.paid_by.get_username }}{% endif %} + {% trans "Betald" %} {{ claim.paid_at|date:"Y-m-d H:i" }} + {% if claim.paid_by %}{% trans "av" %} {{ claim.paid_by.get_username }}{% endif %} {% else %} - Ej markerad som betald + {% trans "Ej markerad som betald" %} {% endif %} {% endif %} @@ -84,25 +79,25 @@
-

Sammanfattning

+

{% trans "Sammanfattning" %}

{{ claim.full_name }}

-

Belopp: {{ claim.amount }} {{ claim.currency }} · Konto: {{ claim.account_number }}

+

{% trans "Belopp" %}: {{ claim.amount }} {{ claim.currency }} · {% trans "Konto" %}: {{ claim.account_number }}

{% if payments_enabled %} {% if claim.is_paid %} -

Markerad som betald

+

{% trans "Markerad som betald" %}

{% else %} -
+ {% csrf_token %}
{% endif %} {% else %} -

Intern betalningshantering är av – markera betalning i ekonomisystemet.

+

{% trans "Intern betalningshantering är av – markera betalning i ekonomisystemet." %}

{% endif %}
@@ -110,7 +105,7 @@
- Logg & tidslinje + {% trans "Logg & tidslinje" %}
    {% for log in claim.logs.all %} @@ -136,17 +131,17 @@

    {{ log.get_action_display }}

    {{ log.created_at|date:"Y-m-d H:i" }}

    {% if log.from_status %} -

    Status: {{ log.get_from_status_display }} → {{ log.get_to_status_display }}

    +

    {% trans "Status" %}: {{ log.get_from_status_display }} → {{ log.get_to_status_display }}

    {% endif %} {% if log.note %}

    "{{ log.note }}"

    {% endif %} {% if log.performed_by %} -

    Av {{ log.performed_by.get_username }}

    +

    {% trans "Av" %} {{ log.performed_by.get_username }}

    {% endif %} {% empty %} -
  • Ingen logg än.
  • +
  • {% trans "Ingen logg än." %}
  • {% endfor %}
@@ -154,26 +149,26 @@ {% if can_change %} {% if claim.is_paid %}

- Utlägget är markerat som betalt. Ändringar av beslut/kommentar är låsta. + {% trans "Utlägget är markerat som betalt. Ändringar av beslut/kommentar är låsta." %}

{% else %}
{% csrf_token %} - - {% for value, label in decision_choices %} {% endfor %} - - + +
{% endif %} @@ -185,8 +180,8 @@
{% else %}
-

Inga utlägg ännu

-

När formuläret tas emot visas posterna automatiskt här.

+

{% trans "Inga utlägg ännu" %}

+

{% trans "När formuläret tas emot visas posterna automatiskt här." %}

{% endif %}
diff --git a/claims/templates/claims/base.html b/claims/templates/claims/base.html index 00da0ad..651b62d 100644 --- a/claims/templates/claims/base.html +++ b/claims/templates/claims/base.html @@ -1,9 +1,11 @@ +{% load i18n %} - +{% get_current_language as LANGUAGE_CODE %} + - {% block title %}Claims{% endblock %} + {% block title %}{% trans "Claims" %}{% endblock %}