Internationalize routes#

Problem#

The page tree should serve several languages, each under its own URL prefix such as /en/ and /de/, and the in-page text must follow the active language.

Solution#

The file router mounts as a single include() in the root URLconf. Wrap that include in i18n_patterns() so Django prepends the language prefix to every routed URL. Add LocaleMiddleware so the prefix sets the active language, and translate template text with the usual Django i18n tags.

Walkthrough#

Enable i18n in settings#

Turn on translation, list the offered languages, and point at a locale directory.

config/settings.py#
USE_I18N = True

LANGUAGE_CODE = "en"

LANGUAGES = [
    ("en", "English"),
    ("de", "Deutsch"),
]

LOCALE_PATHS = [BASE_DIR / "locale"]

Add the locale middleware#

Place LocaleMiddleware after the session middleware and before CommonMiddleware. It reads the URL prefix and sets the active language for the request.

config/settings.py#
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

Mount the router under a language prefix#

Wrap the file router include in i18n_patterns(). Every routed URL now resolves under /en/ and /de/, and the prefix-free URLs redirect to the active language.

config/urls.py#
from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path

urlpatterns = i18n_patterns(
    path("", include("next.urls")),
)

Keep URLs that must not carry a prefix, such as a health check, in a plain urlpatterns list outside the i18n_patterns call.

Translate in-page text#

Mark template strings with the standard Django i18n tags. Load the i18n tag library at the top of the template. The examples below use a separate shop project rather than the Notes project shown elsewhere in the documentation.

shop/routes/template.djx#
{% load i18n %}

<h1>{% translate "Welcome to the shop" %}</h1>
<p>{% blocktranslate %}Browse the catalog below.{% endblocktranslate %}</p>

Translate text built in Python with gettext(). A @context callable returns the already-translated string.

shop/routes/page.py#
from django.utils.translation import gettext as _
from next import context

@context("heading")
def heading() -> str:
    """Return the localized page heading."""
    return _("Featured products")

Compile the catalogs#

Extract the marked strings and compile the binary catalogs.

shell#
uv run python manage.py makemessages -l de
uv run python manage.py compilemessages

Edit locale/de/LC_MESSAGES/django.po with the German strings before compiling.

Verification#

Start the server and request the same route under two prefixes.

shell#
uv run python manage.py runserver

Visiting /en/ renders the English text and /de/ renders the German text. A request to the prefix-free URL redirects to the language picked from the request.

Once the router include is wrapped in i18n_patterns, page_reverse returns a URL carrying the active language prefix. URL names are unchanged. page_reverse reverses the same next:page_<segments> name, and i18n_patterns applies the active prefix at resolve time.

See also#

See also

File router for how the router include is mounted. Reverse URLs for building prefixed URLs from code.