"""Locale utilities."""
import locale
from gettext import NullTranslations, translation
from os import path
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
class _TranslationProxy:
"""
Class for proxy strings from gettext translations. This is a helper for the
lazy_* functions from this module.
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__ = ('_func', '_args')
def __new__(cls, func: Callable[..., str], *args: str) -> '_TranslationProxy':
if not args:
# not called with "function" and "arguments", but a plain string
return str(func) # type: ignore[return-value]
return object.__new__(cls)
def __getnewargs__(self) -> Tuple[str]:
return (self._func,) + self._args # type: ignore
def __init__(self, func: Callable[..., str], *args: str) -> None:
self._func = func
self._args = args
def __str__(self) -> str:
return str(self._func(*self._args))
def __dir__(self) -> List[str]:
return dir(str)
def __getattr__(self, name: str) -> Any:
return getattr(self.__str__(), name)
def __getstate__(self) -> Tuple[Callable[..., str], Tuple[str, ...]]:
return self._func, self._args
def __setstate__(self, tup: Tuple[Callable[..., str], Tuple[str]]) -> None:
self._func, self._args = tup
def __copy__(self) -> '_TranslationProxy':
return _TranslationProxy(self._func, *self._args)
def __repr__(self) -> str:
try:
return 'i' + repr(str(self.__str__()))
except Exception:
return f'<{self.__class__.__name__} broken>'
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):
return hash(self.__str__())
def __eq__(self, other):
return self.__str__() == other
def __lt__(self, string):
return self.__str__() < string
def __contains__(self, char):
return char in self.__str__()
def __len__(self):
return len(self.__str__())
def __getitem__(self, index):
return self.__str__()[index]
translators: Dict[Tuple[str, str], NullTranslations] = {}
[docs]def init(
locale_dirs: List[Optional[str]],
language: Optional[str],
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).
"""
global translators
translator = translators.get((namespace, catalog))
# ignore previously failed attempts to find message catalogs
if translator.__class__ is NullTranslations:
translator = None
# the None entry is the system's default locale path
has_translation = True
if language and '_' in language:
# for language having country code (like "de_AT")
languages: Optional[List[str]] = [language, language.split('_')[0]]
elif language:
languages = [language]
else:
languages = None
# loading
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
# guarantee translators[(namespace, catalog)] exists
if translator is None:
translator = NullTranslations()
has_translation = False
translators[(namespace, catalog)] = translator
return translator, has_translation
def setlocale(category: int, value: Union[str, Iterable[str], None] = None) -> None:
"""Update locale settings.
This does not throw any exception even if update fails.
This is workaround for Python's bug.
For more details:
* https://github.com/sphinx-doc/sphinx/issues/5724
* https://bugs.python.org/issue18378#msg215215
.. note:: Only for internal use. Please don't call this method from extensions.
This will be removed in Sphinx 6.0.
"""
try:
locale.setlocale(category, value)
except locale.Error:
pass
_LOCALE_DIR = path.abspath(path.dirname(__file__))
[docs]def init_console(
locale_dir: str = _LOCALE_DIR,
catalog: str = 'sphinx',
) -> Tuple[NullTranslations, bool]:
"""Initialize locale for console.
.. versionadded:: 1.8
"""
try:
# encoding is ignored
language, _ = locale.getlocale(locale.LC_MESSAGES)
except AttributeError:
# LC_MESSAGES is not always defined. Fallback to the default language
# in case it is not.
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 _lazy_translate(catalog: str, namespace: str, message: str) -> str:
"""Used instead of _ when creating TranslationProxy, because _ is
not bound yet at that time.
"""
translator = get_translator(catalog, namespace)
return translator.gettext(message)
[docs]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::
import os
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 = os.path.abspath(os.path.dirname(__file__))
locale_dir = os.path.join(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, *args: Any) -> str:
if not is_translator_registered(catalog, namespace):
# not initialized yet
return _TranslationProxy(_lazy_translate, catalog, namespace, message) # type: ignore # NOQA
else:
translator = get_translator(catalog, namespace)
if len(args) <= 1:
return translator.gettext(message)
else: # support pluralization
return translator.ngettext(message, args[0], args[1])
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'),
}
# Moved to sphinx.directives.other (will be overridden later)
versionlabels: Dict[str, str] = {}
# Moved to sphinx.domains.python (will be overridden later)
pairindextypes: Dict[str, str] = {}