90% 页面不翻译是 localemiddleware 位置错误:必须位于 sessionmiddleware 和 commonmiddleware 之间,且需启用 use_i18n=true;语言码须为小写连字符格式(如 zh-hans),locale_paths 指向 locale 父目录,静态字符串须用 gettext_lazy,模板中字面量才可被提取。

页面不翻译,90% 是 LocaleMiddleware 没生效,不是字符串没标或 .po 没编译。
LocaleMiddleware 位置错:它必须夹在 SessionMiddleware 和 CommonMiddleware 之间
这个中间件不自己猜语言,它靠读 request.session、request.COOKIES 或 Accept-Language 头来决定当前语言。但前提是 session 要已加载、URL 要已解析完。
-
SessionMiddleware必须在它前面——否则request.session是空的,无法读取用户上次选的语言 -
CommonMiddleware必须在它后面——否则 URL 解析提前结束,中间件链被截断,request.LANGUAGE_CODE根本不会被设 -
USE_I18N = True必须显式写在settings.py里(Django 4.0+ 默认为True,但很多老项目或自定义配置把它关了)
正确顺序示例:
['django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware']
makemessages -l zh-hans 报错或生成空 .po 文件
不是命令错了,是语言码或路径不匹配。Django i18n 层只认 BCP 47 格式:小写字母 + 连字符,比如 zh-hans、en-us,不接受 zh_Hans、zh_CN 或大写。
- 查合法语言码:运行
python manage.py makemessages --list,输出里列出的才是可用值 -
LOCALE_PATHS必须指向locale的父目录,例如[BASE_DIR / 'locale'],不是[BASE_DIR / 'locale' / 'zh-hans'] - 手动创建
locale/目录(Django 不自动建) - 模板里必须用
{% load i18n %},再用{% translate "Login" %}或{% blocktrans %}Hello {{ name }}{% endblocktrans %};直接写{{ _('Login') }}不会被提取
模型字段 verbose_name 不切换语言?别用普通 _() 拼接
模型、表单、序列化器这些静态定义处的字符串,在模块加载时就执行一次。用普通 _() 会立刻求值并固化为启动时语言;拼接更危险,会提前触发求值。
- ❌ 错误写法:
name = _('Hello') + ' ' + _('World')—— 两个_()都立刻执行,语言固定 - ✅ 正确写法:
FULL_NAME = _('Hello {name}').format(name=_('World')),两个_()都是gettext_lazy,延迟到模板渲染时才求值 - 所有静态字符串(
verbose_name、help_text、label)必须用gettext_lazy,不能用gettext
模板里用 {% trans %} 还是 {% translate %}?
两者行为完全一致,{% translate %} 是 Django 3.1+ 推荐写法,但提取逻辑和效果没区别。真正影响是否被提取的,是它是否包裹字面量字符串,以及是否在 {% load i18n %} 之后使用。
- ✅
{% trans "Login" %}、{% translate "Logout" %}都能被makemessages扫到 - ✅ 带变量必须用
{% blocktrans %}Hello {{ user.name }}{% endblocktrans %} - ⚠️
{% trans some_variable %}不会被提取——makemessages只扫描字符串字面量,不分析变量内容
改完 .po 文件后,compilemessages 必须运行,否则新翻译不生效;而且它只编译 LOCALE_PATHS 下的文件,路径错就等于白改。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











