Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.locale

    """
    Provide the Locale API.
    """

    from __future__ import annotations

    from typing import Sequence, TypeAlias

    from babel import Locale, negotiate_locale as babel_negotiate_locale
    from babel.core import UnknownLocaleError
    from betty import fs
    from langcodes import Language

    DEFAULT_LOCALE = "en-US"
    UNDETERMINED_LOCALE = "und"

    _LOCALE_DIRECTORY_PATH = fs.ASSETS_DIRECTORY_PATH / "locale"

    def to_babel_identifier(locale: Localey) -> str:
        """
        Convert a locale or locale metadata to a Babel locale identifier.
        """
        if isinstance(locale, Locale):
            return str(locale)
        language_data = Language.get(locale)
        return "_".join(
            part
            for part in [
                language_data.language,
                language_data.script,
                language_data.territory,
            ]
            if part
        )

    def to_locale(locale: Localey) -> str:
        """
        Ensure that a locale or locale metadata is a locale.
        """
        if isinstance(locale, str):
            return locale
        return "-".join(
            part
            for part in [
                locale.language,
                locale.script,
                locale.territory,
            ]
            if part
        )

    Localey: TypeAlias = str | Locale

    def get_data(locale: Localey) -> Locale:
        """
        Get locale metadata.

        :raises betty.locale.InvalidLocale: Raised if the given identifier is not a valid locale.
        :raises betty.locale.LocaleNotFoundError: Raised if the given locale cannot be found.
        """
        if isinstance(locale, Locale):
            return locale
        try:
            return Locale.parse(to_babel_identifier(locale))
        except ValueError:
            from betty.locale.error import InvalidLocale

            raise InvalidLocale(locale) from None
        except UnknownLocaleError:
            from betty.locale.error import LocaleNotFound

            raise LocaleNotFound(locale) from None

    def get_display_name(
        locale: Localey, display_locale: Localey | None = None
    ) -> str | None:
        """
        Return a locale's human-readable display name.
        """
        locale_data = get_data(locale)
        return locale_data.get_display_name(
            get_data(display_locale) if display_locale else locale_data
        )

    def negotiate_locale(
        preferred_locales: Localey | Sequence[Localey], available_locales: Sequence[Localey]
    ) -> Locale | None:
        """
        Negotiate the preferred locale from a sequence.
        """
        if isinstance(preferred_locales, (str, Locale)):
            preferred_locales = [preferred_locales]
        return _negotiate_locale(
            [to_babel_identifier(locale) for locale in preferred_locales],
            {to_babel_identifier(locale) for locale in available_locales},
            False,
        )

    def _negotiate_locale(
        preferred_locale_babel_identifiers: Sequence[str],
        available_locale_babel_identifiers: set[str],
        root: bool,
    ) -> Locale | None:
        negotiated_locale = babel_negotiate_locale(
            preferred_locale_babel_identifiers, available_locale_babel_identifiers
        )
        if negotiated_locale is not None:
            return Locale.parse(negotiated_locale)
        if not root:
            return _negotiate_locale(
                [
                    (
                        babel_identifier.split("_")[0]
                        if "_" in babel_identifier
                        else babel_identifier
                    )
                    for babel_identifier in preferred_locale_babel_identifiers
                ],
                available_locale_babel_identifiers,
                True,
            )
        return None