Add typing
Change-Id: I2a31ff8a9c034779b813f531ccf7b2cb166d5e07 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
# under the License.
|
||||
"""Translation function factory"""
|
||||
|
||||
from collections.abc import Callable
|
||||
import gettext
|
||||
import os
|
||||
|
||||
@@ -34,7 +35,10 @@ CONTEXT_SEPARATOR = _message.CONTEXT_SEPARATOR
|
||||
class TranslatorFactory:
|
||||
"Create translator functions"
|
||||
|
||||
def __init__(self, domain, localedir=None):
|
||||
domain: str
|
||||
localedir: str | None
|
||||
|
||||
def __init__(self, domain: str, localedir: str | None = None) -> None:
|
||||
"""Establish a set of translation functions for the domain.
|
||||
|
||||
:param domain: Name of translation domain,
|
||||
@@ -49,7 +53,9 @@ class TranslatorFactory:
|
||||
localedir = os.environ.get(variable_name)
|
||||
self.localedir = localedir
|
||||
|
||||
def _make_translation_func(self, domain=None):
|
||||
def _make_translation_func(
|
||||
self, domain: str | None = None
|
||||
) -> Callable[[str], str | _message.Message]:
|
||||
"""Return a translation function ready for use with messages.
|
||||
|
||||
The returned function takes a single value, the unicode string
|
||||
@@ -74,7 +80,7 @@ class TranslatorFactory:
|
||||
# on the python version.
|
||||
m = t.gettext
|
||||
|
||||
def f(msg):
|
||||
def f(msg: str) -> str | _message.Message:
|
||||
"""oslo_i18n.gettextutils translation function."""
|
||||
if _lazy.USE_LAZY:
|
||||
return _message.Message(msg, domain=domain)
|
||||
@@ -82,7 +88,9 @@ class TranslatorFactory:
|
||||
|
||||
return f
|
||||
|
||||
def _make_contextual_translation_func(self, domain=None):
|
||||
def _make_contextual_translation_func(
|
||||
self, domain: str | None = None
|
||||
) -> Callable[[str, str], str | _message.Message]:
|
||||
"""Return a translation function ready for use with context messages.
|
||||
|
||||
The returned function takes two values, the context of
|
||||
@@ -103,7 +111,7 @@ class TranslatorFactory:
|
||||
# on the python version.
|
||||
m = t.gettext
|
||||
|
||||
def f(ctx, msg):
|
||||
def f(ctx: str, msg: str) -> str | _message.Message:
|
||||
"""oslo.i18n.gettextutils translation with context function."""
|
||||
if _lazy.USE_LAZY:
|
||||
msgid = (ctx, msg)
|
||||
@@ -120,7 +128,9 @@ class TranslatorFactory:
|
||||
|
||||
return f
|
||||
|
||||
def _make_plural_translation_func(self, domain=None):
|
||||
def _make_plural_translation_func(
|
||||
self, domain: str | None = None
|
||||
) -> Callable[[str, str, int], str | _message.Message]:
|
||||
"""Return a plural translation function ready for use with messages.
|
||||
|
||||
The returned function takes three values, the single form of
|
||||
@@ -142,7 +152,9 @@ class TranslatorFactory:
|
||||
# on the python version.
|
||||
m = t.ngettext
|
||||
|
||||
def f(msgsingle, msgplural, msgcount):
|
||||
def f(
|
||||
msgsingle: str, msgplural: str, msgcount: int
|
||||
) -> str | _message.Message:
|
||||
"""oslo.i18n.gettextutils plural translation function."""
|
||||
if _lazy.USE_LAZY:
|
||||
msgid = (msgsingle, msgplural, msgcount)
|
||||
@@ -154,12 +166,12 @@ class TranslatorFactory:
|
||||
return f
|
||||
|
||||
@property
|
||||
def primary(self):
|
||||
def primary(self) -> Callable[[str], str | _message.Message]:
|
||||
"The default translation function."
|
||||
return self._make_translation_func()
|
||||
|
||||
@property
|
||||
def contextual_form(self):
|
||||
def contextual_form(self) -> Callable[[str, str], str | _message.Message]:
|
||||
"""The contextual translation function.
|
||||
|
||||
The returned function takes two values, the context of
|
||||
@@ -171,7 +183,7 @@ class TranslatorFactory:
|
||||
return self._make_contextual_translation_func()
|
||||
|
||||
@property
|
||||
def plural_form(self):
|
||||
def plural_form(self) -> Callable[[str, str, int], str | _message.Message]:
|
||||
"""The plural translation function.
|
||||
|
||||
The returned function takes three values, the single form of
|
||||
@@ -183,25 +195,27 @@ class TranslatorFactory:
|
||||
"""
|
||||
return self._make_plural_translation_func()
|
||||
|
||||
def _make_log_translation_func(self, level):
|
||||
def _make_log_translation_func(
|
||||
self, level: str
|
||||
) -> Callable[[str], str | _message.Message]:
|
||||
return self._make_translation_func(self.domain + '-log-' + level)
|
||||
|
||||
@property
|
||||
def log_info(self):
|
||||
def log_info(self) -> Callable[[str], str | _message.Message]:
|
||||
"Translate info-level log messages."
|
||||
return self._make_log_translation_func('info')
|
||||
|
||||
@property
|
||||
def log_warning(self):
|
||||
def log_warning(self) -> Callable[[str], str | _message.Message]:
|
||||
"Translate warning-level log messages."
|
||||
return self._make_log_translation_func('warning')
|
||||
|
||||
@property
|
||||
def log_error(self):
|
||||
def log_error(self) -> Callable[[str], str | _message.Message]:
|
||||
"Translate error-level log messages."
|
||||
return self._make_log_translation_func('error')
|
||||
|
||||
@property
|
||||
def log_critical(self):
|
||||
def log_critical(self) -> Callable[[str], str | _message.Message]:
|
||||
"Translate critical-level log messages."
|
||||
return self._make_log_translation_func('critical')
|
||||
|
@@ -20,6 +20,8 @@ import copy
|
||||
import gettext
|
||||
import locale
|
||||
import os
|
||||
from typing import Any, Literal, overload
|
||||
from collections.abc import Iterable
|
||||
|
||||
from oslo_i18n import _factory
|
||||
from oslo_i18n import _locale
|
||||
@@ -30,7 +32,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
def install(domain):
|
||||
def install(domain: str) -> None:
|
||||
"""Install a _() function using the given translation domain.
|
||||
|
||||
Given a translation domain, install a _() function using gettext's
|
||||
@@ -49,7 +51,7 @@ def install(domain):
|
||||
builtins.__dict__['_'] = tf.primary
|
||||
|
||||
|
||||
_AVAILABLE_LANGUAGES = {}
|
||||
_AVAILABLE_LANGUAGES: dict[str, list[str]] = {}
|
||||
# Copied from Babel so anyone using aliases that were previously provided by
|
||||
# the Babel implementation of get_available_languages continues to work. These
|
||||
# are not recommended for use in new code.
|
||||
@@ -96,7 +98,7 @@ _BABEL_ALIASES = {
|
||||
}
|
||||
|
||||
|
||||
def get_available_languages(domain):
|
||||
def get_available_languages(domain: str) -> list[str]:
|
||||
"""Lists the available languages for the given translation domain.
|
||||
|
||||
:param domain: the domain to get languages for
|
||||
@@ -106,7 +108,7 @@ def get_available_languages(domain):
|
||||
|
||||
localedir = os.environ.get(_locale.get_locale_dir_variable_name(domain))
|
||||
|
||||
def find(x):
|
||||
def find(x: str) -> str | None:
|
||||
return gettext.find(domain, localedir=localedir, languages=[x])
|
||||
|
||||
# NOTE(mrodden): en_US should always be available (and first in case
|
||||
@@ -125,10 +127,54 @@ def get_available_languages(domain):
|
||||
|
||||
|
||||
_original_find = gettext.find
|
||||
_FIND_CACHE = {}
|
||||
_FIND_CACHE: dict[
|
||||
tuple[str, str | None, tuple[str, ...] | None, bool | int], Any
|
||||
] = {}
|
||||
|
||||
|
||||
def cached_find(domain, localedir=None, languages=None, all=0):
|
||||
@overload
|
||||
def cached_find(
|
||||
domain: str,
|
||||
localedir: str | None = None,
|
||||
languages: Iterable[str] | None = None,
|
||||
all: Literal[False] = False,
|
||||
) -> str | None: ...
|
||||
|
||||
|
||||
@overload
|
||||
def cached_find(
|
||||
domain: str,
|
||||
localedir: str | None = None,
|
||||
languages: Iterable[str] | None = None,
|
||||
*,
|
||||
all: Literal[True],
|
||||
) -> list[str]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def cached_find(
|
||||
domain: str,
|
||||
localedir: str | None,
|
||||
languages: Iterable[str] | None,
|
||||
all: Literal[True],
|
||||
) -> list[str]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def cached_find(
|
||||
domain: str,
|
||||
localedir: str | None = None,
|
||||
languages: Iterable[str] | None = None,
|
||||
all: bool = False,
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
def cached_find(
|
||||
domain: str,
|
||||
localedir: str | None = None,
|
||||
languages: Iterable[str] | None = None,
|
||||
all: bool = False,
|
||||
) -> Any:
|
||||
"""A version of gettext.find using a cache.
|
||||
|
||||
gettext.find looks for mo files on the disk using os.path.exists. Those
|
||||
@@ -149,4 +195,4 @@ def cached_find(domain, localedir=None, languages=None, all=0):
|
||||
return result
|
||||
|
||||
|
||||
gettext.find = cached_find
|
||||
gettext.find = cached_find # type: ignore[assignment]
|
||||
|
@@ -21,7 +21,7 @@ __all__ = [
|
||||
USE_LAZY = False
|
||||
|
||||
|
||||
def enable_lazy(enable=True):
|
||||
def enable_lazy(enable: bool = True) -> None:
|
||||
"""Convenience function for configuring _() to use lazy gettext
|
||||
|
||||
Call this at the start of execution to enable the gettextutils._
|
||||
|
@@ -15,7 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
def get_locale_dir_variable_name(domain):
|
||||
def get_locale_dir_variable_name(domain: str) -> str:
|
||||
"""Build environment variable name for local dir.
|
||||
|
||||
Convert a translation domain name to a variable for specifying
|
||||
|
@@ -20,6 +20,7 @@ import gettext
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
import warnings
|
||||
|
||||
from oslo_i18n import _locale
|
||||
@@ -40,16 +41,23 @@ class Message(str):
|
||||
and can be treated as such.
|
||||
"""
|
||||
|
||||
# Declare attributes that are set in __new__
|
||||
msgid: str | tuple[str, str] | tuple[str, str, int]
|
||||
domain: str
|
||||
params: Any
|
||||
has_contextual_form: bool
|
||||
has_plural_form: bool
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
msgid,
|
||||
msgtext=None,
|
||||
params=None,
|
||||
domain='oslo',
|
||||
has_contextual_form=False,
|
||||
has_plural_form=False,
|
||||
*args,
|
||||
):
|
||||
msgid: str | tuple[str, str] | tuple[str, str, int],
|
||||
msgtext: str | None = None,
|
||||
params: Any = None,
|
||||
domain: str = 'oslo',
|
||||
has_contextual_form: bool = False,
|
||||
has_plural_form: bool = False,
|
||||
*args: Any,
|
||||
) -> 'Message':
|
||||
"""Create a new Message object.
|
||||
|
||||
In order for translation to work gettext requires a message ID, this
|
||||
@@ -72,7 +80,7 @@ class Message(str):
|
||||
msg.has_plural_form = has_plural_form
|
||||
return msg
|
||||
|
||||
def translation(self, desired_locale=None):
|
||||
def translation(self, desired_locale: str | None = None) -> str:
|
||||
"""Translate this message to the desired locale.
|
||||
|
||||
:param desired_locale: The desired locale to translate the message to,
|
||||
@@ -105,12 +113,12 @@ class Message(str):
|
||||
|
||||
@staticmethod
|
||||
def _translate_msgid(
|
||||
msgid,
|
||||
domain,
|
||||
desired_locale=None,
|
||||
has_contextual_form=False,
|
||||
has_plural_form=False,
|
||||
):
|
||||
msgid: str | tuple[str, str] | tuple[str, str, int],
|
||||
domain: str,
|
||||
desired_locale: str | None = None,
|
||||
has_contextual_form: bool = False,
|
||||
has_plural_form: bool = False,
|
||||
) -> str:
|
||||
if not desired_locale:
|
||||
system_locale = locale.getlocale(locale.LC_CTYPE)
|
||||
# If the system locale is not available to the runtime use English
|
||||
@@ -132,6 +140,9 @@ class Message(str):
|
||||
if not has_contextual_form and not has_plural_form:
|
||||
# This is the most common case, so check it first.
|
||||
translator = lang.gettext
|
||||
# narrow type
|
||||
if not isinstance(msgid, str):
|
||||
raise ValueError("Simple msgid must be a string")
|
||||
translated_message = translator(msgid)
|
||||
|
||||
elif has_contextual_form and has_plural_form:
|
||||
@@ -140,7 +151,13 @@ class Message(str):
|
||||
raise ValueError("Unimplemented.")
|
||||
|
||||
elif has_contextual_form:
|
||||
(msgctx, msgtxt) = msgid
|
||||
# narrow type
|
||||
if not isinstance(msgid, tuple) or len(msgid) != 2:
|
||||
raise ValueError(
|
||||
"contextual msgid must be a tuple of (context, text)"
|
||||
)
|
||||
|
||||
msgctx, msgtxt = msgid
|
||||
translator = lang.gettext
|
||||
|
||||
msg_with_ctx = f"{msgctx}{CONTEXT_SEPARATOR}{msgtxt}"
|
||||
@@ -151,19 +168,24 @@ class Message(str):
|
||||
translated_message = msgtxt
|
||||
|
||||
elif has_plural_form:
|
||||
(msgsingle, msgplural, msgcount) = msgid
|
||||
translator = lang.ngettext
|
||||
translated_message = translator(msgsingle, msgplural, msgcount)
|
||||
# narrow type
|
||||
if not isinstance(msgid, tuple) or len(msgid) != 3:
|
||||
raise ValueError(
|
||||
"plural msgid must be a tuple of (singular, plural, count)"
|
||||
)
|
||||
|
||||
msgsingle, msgplural, msgcount = msgid
|
||||
translated_message = lang.ngettext(msgsingle, msgplural, msgcount)
|
||||
|
||||
return translated_message
|
||||
|
||||
def _safe_translate(self, translated_message, translated_params):
|
||||
def _safe_translate(
|
||||
self, translated_message: str, translated_params: Any
|
||||
) -> str:
|
||||
"""Trap translation errors and fall back to default translation.
|
||||
|
||||
:param translated_message: the requested translation
|
||||
|
||||
:param translated_params: the params to be inserted
|
||||
|
||||
:return: if parameter insertion is successful then it is the
|
||||
translated_message with the translated_params inserted, if the
|
||||
requested translation fails then it is the default translation
|
||||
@@ -195,7 +217,7 @@ class Message(str):
|
||||
|
||||
return translated_message
|
||||
|
||||
def __mod__(self, other):
|
||||
def __mod__(self, other: Any) -> 'Message':
|
||||
# When we mod a Message we want the actual operation to be performed
|
||||
# by the base class (i.e. unicode()), the only thing we do here is
|
||||
# save the original msgid and the parameters in case of a translation
|
||||
@@ -206,7 +228,7 @@ class Message(str):
|
||||
)
|
||||
return modded
|
||||
|
||||
def _sanitize_mod_params(self, other):
|
||||
def _sanitize_mod_params(self, other: Any) -> Any:
|
||||
"""Sanitize the object being modded with this Message.
|
||||
|
||||
- Add support for modding 'None' so translation supports it
|
||||
@@ -216,7 +238,7 @@ class Message(str):
|
||||
translated, it will be used as it was when the Message was created
|
||||
"""
|
||||
if other is None:
|
||||
params = (other,)
|
||||
params: Any = (other,)
|
||||
elif isinstance(other, dict):
|
||||
# Merge the dictionaries
|
||||
# Copy each item in case one does not support deep copy.
|
||||
@@ -233,7 +255,7 @@ class Message(str):
|
||||
params = self._copy_param(other)
|
||||
return params
|
||||
|
||||
def _copy_param(self, param):
|
||||
def _copy_param(self, param: Any) -> Any:
|
||||
try:
|
||||
return copy.deepcopy(param)
|
||||
except Exception:
|
||||
@@ -241,11 +263,11 @@ class Message(str):
|
||||
# python code-like objects that can't be deep-copied
|
||||
return str(param)
|
||||
|
||||
def __add__(self, other):
|
||||
def __add__(self, other: Any) -> str:
|
||||
from oslo_i18n._i18n import _
|
||||
|
||||
msg = _('Message objects do not support addition.')
|
||||
raise TypeError(msg)
|
||||
|
||||
def __radd__(self, other):
|
||||
def __radd__(self, other: Any) -> str:
|
||||
return self.__add__(other)
|
||||
|
@@ -19,7 +19,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
def translate(obj, desired_locale=None):
|
||||
def translate(obj: object, desired_locale: str | None = None) -> object:
|
||||
"""Gets the translated unicode representation of the given object.
|
||||
|
||||
If the object is not translatable it is returned as-is.
|
||||
@@ -48,7 +48,7 @@ def translate(obj, desired_locale=None):
|
||||
return obj
|
||||
|
||||
|
||||
def translate_args(args, desired_locale=None):
|
||||
def translate_args(args: object, desired_locale: str | None = None) -> object:
|
||||
"""Translates all the translatable elements of the given arguments object.
|
||||
|
||||
This method is used for translating the translatable values in method
|
||||
|
@@ -12,6 +12,7 @@
|
||||
"""Test fixtures for working with oslo_i18n."""
|
||||
|
||||
import gettext
|
||||
from typing import Any
|
||||
|
||||
import fixtures
|
||||
|
||||
@@ -19,7 +20,7 @@ from oslo_i18n import _lazy
|
||||
from oslo_i18n import _message
|
||||
|
||||
|
||||
class Translation(fixtures.Fixture):
|
||||
class Translation(fixtures.Fixture): # type: ignore[misc]
|
||||
"""Fixture for managing translatable strings.
|
||||
|
||||
This class provides methods for creating translatable strings
|
||||
@@ -34,7 +35,7 @@ class Translation(fixtures.Fixture):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, domain='test-domain'):
|
||||
def __init__(self, domain: str = 'test-domain') -> None:
|
||||
"""Initialize the fixture.
|
||||
|
||||
:param domain: The translation domain. This is not expected to
|
||||
@@ -44,7 +45,7 @@ class Translation(fixtures.Fixture):
|
||||
"""
|
||||
self.domain = domain
|
||||
|
||||
def lazy(self, msg):
|
||||
def lazy(self, msg: str) -> _message.Message:
|
||||
"""Return a lazily translated message.
|
||||
|
||||
:param msg: Input message string. May optionally include
|
||||
@@ -54,7 +55,7 @@ class Translation(fixtures.Fixture):
|
||||
"""
|
||||
return _message.Message(msg, domain=self.domain)
|
||||
|
||||
def immediate(self, msg):
|
||||
def immediate(self, msg: str) -> str:
|
||||
"""Return a string as though it had been translated immediately.
|
||||
|
||||
:param msg: Input message string. May optionally include
|
||||
@@ -65,10 +66,10 @@ class Translation(fixtures.Fixture):
|
||||
return str(msg)
|
||||
|
||||
|
||||
class ToggleLazy(fixtures.Fixture):
|
||||
class ToggleLazy(fixtures.Fixture): # type: ignore[misc]
|
||||
"""Fixture to toggle lazy translation on or off for a test."""
|
||||
|
||||
def __init__(self, enabled):
|
||||
def __init__(self, enabled: bool) -> None:
|
||||
"""Force lazy translation on or off.
|
||||
|
||||
:param enabled: Flag controlling whether to enable or disable
|
||||
@@ -79,12 +80,12 @@ class ToggleLazy(fixtures.Fixture):
|
||||
self._enabled = enabled
|
||||
self._original_value = _lazy.USE_LAZY
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.addCleanup(self._restore_original)
|
||||
_lazy.enable_lazy(self._enabled)
|
||||
|
||||
def _restore_original(self):
|
||||
def _restore_original(self) -> None:
|
||||
_lazy.enable_lazy(self._original_value)
|
||||
|
||||
|
||||
@@ -99,25 +100,26 @@ class _PrefixTranslator(gettext.NullTranslations):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fp=None, prefix='noprefix'):
|
||||
def __init__(self, fp: Any = None, prefix: str = 'noprefix') -> None:
|
||||
gettext.NullTranslations.__init__(self, fp)
|
||||
self.prefix = prefix
|
||||
|
||||
def gettext(self, message):
|
||||
def gettext(self, message: str) -> str:
|
||||
msg = gettext.NullTranslations.gettext(self, message)
|
||||
return self.prefix + msg
|
||||
|
||||
def ugettext(self, message):
|
||||
msg = gettext.NullTranslations.ugettext(self, message)
|
||||
def ugettext(self, message: str) -> str:
|
||||
# Note: ugettext is deprecated, fallback to gettext
|
||||
msg = gettext.NullTranslations.gettext(self, message)
|
||||
return self.prefix + msg
|
||||
|
||||
|
||||
def _prefix_translations(*x, **y):
|
||||
def _prefix_translations(*x: Any, **y: Any) -> _PrefixTranslator:
|
||||
"""Use message id prefixed with domain and language as translation"""
|
||||
return _PrefixTranslator(prefix=x[0] + '/' + y['languages'][0] + ': ')
|
||||
|
||||
|
||||
class PrefixLazyTranslation(fixtures.Fixture):
|
||||
class PrefixLazyTranslation(fixtures.Fixture): # type: ignore[misc]
|
||||
"""Fixture to prefix lazy translation enabled messages
|
||||
|
||||
Use of this fixture will cause messages supporting lazy translation to
|
||||
@@ -140,12 +142,14 @@ class PrefixLazyTranslation(fixtures.Fixture):
|
||||
|
||||
_DEFAULT_LANG = 'en_US'
|
||||
|
||||
def __init__(self, languages=None, locale=None):
|
||||
def __init__(
|
||||
self, languages: list[str] | None = None, locale: Any = None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.languages = languages or [PrefixLazyTranslation._DEFAULT_LANG]
|
||||
self.locale = locale
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.useFixture(ToggleLazy(True))
|
||||
self.useFixture(
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
"""logging utilities for translation"""
|
||||
|
||||
import logging
|
||||
from logging import handlers
|
||||
|
||||
from oslo_i18n import _translate
|
||||
@@ -55,7 +56,9 @@ class TranslationHandler(handlers.MemoryHandler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, locale=None, target=None):
|
||||
def __init__(
|
||||
self, locale: str | None = None, target: logging.Handler | None = None
|
||||
) -> None:
|
||||
"""Initialize a TranslationHandler
|
||||
|
||||
:param locale: locale to use for translating messages
|
||||
@@ -70,10 +73,11 @@ class TranslationHandler(handlers.MemoryHandler):
|
||||
handlers.MemoryHandler.__init__(self, capacity=0, target=target)
|
||||
self.locale = locale
|
||||
|
||||
def setFormatter(self, fmt):
|
||||
self.target.setFormatter(fmt)
|
||||
def setFormatter(self, fmt: logging.Formatter | None) -> None:
|
||||
if self.target is not None:
|
||||
self.target.setFormatter(fmt)
|
||||
|
||||
def emit(self, record):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
# We save the message from the original record to restore it
|
||||
# after translation, so other handlers are not affected by this
|
||||
original_msg = record.msg
|
||||
@@ -85,12 +89,13 @@ class TranslationHandler(handlers.MemoryHandler):
|
||||
record.msg = original_msg
|
||||
record.args = original_args
|
||||
|
||||
def _translate_and_log_record(self, record):
|
||||
def _translate_and_log_record(self, record: logging.LogRecord) -> None:
|
||||
record.msg = _translate.translate(record.msg, self.locale)
|
||||
|
||||
# In addition to translating the message, we also need to translate
|
||||
# arguments that were passed to the log method that were not part
|
||||
# of the main message e.g., log.info(_('Some message %s'), this_one))
|
||||
record.args = _translate.translate_args(record.args, self.locale)
|
||||
record.args = _translate.translate_args(record.args, self.locale) # type: ignore[assignment]
|
||||
|
||||
self.target.emit(record)
|
||||
if self.target is not None:
|
||||
self.target.emit(record)
|
||||
|
@@ -41,8 +41,10 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
|
||||
self.logger.addHandler(self.translation_handler)
|
||||
|
||||
def test_set_formatter(self):
|
||||
formatter = 'some formatter'
|
||||
formatter = logging.Formatter()
|
||||
self.translation_handler.setFormatter(formatter)
|
||||
# narrow types https://github.com/python/mypy/issues/5088
|
||||
assert self.translation_handler.target is not None
|
||||
self.assertEqual(formatter, self.translation_handler.target.formatter)
|
||||
|
||||
@mock.patch('gettext.translation')
|
||||
|
@@ -346,7 +346,7 @@ class MessageTestCase(test_base.BaseTestCase):
|
||||
obj = utils.SomeObject(message)
|
||||
unicoded_obj = str(obj)
|
||||
|
||||
self.assertEqual(es_translation, unicoded_obj.translation('es'))
|
||||
self.assertEqual(es_translation, unicoded_obj.translation('es')) # type: ignore[attr-defined]
|
||||
|
||||
@mock.patch('gettext.translation')
|
||||
def test_translate_multiple_languages(self, mock_translation):
|
||||
@@ -520,8 +520,8 @@ class MessageTestCase(test_base.BaseTestCase):
|
||||
obj = utils.SomeObject(msg)
|
||||
unicoded_obj = str(obj)
|
||||
|
||||
self.assertEqual(expected_translation, unicoded_obj.translation('es'))
|
||||
self.assertEqual(default_translation, unicoded_obj.translation('XX'))
|
||||
self.assertEqual(expected_translation, unicoded_obj.translation('es')) # type: ignore[attr-defined]
|
||||
self.assertEqual(default_translation, unicoded_obj.translation('XX')) # type: ignore[attr-defined]
|
||||
|
||||
@mock.patch('gettext.translation')
|
||||
def test_translate_message_with_message_parameter(self, mock_translation):
|
||||
|
@@ -46,3 +46,6 @@ docstring-code-format = true
|
||||
[tool.ruff.lint]
|
||||
select = ["E4", "E7", "E9", "F", "S", "UP"]
|
||||
ignore = ["F403"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"oslo_i18n/tests/*" = ["S"]
|
||||
|
Reference in New Issue
Block a user