Repository: python-babel/babel

Issue:
`locale` parameter cannot be `None`
## Overview Description

The type annotations for the `locale` parameter (e.g. in `babel.numbers.format_currency()`) suggest that the value can be `None`, but this causes a `TypeError` in `Locale.parse()`.

## Steps to Reproduce

```python
babel.numbers.format_currency(0, "USD", locale=None)
```

## Actual Results

```python
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    babel.numbers.format_currency(0, "USD", locale=None)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.13/site-packages/babel/numbers.py", line 777, in format_currency
    locale = Locale.parse(locale)
  File ".venv/lib/python3.13/site-packages/babel/core.py", line 332, in parse
    raise TypeError(f"Unexpected value for identifier: {identifier!r}")
TypeError: Unexpected value for identifier: None
```

## Expected Results

I would expect a value of `None` to use the default value, or `None` should be removed from the type annotations entirely.

## Reproducibility

## Additional Information



Discussion:
If possible, I'd like to work on this please.
> If possible, I'd like to work on this please.

Go for it! Feel free to ask if you need help :)
@tomasr8 Please let me know if the PR looks good or missing anything!



Patch:
diff --git a/babel/core.py b/babel/core.py
index a2e1e1de5..a3ebb40e0 100644
--- a/babel/core.py
+++ b/babel/core.py
@@ -269,7 +269,7 @@ def negotiate(
     @classmethod
     def parse(
         cls,
-        identifier: str | Locale | None,
+        identifier: Locale | str | None,
         sep: str = '_',
         resolve_likely_subtags: bool = True,
     ) -> Locale:
@@ -286,8 +286,8 @@ def parse(
         Locale('de', territory='DE')
 
         If the `identifier` parameter is neither of these, such as `None`
-        e.g. because a default locale identifier could not be determined,
-        a `TypeError` is raised:
+        or an empty string, e.g. because a default locale identifier
+        could not be determined, a `TypeError` is raised:
 
         >>> Locale.parse(None)
         Traceback (most recent call last):
@@ -325,10 +325,23 @@ def parse(
         :raise `UnknownLocaleError`: if no locale data is available for the
                                      requested locale
         :raise `TypeError`: if the identifier is not a string or a `Locale`
+        :raise `ValueError`: if the identifier is not a valid string
         """
         if isinstance(identifier, Locale):
             return identifier
-        elif not isinstance(identifier, str):
+
+        if not identifier:
+            msg = (
+                f"Empty locale identifier value: {identifier!r}\n\n"
+                f"If you didn't explicitly pass an empty value to a Babel function, "
+                f"this could be caused by there being no suitable locale environment "
+                f"variables for the API you tried to use.",
+            )
+            if isinstance(identifier, str):
+                raise ValueError(msg)  # `parse_locale` would raise a ValueError, so let's do that here
+            raise TypeError(msg)
+
+        if not isinstance(identifier, str):
             raise TypeError(f"Unexpected value for identifier: {identifier!r}")
 
         parts = parse_locale(identifier, sep=sep)
@@ -1235,6 +1248,8 @@ def parse_locale(
     :raise `ValueError`: if the string does not appear to be a valid locale
                          identifier
     """
+    if not identifier:
+        raise ValueError("empty locale identifier")
     identifier, _, modifier = identifier.partition('@')
     if '.' in identifier:
         # this is probably the charset/encoding, which we don't care about
diff --git a/babel/dates.py b/babel/dates.py
index 2e481852a..8c82d72d4 100644
--- a/babel/dates.py
+++ b/babel/dates.py
@@ -252,8 +252,11 @@ def get_timezone(zone: str | datetime.tzinfo | None = None) -> datetime.tzinfo:
     raise LookupError(f"Unknown timezone {zone}") from exc
 
 
-def get_period_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
-                     context: _Context = 'stand-alone', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:
+def get_period_names(
+    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
+    context: _Context = 'stand-alone',
+    locale: Locale | str | None = None,
+) -> LocaleDataDict:
     """Return the names for day periods (AM/PM) used by the locale.
 
     >>> get_period_names(locale='en_US')['am']
@@ -261,13 +264,16 @@ def get_period_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
 
     :param width: the width to use, one of "abbreviated", "narrow", or "wide"
     :param context: the context, either "format" or "stand-alone"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).day_periods[context][width]
+    return Locale.parse(locale or LC_TIME).day_periods[context][width]
 
 
-def get_day_names(width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wide',
-                  context: _Context = 'format', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:
+def get_day_names(
+    width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wide',
+    context: _Context = 'format',
+    locale: Locale | str | None = None,
+) -> LocaleDataDict:
     """Return the day names used by the locale for the specified format.
 
     >>> get_day_names('wide', locale='en_US')[1]
@@ -281,13 +287,16 @@ def get_day_names(width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wi
 
     :param width: the width to use, one of "wide", "abbreviated", "short" or "narrow"
     :param context: the context, either "format" or "stand-alone"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).days[context][width]
+    return Locale.parse(locale or LC_TIME).days[context][width]
 
 
-def get_month_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
-                    context: _Context = 'format', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:
+def get_month_names(
+    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
+    context: _Context = 'format',
+    locale: Locale | str | None = None,
+) -> LocaleDataDict:
     """Return the month names used by the locale for the specified format.
 
     >>> get_month_names('wide', locale='en_US')[1]
@@ -299,13 +308,16 @@ def get_month_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
 
     :param width: the width to use, one of "wide", "abbreviated", or "narrow"
     :param context: the context, either "format" or "stand-alone"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).months[context][width]
+    return Locale.parse(locale or LC_TIME).months[context][width]
 
 
-def get_quarter_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
-                      context: _Context = 'format', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:
+def get_quarter_names(
+    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
+    context: _Context = 'format',
+    locale: Locale | str | None = None,
+) -> LocaleDataDict:
     """Return the quarter names used by the locale for the specified format.
 
     >>> get_quarter_names('wide', locale='en_US')[1]
@@ -317,13 +329,15 @@ def get_quarter_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
 
     :param width: the width to use, one of "wide", "abbreviated", or "narrow"
     :param context: the context, either "format" or "stand-alone"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).quarters[context][width]
+    return Locale.parse(locale or LC_TIME).quarters[context][width]
 
 
-def get_era_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
-                  locale: Locale | str | None = LC_TIME) -> LocaleDataDict:
+def get_era_names(
+    width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
+    locale: Locale | str | None = None,
+) -> LocaleDataDict:
     """Return the era names used by the locale for the specified format.
 
     >>> get_era_names('wide', locale='en_US')[1]
@@ -332,12 +346,15 @@ def get_era_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
     u'n. Chr.'
 
     :param width: the width to use, either "wide", "abbreviated", or "narrow"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).eras[width]
+    return Locale.parse(locale or LC_TIME).eras[width]
 
 
-def get_date_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = LC_TIME) -> DateTimePattern:
+def get_date_format(
+    format: _PredefinedTimeFormat = 'medium',
+    locale: Locale | str | None = None,
+) -> DateTimePattern:
     """Return the date formatting patterns used by the locale for the specified
     format.
 
@@ -348,12 +365,15 @@ def get_date_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | s
 
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).date_formats[format]
+    return Locale.parse(locale or LC_TIME).date_formats[format]
 
 
-def get_datetime_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = LC_TIME) -> DateTimePattern:
+def get_datetime_format(
+    format: _PredefinedTimeFormat = 'medium',
+    locale: Locale | str | None = None,
+) -> DateTimePattern:
     """Return the datetime formatting patterns used by the locale for the
     specified format.
 
@@ -362,15 +382,18 @@ def get_datetime_format(format: _PredefinedTimeFormat = 'medium', locale: Locale
 
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    patterns = Locale.parse(locale).datetime_formats
+    patterns = Locale.parse(locale or LC_TIME).datetime_formats
     if format not in patterns:
         format = None
     return patterns[format]
 
 
-def get_time_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = LC_TIME) -> DateTimePattern:
+def get_time_format(
+    format: _PredefinedTimeFormat = 'medium',
+    locale: Locale | str | None = None,
+) -> DateTimePattern:
     """Return the time formatting patterns used by the locale for the specified
     format.
 
