首先在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>

标签: none