Sphinx

# Source code for sphinx.locale

    """Locale utilities."""

    from __future__ import annotations

    import locale
    import sys
    from gettext import NullTranslations, translation
    from pathlib import Path
    from typing import TYPE_CHECKING

    from sphinx import package_dir

    if TYPE_CHECKING:
        import os
        from collections.abc import Callable, Iterable
        from typing import Any

    _LOCALE_DIR = Path(package_dir, 'locale')

    class _TranslationProxy:
        """The proxy implementation attempts to be as complete as possible, so that
        the lazy objects should mostly work as expected, for example for sorting.
        """

        __slots__ = '_catalogue', '_namespace', '_message'

        def __init__(self, catalogue: str, namespace: str, message: str) -> None:
            self._catalogue = catalogue
            self._namespace = namespace
            self._message = message

        def __str__(self) -> str:
            try:
                return translators[self._namespace, self._catalogue].gettext(self._message)
            except KeyError:
                # NullTranslations().gettext(self._message) == self._message
                return self._message

        def __dir__(self) -> list[str]:
            return dir(str)

        def __getattr__(self, name: str) -> Any:
            return getattr(self.__str__(), name)

        def __getstate__(self) -> tuple[str, str, str]:
            return self._catalogue, self._namespace, self._message

        def __setstate__(self, tup: tuple[str, str, str]) -> None:
            self._catalogue, self._namespace, self._message = tup

        def __copy__(self) -> _TranslationProxy:
            return _TranslationProxy(self._catalogue, self._namespace, self._message)

        def __repr__(self) -> str:
            try:
                return f'i{self.__str__()!r}'
            except Exception:
                return (
                    self.__class__.__name__
                    + f'({self._catalogue}, {self._namespace}, {self._message})'
                )

        def __add__(self, other: str) -> str:
            return self.__str__() + other

        def __radd__(self, other: str) -> str:
            return other + self.__str__()

        def __mod__(self, other: str) -> str:
            return self.__str__() % other

        def __rmod__(self, other: str) -> str:
            return other % self.__str__()

        def __mul__(self, other: Any) -> str:
            return self.__str__() * other

        def __rmul__(self, other: Any) -> str:
            return other * self.__str__()

        def __hash__(self) -> int:
            return hash(self.__str__())

        def __eq__(self, other: object) -> bool:
            return self.__str__() == other

        def __lt__(self, string: str) -> bool:
            return self.__str__() < string

        def __contains__(self, char: str) -> bool:
            return char in self.__str__()

        def __len__(self) -> int:
            return len(self.__str__())

        def __getitem__(self, index: int | slice) -> str:
            return self.__str__()[index]

    translators: dict[tuple[str, str], NullTranslations] = {}

    def init(
        locale_dirs: Iterable[str | os.PathLike[str] | None],
        language: str | None,
        catalog: str = 'sphinx',
        namespace: str = 'general',
    ) -> tuple[NullTranslations, bool]:
        """Look for message catalogs in `locale_dirs` and *ensure* that there is at
        least a NullTranslations catalog set in `translators`. If called multiple
        times or if several ``.mo`` files are found, their contents are merged
        together (thus making ``init`` reentrant).
        """
        translator = translators.get((namespace, catalog))
        # ignore previously failed attempts to find message catalogs
        if translator.__class__ is NullTranslations:
            translator = None

        if language:
            languages: list[str] | None = [language]
        else:
            languages = None

        # loading
        # the None entry is the system's default locale path
        for dir_ in locale_dirs:
            try:
                trans = translation(catalog, localedir=dir_, languages=languages)
                if translator is None:
                    translator = trans
                else:
                    translator.add_fallback(trans)
            except Exception:
                # Language couldn't be found in the specified path
                pass
        if translator is not None:
            has_translation = True
        else:
            translator = NullTranslations()
            has_translation = False
        # guarantee translators[(namespace, catalog)] exists
        translators[namespace, catalog] = translator
        return translator, has_translation

    def init_console(
        locale_dir: str | os.PathLike[str] | None = None,
        catalog: str = 'sphinx',
    ) -> tuple[NullTranslations, bool]:
        """Initialize locale for console.

        .. versionadded:: 1.8
        """
        if locale_dir is None:
            locale_dir = _LOCALE_DIR
        if sys.platform == 'win32':
            language = None
        else:
            try:
                # encoding is ignored
                language, _ = locale.getlocale(locale.LC_MESSAGES)
            except AttributeError:
                # Fallback to the default language in case LC_MESSAGES is not defined.
                language = None
        return init([locale_dir], language, catalog, 'console')

    def get_translator(
        catalog: str = 'sphinx', namespace: str = 'general'
    ) -> NullTranslations:
        return translators.get((namespace, catalog), NullTranslations())

    def is_translator_registered(
        catalog: str = 'sphinx', namespace: str = 'general'
    ) -> bool:
        return (namespace, catalog) in translators

    def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str], str]:
        """Get a translation function based on the *catalog* and *namespace*.

        The extension can use this API to translate the messages on the
        extension::

            from pathlib import Path
            from sphinx.locale import get_translation

            MESSAGE_CATALOG_NAME = 'myextension'  # name of *.pot, *.po and *.mo files
            _ = get_translation(MESSAGE_CATALOG_NAME)
            text = _('Hello Sphinx!')

            def setup(app):
                package_dir = Path(__file__).resolve().parent
                locale_dir = package_dir / 'locales'
                app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir)

        With this code, sphinx searches a message catalog from
        ``${package_dir}/locales/${language}/LC_MESSAGES/myextension.mo``.
        The :confval:`language` is used for the searching.

        .. versionadded:: 1.8
        """

        def gettext(message: str) -> str:
            if not is_translator_registered(catalog, namespace):
                # not initialized yet
                return _TranslationProxy(catalog, namespace, message)  # type: ignore[return-value]
            else:
                translator = get_translator(catalog, namespace)
                return translator.gettext(message)

        return gettext

    # A shortcut for sphinx-core
    #: Translation function for messages on documentation (menu, labels, themes and so on).
    #: This function follows :confval:`language` setting.
    _ = get_translation('sphinx')
    #: Translation function for console messages
    #: This function follows locale setting (`LC_ALL`, `LC_MESSAGES` and so on).
    __ = get_translation('sphinx', 'console')

    # labels
    admonitionlabels = {
        'attention': _('Attention'),
        'caution': _('Caution'),
        'danger': _('Danger'),
        'error': _('Error'),
        'hint': _('Hint'),
        'important': _('Important'),
        'note': _('Note'),
        'seealso': _('See also'),
        'tip': _('Tip'),
        'warning': _('Warning'),
    }