Opened 9 months ago

Last modified 5 days ago

#36884 assigned Bug

Translation of admin log messages fails due to case inconsistency in field names

Reported by: Meiyer Owned by: Sahil Advani
Component: contrib.admin Version: 3.0
Severity: Normal Keywords:
Cc: jaffar Khan Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

The Django Admin builds the log messages using construct_change_message, which looks at the fields of the change form, specifically their labels (​https://github.com/django/django/blob/9814676ea75bfe5c5d5244c50ccfe1f652a2f058/django/contrib/admin/utils.py#L618). The helper function turns off the translation when building the change JSON, to apply the translation when a LogMessage is displayed. However, there is a flaw in the logic:

  • the verbose_name parameter of the model field would typically be lower-cased (from the docs: “The convention is not to capitalize the first letter of the verbose_name. Django will automatically capitalize the first letter where it needs to.” ).
  • the labels generated by the forms have the first letter capitalized (from the docs: “the default label for a Field is generated from the field name by converting all underscores to spaces and upper-casing the first letter”) where for Model Forms, “Django will fall back on that model field’s verbose_name and then the field’s attribute name”.

This results in translation mismatch at render time. For example, the log message would include "fields": ["Occupation"] while the translation catalog has only the key “occupation”, coming from that field’s verbose_name. As a result, the field names are not properly translated.

I tracked the issue back to changes introduced in 3.0 and it persists in all versions up to 6.0.

Change History (18)

comment:1 by Thrishagowdabl, 9 months ago

Owner: set to Thrishagowdabl
Status: new → assigned

comment:2 by Jacob Walls, 9 months ago

Summary: Translation of fields in admin log messages works incorrectly → Translation of admin log messages fails due to case inconsistency in field names
Triage Stage: Unreviewed → Accepted

Thanks for the clear report and diagnosis.

comment:3 by Jacob Walls, 9 months ago

Patch needs improvement: set

Linters are failing.

comment:4 by jaffar Khan, 9 months ago

Wants to work on this ticket if the current assignee is no longer interested

Last edited 9 months ago by jaffar Khan (previous) (diff)

comment:5 by jaffar Khan, 9 months ago

Cc: jaffar Khan added

comment:6 by master, 9 months ago

I confirm the issue.
The capitalisation is probably done in db\models\fields\__init__.py with "label": capfirst(self.verbose_name),.

For example, this column content: [{'changed': {'fields': ['Status']}}] is rendered in French str as: Modification de « t2>t2:90 » — Modification de Status.
According to the app .po file, the end of the text should be état., or maybe État.

comment:7 by master, 9 months ago

Here is a proposed patch of contrib/admin/utils.py:

--- C:/D/tmp/utils_old.py	Tue Feb 10 11:20:50 2026
+++ C:/D/tmp/utils_new.py	Tue Feb 10 11:21:01 2026
@@ -1,8 +1,10 @@
 def _get_changed_field_labels_from_form(form, changed_data):
     changed_field_labels = []
+    opts = form.instance._meta
     for field_name in changed_data:
         try:
-            verbose_field_name = form.fields[field_name].label or field_name
+            # can't use form.fields[field_name].label because it is wrapped in capfirst()
+            verbose_field_name = opts.get_field(field_name).verbose_name or field_name
         except KeyError:
             verbose_field_name = field_name
         changed_field_labels.append(str(verbose_field_name))

Now, my example is:
column content: [{"changed": {"fields": ["status"]}}]
str of logentry: Modification de « t2>t2:90 » — Modification de état.

comment:8 by Yang, 8 months ago

Has patch: set
Patch needs improvement: unset

in reply to:  1 comment:9 by Yang, 8 months ago

Replying to Thrishagowdabl:

Hi @Thrishagowdabl, I submitted a PR for this ticket (​https://github.com/django/django/pull/20748). Are you still working on this? If so, I'm happy to close my PR or help with your patch instead. Let me know!

comment:10 by Moreno Mazzocchetti, 6 weeks ago

Owner: changed from Thrishagowdabl to Moreno Mazzocchetti

comment:11 by blighj, 6 weeks ago

Has patch: unset

comment:12 by Moreno Mazzocchetti, 3 weeks ago

Owner: Moreno Mazzocchetti removed
Status: assigned → new

comment:13 by NAYAN SHARMA, 2 weeks ago

Hi, I noticed that the ticket is now unassigned. Is anyone currently working on this issue or planning to submit an updated patch? I'd be interested in working on it if it's available, and I want to avoid duplicating anyone's effort. Thanks!

comment:14 by NAYAN SHARMA, 2 weeks ago

Has patch: set
Owner: set to NAYAN SHARMA
Status: new → assigned

comment:15 by NAYAN SHARMA, 2 weeks ago

Has patch: unset
Owner: NAYAN SHARMA removed
Status: assigned → new
Triage Stage: Accepted → Unreviewed

comment:16 by David Smith, 12 days ago

Triage Stage: Unreviewed → Accepted

comment:17 by Sahil Advani, 10 days ago

Hi, I'm interested in working on this ticket as my next Django contribution. I noticed that PR #22151 was closed due to the PR submission requirements rather than a code review. Nayan, are you planning to resubmit your fix, or would it be okay for me to take this forward? I want to avoid duplicating active work. Thanks!

comment:18 by Sahil Advani, 5 days ago

Owner: set to Sahil Advani
Status: new → assigned
Note: See TracTickets for help on using tickets.
Back to Top