diff --git a/bsmain/templates/bsmain/auth_token_list.html b/bsmain/templates/bsmain/auth_token_list.html index 1c069a6..623a723 100644 --- a/bsmain/templates/bsmain/auth_token_list.html +++ b/bsmain/templates/bsmain/auth_token_list.html @@ -1,7 +1,8 @@ {% extends "base.html" %} {% load static %} +{% load i18n %} -{% block title %}Auth Tokens · {{ site_title }}{% endblock %} +{% block title %}{% translate "Auth Tokens" %} · {{ site_title }}{% endblock %} {% block content %} @@ -19,12 +20,12 @@ {% endif %}
-

Auth Tokens

+

{% translate "Auth Tokens" %}

{% csrf_token %} {# margins display slightly different from the Add Token +
@@ -37,7 +38,7 @@ - Auth Tokens + {% translate "Auth Tokens" %} {% for auth_token in auth_tokens %} @@ -50,7 +51,7 @@
- +
@@ -59,7 +60,7 @@
- No Auth Tokens. + {% translate "No Auth Tokens." %}
diff --git a/bsmain/views.py b/bsmain/views.py index 27225ad..9352813 100644 --- a/bsmain/views.py +++ b/bsmain/views.py @@ -20,7 +20,7 @@ def auth_token_list(request): if action == "delete": AuthToken.objects.get(pk=pk).delete() - messages.success(request, 'Token deleted') + messages.success(request, _('Token deleted')) return redirect('auth_token_list') return render(request, 'bsmain/auth_token_list.html', { diff --git a/bugsink/middleware.py b/bugsink/middleware.py index 3578fd3..0680c9c 100644 --- a/bugsink/middleware.py +++ b/bugsink/middleware.py @@ -128,3 +128,32 @@ class SetRemoteAddrMiddleware: request.META["REMOTE_ADDR"] = self.parse_x_forwarded_for(request.META.get("HTTP_X_FORWARDED_FOR", None)) return self.get_response(request) + + +class UserLanguageMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + + if (request.user.is_authenticated and + hasattr(request.user, 'language')): + + user_language = request.user.language + current_cookie = request.COOKIES.get('django_language') + + if user_language == 'auto': + if current_cookie is not None: + response.delete_cookie('django_language') + else: + if current_cookie != user_language: + response.set_cookie( + 'django_language', + user_language, + max_age=365 * 24 * 60 * 60, + httponly=False, + samesite='Lax' + ) + + return response diff --git a/bugsink/settings/default.py b/bugsink/settings/default.py index 2c9f734..6fb76b7 100644 --- a/bugsink/settings/default.py +++ b/bugsink/settings/default.py @@ -97,11 +97,13 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'verbose_csrf_middleware.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'bugsink.middleware.LoginRequiredMiddleware', + 'bugsink.middleware.UserLanguageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -241,6 +243,14 @@ TIME_ZONE = 'Europe/Amsterdam' USE_I18N = True +USE_L10N = True + +LOCALE_PATHS = [BASE_DIR / "locale"] +LANGUAGES = ( + ("en", "English"), + ("zh-Hans", "简体中文"), +) + USE_TZ = True diff --git a/issues/models.py b/issues/models.py index 7637521..534b912 100644 --- a/issues/models.py +++ b/issues/models.py @@ -8,6 +8,7 @@ from django.db.models.functions import Concat from django.template.defaultfilters import date as default_date_filter from django.conf import settings from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ from bugsink.utils import assert_ from bugsink.volume_based_condition import VolumeBasedCondition @@ -485,16 +486,16 @@ class IssueQuerysetStateManager(object): class TurningPointKind(models.IntegerChoices): # The language of the kinds reflects a historic view of the system, e.g. "first seen" as opposed to "new issue"; an # alternative take (which is more consistent with the language used elsewhere" is a more "active" language. - FIRST_SEEN = 1, "First seen" - RESOLVED = 2, "Resolved" - MUTED = 3, "Muted" - REGRESSED = 4, "Marked as regressed" - UNMUTED = 5, "Unmuted" + FIRST_SEEN = 1, _("First seen") + RESOLVED = 2, _("Resolved") + MUTED = 3, _("Muted") + REGRESSED = 4, _("Marked as regressed") + UNMUTED = 5, _("Unmuted") - NEXT_MATERIALIZED = 10, "Release info added" + NEXT_MATERIALIZED = 10, _("Release info added") # ASSGINED = 10, "Assigned to user" # perhaps later - MANUAL_ANNOTATION = 100, "Manual annotation" + MANUAL_ANNOTATION = 100, _("Manual annotation") class TurningPoint(models.Model): diff --git a/issues/templates/issues/_event_nav.html b/issues/templates/issues/_event_nav.html index 5d7e65d..a36381f 100644 --- a/issues/templates/issues/_event_nav.html +++ b/issues/templates/issues/_event_nav.html @@ -1,7 +1,8 @@ {% load add_to_qs %} +{% load i18n %}
{# nav="last": when doing a new search on an event-page, you want the most recent matching event to show up #} - +
{% if has_prev %} {# no need for 'is_first': if you can go to the left, you can go all the way to the left too #} diff --git a/issues/templates/issues/base.html b/issues/templates/issues/base.html index 9beecc8..668f6ee 100644 --- a/issues/templates/issues/base.html +++ b/issues/templates/issues/base.html @@ -4,6 +4,8 @@ {% load humanize %} {% load stricter_templates %} {% load add_to_qs %} +{% load i18n %} + {% block title %}{{ issue.title }} · {{ block.super }}{% endblock %} {% block content %} @@ -18,12 +20,12 @@ {% spaceless %}{# needed to avoid whitespace between the looks-like-one-buttons #} {% if issue.project.has_releases %} - + {# we just hide the whole dropdown; this is the easiest implementation of not-showing the dropdown #} {% else %} - + {% endif %} {% endspaceless %} @@ -32,7 +34,7 @@ {% if issue.project.has_releases %} {# 'by next' is shown even if 'by current' is also shown: just because you haven't seen 'by current' doesn't mean it's actually already solved; and in fact we show this option first precisely because we can always show it #} - +