Skip to content

Commit

Permalink
refactor: rip out Python 2/3 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
tseaver committed May 26, 2024
1 parent b79dbee commit e7b8115
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 100 deletions.
63 changes: 29 additions & 34 deletions translationstring/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import re
from gettext import NullTranslations
from translationstring.compat import text_type
from translationstring.compat import string_types
from translationstring.compat import PY3

NAME_RE = r"[a-zA-Z][-a-zA-Z0-9_]*"

_interp_regex = re.compile(r'(?<!\$)(\$(?:(%(n)s)|{(%(n)s)}))'
% ({'n': NAME_RE}))

CONTEXT_MASK = text_type('%s\x04%s')
CONTEXT_MASK = u'%s\x04%s'

class TranslationString(text_type):
class TranslationString(str):
"""
The constructor for a :term:`translation string`. A translation
string is a Unicode-like object that has some extra metadata.
Expand Down Expand Up @@ -63,13 +60,15 @@ class TranslationString(text_type):
"""
__slots__ = ('domain', 'context', 'default', 'mapping')

def __new__(self, msgid, domain=None, default=None, mapping=None, context=None):
def __new__(
self, msgid, domain=None, default=None, mapping=None, context=None
):

# NB: this function should never never lose the *original
# identity* of a non-``None`` but empty ``default`` value
# provided to it. See the comment in ChameleonTranslate.

self = text_type.__new__(self, msgid)
self = str.__new__(self, msgid)
if isinstance(msgid, self.__class__):
domain = domain or msgid.domain and msgid.domain[:]
context = context or msgid.context and msgid.context[:]
Expand All @@ -80,11 +79,11 @@ def __new__(self, msgid, domain=None, default=None, mapping=None, context=None):
mapping.setdefault(k, v)
else:
mapping = msgid.mapping.copy()
msgid = text_type(msgid)
msgid = str(msgid)
self.domain = domain
self.context = context
if default is None:
default = text_type(msgid)
default = str(msgid)
self.default = default
self.mapping = mapping
return self
Expand Down Expand Up @@ -129,7 +128,7 @@ def interpolate(self, translated=None):
if self.mapping and translated:
def replace(match):
whole, param1, param2 = match.groups()
return text_type(self.mapping.get(param1 or param2, whole))
return str(self.mapping.get(param1 or param2, whole))
translated = _interp_regex.sub(replace, translated)

return translated
Expand All @@ -138,7 +137,7 @@ def __reduce__(self):
return self.__class__, self.__getstate__()

def __getstate__(self):
return text_type(self), self.domain, self.default, self.mapping, self.context
return str(self), self.domain, self.default, self.mapping, self.context

