Skip to content

Commit

Permalink
Add TranslatedString class that handles KeyError during % substitution
Browse files Browse the repository at this point in the history
  • Loading branch information
kozlovsky authored and drew2a committed Apr 19, 2023
1 parent 987a17a commit 47d94b4
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 10 deletions.
46 changes: 41 additions & 5 deletions src/tribler/gui/tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import pytest

from tribler.gui.utilities import compose_magnetlink, create_api_key, dict_item_is_any_of, duration_to_string, \
from tribler.gui.utilities import TranslatedString, compose_magnetlink, create_api_key, dict_item_is_any_of, \
duration_to_string, \
format_api_key, \
quote_plus_unicode, set_api_key, tr, unicode_quoter

Expand Down Expand Up @@ -175,7 +176,42 @@ def test_duration_to_string(seconds, translation):
assert duration_to_string(seconds) == translation


@patch('tribler.gui.utilities.QCoreApplication.translate', new=Mock(side_effect=KeyError('key is invalid')))
def test_tr_exception():
# test if the tr function returns the key if the translation is not found
assert tr('0s') == '0s'
def test_correct_translation():
original_string = 'original %(key1)s'
translated_string = 'translated %(key1)s'
s = TranslatedString(translated_string, original_string)
assert s % {'key1': '123'} == 'translated 123'


@patch('tribler.gui.utilities.logger.warning')
def test_missed_key_in_translated_string(warning: Mock):
original_string = 'original %(key1)s'
translated_string = 'translated %(key2)s'
s = TranslatedString(translated_string, original_string)

# In this test, we pass the correct param 'key1' presented in the original string but missed in the translation.
# The KeyError is intercepted, the original string is used instead of the translation, and the error is logged
# as a warning.
assert s % {'key1': '123'} == 'original 123'

warning.assert_called_once_with('KeyError: No value provided for \'key2\' in translation "translated %(key2)s", '
'original string: "original %(key1)s"')


@patch('tribler.gui.utilities.logger.warning')
def test_missed_key_in_both_translated_and_original_strings(warning: Mock):
original_string = 'original %(key1)s'
translated_string = 'translated %(key2)s'
s = TranslatedString(translated_string, original_string)

with pytest.raises(KeyError, match=r"^'key1'$"):
# In this test, we pass an incorrect param 'key3' for interpolation, and also, the translation
# string (with param 'key2') differs from the original string (with param 'key1'). First,
# translated string tries to interpolate params and issues a warning that 'key2' is missed.
# Then, the original string tries to interpolate params and again gets a KeyError because 'key1'
# is also missed. This second exception is propagated because the main reason for the error is
# in the outside code that passes an incorrect parameter.
s % {'key3': '123'}

warning.assert_called_once_with('KeyError: No value provided for \'key2\' in translation "translated %(key2)s", '
'original string: "original %(key1)s"')
24 changes: 19 additions & 5 deletions src/tribler/gui/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,26 @@
NUM_VOTES_BARS = 8


class TranslatedString(str):
def __new__(cls, translation, original_string):
return super().__new__(cls, translation)

def __init__(self, translation: str, original_string: str): # pylint: disable=unused-argument
super().__init__()
self.original_string = original_string

def __mod__(self, other):
try:
return str.__mod__(self, other)
except KeyError as e:
msg = f'No value provided for {e} in translation "{self}", original string: "{self.original_string}"'
logger.warning(f'{type(e).__name__}: {msg}')
return self.original_string % other


def tr(key):
try:
return str(QCoreApplication.translate('@default', key))
except KeyError as e:
logger.warning(f'{type(e).__name__}: {e} in "{key}"')
return key
translated_string = QCoreApplication.translate('@default', key)
return TranslatedString(translated_string, original_string=key)


VOTES_RATING_DESCRIPTIONS = (
Expand Down

0 comments on commit 47d94b4

Please sign in to comment.