Skip to content

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
1. EventBrowser sends cookies and Accept-Language with each Reflex event.
2. BridgeSynthetic Django request runs your MIDDLEWARE chain.
3. LocaleLocaleMiddleware picks the language (cookie beats header).
4. Handlergettext runs with the active locale.
5. UIAppState.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:

msgid "Welcome"
msgstr "Willkommen"

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
When to use which: Most pages already on 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:

rx.box(
    page_content(),
    direction=rx.cond(HomeState.language_bidi, "rtl", "ltr"),
)

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