Repository: django/django

Issue:
# Refs #28877 -- Added special ordinal context when humanizing value 1.

In french we need to specialize 1st which does not work like 81st, it's 1<sup>er</sup> and 81<sup>ième</sup>. For zero is tricky but it's attested that some use 0<sup>ième</sup>.

Also fixed 101 that was tested to be `101er` while in french it's `101e` (sourced in my commit).


#### Checklist
- [x] This PR targets the `main` branch. <!-- Backports will be evaluated and done by mergers, when necessary. -->
- [x] The commit message is written in past tense, mentions the ticket number, and ends with a period.
- [ ] I have checked the "Has patch" ticket flag in the Trac system.
- [x] I have added or updated relevant tests.
- [ ] I have added or updated relevant docs, including release notes if applicable.
- [ ] I have attached screenshots in both light and dark modes for any UI changes.


Implementation patch:
diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py
index 26a9dd3a3f68..79d2e1566721 100644
--- a/django/contrib/humanize/templatetags/humanize.py
+++ b/django/contrib/humanize/templatetags/humanize.py
@@ -32,7 +32,10 @@ def ordinal(value):
         return value
     if value < 0:
         return str(value)
-    if value % 100 in (11, 12, 13):
+    if value == 1:
+        # Translators: Ordinal format when value is 1 (1st).
+        value = pgettext("ordinal is 1", "{}st").format(value)
+    elif value % 100 in (11, 12, 13):
         # Translators: Ordinal format for 11 (11th), 12 (12th), and 13 (13th).
         value = pgettext("ordinal 11, 12, 13", "{}th").format(value)
     else:


Test patch:
diff --git a/tests/humanize_tests/tests.py b/tests/humanize_tests/tests.py
index 7c2863d3c478..91e9127a65bf 100644
--- a/tests/humanize_tests/tests.py
+++ b/tests/humanize_tests/tests.py
@@ -1,4 +1,5 @@
 import datetime
+import os
 from decimal import Decimal
 
 from django.contrib.humanize.templatetags import humanize
@@ -9,10 +10,10 @@
 from django.utils.timezone import get_fixed_timezone
 from django.utils.translation import gettext as _
 
+here = os.path.dirname(os.path.abspath(__file__))
 # Mock out datetime in some tests so they don't fail occasionally when they
 # run too slow. Use a fixed datetime for datetime.now(). DST change in
 # America/Chicago (the default time zone) happened on March 11th in 2012.
-
 now = datetime.datetime(2012, 3, 9, 22, 30)
 
 
@@ -83,6 +84,7 @@ def test_ordinal(self):
         with translation.override("en"):
             self.humanize_tester(test_list, result_list, "ordinal")
 
+    @override_settings(LOCALE_PATHS=[os.path.join(here, "locale")])
     def test_i18n_html_ordinal(self):
         """Allow html in output on i18n strings"""
         test_list = (
@@ -93,6 +95,8 @@ def test_i18n_html_ordinal(self):
             "11",
             "12",
             "13",
+            "21",
+            "31",
             "101",
             "102",
             "103",
@@ -108,7 +112,9 @@ def test_i18n_html_ordinal(self):
             "11<sup>e</sup>",
             "12<sup>e</sup>",
             "13<sup>e</sup>",
-            "101<sup>er</sup>",
+            "21<sup>e</sup>",
+            "31<sup>e</sup>",
+            "101<sup>e</sup>",
             "102<sup>e</sup>",
             "103<sup>e</sup>",
             "111<sup>e</sup>",