Repository: wagtail/wagtail

Issue:
Remove capitalize() calls to avoid issues with other languages
### Is your proposal related to a problem?

There are (only) 4 calls of `capitalize()` in wagtail's codebase and 3 of them actually lead to a bad wording in german, for example.

**1) `get_content_type_label()`:**
https://github.com/wagtail/wagtail/blob/bf3a054c04eb66a923bac538caa64165fef79131/wagtail/coreutils.py#L153-L163
I think the second call (fallback if no model exists) can be kept, but the first one, which uses `verbose_name`, should be removed.

This is used in the audit logs for example:
![wagtail_capitalize_calls_german](https://user-images.githubusercontent.com/468164/190669596-05cabfe5-8fe3-4fb7-a394-cf8894ac7783.png)
"Live-Update" (stored in `verbose_name`) is transformed to "Live-update", which looks like a mistake in german.

**2) Site settings:**
https://github.com/wagtail/wagtail/blob/bf3a054c04eb66a923bac538caa64165fef79131/wagtail/contrib/settings/models.py#L146-L147
https://github.com/wagtail/wagtail/blob/bf3a054c04eb66a923bac538caa64165fef79131/wagtail/contrib/settings/models.py#L201-L202

This results in the following:
![wagtail_site_settings_capitalize](https://user-images.githubusercontent.com/468164/190670976-d2d46dc9-0411-4e45-b5b0-d737dd626dcf.png)
This example also shows some other translation issues, which I've fixed in #9217 (and in transifex).

### Describe the solution you'd like

Can we remove the `capitalize()` calls where `verbose_name` is used, so it it can be properly translated?


Discussion:

Resolving patch:
diff --git a/wagtail/contrib/settings/models.py b/wagtail/contrib/settings/models.py
--- a/wagtail/contrib/settings/models.py
+++ b/wagtail/contrib/settings/models.py
@@ -2,6 +2,7 @@
 
 from django.db import models
 from django.utils.functional import cached_property
+from django.utils.translation import gettext as _
 
 from wagtail.coreutils import InvokeViaAttributeShortcut
 from wagtail.models import Site
@@ -144,7 +145,10 @@ def for_site(cls, site):
         return instance
 
     def __str__(self):
-        return "%s for %s" % (self._meta.verbose_name.capitalize(), self.site)
+        return _("%(site_setting)s for %(site)s") % {
+            "site_setting": self._meta.verbose_name,
+            "site": self.site,
+        }
 
 
 class BaseGenericSetting(AbstractSetting):
@@ -199,7 +203,7 @@ def load(cls, request_or_site=None):
         return obj
 
     def __str__(self):
-        return self._meta.verbose_name.capitalize()
+        return self._meta.verbose_name
 
 
 class BaseSetting(BaseSiteSetting):
diff --git a/wagtail/coreutils.py b/wagtail/coreutils.py
--- a/wagtail/coreutils.py
+++ b/wagtail/coreutils.py
@@ -16,7 +16,7 @@
 from django.dispatch import receiver
 from django.http import HttpRequest
 from django.utils.encoding import force_str
-from django.utils.text import slugify
+from django.utils.text import capfirst, slugify
 from django.utils.translation import check_for_language, get_supported_language_variant
 
 if TYPE_CHECKING:
@@ -157,10 +157,10 @@ def get_content_type_label(content_type):
     """
     model = content_type.model_class()
     if model:
-        return model._meta.verbose_name.capitalize()
+        return str(capfirst(model._meta.verbose_name))
     else:
         # no corresponding model class found; fall back on the name field of the ContentType
-        return content_type.model.capitalize()
+        return capfirst(content_type.model)
 
 
 def accepts_kwarg(func, kwarg):


Test patch: