How to make Django messages framework play nicely with i18n?
Using django 4.0.6 I was using the messages framework to display messages when users successfully completed a form. Then I added i18n:
When the default language is selected, messages are shown on the second screen after the form is submitted, not the first.
When the not-default language is active, untranslated messages are shown, on the first screen after the message is created (as expected).
I've tried using both gettext_lazy and gettext and it didnt help.
Its an unusual bug and Im not sure what I've done wrong?
views:
from django.utils.translation import gettext_lazy as _
from django.views.generic.edit import CreateView
...
class ContactView(SuccessMessageMixin, CreateView):
template_name = "contact-form.html"
form_class = ContactForm
success_url = reverse_lazy("base:home")
success_message = _("Thanks for contacting us.")
def form_valid(self, form):
if contact_form_filter(form):
create_and_send_contact_form_email(form)
return super().form_valid(form)
def form_invalid(self, form):
"""If the form is invalid, render the invalid form."""
return self.render_to_response(self.get_context_data(form=form))
settings:
TIME_ZONE = "CET"
LANGUAGE_CODE = "en"
USE_I18N = True
WAGTAIL_I18N_ENABLED = True
USE_L10N = True # Deprecated in Django 4, but still used in Wagtail I believe
USE_TZ = True
WAGTAIL_CONTENT_LANGUAGES = LANGUAGES = [
("en", _("English")),
("nl", _("Dutch")),
]
INSTALLED_APPS = [
...
"django.contrib.messages",
...
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.cache.UpdateCacheMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.cache.FetchFromCacheMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
"compression_middleware.middleware.CompressionMiddleware",
]
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [str(APPS_DIR / "built"), str(APPS_DIR / "templates")],
"OPTIONS": {
"loaders": [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"lettergun.utils.context_processors.settings_context",
"lettergun.utils.context_processors.pass_",
"lettergun.utils.context_processors.toggle_customer_testimonials",
],
},
}
]
Translations together with messages is a very interesting thing.
Every message in messages dictionary wait till own render through {{ messages }} in template.
It means, if next template don't have this tag - you can not see the messages and they can not be rendered and they should wait.
A lazy_translation is a wrapper that does nothing until the str/unicode function for that translation is called. In your case - message has lazy_translation, and they wait render call.
Let's try - choose language. Open the from. Fill the form. Press Save. Close the browser before you see the answer.
Your messages still waiting the moment to render self. Lasy_translation wait too.
Open your browser, open completely other link with other language. If template has {{ messages }}, you see the old message with old info from other view with translation on the current language.
If you change lazy_translation on gettext - and made the same process. You should see the message with old language.
My opinion. Every should override get_success_message function and wrap super().get_success_message in str. It's made the expected behavior of translations in messages.
The next question - why you not see the messages. I need more information about view and templates to answer.
I solved the problems and the site now behaves consistently and as expected. The odd behavior was caused by two issues:
- The order of middleware is important.
- Take care that any custom template tags that are used to set or switch the site language are not creating side effects. See this answer for this specific issue.
I set the middleware to the below and messages and translations now work as expected:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.middleware.http.ConditionalGetMiddleware",
"django.middleware.cache.UpdateCacheMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
"django.middleware.cache.FetchFromCacheMiddleware",
]