i18n¶
Use Django's translation stack inside Reflex. When bridge is enabled and an event resolves to a Django-bound tier, LocaleMiddleware runs on the synthetic request, so gettext and translation.get_language() work in handlers the same way they do in Django views.
How it works¶
sequenceDiagram
participant Browser
participant Bridge as DjangoEventBridge
participant Locale as LocaleMiddleware
participant Handler as AppState handler
Browser->>Bridge: WebSocket event + cookies + Accept-Language
Bridge->>Locale: Synthetic HttpRequest through MIDDLEWARE
Locale->>Locale: Set request.LANGUAGE_CODE, activate translation
Locale->>Handler: Handler runs with active locale
Handler->>Handler: _("Welcome") returns translated string
Bridge->>Browser: Mirror language into state vars
Accept-Language with each Reflex event.MIDDLEWARE chain.LocaleMiddleware picks the language (cookie beats header).gettext runs with the active locale.AppState.language mirrors locale for components.Language cookie name defaults to Django's LANGUAGE_COOKIE_NAME (usually django_language).
Django setup¶
Enable i18n in settings.py and add LocaleMiddleware after SessionMiddleware:
# settings.py — i18n (add to your existing settings)
USE_I18N = True
USE_L10N = True
LANGUAGE_CODE = "en"
LANGUAGES = [
("en", "English"),
("de", "Deutsch"),
("ar", "Arabic"),
]
LOCALE_PATHS = [BASE_DIR / "locale"]
# LocaleMiddleware after SessionMiddleware, before CommonMiddleware
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
# ... auth, messages, clickjacking ...
"reflex_django.bridge.streaming.AsyncStreamingMiddleware",
]
Wire Django's built-in language switcher in urls.py:
# config/urls.py — language switcher endpoint
from django.contrib import admin
from django.urls import path
from django.views.i18n import set_language
urlpatterns = [
path("admin/", admin.site.urls),
path("i18n/setlang/", set_language, name="set_language"),
]
Create message files for each locale:
reflex django makemessages -l de
reflex django makemessages -l ar
# edit locale/de/LC_MESSAGES/django.po, then:
reflex django compilemessages
Example locale/de/LC_MESSAGES/django.po entry:
Translate in handlers¶
Call Django's gettext inside @rx.event handlers (after the bridge activated the locale):
# shop/views.py
import reflex as rx
from django.utils.translation import gettext as _
from reflex_django.states import AppState
class HomeState(AppState):
greeting: str = ""
welcome_label: str = ""
@rx.event
async def on_load(self):
# translation.get_language() is active here (LocaleMiddleware + bridge)
self.welcome_label = _("Welcome")
user = self.request.user
if user.is_authenticated:
self.greeting = _("Hi, {name}!").format(name=user.get_username())
else:
self.greeting = _("Hello, guest.")
def language_switcher() -> rx.Component:
"""POST to Django set_language with CSRF from AppState."""
return rx.form(
rx.input(
type_="hidden", name="csrfmiddlewaretoken", value=HomeState.csrf_token
),
rx.input(type_="hidden", name="next", value="/"),
rx.hstack(
rx.button("English", type="submit", name="language", value="en"),
rx.button("Deutsch", type="submit", name="language", value="de"),
spacing="3",
),
action="/i18n/setlang/",
method="POST",
)
def index() -> rx.Component:
return rx.vstack(
language_switcher(),
rx.text(HomeState.welcome_label, font_weight="bold"),
rx.text(HomeState.greeting),
rx.cond(
HomeState.language_bidi,
rx.text("(RTL layout)"),
),
rx.text("Locale: ", HomeState.language),
spacing="3",
padding="2em",
direction=rx.cond(HomeState.language_bidi, "rtl", "ltr"),
)
Register with app.add_page in shop/shop.py and set on_load=HomeState.on_load.
| API | Use in |
|---|---|
from django.utils.translation import gettext as _ |
Handlers (translated string for active language) |
gettext_lazy as _ |
Module-level model labels only (not reactive UI text) |
current_language() |
Any code during an event (from reflex_django import current_language) |
HomeState.language |
UI (reactive snapshot from middleware) |
HomeState.language_bidi |
UI (True for Arabic, Hebrew, etc.) |
Do not translate in components
Store translated strings in state vars (like welcome_label above). Do not call _() inside component render functions. Those run in the client bundle and cannot read Django .mo catalogs.
Language switcher¶
Django's set_language view expects POST with CSRF. Use HomeState.csrf_token (mirrored by the bridge):
rx.form(
rx.input(type_="hidden", name="csrfmiddlewaretoken", value=HomeState.csrf_token),
rx.input(type_="hidden", name="next", value="/"),
rx.button("Deutsch", type="submit", name="language", value="de"),
action="/i18n/setlang/",
method="POST",
)
The full multi-language switcher is in the snippet above. After submit, Django sets the language cookie and redirects to next.
DjangoI18nState¶
When you need locale without the full auth snapshot tree:
from reflex_django.states import DjangoI18nState
@rx.event
async def on_load(self):
await DjangoI18nState.sync_from_django()
# In components:
DjangoI18nState.django_language_code
DjangoI18nState.django_language_bidi
AppState should use language and language_bidi. Use DjangoI18nState for locale-only widgets (language bar, footer) without pulling in auth vars.
RTL layout¶
Use language_bidi to flip direction:
Pair with translated copy from handlers for a full RTL experience.
AppState vs DjangoI18nState¶
| Aspect | AppState | DjangoI18nState |
|---|---|---|
| Fields | language, language_bidi (+ auth, CSRF, messages) |
django_language_code, django_language_bidi |
| When | Most pages already on AppState | Locale-only widgets |
| Refresh | sync_from_django() on AppState |
DjangoI18nState.sync_from_django() |
Both read the same middleware result. Pick one per page to avoid duplication.
Settings reference¶
| Setting | Default | Purpose |
|---|---|---|
USE_I18N |
Django default True |
Must be True |
LANGUAGE_CODE |
Django default en-us |
Fallback locale |
LANGUAGES |
Django global defaults unless set | Choices for set_language |
LOCALE_PATHS |
[] |
Where project .po / .mo files live |
LANGUAGE_COOKIE_NAME |
django_language |
Cookie read by LocaleMiddleware |
LocaleMiddleware |
not installed by default | Required in MIDDLEWARE |
RX_MIRROR_LANGUAGE |
True |
Copy locale into AppState vars |
RX_PERFORMANCE_PRESET |
default |
lean sets RX_MIRROR_LANGUAGE=False |
See Bridge utilities for current_language() and mirror toggles.
Middleware order
Run LocaleMiddleware before CommonMiddleware (Django requirement) and before AsyncStreamingMiddleware (must stay last). Re-run compilemessages in CI after updating .po files.
Next: Uploads and media