On a Django tourism site using i18n_patterns, what does prefix_default_language=False change, and what trade-off comes with it?
answer
- which language loses its prefix
- existing URLs keep working
- unprefixed means LANGUAGE_CODE, always
- no redirect, no header negotiation
basics
~10 si18n_patterns() prefixes wrapped URLs with the active language, as in /fr/tours/. With prefix_default_language=False the LANGUAGE_CODE language is served unprefixed at /tours/, but unprefixed URLs then always render LANGUAGE_CODE, ignoring cookie and Accept-Language.
solid answer
~40 s`i18n_patterns()` in the root URLconf wraps patterns in a `LocalePrefixPattern`, so `/fr/tours/` and `/de/tours/` resolve to the same view and `reverse()` adds the active language's prefix. By default every language is prefixed, English included, and a bare `/tours/` 404s and is redirected by `LocaleMiddleware` to the prefix it detected from cookie or `Accept-Language`. With `prefix_default_language=False`, the `LANGUAGE_CODE` language lives at `/tours/`, which keeps an existing site's URLs stable. The trade-off: an unprefixed request always gets `LANGUAGE_CODE`, because the middleware skips the cookie and header there, and there is no automatic redirect, so a German visitor landing on `/tours/` sees English until they follow a `/de/` link.
code
python · 14 linesfrom django.conf.urls.i18n import i18n_patterns
from django.urls import include, path
from tours import views
urlpatterns = [
path("i18n/", include("django.conf.urls.i18n")), # set_language, unprefixed
]
urlpatterns += i18n_patterns(
path("tours/", views.tour_list, name="tour-list"),
path("tours/<slug:slug>/", views.tour_detail, name="tour-detail"),
prefix_default_language=False,
)go deeper
Recall that i18n_patterns adds a language prefix like /fr/ to wrapped URLs and must be used in the root URLconf.
Explain the default redirect of bare URLs and exactly what prefix_default_language=False changes for the LANGUAGE_CODE language.
Diagnose 'my browser language is ignored' reports on unprefixed URLs and plan which endpoints stay outside i18n_patterns.
Decide URL strategy for a multilingual launch versus retrofitting translations onto an indexed site, weighing canonical URLs against stability.
## What i18n_patterns does `django.conf.urls.i18n.i18n_patterns(*urls, prefix_default_language=True)` wraps URL patterns in a resolver whose pattern is a **`LocalePrefixPattern`**. That pattern's prefix is computed from the **currently active language**, so the same patterns match `/en/tours/`, `/fr/tours/` and `/de/tours/`, and `reverse("tours:list")` returns `/fr/tours/` while French is active. - It is allowed **only in the root URLconf**. Returning it from a module passed to `include()` raises `ImproperlyConfigured("Using i18n_patterns in an included URLconf is not allowed.")`. - With `USE_I18N = False` it returns the patterns unchanged, with no prefix. - Its presence is what switches on the **URL-prefix** step of `LocaleMiddleware`'s discovery order; without it, a path segment like `/fr/` is just part of a route. - Patterns outside the call stay unprefixed: `sitemap.xml`, webhooks, health checks and the `set_language` endpoint usually live there. ## The default: every language prefixed With `prefix_default_language=True` (the default) every language, including `LANGUAGE_CODE`, carries its prefix: English is at `/en/tours/`. When a visitor types the bare `/tours/`: 1. `LocaleMiddleware` detects a language from the cookie, `Accept-Language` or `LANGUAGE_CODE`, say French. 2. The resolver looks for `fr/` at the start of the path, finds nothing, and the request 404s. 3. In `process_response`, the middleware sees a 404 on an unprefixed path, checks that `/fr/tours/` would resolve (also trying a trailing slash when `APPEND_SLASH` is on) and **redirects** there. 4. The redirect carries `Vary: Accept-Language, Cookie`, because its target depends on both. So bare URLs still work, but every language has one canonical prefixed URL, which is what search engines and shared links want. ## prefix_default_language=False: the default language unprefixed Passing `prefix_default_language=False` removes the prefix for the `LANGUAGE_CODE` language only: | URL | Language served | |---|---| | `/tours/` | `LANGUAGE_CODE` (say `en`) | | `/fr/tours/` | French | | `/de/tours/` | German | The documented use is adding translations to an **existing site** without changing its current URLs. The trade-offs: - **No negotiation on unprefixed URLs.** For a path with no language prefix, `LocaleMiddleware` activates `LANGUAGE_CODE` directly and does **not** consult the cookie or `Accept-Language`. A German visitor landing on `/tours/` sees English until they follow a `/de/` link. - **No automatic redirect.** The 404-then-redirect step above only runs when the default language is prefixed. - **Asymmetric URLs.** Code that builds URLs by string concatenation instead of `reverse()` or `{% url %}` easily produces a wrong prefix for the default language. ## The same requests under both settings With `LANGUAGE_CODE = "en"` and a visitor whose browser sends `Accept-Language: de`: | Request | `prefix_default_language=True` | `prefix_default_language=False` | |---|---|---| | `/tours/` | redirected to `/de/tours/` | English page | | `/de/tours/` | German page | German page | | `/fr/tours/` | French page | French page | | `reverse("tour-list")` while English is active | `/en/tours/` | `/tours/` | The difference is concentrated in the first and last rows: the unprefixed URL either negotiates and redirects, or it is simply the English page; and the English URLs either carry `/en/` or carry nothing. ## Choosing between them for a tourism site A tourism site that is launched multilingual usually keeps the default: every page has one explicit URL per language, visitors arriving at a bare URL are redirected by their browser language, and nothing is special about English. A site that already has years of indexed English URLs often picks `prefix_default_language=False` so those URLs keep working unchanged, accepting that the bare URLs will always be English. ## Pitfalls that show up in review - **Colliding patterns.** A non-prefixed pattern whose first segment looks like a language code (a `/de/` marketing page outside `i18n_patterns`) collides with the language prefix; the docs warn against it. - **Translated routes.** Route strings inside `i18n_patterns` can themselves be marked for translation, so `/nl/nieuws/` reverses from the same name as `/en/news/`; how to mark them belongs to the string-marking topic. - **Reversing for another language.** `reverse()` uses the active language, so a link to another language's page needs `translation.override()` in Python or the `{% language %}` block tag in templates. - **Caching.** Prefixed URLs are naturally cache-safe per language; unprefixed pages that negotiate by header rely on `Vary: Accept-Language`.
- What happens if i18n_patterns is returned from an app's urls.py that the root URLconf includes?Django raises `ImproperlyConfigured` with "Using i18n_patterns in an included URLconf is not allowed." The prefix has to be the first thing matched in the path, so the call must sit in the root URLconf, with app URLconfs included inside it.
- How do you render a link to the German version of the current tour page from an English template?Wrap the `{% url %}` call in `{% language "de" %}` … `{% endlanguage %}` from the `i18n` library, which activates German for that block so the reversed URL gets the `/de/` prefix. In Python, `translation.override("de")` around `reverse()` does the same.
saying these in an interview costs you the question
- i18n_patterns can be used in any included app URLconf
- With prefix_default_language=False, /tours/ still negotiates by browser language
- The prefix is a URL kwarg every view must accept
- Unprefixed URLs 404 permanently under the default setting
- prefix_default_language=False removes prefixes for all languages