Bulk actions: \"N … changed.\" picks its noun by English plural rules and cannot agree in other languages #390
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#390
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the bulk actions' messages and their catalogues.Checked again on 2026-10-01 against
mainat0a5cad587: still present.The sentence shown after every bulk action is built from a generic template and a standalone noun, with the noun chosen by
count == 1. That is English's two-way plural rule applied to every language, and it leaves the participle unable to agree with the noun. It is the same hand-chosen plural that #225 removed from the "N rows ticked" count in the script, still present on the server.core/bulk.py:102-116(changed); the nouns it is given atapplications/tables.py:21,jobs/tables.py:25andjobs/tables.py:178; called fromapplications/views.py:301,jobs/views.py:237andjobs/listing_views.py:193.changedreturns_("%(count)d %(noun)s changed.") % {"count": count, "noun": singular if count == 1 else plural}. The catalogues can only translate the frame once for every table and every number:"%(count)d %(noun)s modifiés.", and all three nouns are feminine (candidatures,entreprises,offres), so every French bulk message reads "3 candidatures modifiés" where it should read "modifiées"."%(count)d %(noun)s zmieniono."withaplikacje; five or more needs the genitive plural (aplikacji), and 22 needs the form for 2–4 again. Czech, Ukrainian, Lithuanian, Irish, Welsh and Arabic have the same problem.core/bulk.py:110-112explains the choice ("a plural form here would have to pluralise a word it does not know"), but the table does know its own word, so the table can carry the whole sentence.changed_message = ngettext_lazy("%(count)d application changed.", "%(count)d applications changed.", "count")(and the same for companies and listings), and havebulk.changed(count, table)formattable.changed_message % {"count": count}. Retire the generic"%(count)d %(noun)s changed."msgid and thenounattribute. The standalone"application"/"applications"msgids stay, since the models'verbose_nameuses them.Related to #225 (closed), which fixed the same pattern in
app.jsand did not reachcore/bulk.py.Done when
tests/security/test_bulk_actions.py(or a newtests/test_bulk_messages.py) checks that each of the three tables' message is anngettextwith acountplaceholder, and that "1 application changed." and "5 applications changed." still read as they do today in English.tests/security/test_bulk_actions.py::test_what_is_said_afterwards_counts_only_what_changedkeeps passing.