先搞清楚i18n_patterns到底做了什么
很多项目上线多语言版本时,会使用Django提供的i18n_patterns来包裹原有路由,期望每个URL都能自动带上语言前缀,例如/en/articles/5/和/zh-hans/articles/5/。但实际部署后经常出现一种情况:普通静态路径访问正常,一旦URL中包含动态参数就抛出404。要解决这个问题,首先要理解i18n_patterns的本质——它并不是魔法,只是在请求进入时检查路径首段是否是合法语言代码,匹配后剥离前缀再把剩余路径交给内部urlpatterns处理。理解了这一点,就会明白动态URL的404问题往往出在剥离后的路径与内部路由不匹配。

需要注意的是,i18n_patterns要求项目根URLConf的结构必须清晰。推荐的做法是让项目级的urls.py只负责语言前缀,把具体应用的路由include进来。如果直接在i18n_patterns内部混写大量带参数的路由,一旦拼写或参数名不一致,排查起来非常痛苦。下面是一个结构良好的示例:
# 项目urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns
urlpatterns = [
path("admin/", admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")), # set_language视图
]
urlpatterns += i18n_patterns(
path("articles/", include("articles.urls")),
)同时,中间件的顺序至关重要。LocaleMiddleware必须位于SessionMiddleware之后、CommonMiddleware之前,否则Django无法正确判断当前激活的语言,语言前缀解析就会失败,直接导致所有带前缀的请求命中404。这是新手最容易忽略的一处配置。
MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware", # 位置必须在Session之后
"django.middleware.common.CommonMiddleware",
]动态URL参数与reverse反向解析的正确姿势
多语言站点中的动态路由,最典型的错误场景是:模板里直接硬编码了链接地址,或者用reverse时传错了参数名。当URL模式定义为path("articles/<int:pk>/", ...)并通过i18n_patterns注册后,反向解析必须带上与URL模式声明一致的参数。参数名不匹配时,NoReverseMatch异常会连同404一起出现,让人误以为是路由配置问题。
from django.urls import path
from . import views
app_name = "articles"
urlpatterns = [
path("", views.ArticleListView.as_view(), name="list"),
path("<int:pk>/", views.ArticleDetailView.as_view(), name="detail"),
]
# 模板中正确用法,必须使用url标签反向解析
# {% url 'articles:detail' article.pk %}
# 错误做法:硬编码 /articles/5/,语言前缀将丢失或错乱如果你使用了re_path配合正则表达式定义动态路由,还要留意正则分组与reverse参数的对应关系。命名组用kwargs传递,未命名组用args按顺序传递,混用会导致解析异常。多语言场景下建议统一使用path路由的转换器写法,可读性更好,也更不容易出错。
另一个隐蔽的问题是prefix_default_language参数。默认情况下默认语言也会带前缀,比如/en/。如果你设置了prefix_default_language=False,那么默认语言的URL没有前缀,其他语言有前缀。此时如果模板中某些链接是硬编码的,就会出现一半链接能访问、一半404的诡异现象。遇到这种问题,先检查这个参数的取值。
urlpatterns += i18n_patterns(
path("articles/", include("articles.urls")),
prefix_default_language=False, # 默认语言不带前缀
)系统化的排查思路与语言切换最佳实践
当404出现时,建议按照固定顺序排查。第一步,确认settings.py中的LANGUAGES配置包含你访问的语言代码,比如访问/zh-hans/开头但LANGUAGES里只声明了zh,前缀就无法匹配。第二步,检查中间件顺序。第三步,用django-admin shell执行reverse测试路由能否解析成功。第四步,查看Django调试页面的“Using the URLconf defined in”信息,确认请求路径进入的是哪一个URLconf,剥掉语言前缀后剩余的路径是什么。
from django.urls import reverse, translate_url
from django.utils.translation import activate
activate("zh-hans")
print(reverse("articles:detail", kwargs={"pk": 5}))
# 输出应为 /zh-hans/articles/5/语言切换功能推荐使用Django自带的set_language视图,它会基于当前页面URL自动生成目标语言的新地址,并妥善处理动态参数,比自己写跳转逻辑可靠得多。配合POST表单即可实现无刷新感的多语言切换:
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}">
<select name="language">
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as langs %}
{% for lang in langs %}
<option value="{{ lang.code }}">{{ lang.name_local }}</option>
{% endfor %}
</select>
<button type="submit">切换语言</button>
</form>总结来说,i18n_patterns下的动态URL出现404,绝大多数情况可以归结为四类原因:中间件顺序不对、语言代码不在LANGUAGES声明范围内、reverse参数与路由声明不匹配、以及prefix_default_language设置与硬编码链接冲突。按照本文给出的结构拆分URL配置、坚持使用url标签反向解析、利用shell快速验证reverse结果,基本可以覆盖所有常见场景。多语言路由本身并不复杂,关键是保持配置的一致性,让每一个链接都经由Django的解析机制生成,而不是手工拼接。
Djangoi18n_patternsURL路由修改时间:2026-08-31 00:59:11