def TranslationStringFactory(factory_domain):
""" Create a factory which will generate translation strings
Expand Down Expand Up @@ -214,15 +213,17 @@ def translate(msgid, domain=None, mapping=None, context=None,
# preserving ``default`` in the aforementioned case. So we
# spray these indignant comments all over this module. ;-)

if not isinstance(msgid, string_types):
if not isinstance(msgid, str):
if msgid is not None:
msgid = text_type(msgid)
msgid = str(msgid)
return msgid

tstring = msgid

if not hasattr(tstring, 'interpolate'):
tstring = TranslationString(msgid, domain, default, mapping, context)
tstring = TranslationString(
msgid, domain, default, mapping, context
)
if translator is None:
result = tstring.interpolate()
else:
Expand All @@ -236,10 +237,7 @@ def ugettext_policy(translations, tstring, domain, context):
""" A translator policy function which unconditionally uses the
``ugettext`` API on the translations object."""

if PY3: # pragma: no cover
_gettext = translations.gettext
else: # pragma: no cover
_gettext = translations.ugettext
_gettext = translations.gettext

if context:
# Workaround for http://bugs.python.org/issue2504?
Expand Down Expand Up @@ -267,10 +265,7 @@ def dugettext_policy(translations, tstring, domain, context):
if getattr(translations, 'dugettext', None) is not None:
translated = translations.dugettext(domain, msgid)
else:
if PY3: # pragma: no cover
_gettext = translations.gettext
else: # pragma: no cover
_gettext = translations.ugettext
_gettext = translations.gettext

translated = _gettext(msgid)
return tstring if translated == msgid else translated
Expand Down Expand Up @@ -305,14 +300,18 @@ def Translator(translations=None, policy=None):
policy = dugettext_policy
def translator(tstring, domain=None, mapping=None, context=None):
if not hasattr(tstring, 'interpolate'):
tstring = TranslationString(tstring, domain=domain, mapping=mapping, context=context)
tstring = TranslationString(
tstring, domain=domain, mapping=mapping, context=context
)
elif mapping:
if tstring.mapping:
new_mapping = tstring.mapping.copy()
new_mapping.update(mapping)
else:
new_mapping = mapping
tstring = TranslationString(tstring, domain=domain, mapping=new_mapping, context=context)
tstring = TranslationString(
tstring, domain=domain, mapping=new_mapping, context=context
)
translated = tstring
domain = domain or tstring.domain
context = context or tstring.context
Expand All @@ -329,10 +328,7 @@ def ungettext_policy(translations, singular, plural, n, domain, context):
""" A pluralizer policy function which unconditionally uses the
``ungettext`` API on the translations object."""

if PY3: # pragma: no cover
_gettext = translations.ngettext
else: # pragma: no cover
_gettext = translations.ungettext
_gettext = translations.ngettext

if context:
# Workaround for http://bugs.python.org/issue2504?
Expand All @@ -358,10 +354,7 @@ def dungettext_policy(translations, singular, plural, n, domain, context):
if getattr(translations, 'dungettext', None) is not None:
translated = translations.dungettext(domain, msgid, plural, n)
else:
if PY3: # pragma: no cover
_gettext = translations.ngettext
else: # pragma: no cover
_gettext = translations.ungettext
_gettext = translations.ngettext

translated = _gettext(msgid, plural, n)
return singular if translated == msgid else translated
Expand Down Expand Up @@ -400,9 +393,11 @@ def pluralizer(singular, plural, n, domain=None, mapping=None):
policy = dungettext_policy
if translations is None:
translations = NullTranslations()
def pluralizer(singular, plural, n, domain=None, mapping=None, context=None):
def pluralizer(
singular, plural, n, domain=None, mapping=None, context=None
):
""" Pluralize this object """
translated = text_type(
translated = str(
policy(translations, singular, plural, n, domain, context))
if translated and '$' in translated and mapping:
return TranslationString(translated, mapping=mapping).interpolate()
Expand Down
18 changes: 0 additions & 18 deletions translationstring/compat.py

This file was deleted.

72 changes: 35 additions & 37 deletions translationstring/tests/test__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import unittest
from translationstring import text_type
from translationstring.compat import u

class TestTranslationString(unittest.TestCase):

Expand All @@ -14,7 +12,7 @@ def _makeOne(self, msgid, **kw):

def test_is_text_type_subclass(self):
inst = self._makeOne('msgid')
self.assertTrue(isinstance(inst, text_type))
self.assertTrue(isinstance(inst, str))

def test_msgid_is_translation_string(self):
another = self._makeOne('msgid', domain='domain', default='default',
Expand Down Expand Up @@ -77,56 +75,56 @@ def test_interpolate_substitution(self):
inst = self._makeOne('This is $name version ${version}.',
mapping=mapping)
result = inst.interpolate()
self.assertEqual(result, u('This is Zope version 3.'))
self.assertEqual(result, u'This is Zope version 3.')

def test_interpolate_subsitution_more_than_once(self):
mapping = {"name": "Zope", "version": 3}
inst = self._makeOne(
u("This is $name version ${version}. ${name} $version!"),
u"This is $name version ${version}. ${name} $version!",
mapping=mapping)
result = inst.interpolate()
self.assertEqual(result, u('This is Zope version 3. Zope 3!'))
self.assertEqual(result, u'This is Zope version 3. Zope 3!')

def test_interpolate_double_dollar_escape(self):
mapping = {"name": "Zope", "version": 3}
inst = self._makeOne('$$name', mapping=mapping)
result = inst.interpolate()
self.assertEqual(result, u('$$name'))
self.assertEqual(result, u'$$name')

def test_interpolate_missing_not_interpolated(self):
mapping = {"name": "Zope", "version": 3}
inst = self._makeOne(
u("This is $name $version. $unknown $$name $${version}."),
u"This is $name $version. $unknown $$name $${version}.",
mapping=mapping)
result = inst.interpolate()
self.assertEqual(result,
u('This is Zope 3. $unknown $$name $${version}.'))
u'This is Zope 3. $unknown $$name $${version}.')

def test_interpolate_missing_mapping(self):
inst = self._makeOne(u("This is ${name}"))
inst = self._makeOne(u"This is ${name}")
result = inst.interpolate()
self.assertEqual(result, u('This is ${name}'))
self.assertEqual(result, u'This is ${name}')

def test_interpolate_passed_translated(self):
mapping = {"name": "Zope", "version": 3}
inst = self._makeOne(u("This is ${name}"), mapping = mapping)
inst = self._makeOne(u"This is ${name}", mapping = mapping)
result = inst.interpolate('That is ${name}')
self.assertEqual(result, u('That is Zope'))
self.assertEqual(result, u'That is Zope')

def test___reduce__(self):
klass = self._getTargetClass()
inst = self._makeOne('msgid', default='default', domain='domain',
mapping='mapping')
result = inst.__reduce__()
self.assertEqual(result, (klass, (u('msgid'), 'domain', u('default'),
self.assertEqual(result, (klass, (u'msgid', 'domain', u'default',
'mapping', None)))

def test___getstate__(self):
inst = self._makeOne('msgid', default='default', domain='domain',
mapping='mapping')
result = inst.__getstate__()
self.assertEqual(result,
(u('msgid'), 'domain', u('default'), 'mapping', None))
(u'msgid', 'domain', u'default', 'mapping', None))

class TestTranslationStringFactory(unittest.TestCase):

Expand Down Expand Up @@ -212,10 +210,10 @@ def test_msgid_translationstring_translator_is_None(self):
self.assertEqual(result, 'interpolated')

def test_msgid_text_type_translator_is_None(self):
msgid = u('foo')
msgid = u'foo'
translate = self._makeOne(None)
result = translate(msgid)
self.assertEqual(result, u('foo'))
self.assertEqual(result, u'foo')

def test_msgid_translationstring_translator_is_not_None(self):
msgid = DummyTranslationString()
Expand Down Expand Up @@ -324,15 +322,15 @@ def test_it(self):

def test_msgctxt(self):
translations = DummyTranslations('result')
result = self._callFUT(translations, u('p\xf8f'), None, 'button')
self.assertEqual(translations.params, (u('button\x04p\xf8f'),))
result = self._callFUT(translations, u'p\xf8f', None, 'button')
self.assertEqual(translations.params, (u'button\x04p\xf8f',))
self.assertEqual(result, 'result')

def test_msgctxt_no_translation_found(self):
input = u('p\xf8f')
input = u'p\xf8f'
translations = DummyTranslations(input)
result = self._callFUT(translations, input, None, 'button')
self.assertEqual(result, u('p\xf8f'))
self.assertEqual(result, u'p\xf8f')

class Test_dugettext_policy(unittest.TestCase):

Expand Down Expand Up @@ -377,25 +375,25 @@ def test_it_translations_has_no_dugettext(self):

def test_msgctxt_from_tstring(self):
translations = DummyTranslations('result')
tstring = DummyTranslationString(u('p\xf8f'), context='button')
tstring = DummyTranslationString(u'p\xf8f', context='button')
result = self._callFUT(translations, tstring, None)
self.assertEqual(translations.params,
('messages', u('button\x04p\xf8f'),))
('messages', u'button\x04p\xf8f',))
self.assertEqual(result, 'result')

def test_msgctxt_override(self):
translations = DummyTranslations('result')
tstring = DummyTranslationString(u('p\xf8f'), context='other')
tstring = DummyTranslationString(u'p\xf8f', context='other')
result = self._callFUT(translations, tstring, None, context='button')
self.assertEqual(translations.params,
('messages', u('button\x04p\xf8f'),))
('messages', u'button\x04p\xf8f',))
self.assertEqual(result, 'result')

def test_msgctxt_no_translation_found(self):
translations = DummyTranslations(u('button\x04p\xf8f'))
tstring = DummyTranslationString(u('p\xf8f'), context='button')
translations = DummyTranslations(u'button\x04p\xf8f')
tstring = DummyTranslationString(u'p\xf8f', context='button')
result = self._callFUT(translations, tstring, None)
self.assertEqual(result, u('p\xf8f'))
self.assertEqual(result, u'p\xf8f')

class Test_ungettext_policy(unittest.TestCase):

Expand All @@ -413,18 +411,18 @@ def test_it(self):
def test_msgctxt(self):
translations = DummyTranslations('result')
result = self._callFUT(
translations, u('p\xf8f'), 'plural', 1, context='button')
translations, u'p\xf8f', 'plural', 1, context='button')
self.assertEqual(translations.params,
(u('button\x04p\xf8f'), 'plural', 1))
(u'button\x04p\xf8f', 'plural', 1))
self.assertEqual(result, 'result')

def test_msgctxt_no_translation(self):
translations = DummyTranslations(u('button\x04p\xf8f'))
translations = DummyTranslations(u'button\x04p\xf8f')
result = self._callFUT(
translations, u('p\xf8f'), 'plural', 1, context='button')
translations, u'p\xf8f', 'plural', 1, context='button')
self.assertEqual(translations.params,
(u('button\x04p\xf8f'), 'plural', 1))
self.assertEqual(result, u('p\xf8f'))
(u'button\x04p\xf8f', 'plural', 1))
self.assertEqual(result, u'p\xf8f')

class Test_dungettext_policy(unittest.TestCase):

Expand Down Expand Up @@ -490,12 +488,12 @@ def dungettext(self, domain, singular, plural, n): # pragma: no cover
self.asked_domain = domain
return self.result

class DummyTranslationString(text_type):
class DummyTranslationString(str):

def __new__(cls, msgid='', domain=None, default=None, mapping=None,
context=None):
self = text_type.__new__(cls, msgid)
text_type.__init__(self, msgid)
self = str.__new__(cls, msgid)
str.__init__(self, msgid)
self.domain = domain
self.context = context
self.mapping = mapping
Expand Down
Loading

0 comments on commit e7b8115

Please sign in to comment.