Django表单__init__构造函数内的验证错误信息无法被gettext翻译
Django表单__init__构造函数内的验证错误信息无法被gettext翻译
看起来你碰到了Django国际化里一个挺典型的小坑——在表单的__init__方法里设置的验证错误消息,明明写了翻译,却只显示原始的msgid,但在Meta类里设置的就正常生效,对吧?我来帮你拆解问题原因和解决办法。
问题根源
核心问题出在翻译的时机和lazy对象的处理上:
- 如果你用的是
gettext(而非gettext_lazy)作为_的别名,它会在调用时立即执行翻译。但表单的__init__方法执行时,请求的语言上下文还没完全绑定,甚至Django的翻译系统可能还没加载好对应的语言文件,导致无法找到翻译,直接返回原始的msgid。 - 就算你用了
gettext_lazy,当你调用_("xxx").format(...)时,format方法会把lazy翻译对象直接转换成普通字符串,失去了“延迟翻译”的特性——翻译会在__init__执行时完成,而不是等到模板渲染(此时请求语言已确定)的时候。
而Meta类里的设置能生效,是因为Meta中的error_messages是在类定义时被赋值为lazy对象,直到请求渲染时才会被实际解析,这时候语言上下文已经就绪,翻译就能正常工作。
解决办法
我们需要让翻译保持“延迟”状态,直到实际渲染时再结合请求的语言环境完成翻译和格式化,具体分四步:
1. 确保导入正确的翻译工具
在你的表单文件顶部,替换原来的翻译导入:
from django.utils.translation import gettext_lazy as _ from django.utils.text import format_lazy # 用来处理带占位符的lazy字符串格式化
2. 修改__init__中的错误消息设置
把原来直接用format的地方,换成format_lazy,它能保留翻译对象的lazy特性:
修改后的RegisterForm代码:
class RegisterForm(UserCreationForm): class Meta: model = CustomUser fields = ["email", "password1", "password2", "phone", "first_name", "last_name", "birth_date"] widgets = {"birth_date": forms.DateInput(attrs={"type": "date"})} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 验证器消息直接用_()即可(保持lazy状态) self.fields["email"].validators = [EmailValidator(message=_("email.email"))] self.fields["phone"].validators = [RegexValidator( regex=r"^\+381 \d{2} \d{6,7}$", message=_("phone.regex") )] # 带占位符的消息用format_lazy处理 self.fields["first_name"].validators = [ AlphaValidator(message=format_lazy(_("field.alpha"), field=_("first_name"))) ] self.fields["last_name"].validators = [ AlphaValidator(message=format_lazy(_("field.alpha"), field=_("last_name"))) ] self.fields["birth_date"].validators = [AdultValidator()] for field in self.fields: self.fields[field].widget.attrs["class"] = "form-control" # 错误消息用format_lazy保持lazy状态 self.fields[field].error_messages = { "required": format_lazy(_("field.required"), field=_(field)) } def clean_password2(self): pass1 = self.cleaned_data["password1"] pass2 = self.cleaned_data["password2"] if pass1 and pass2 and pass1 != pass2: # clean方法在请求上下文内执行,直接用_()没问题 raise forms.ValidationError(_("password2.confirmed")) return pass2
3. 修正自定义验证器中的翻译
同样在validators.py里用format_lazy处理带占位符的消息:
from django.utils.translation import gettext_lazy as _ from django.utils.text import format_lazy class AdultValidator(BaseValidator): def __init__(self, limit_value=18, message=None): super().__init__(limit_value, message) def __call__(self, value: date): today = date.today() age = today.year - value.year - ((today.month, today.day) < (value.month, value.day)) if age < self.limit_value: # 这里如果msgstr有占位符就用format_lazy,没有直接用_()即可 raise ValidationError(_("birth_date.adult"))
4. 最后确认翻译文件
确保你已经重新编译过翻译文件:
py manage.py compilemessages
检查locale/sr/LC_MESSAGES/下是否生成了django.mo文件,这是Django实际使用的编译后的翻译文件。
为什么这样能解决问题?
format_lazy会返回一个lazy对象,和gettext_lazy一样,它不会立即执行翻译和格式化,直到这个字符串在模板中被实际渲染——这时候请求的语言环境已经完全绑定,Django能根据当前用户的语言设置正确加载对应的翻译内容,自然就能显示你写的塞尔维亚语消息了。
备注:内容来源于stack exchange,提问作者IgorArnaut
相关产品推荐
相关产品推荐

