Activate user's preferred language in JavaScriptCatalog view

pull/11075/head
Bernhard Bliem 2023-10-17 18:30:50 +02:00 zatwierdzone przez Sage Abdullah
rodzic 4f37b011f5
commit 9c4136635b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: EB1A33CC51CC0217
7 zmienionych plików z 78 dodań i 43 usunięć

Wyświetl plik

@ -5,6 +5,7 @@ Changelog
~~~~~~~~~~~~~~~~
* Add `WAGTAIL_` prefix to Wagtail-specific tag settings (Aayushman Singh)
* Fix: Take preferred language into account for translatable strings in client-side code (Bernhard Bliem, Sage Abdullah)
* Docs: Add missing `django.contrib.admin` to list of apps in "add to Django project" guide (Mohamed Rabiaa)

Wyświetl plik

@ -17,7 +17,7 @@ depth: 1
### Bug fixes
* ...
* Take preferred language into account for translatable strings in client-side code (Bernhard Bliem, Sage Abdullah)
### Documentation

Wyświetl plik

@ -1,15 +1,13 @@
import types
from functools import wraps
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.timezone import override as override_tz
from django.utils.translation import gettext as _
from django.utils.translation import override
from wagtail.admin import messages
from wagtail.admin.localization import get_localized_response
from wagtail.log_actions import LogContext
from wagtail.permissions import page_permission_policy
@ -135,42 +133,8 @@ def require_admin_access(view_func):
if user.has_perms(["wagtailadmin.access_admin"]):
try:
preferred_language = None
if hasattr(user, "wagtail_userprofile"):
preferred_language = (
user.wagtail_userprofile.get_preferred_language()
)
time_zone = user.wagtail_userprofile.get_current_time_zone()
else:
time_zone = settings.TIME_ZONE
with override_tz(time_zone), LogContext(user=user):
if preferred_language:
with override(preferred_language):
response = view_func(request, *args, **kwargs)
else:
response = view_func(request, *args, **kwargs)
if hasattr(response, "render"):
# If the response has a render() method, Django treats it
# like a TemplateResponse, so we should do the same
# In this case, we need to guarantee that when the TemplateResponse
# is rendered, it is done within the override context manager
# or the user preferred_language/timezone will not be used
# (this could be replaced with simply rendering the TemplateResponse
# for simplicity but this does remove some of its middleware modification
# potential)
render = response.render
def overridden_render(response):
with override_tz(time_zone):
if preferred_language:
with override(preferred_language):
return render()
return render()
response.render = types.MethodType(overridden_render, response)
# decorate the response render method with the override context manager
return response
with LogContext(user=user):
return get_localized_response(view_func, request, *args, **kwargs)
except PermissionDenied:
if request.headers.get("x-requested-with") == "XMLHttpRequest":

Wyświetl plik

@ -1,10 +1,13 @@
import functools
import types
import zoneinfo
from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.utils.dates import MONTHS, WEEKDAYS, WEEKDAYS_ABBR
from django.utils.timezone import override as override_tz
from django.utils.translation import gettext as _
from django.utils.translation import override
# Wagtail languages with >=90% coverage
# This list is manually maintained
@ -117,3 +120,41 @@ def get_available_admin_time_zones():
return getattr(
settings, "WAGTAIL_USER_TIME_ZONES", sorted(zoneinfo.available_timezones())
)
def get_localized_response(view_func, request, *args, **kwargs):
user = request.user
preferred_language = None
if hasattr(user, "wagtail_userprofile"):
preferred_language = user.wagtail_userprofile.get_preferred_language()
time_zone = user.wagtail_userprofile.get_current_time_zone()
else:
time_zone = settings.TIME_ZONE
with override_tz(time_zone):
if preferred_language:
with override(preferred_language):
response = view_func(request, *args, **kwargs)
else:
response = view_func(request, *args, **kwargs)
if hasattr(response, "render"):
# If the response has a render() method, Django treats it
# like a TemplateResponse, so we should do the same
# In this case, we need to guarantee that when the TemplateResponse
# is rendered, it is done within the override context manager
# or the user preferred_language/timezone will not be used
# (this could be replaced with simply rendering the TemplateResponse
# for simplicity but this does remove some of its middleware modification
# potential)
render = response.render
def overridden_render(response):
with override_tz(time_zone):
if preferred_language:
with override(preferred_language):
return render()
return render()
response.render = types.MethodType(overridden_render, response)
# decorate the response render method with the override context manager
return response

Wyświetl plik

@ -6,6 +6,7 @@ from wagtail.admin.forms.auth import PasswordResetForm
from wagtail.admin.tests.test_forms import CustomLoginForm, CustomPasswordResetForm
from wagtail.models import Page
from wagtail.test.utils import WagtailTestUtils
from wagtail.users.models import UserProfile
class TestLoginView(WagtailTestUtils, TestCase):
@ -224,10 +225,24 @@ class TestPasswordResetView(TestCase):
)
class TestJsi18nView(TestCase):
class TestJsi18nView(WagtailTestUtils, TestCase):
def test_jsi18n_does_not_require_login(self):
response = self.client.get(reverse("wagtailadmin_javascript_catalog"))
self.assertEqual(response.status_code, 200)
# get content type header without the "charset" suffix
content_type = response["content-type"].split(";")[0]
self.assertEqual(content_type, "text/javascript")
def test_jsi18n_is_localized(self):
user = self.login()
profile = UserProfile.get_for_user(user)
# If a non-default language is activated, the response should contain this:
pattern = "const newcatalog = "
# If the preferred language is the default language, the catalog should be empty
response = self.client.get(reverse("wagtailadmin_javascript_catalog"))
self.assertNotContains(response, pattern)
# Changing the language should make the catalog appear
profile.preferred_language = "de"
profile.save()
response = self.client.get(reverse("wagtailadmin_javascript_catalog"))
self.assertContains(response, pattern)

Wyświetl plik

@ -6,7 +6,6 @@ from django.urls import include, path, re_path
from django.views.decorators.cache import never_cache
from django.views.defaults import page_not_found
from django.views.generic import TemplateView
from django.views.i18n import JavaScriptCatalog
from wagtail import hooks
from wagtail.admin.api import urls as api_urls
@ -20,6 +19,7 @@ from wagtail.admin.urls import workflows as wagtailadmin_workflows_urls
from wagtail.admin.views import account, chooser, dismissibles, home, tags
from wagtail.admin.views.bulk_action import index as bulk_actions
from wagtail.admin.views.generic.preview import StreamFieldBlockPreview
from wagtail.admin.views.i18n import localized_js_catalog
from wagtail.admin.views.pages import listing
from wagtail.utils.urlpatterns import decorate_urlpatterns
@ -147,7 +147,7 @@ urlpatterns += [
# JS translation catalog
path(
"jsi18n/",
JavaScriptCatalog.as_view(packages=["wagtail.admin"]),
localized_js_catalog,
name="wagtailadmin_javascript_catalog",
),
]

Wyświetl plik

@ -0,0 +1,14 @@
from django.views.i18n import JavaScriptCatalog
from wagtail.admin.localization import get_localized_response
js_catalog = JavaScriptCatalog.as_view(packages=["wagtail.admin"])
def localized_js_catalog(request, *args, **kwargs):
"""
Django's JavaScriptCatalog that has been decorated to ensure it is localized
in the user's preferred language.
https://github.com/wagtail/wagtail/issues/11074
"""
return get_localized_response(js_catalog, request, *args, **kwargs)