django多语言实现
首先在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',]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug', # 调试处理器,向模板注入 debug(环境标识)和 sql_queries(SQL 查询记录)两个变量。仅在 DEBUG=True 时生效,
'django.template.context_processors.request', # 必须有(request对象)
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n', # 关键:负责注入LANGUAGE_CODE,LANGUAGES,LANGUAGE_BIDI,还是模板中使用 {% trans %} 等国际化标签的基础
],
},
},]
LANGUAGE_CODE = 'en'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
支持的语言(英/中/俄)
LANGUAGES = [
('en', 'English'),
#('zh-hans', 'Simplified Chinese'),
('ru', 'Russian'),]
其次:创建一个应用 python manage.py startapp core 或 common
在settings.py
INSTALLED_APPS = [
'modeltranslation',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_extensions',
'core',] 加入应用
在应用的views.py中放入一下代码
from django.conf import settings
from django.http import HttpRequest, HttpResponseRedirect
from django.urls import translate_url
from django.utils.translation import activate
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.i18n import set_language as django_set_language
# 【关键修复】手动定义 LANGUAGE_SESSION_KEY
# Django 内部一直使用这个固定的字符串作为 Session 键名
LANGUAGE_SESSION_KEY = 'django_language'
def custom_set_language(request: HttpRequest) -> HttpResponseRedirect:
"""
Django 4.1+ / 5.x 专用语言切换视图
"""
# 非 POST 请求:委托给原生视图处理
if request.method != 'POST':
return django_set_language(request)
# 1. 获取目标语言
lang_code = request.POST.get('language')
# 2. 验证语言是否合法
valid_langs = [lang[0] for lang in settings.LANGUAGES]
if lang_code not in valid_langs:
next_url = request.POST.get('next') or request.META.get('HTTP_REFERER', '/')
if not url_has_allowed_host_and_scheme(
url=next_url,
allowed_hosts={request.get_host()},
require_https=request.is_secure()
):
next_url = '/'
return HttpResponseRedirect(next_url)
# 3. 确定跳转目标
next_url = request.POST.get('next')
if not next_url:
next_url = request.META.get('HTTP_REFERER', '/')
if not url_has_allowed_host_and_scheme(
url=next_url,
allowed_hosts={request.get_host()},
require_https=request.is_secure()
):
next_url = '/'
# 4. 生成带语言前缀的新 URL
new_url = translate_url(next_url, lang_code)
# 5. 执行语言激活
activate(lang_code)
# 设置 Session (使用我们手动定义的常量)
if hasattr(request, 'session'):
request.session[LANGUAGE_SESSION_KEY] = lang_code
response = HttpResponseRedirect(new_url)
# 设置 Cookie
response.set_cookie(
settings.LANGUAGE_COOKIE_NAME,
lang_code,
max_age=settings.LANGUAGE_COOKIE_AGE,
path=settings.LANGUAGE_COOKIE_PATH,
domain=settings.LANGUAGE_COOKIE_DOMAIN,
secure=settings.LANGUAGE_COOKIE_SECURE,
httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
samesite=getattr(settings, 'LANGUAGE_COOKIE_SAMESITE', 'Lax'),
)
return response
- 稳定性:'django_language' 这个字符串在 Django 源码中存在了十几年,从未改变过。即使未来 Django 移动了内部文件,这个 Session 键名也不会变,除非 Django 进行破坏性更新(届时会在大版本说明中强调)。
- 避免导入错误:不再依赖 Django 内部模块的非公开导出,彻底解决 ImportError。
- 兼容性:这段代码现在可以在 Django 2.0 到 Django 5.x 的任何版本上运行。
第三步定义urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns
from django.conf import settings
from django.conf.urls.static import static
# 1. 导入你的自定义视图
from core.views import custom_set_language # 导入自定义视图(后续写)
urlpatterns = [
path('admin/', admin.site.urls),
]
# 多语言路由(自动添加/en/ /zh-hans/ /ru/前缀)
# 2. 在 i18n_patterns 中,优先定义 set_language
# 注意:路径必须是 'set_language/' (Django 默认路径)
urlpatterns += i18n_patterns(
# 【关键】这里定义的路由会覆盖 django.conf.urls.i18n 中的默认路由 ,在主 urls.py 的 i18n_patterns 块中,第一行就写上 path('set_language/', core.views.custom_set_language, name='set_language')。
path('i18n/setlang/', custom_set_language, name='set_language'),
# 然后包含其他应用的路由
path('', include('products.urls')),
prefix_default_language=False, # 英文默认不显示/en/(可选,若要显示设为True)
)
# ⚠️ 注意:不要再包含 from django.conf.urls.i18n import set_language 了
# 因为上面的自定义路径已经拦截了请求。
# 如果你之前用了 include('django.conf.urls.i18n'),请确保自定义路径在它前面,
# 或者干脆不要 include 那个,手动写需要的 i18n 路由。
# 开发环境媒体文件访问
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
关键注意事项:
- 路径必须匹配:
- Django 原生的语言切换表单通常提交到 /set_language/。确保你的 path() 定义也是 'set_language/'。如果你在模板中使用了 {% url 'set_language' %},只要 name='set_language' 保持一致,模板不需要修改。
顺序很重要:
- 在 urlpatterns 列表中,自定义的路由必须放在默认路由之前,或者确保不要重复引入默认的 set_language。
- ❌ 错误做法:先 include('django.conf.urls.i18n') (里面包含了原生 set_language),然后再写自定义的。虽然 Django 按顺序匹配,但如果 include 在前,且路径完全一致,可能会产生混淆(取决于具体 include 的实现)。
- ✅ 正确做法:手动写出 path('set_language/', ...) 放在 i18n_patterns 的最前面。
- CSRF 保护:
- 你的视图接收 POST 请求。Django 默认要求 POST 请求必须携带 CSRF Token。
- 确保你的 HTML 表单中包含 {% csrf_token %}:
项目根目录(和 settings.py 同级)
最后定义base.html的语言切换
<form action="{% url 'set_language' %}" method="post" class="d-flex align-items-center">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.path_info }}">
<select name="language" class="form-select form-select-sm me-2" onchange="this.form.submit()">
{% get_language_info_list for LANGUAGES as languages %}
{% for lang in languages %}
<option value="{{ lang.code }}" {% if lang.code == LANGUAGE_CODE %}selected{% endif %}>
{{ lang.name_local }}
</option>
{% endfor %}
</select>
</form>