@@ -381,15 +404,15 @@ def get_time_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | s
 
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     """
-    return Locale.parse(locale).time_formats[format]
+    return Locale.parse(locale or LC_TIME).time_formats[format]
 
 
 def get_timezone_gmt(
     datetime: _Instant = None,
     width: Literal['long', 'short', 'iso8601', 'iso8601_short'] = 'long',
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
     return_z: bool = False,
 ) -> str:
     """Return the timezone associated with the given `datetime` object formatted
@@ -423,12 +446,12 @@ def get_timezone_gmt(
     :param datetime: the ``datetime`` object; if `None`, the current date and
                      time in UTC is used
     :param width: either "long" or "short" or "iso8601" or "iso8601_short"
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     :param return_z: True or False; Function returns indicator "Z"
                      when local time offset is 0
     """
     datetime = _ensure_datetime_tzinfo(_get_datetime(datetime))
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
 
     offset = datetime.tzinfo.utcoffset(datetime)
     seconds = offset.days * 24 * 60 * 60 + offset.seconds
@@ -448,7 +471,7 @@ def get_timezone_gmt(
 
 def get_timezone_location(
     dt_or_tzinfo: _DtOrTzinfo = None,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
     return_city: bool = False,
 ) -> str:
     """Return a representation of the given timezone using "location format".
@@ -479,13 +502,13 @@ def get_timezone_location(
     :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
                          the timezone; if `None`, the current date and time in
                          UTC is assumed
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     :param return_city: True or False, if True then return exemplar city (location)
                         for the time zone
     :return: the localized timezone name using location format
 
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
 
     zone = _get_tz_name(dt_or_tzinfo)
 
@@ -530,7 +553,7 @@ def get_timezone_name(
     dt_or_tzinfo: _DtOrTzinfo = None,
     width: Literal['long', 'short'] = 'long',
     uncommon: bool = False,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
     zone_variant: Literal['generic', 'daylight', 'standard'] | None = None,
     return_zone: bool = False,
 ) -> str:
@@ -600,12 +623,12 @@ def get_timezone_name(
                            ``'generic'`` variation is assumed.  The following
                            values are valid: ``'generic'``, ``'daylight'`` and
                            ``'standard'``.
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     :param return_zone: True or False. If true then function
                         returns long time zone ID
     """
     dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
 
     zone = _get_tz_name(dt_or_tzinfo)
 
@@ -651,7 +674,7 @@ def get_timezone_name(
 def format_date(
     date: datetime.date | None = None,
     format: _PredefinedTimeFormat | str = 'medium',
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
 ) -> str:
     """Return a date formatted according to the given pattern.
 
@@ -672,14 +695,14 @@ def format_date(
                  date is used
     :param format: one of "full", "long", "medium", or "short", or a custom
                    date/time pattern
-    :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     """
     if date is None:
         date = datetime.date.today()
     elif isinstance(date, datetime.datetime):
         date = date.date()
 
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
     if format in ('full', 'long', 'medium', 'short'):
         format = get_date_format(format, locale=locale)
     pattern = parse_pattern(format)
@@ -690,7 +713,7 @@ def format_datetime(
     datetime: _Instant = None,
     format: _PredefinedTimeFormat | str = 'medium',
     tzinfo: datetime.tzinfo | None = None,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
 ) -> str:
     r"""Return a date formatted according to the given pattern.
 
@@ -713,11 +736,11 @@ def format_datetime(
     :param format: one of "full", "long", "medium", or "short", or a custom
                    date/time pattern
     :param tzinfo: the timezone to apply to the time for display
-    :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     """
     datetime = _ensure_datetime_tzinfo(_get_datetime(datetime), tzinfo)
 
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
     if format in ('full', 'long', 'medium', 'short'):
         return get_datetime_format(format, locale=locale) \
             .replace("'", "") \
@@ -731,7 +754,8 @@ def format_datetime(
 def format_time(
     time: datetime.time | datetime.datetime | float | None = None,
     format: _PredefinedTimeFormat | str = 'medium',
-    tzinfo: datetime.tzinfo | None = None, locale: Locale | str | None = LC_TIME,
+    tzinfo: datetime.tzinfo | None = None,
+    locale: Locale | str | None = None,
 ) -> str:
     r"""Return a time formatted according to the given pattern.
 
@@ -786,7 +810,7 @@ def format_time(
     :param format: one of "full", "long", "medium", or "short", or a custom
                    date/time pattern
     :param tzinfo: the time-zone to apply to the time for display
-    :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     """
 
     # get reference date for if we need to find the right timezone variant
@@ -795,7 +819,7 @@ def format_time(
 
     time = _get_time(time, tzinfo)
 
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
     if format in ('full', 'long', 'medium', 'short'):
         format = get_time_format(format, locale=locale)
     return parse_pattern(format).apply(time, locale, reference_date=ref_date)
@@ -806,7 +830,7 @@ def format_skeleton(
     datetime: _Instant = None,
     tzinfo: datetime.tzinfo | None = None,
     fuzzy: bool = True,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
 ) -> str:
     r"""Return a time and/or date formatted according to the given pattern.
 
@@ -842,9 +866,9 @@ def format_skeleton(
     :param fuzzy: If the skeleton is not found, allow choosing a skeleton that's
                   close enough to it. If there is no close match, a `KeyError`
                   is thrown.
-    :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
     if fuzzy and skeleton not in locale.datetime_skeletons:
         skeleton = match_skeleton(skeleton, locale.datetime_skeletons)
     format = locale.datetime_skeletons[skeleton]
@@ -868,7 +892,7 @@ def format_timedelta(
     threshold: float = .85,
     add_direction: bool = False,
     format: Literal['narrow', 'short', 'medium', 'long'] = 'long',
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
 ) -> str:
     """Return a time delta according to the rules of the given locale.
 
@@ -923,7 +947,7 @@ def format_timedelta(
     :param format: the format, can be "narrow", "short" or "long". (
                    "medium" is deprecated, currently converted to "long" to
                    maintain compatibility)
-    :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     """
     if format not in ('narrow', 'short', 'medium', 'long'):
         raise TypeError('Format must be one of "narrow", "short" or "long"')
@@ -939,7 +963,7 @@ def format_timedelta(
         seconds = int((delta.days * 86400) + delta.seconds)
     else:
         seconds = delta
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
     date_fields = locale._data["date_fields"]
     unit_patterns = locale._data["unit_patterns"]
 
@@ -988,7 +1012,7 @@ def _format_fallback_interval(
     end: _Instant,
     skeleton: str | None,
     tzinfo: datetime.tzinfo | None,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale,
 ) -> str:
     if skeleton in locale.datetime_skeletons:  # Use the given skeleton
         format = lambda dt: format_skeleton(skeleton, dt, tzinfo, locale=locale)
@@ -1018,7 +1042,7 @@ def format_interval(
     skeleton: str | None = None,
     tzinfo: datetime.tzinfo | None = None,
     fuzzy: bool = True,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
 ) -> str:
     """
     Format an interval between two instants according to the locale's rules.
@@ -1058,10 +1082,10 @@ def format_interval(
     :param tzinfo: tzinfo to use (if none is already attached)
     :param fuzzy: If the skeleton is not found, allow choosing a skeleton that's
                   close enough to it.
-    :param locale: A locale object or identifier.
+    :param locale: A locale object or identifier. Defaults to the system time locale.
     :return: Formatted interval
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
 
     # NB: The quote comments below are from the algorithm description in
     #     https://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
@@ -1121,7 +1145,7 @@ def get_period_id(
     time: _Instant,
     tzinfo: datetime.tzinfo | None = None,
     type: Literal['selection'] | None = None,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
 ) -> str:
     """
     Get the day period ID for a given time.
@@ -1143,12 +1167,12 @@ def get_period_id(
     :param type: The period type to use. Either "selection" or None.
                  The selection type is used for selecting among phrases such as
                  “Your email arrived yesterday evening” or “Your email arrived last night”.
-    :param locale: the `Locale` object, or a locale string
+    :param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
     :return: period ID. Something is always returned -- even if it's just "am" or "pm".
     """
     time = _get_time(time, tzinfo)
     seconds_past_midnight = int(time.hour * 60 * 60 + time.minute * 60 + time.second)
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_TIME)
 
     # The LDML rules state that the rules may not overlap, so iterating in arbitrary
     # order should be alright, though `at` periods should be preferred.
@@ -1199,7 +1223,7 @@ class ParseError(ValueError):
 
 def parse_date(
     string: str,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
     format: _PredefinedTimeFormat | str = 'medium',
 ) -> datetime.date:
     """Parse a date from a string.
@@ -1228,6 +1252,7 @@ def parse_date(
 
     :param string: the string containing the date
     :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     :param format: the format to use, either an explicit date format,
                    or one of "full", "long", "medium", or "short"
                    (see ``get_time_format``)
@@ -1275,7 +1300,7 @@ def parse_date(
 
 def parse_time(
     string: str,
-    locale: Locale | str | None = LC_TIME,
+    locale: Locale | str | None = None,
     format: _PredefinedTimeFormat | str = 'medium',
 ) -> datetime.time:
     """Parse a time from a string.
@@ -1292,7 +1317,7 @@ def parse_time(
     datetime.time(15, 30)
 
     :param string: the string containing the time
-    :param locale: a `Locale` object or a locale identifier
+    :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
     :param format: the format to use, either an explicit time format,
                    or one of "full", "long", "medium", or "short"
                    (see ``get_time_format``)
diff --git a/babel/lists.py b/babel/lists.py
index 6c34cb099..f1d3790d1 100644
--- a/babel/lists.py
+++ b/babel/lists.py
@@ -15,6 +15,7 @@
 """
 from __future__ import annotations
 
+import warnings
 from collections.abc import Sequence
 from typing import TYPE_CHECKING
 
@@ -23,13 +24,26 @@
 if TYPE_CHECKING:
     from typing_extensions import Literal
 
-DEFAULT_LOCALE = default_locale()
+
+
+_DEFAULT_LOCALE = default_locale()  # TODO(3.0): Remove this.
+
+
+def __getattr__(name):
+    if name == "DEFAULT_LOCALE":
+        warnings.warn(
+            "The babel.lists.DEFAULT_LOCALE constant is deprecated and will be removed.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return _DEFAULT_LOCALE
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
 
 
 def format_list(
     lst: Sequence[str],
     style: Literal['standard', 'standard-short', 'or', 'or-short', 'unit', 'unit-short', 'unit-narrow'] = 'standard',
-    locale: Locale | str | None = DEFAULT_LOCALE,
+    locale: Locale | str | None = None,
 ) -> str:
     """
     Format the items in `lst` as a list.
@@ -74,9 +88,9 @@ def format_list(
 
     :param lst: a sequence of items to format in to a list
     :param style: the style to format the list with. See above for description.
-    :param locale: the locale
+    :param locale: the locale. Defaults to the system locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or _DEFAULT_LOCALE)
     if not lst:
         return ''
     if len(lst) == 1:
diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py
index cff22f9b5..1036ab826 100644
--- a/babel/messages/catalog.py
+++ b/babel/messages/catalog.py
@@ -279,7 +279,7 @@ class Catalog:
 
     def __init__(
         self,
-        locale: str | Locale | None = None,
+        locale: Locale | str | None = None,
         domain: str | None = None,
         header_comment: str | None = DEFAULT_HEADER,
         project: str | None = None,
diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py
index 01c38f4cd..cb1ea5b30 100644
--- a/babel/messages/plurals.py
+++ b/babel/messages/plurals.py
@@ -220,11 +220,13 @@ def __str__(self) -> str:
         return self.plural_forms
 
 
-def get_plural(locale: str | None = LC_CTYPE) -> _PluralTuple:
+def get_plural(locale: Locale | str | None = None) -> _PluralTuple:
     """A tuple with the information catalogs need to perform proper
     pluralization.  The first item of the tuple is the number of plural
     forms, the second the plural expression.
 
+    :param locale: the `Locale` object or locale identifier. Defaults to the system character type locale.
+
     >>> get_plural(locale='en')
     (2, '(n != 1)')
     >>> get_plural(locale='ga')
@@ -246,7 +248,7 @@ def get_plural(locale: str | None = LC_CTYPE) -> _PluralTuple:
     >>> str(tup)
     'nplurals=1; plural=0;'
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_CTYPE)
     try:
         tup = PLURALS[str(locale)]
     except KeyError:
diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py
index 721707657..a292c8a0c 100644
--- a/babel/messages/pofile.py
+++ b/babel/messages/pofile.py
@@ -380,7 +380,7 @@ def _invalid_pofile(self, line, lineno, msg) -> None:
 
 def read_po(
     fileobj: IO[AnyStr] | Iterable[AnyStr],
-    locale: str | Locale | None = None,
+    locale: Locale | str | None = None,
     domain: str | None = None,
     ignore_obsolete: bool = False,
     charset: str | None = None,
diff --git a/babel/numbers.py b/babel/numbers.py
index 624e8d61e..aed9aa4c4 100644
--- a/babel/numbers.py
+++ b/babel/numbers.py
@@ -108,7 +108,7 @@ def normalize_currency(currency: str, locale: Locale | str | None = None) -> str
 def get_currency_name(
     currency: str,
     count: float | decimal.Decimal | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
 ) -> str:
     """Return the name used by the locale for the specified currency.
 
@@ -120,9 +120,9 @@ def get_currency_name(
     :param currency: the currency code.
     :param count: the optional count.  If provided the currency name
                   will be pluralized to that number if possible.
-    :param locale: the `Locale` object or locale identifier.
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     """
-    loc = Locale.parse(locale)
+    loc = Locale.parse(locale or LC_NUMERIC)
     if count is not None:
         try:
             plural_form = loc.plural_form(count)
@@ -138,16 +138,16 @@ def get_currency_name(
     return loc.currencies.get(currency, currency)
 
 
-def get_currency_symbol(currency: str, locale: Locale | str | None = LC_NUMERIC) -> str:
+def get_currency_symbol(currency: str, locale: Locale | str | None = None) -> str:
     """Return the symbol used by the locale for the specified currency.
 
     >>> get_currency_symbol('USD', locale='en_US')
     u'$'
 
     :param currency: the currency code.
-    :param locale: the `Locale` object or locale identifier.
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     """
-    return Locale.parse(locale).currency_symbols.get(currency, currency)
+    return Locale.parse(locale or LC_NUMERIC).currency_symbols.get(currency, currency)
 
 
 def get_currency_precision(currency: str) -> int:
@@ -167,7 +167,7 @@ def get_currency_precision(currency: str) -> int:
 def get_currency_unit_pattern(
     currency: str,
     count: float | decimal.Decimal | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
 ) -> str:
     """
     Return the unit pattern used for long display of a currency value
@@ -184,9 +184,9 @@ def get_currency_unit_pattern(
     :param currency: the currency code.
     :param count: the optional count.  If provided the unit
                   pattern for that number will be returned.
-    :param locale: the `Locale` object or locale identifier.
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     """
-    loc = Locale.parse(locale)
+    loc = Locale.parse(locale or LC_NUMERIC)
     if count is not None:
         plural_form = loc.plural_form(count)
         try:
@@ -325,16 +325,15 @@ def _get_numbering_system(locale: Locale, numbering_system: Literal["default"] |
 
 
 def _get_number_symbols(
-    locale: Locale | str | None,
+    locale: Locale,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> LocaleDataDict:
-    parsed_locale = Locale.parse(locale)
-    numbering_system = _get_numbering_system(parsed_locale, numbering_system)
+    numbering_system = _get_numbering_system(locale, numbering_system)
     try:
-        return parsed_locale.number_symbols[numbering_system]
+        return locale.number_symbols[numbering_system]
     except KeyError as error:
-        raise UnsupportedNumberingSystemError(f"Unknown numbering system {numbering_system} for Locale {parsed_locale}.") from error
+        raise UnsupportedNumberingSystemError(f"Unknown numbering system {numbering_system} for Locale {locale}.") from error
 
 
 class UnsupportedNumberingSystemError(Exception):
@@ -343,7 +342,7 @@ class UnsupportedNumberingSystemError(Exception):
 
 
 def get_decimal_symbol(
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -356,16 +355,17 @@ def get_decimal_symbol(
     >>> get_decimal_symbol('ar_EG', numbering_system='latn')
     u'.'
 
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
     return _get_number_symbols(locale, numbering_system=numbering_system).get('decimal', '.')
 
 
 def get_plus_sign_symbol(
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -378,16 +378,17 @@ def get_plus_sign_symbol(
     >>> get_plus_sign_symbol('ar_EG', numbering_system='latn')
     u'\u200e+'
 
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
     return _get_number_symbols(locale, numbering_system=numbering_system).get('plusSign', '+')
 
 
 def get_minus_sign_symbol(
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -400,16 +401,17 @@ def get_minus_sign_symbol(
     >>> get_minus_sign_symbol('ar_EG', numbering_system='latn')
     u'\u200e-'
 
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
     return _get_number_symbols(locale, numbering_system=numbering_system).get('minusSign', '-')
 
 
 def get_exponential_symbol(
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -422,16 +424,17 @@ def get_exponential_symbol(
     >>> get_exponential_symbol('ar_EG', numbering_system='latn')
     u'E'
 
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
     return _get_number_symbols(locale, numbering_system=numbering_system).get('exponential', 'E')
 
 
 def get_group_symbol(
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -444,16 +447,17 @@ def get_group_symbol(
     >>> get_group_symbol('ar_EG', numbering_system='latn')
     u','
 
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
     return _get_number_symbols(locale, numbering_system=numbering_system).get('group', ',')
 
 
 def get_infinity_symbol(
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -466,15 +470,16 @@ def get_infinity_symbol(
     >>> get_infinity_symbol('ar_EG', numbering_system='latn')
     u'∞'
 
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for fetching the symbol. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
     return _get_number_symbols(locale, numbering_system=numbering_system).get('infinity', '∞')
 
 
-def format_number(number: float | decimal.Decimal | str, locale: Locale | str | None = LC_NUMERIC) -> str:
+def format_number(number: float | decimal.Decimal | str, locale: Locale | str | None = None) -> str:
     """Return the given number formatted for a specific locale.
 
     >>> format_number(1099, locale='en_US')  # doctest: +SKIP
@@ -487,7 +492,7 @@ def format_number(number: float | decimal.Decimal | str, locale: Locale | str |
        Use babel.numbers.format_decimal() instead.
 
     :param number: the number to format
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
 
 
     """
@@ -518,7 +523,7 @@ def get_decimal_quantum(precision: int | decimal.Decimal) -> decimal.Decimal:
 def format_decimal(
     number: float | decimal.Decimal | str,
     format: str | NumberPattern | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     decimal_quantization: bool = True,
     group_separator: bool = True,
     *,
@@ -562,7 +567,7 @@ def format_decimal(
 
     :param number: the number to format
     :param format:
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param decimal_quantization: Truncate and round high-precision numbers to
                                  the format pattern. Defaults to `True`.
     :param group_separator: Boolean to switch group separator on/off in a locale's
@@ -571,7 +576,7 @@ def format_decimal(
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     if format is None:
         format = locale.decimal_formats[format]
     pattern = parse_pattern(format)
@@ -583,7 +588,7 @@ def format_compact_decimal(
     number: float | decimal.Decimal | str,
     *,
     format_type: Literal["short", "long"] = "short",
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     fraction_digits: int = 0,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -606,13 +611,13 @@ def format_compact_decimal(
 
     :param number: the number to format
     :param format_type: Compact format to use ("short" or "long")
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     compact_format = locale.compact_decimal_formats[format_type]
     number, format = _get_compact_format(number, compact_format, locale, fraction_digits)
     # Did not find a format, fall back.
@@ -670,7 +675,7 @@ def format_currency(
     number: float | decimal.Decimal | str,
     currency: str,
     format: str | NumberPattern | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     currency_digits: bool = True,
     format_type: Literal["name", "standard", "accounting"] = "standard",
     decimal_quantization: bool = True,
@@ -758,7 +763,7 @@ def format_currency(
     :param number: the number to format
     :param currency: the currency code
     :param format: the format string to use
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param currency_digits: use the currency's natural number of decimal digits
     :param format_type: the currency format type to use
     :param decimal_quantization: Truncate and round high-precision numbers to
@@ -769,12 +774,20 @@ def format_currency(
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
+    locale = Locale.parse(locale or LC_NUMERIC)
+
     if format_type == 'name':
-        return _format_currency_long_name(number, currency, format=format,
-                                          locale=locale, currency_digits=currency_digits,
-                                          decimal_quantization=decimal_quantization, group_separator=group_separator,
-                                          numbering_system=numbering_system)
-    locale = Locale.parse(locale)
+        return _format_currency_long_name(
+            number,
+            currency,
+            locale=locale,
+            format=format,
+            currency_digits=currency_digits,
+            decimal_quantization=decimal_quantization,
+            group_separator=group_separator,
+            numbering_system=numbering_system,
+        )
+
     if format:
         pattern = parse_pattern(format)
     else:
@@ -791,18 +804,17 @@ def format_currency(
 def _format_currency_long_name(
     number: float | decimal.Decimal | str,
     currency: str,
-    format: str | NumberPattern | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
-    currency_digits: bool = True,
-    format_type: Literal["name", "standard", "accounting"] = "standard",
-    decimal_quantization: bool = True,
-    group_separator: bool = True,
     *,
-    numbering_system: Literal["default"] | str = "latn",
+    locale: Locale,
+    format: str | NumberPattern | None,
+    currency_digits: bool,
+    decimal_quantization: bool,
+    group_separator: bool,
+    numbering_system: Literal["default"] | str,
 ) -> str:
     # Algorithm described here:
     # https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies
-    locale = Locale.parse(locale)
+
     # Step 1.
     # There are no examples of items with explicit count (0 or 1) in current
     # locale data. So there is no point implementing that.
@@ -835,7 +847,7 @@ def format_compact_currency(
     currency: str,
     *,
     format_type: Literal["short"] = "short",
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     fraction_digits: int = 0,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -851,13 +863,13 @@ def format_compact_currency(
     :param number: the number to format
     :param currency: the currency code
     :param format_type: the compact format type to use. Defaults to "short".
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     try:
         compact_format = locale.compact_currency_formats[format_type]
     except KeyError as error:
@@ -885,7 +897,7 @@ def format_compact_currency(
 def format_percent(
     number: float | decimal.Decimal | str,
     format: str | NumberPattern | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     decimal_quantization: bool = True,
     group_separator: bool = True,
     *,
@@ -924,7 +936,7 @@ def format_percent(
 
     :param number: the percent number to format
     :param format:
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param decimal_quantization: Truncate and round high-precision numbers to
                                  the format pattern. Defaults to `True`.
     :param group_separator: Boolean to switch group separator on/off in a locale's
@@ -933,7 +945,7 @@ def format_percent(
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     if not format:
         format = locale.percent_formats[None]
     pattern = parse_pattern(format)
@@ -944,12 +956,12 @@ def format_percent(
 
 
 def format_scientific(
-        number: float | decimal.Decimal | str,
-        format: str | NumberPattern | None = None,
-        locale: Locale | str | None = LC_NUMERIC,
-        decimal_quantization: bool = True,
-        *,
-        numbering_system: Literal["default"] | str = "latn",
+    number: float | decimal.Decimal | str,
+    format: str | NumberPattern | None = None,
+    locale: Locale | str | None = None,
+    decimal_quantization: bool = True,
+    *,
+    numbering_system: Literal["default"] | str = "latn",
 ) -> str:
     """Return value formatted in scientific notation for a specific locale.
 
@@ -974,14 +986,14 @@ def format_scientific(
 
     :param number: the number to format
     :param format:
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param decimal_quantization: Truncate and round high-precision numbers to
                                  the format pattern. Defaults to `True`.
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     if not format:
         format = locale.scientific_formats[None]
     pattern = parse_pattern(format)
@@ -1009,7 +1021,7 @@ def __init__(self, message: str, suggestions: list[str] | None = None) -> None:
 
 def parse_number(
     string: str,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> int:
@@ -1028,7 +1040,7 @@ def parse_number(
     NumberFormatError: '1.099,98' is not a valid number
 
     :param string: the string to parse
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :return: the parsed number
@@ -1053,7 +1065,7 @@ def parse_number(
 
 def parse_decimal(
     string: str,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     strict: bool = False,
     *,
     numbering_system: Literal["default"] | str = "latn",
@@ -1090,7 +1102,7 @@ def parse_decimal(
     NumberFormatError: '0.00' is not a properly formatted decimal number. Did you mean '0'?
 
     :param string: the string to parse
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param strict: controls whether numbers formatted in a weird way are
                    accepted or rejected
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
@@ -1099,7 +1111,7 @@ def parse_decimal(
                               decimal number
     :raise UnsupportedNumberingSystemError: if the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     group_symbol = get_group_symbol(locale, numbering_system=numbering_system)
     decimal_symbol = get_decimal_symbol(locale, numbering_system=numbering_system)
 
diff --git a/babel/support.py b/babel/support.py
index 7dcda5c63..a4484ce08 100644
--- a/babel/support.py
+++ b/babel/support.py
@@ -642,7 +642,7 @@ def __init__(self, fp: gettext._TranslationsReader | None = None, domain: str |
     def load(
         cls,
         dirname: str | os.PathLike[str] | None = None,
-        locales: Iterable[str | Locale] | str | Locale | None = None,
+        locales: Iterable[str | Locale] | Locale | str | None = None,
         domain: str | None = None,
     ) -> NullTranslations:
         """Load translations from the given directory.
@@ -709,7 +709,7 @@ def merge(self, translations: Translations):
 
 
 def _locales_to_names(
-    locales: Iterable[str | Locale] | str | Locale | None,
+    locales: Iterable[str | Locale] | Locale | str | None,
 ) -> list[str] | None:
     """Normalize a `locales` argument to a list of locale names.
 
diff --git a/babel/units.py b/babel/units.py
index 8dae6947e..1ff71eb69 100644
--- a/babel/units.py
+++ b/babel/units.py
@@ -18,7 +18,7 @@ def __init__(self, unit: str, locale: Locale) -> None:
 def get_unit_name(
     measurement_unit: str,
     length: Literal['short', 'long', 'narrow'] = 'long',
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
 ) -> str | None:
     """
     Get the display name for a measurement unit in the given locale.
@@ -38,17 +38,17 @@ def get_unit_name(
                              https://unicode.org/repos/cldr/tags/latest/common/validity/unit.xml
 
     :param length: "short", "long" or "narrow"
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :return: The unit display name, or None.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     unit = _find_unit_pattern(measurement_unit, locale=locale)
     if not unit:
         raise UnknownUnitError(unit=measurement_unit, locale=locale)
     return locale.unit_display_names.get(unit, {}).get(length)
 
 
-def _find_unit_pattern(unit_id: str, locale: Locale | str | None = LC_NUMERIC) -> str | None:
+def _find_unit_pattern(unit_id: str, locale: Locale | str | None = None) -> str | None:
     """
     Expand a unit into a qualified form.
 
@@ -65,7 +65,7 @@ def _find_unit_pattern(unit_id: str, locale: Locale | str | None = LC_NUMERIC) -
     :param unit_id: the code of a measurement unit.
     :return: A key to the `unit_patterns` mapping, or None.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
     unit_patterns = locale._data["unit_patterns"]
     if unit_id in unit_patterns:
         return unit_id
@@ -80,7 +80,7 @@ def format_unit(
     measurement_unit: str,
     length: Literal['short', 'long', 'narrow'] = 'long',
     format: str | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str:
@@ -130,12 +130,12 @@ def format_unit(
                              https://unicode.org/repos/cldr/tags/latest/common/validity/unit.xml
     :param length: "short", "long" or "narrow"
     :param format: An optional format, as accepted by `format_decimal`.
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
 
     q_unit = _find_unit_pattern(measurement_unit, locale=locale)
     if not q_unit:
@@ -161,7 +161,7 @@ def format_unit(
 def _find_compound_unit(
     numerator_unit: str,
     denominator_unit: str,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
 ) -> str | None:
     """
     Find a predefined compound unit pattern.
@@ -182,11 +182,11 @@ def _find_compound_unit(
 
     :param numerator_unit: The numerator unit's identifier
     :param denominator_unit: The denominator unit's identifier
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :return: A key to the `unit_patterns` mapping, or None.
     :rtype: str|None
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
 
     # Qualify the numerator and denominator units.  This will turn possibly partial
     # units like "kilometer" or "hour" into actual units like "length-kilometer" and
@@ -217,7 +217,7 @@ def format_compound_unit(
     denominator_unit: str | None = None,
     length: Literal["short", "long", "narrow"] = "long",
     format: str | None = None,
-    locale: Locale | str | None = LC_NUMERIC,
+    locale: Locale | str | None = None,
     *,
     numbering_system: Literal["default"] | str = "latn",
 ) -> str | None:
@@ -265,13 +265,13 @@ def format_compound_unit(
     :param denominator_unit: The denominator unit. See `format_unit`.
     :param length: The formatting length. "short", "long" or "narrow"
     :param format: An optional format, as accepted by `format_decimal`.
-    :param locale: the `Locale` object or locale identifier
+    :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
     :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
                              The special value "default" will use the default numbering system of the locale.
     :return: A formatted compound value.
     :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
     """
-    locale = Locale.parse(locale)
+    locale = Locale.parse(locale or LC_NUMERIC)
 
     # Look for a specific compound unit first...
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 21debf6c9..2dc571f5d 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -308,6 +308,9 @@ def test_parse_locale():
     assert (core.parse_locale('de_DE.iso885915@euro') ==
             ('de', 'DE', None, None, 'euro'))
 
+    with pytest.raises(ValueError, match="empty"):
+        core.parse_locale("")
+
 
 @pytest.mark.parametrize('filename', [
     'babel/global.dat',
@@ -375,3 +378,12 @@ def test_language_alt_official_not_used():
     locale = Locale('mus')
     assert locale.get_display_name() == 'Mvskoke'
     assert locale.get_display_name(Locale('en')) == 'Muscogee'
+
+
+def test_locale_parse_empty():
+    with pytest.raises(ValueError, match="Empty"):
+        Locale.parse("")
+    with pytest.raises(TypeError, match="Empty"):
+        Locale.parse(None)
+    with pytest.raises(TypeError, match="Empty"):
+        Locale.parse(False)  # weird...!
diff --git a/tests/test_lists.py b/tests/test_lists.py
index 46ca10d02..ef522f223 100644
--- a/tests/test_lists.py
+++ b/tests/test_lists.py
@@ -30,3 +30,8 @@ def test_issue_1098():
         # Translation verified using Google Translate. It would add more spacing, but the glyphs are correct.
         "1英尺5英寸"
     )
+
+
+def test_lists_default_locale_deprecation():
+    with pytest.warns(DeprecationWarning):
+        _ = lists.DEFAULT_LOCALE
diff --git a/tests/test_numbers.py b/tests/test_numbers.py
index 106b83edf..9787afd16 100644
--- a/tests/test_numbers.py
+++ b/tests/test_numbers.py
@@ -486,6 +486,19 @@ def test_format_currency():
             == 'US$0,00')          # other
 
 
+def test_format_currency_with_none_locale_with_default(monkeypatch):
+    """Test that the default locale is used when locale is None."""
+    monkeypatch.setattr(numbers, "LC_NUMERIC", "fi_FI")
+    assert numbers.format_currency(0, "USD", locale=None) == "0,00\xa0$"
+
+
+def test_format_currency_with_none_locale(monkeypatch):
+    """Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too."""
+    monkeypatch.setattr(numbers, "LC_NUMERIC", None)  # Pretend we couldn't find any locale when importing the module
+    with pytest.raises(TypeError, match="Empty"):
+        numbers.format_currency(0, "USD", locale=None)
+
+
 def test_format_currency_format_type():
     assert (numbers.format_currency(1099.98, 'USD', locale='en_US',
                                     format_type="standard")