summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSouheil CHELFOUH <trollfot@gmail.com>2018-09-06 12:59:53 +0200
committerSouheil CHELFOUH <trollfot@gmail.com>2018-09-06 12:59:53 +0200
commitcff01f73c0c9b30b2b32bb319deac4cbe5d1c6a6 (patch)
tree43a1fef79509fb4f79b2cec4396bed8c87aa6067
parentdafecabd80e3b9cda4fd96751479853361bb14be (diff)
downloadzope-i18n-cff01f73c0c9b30b2b32bb319deac4cbe5d1c6a6.tar.gz
Added plural capabilities to the different translate methods. It echoes the changes made in the zope.i18nmessageid. This needs heavy testing. Also started to clean up the code to match pep8 recommandations.
-rw-r--r--src/zope/i18n/__init__.py23
-rw-r--r--src/zope/i18n/format.py8
-rw-r--r--src/zope/i18n/gettextmessagecatalog.py6
-rw-r--r--src/zope/i18n/interfaces/__init__.py13
-rw-r--r--src/zope/i18n/negotiator.py4
-rw-r--r--src/zope/i18n/simpletranslationdomain.py8
-rw-r--r--src/zope/i18n/tests/pl-default.po2
-rw-r--r--src/zope/i18n/tests/test_plurals.py6
-rw-r--r--src/zope/i18n/translationdomain.py46
-rw-r--r--src/zope/i18n/zcml.py2
10 files changed, 84 insertions, 34 deletions
diff --git a/src/zope/i18n/__init__.py b/src/zope/i18n/__init__.py
index 3088074..6abf973 100644
--- a/src/zope/i18n/__init__.py
+++ b/src/zope/i18n/__init__.py
@@ -39,6 +39,7 @@ class _FallbackNegotiator(object):
def getLanguage(self, _allowed, _context):
return None
+
_fallback_negotiator = _FallbackNegotiator()
@@ -79,8 +80,10 @@ def negotiate(context):
negotiator = queryUtility(INegotiator, default=_fallback_negotiator)
return negotiator.getLanguage(ALLOWED_LANGUAGES, context)
+
def translate(msgid, domain=None, mapping=None, context=None,
- target_language=None, default=None):
+ target_language=None, default=None, msgid_plural=None,
+ default_plural=None, number=None):
"""Translate text.
First setup some test components:
@@ -160,6 +163,9 @@ def translate(msgid, domain=None, mapping=None, context=None,
domain = msgid.domain
default = msgid.default
mapping = msgid.mapping
+ msgid_plural = msgid.msgid_plural
+ default_plural = msgid.default_plural
+ number = msgid.number
if default is None:
default = text_type(msgid)
@@ -181,7 +187,11 @@ def translate(msgid, domain=None, mapping=None, context=None,
if target_language is None and context is not None:
target_language = negotiate(context)
- return util.translate(msgid, mapping, context, target_language, default)
+ print(util.translate)
+ return util.translate(
+ msgid, mapping, context, target_language, default,
+ msgid_plural, default_plural, number)
+
def interpolate(text, mapping=None):
"""Insert the data passed from mapping into the text.
@@ -197,15 +207,16 @@ def interpolate(text, mapping=None):
Interpolation variables can be used more than once in the text:
- >>> print(interpolate(u"This is $name version ${version}. ${name} $version!",
- ... mapping))
+ >>> print(interpolate(
+ ... u"This is $name version ${version}. ${name} $version!", mapping))
This is Zope version 3. Zope 3!
In case if the variable wasn't found in the mapping or '$$' form
was used no substitution will happens:
- >>> print(interpolate(u"This is $name $version. $unknown $$name $${version}.",
- ... mapping))
+ >>> print(interpolate(
+ ... u"This is $name $version. $unknown $$name $${version}.", mapping))
+
This is Zope 3. $unknown $$name $${version}.
>>> print(interpolate(u"This is ${name}"))
diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py
index ee2e239..96f5ef3 100644
--- a/src/zope/i18n/format.py
+++ b/src/zope/i18n/format.py
@@ -33,6 +33,7 @@ try:
except NameError:
pass # Py3
+
def roundHalfUp(n):
"""Works like round() in python2.x
@@ -42,18 +43,20 @@ def roundHalfUp(n):
"""
return math.floor(n + math.copysign(0.5, n))
+
def _findFormattingCharacterInPattern(char, pattern):
return [entry for entry in pattern
if isinstance(entry, tuple) and entry[0] == char]
+
class DateTimeParseError(Exception):
"""Error is raised when parsing of datetime failed."""
+
@implementer(IDateTimeFormat)
class DateTimeFormat(object):
__doc__ = IDateTimeFormat.__doc__
-
_DATETIMECHARS = "aGyMdEDFwWhHmsSkKz"
calendar = None
@@ -486,11 +489,11 @@ class NumberFormat(object):
return text_type(text)
-
DEFAULT = 0
IN_QUOTE = 1
IN_DATETIMEFIELD = 2
+
class DateTimePatternParseError(Exception):
"""DateTime Pattern Parse Error"""
@@ -760,6 +763,7 @@ SUFFIX = 7
PADDING4 = 8
GROUPING = 9
+
class NumberPatternParseError(Exception):
"""Number Pattern Parse Error"""
diff --git a/src/zope/i18n/gettextmessagecatalog.py b/src/zope/i18n/gettextmessagecatalog.py
index 6a0dbe7..99f1951 100644
--- a/src/zope/i18n/gettextmessagecatalog.py
+++ b/src/zope/i18n/gettextmessagecatalog.py
@@ -26,7 +26,7 @@ class _KeyErrorRaisingFallback(object):
def ungettext(self, singular, plural, n):
raise KeyError(singular)
-
+
gettext = ugettext
ngettext = ungettext
@@ -91,14 +91,14 @@ class GettextMessageCatalog(object):
if self._catalog.plural(n):
return dft2
return dft1
-
+
def queryMessage(self, id, default=None):
'See IMessageCatalog'
try:
return self._gettext(id)
except KeyError:
return default
-
+
def getIdentifier(self):
'See IMessageCatalog'
return self._path_to_file
diff --git a/src/zope/i18n/interfaces/__init__.py b/src/zope/i18n/interfaces/__init__.py
index 187ba16..5935fc1 100644
--- a/src/zope/i18n/interfaces/__init__.py
+++ b/src/zope/i18n/interfaces/__init__.py
@@ -121,6 +121,10 @@ class ITranslationDomain(Interface):
target_language -- The language to translate to.
+ msgid_plural -- The id of the plural message that should be translated.
+
+ number -- The number of items linked to the plural of the message.
+
context -- An object that provides contextual information for
determining client language preferences. It must implement
or have an adapter that implements IUserPreferredLanguages.
@@ -136,7 +140,8 @@ class ITranslationDomain(Interface):
required=True)
def translate(msgid, mapping=None, context=None, target_language=None,
- default=None):
+ default=None, msgid_plural=None, default_plural=None,
+ number=None):
"""Return the translation for the message referred to by msgid.
Return the default if no translation is found.
@@ -150,6 +155,7 @@ class ITranslationDomain(Interface):
"""
+
class IFallbackTranslationDomainFactory(Interface):
"""Factory for creating fallback translation domains
@@ -161,6 +167,7 @@ class IFallbackTranslationDomainFactory(Interface):
"""Return a fallback translation domain for the given domain id.
"""
+
class ITranslator(Interface):
"""A collaborative object which contains the domain, context, and locale.
@@ -168,7 +175,8 @@ class ITranslator(Interface):
the domain, context, and target language.
"""
- def translate(msgid, mapping=None, default=None):
+ def translate(msgid, mapping=None, default=None,
+ msgid_plural=None, default_plural=None, number=None):
"""Translate the source msgid using the given mapping.
See ITranslationService for details.
@@ -215,6 +223,7 @@ class IUserPreferredLanguages(Interface):
languages first.
"""
+
class IModifiableUserPreferredLanguages(IUserPreferredLanguages):
def setPreferredLanguages(languages):
diff --git a/src/zope/i18n/negotiator.py b/src/zope/i18n/negotiator.py
index 66417c0..3c5fa17 100644
--- a/src/zope/i18n/negotiator.py
+++ b/src/zope/i18n/negotiator.py
@@ -14,16 +14,17 @@
"""Language Negotiator
"""
from zope.interface import implementer
-
from zope.i18n.interfaces import INegotiator
from zope.i18n.interfaces import IUserPreferredLanguages
+
def normalize_lang(lang):
lang = lang.strip().lower()
lang = lang.replace('_', '-')
lang = lang.replace(' ', '')
return lang
+
def normalize_langs(langs):
# Make a mapping from normalized->original so we keep can match
# the normalized lang and return the original string.
@@ -32,6 +33,7 @@ def normalize_langs(langs):
n_langs[normalize_lang(l)] = l
return n_langs
+
@implementer(INegotiator)
class Negotiator(object):
diff --git a/src/zope/i18n/simpletranslationdomain.py b/src/zope/i18n/simpletranslationdomain.py
index b50fb68..b093db8 100644
--- a/src/zope/i18n/simpletranslationdomain.py
+++ b/src/zope/i18n/simpletranslationdomain.py
@@ -18,8 +18,10 @@ from zope.component import getUtility
from zope.i18n.interfaces import ITranslationDomain, INegotiator
from zope.i18n import interpolate
+
text_type = str if bytes is not str else unicode
+
@implementer(ITranslationDomain)
class SimpleTranslationDomain(object):
"""This is the simplest implementation of the ITranslationDomain I
@@ -39,12 +41,14 @@ class SimpleTranslationDomain(object):
def __init__(self, domain, messages=None):
"""Initializes the object. No arguments are needed."""
- self.domain = domain.decode("utf-8") if isinstance(domain, bytes) else domain
+ self.domain = (
+ domain.decode("utf-8") if isinstance(domain, bytes) else domain)
self.messages = messages if messages is not None else {}
assert self.messages is not None
def translate(self, msgid, mapping=None, context=None,
- target_language=None, default=None):
+ target_language=None, default=None, msgid_plural=None,
+ default_plural=None, number=None):
'''See interface ITranslationDomain'''
# Find out what the target language should be
if target_language is None and context is not None:
diff --git a/src/zope/i18n/tests/pl-default.po b/src/zope/i18n/tests/pl-default.po
index 26feeaa..3951a9c 100644
--- a/src/zope/i18n/tests/pl-default.po
+++ b/src/zope/i18n/tests/pl-default.po
@@ -19,4 +19,4 @@ msgid "There is one file."
msgid_plural "There are %d files."
msgstr[0] "Istnieje %d plik."
msgstr[1] "Istnieją %d pliki."
-msgstr[2] "Istnieją %d pliko'w."
+msgstr[2] "Istnieją %d plików."
diff --git a/src/zope/i18n/tests/test_plurals.py b/src/zope/i18n/tests/test_plurals.py
index ce97a72..a0ca9bf 100644
--- a/src/zope/i18n/tests/test_plurals.py
+++ b/src/zope/i18n/tests/test_plurals.py
@@ -77,7 +77,7 @@ class TestPlurals(unittest.TestCase):
self.assertEqual(catalog.getPluralMessage(
'There is one file.', 'There are %d files.', 0),
- "Istnieją 0 pliko'w.")
+ "Istnieją 0 plików.")
self.assertEqual(catalog.getPluralMessage(
'There is one file.', 'There are %d files.', 1),
@@ -89,7 +89,7 @@ class TestPlurals(unittest.TestCase):
self.assertEqual(catalog.getPluralMessage(
'There is one file.', 'There are %d files.', 17),
- "Istnieją 17 pliko'w.")
+ "Istnieją 17 plików.")
self.assertEqual(catalog.getPluralMessage(
'There is one file.', 'There are %d files.', 23),
@@ -97,7 +97,7 @@ class TestPlurals(unittest.TestCase):
self.assertEqual(catalog.getPluralMessage(
'There is one file.', 'There are %d files.', 28),
- "Istnieją 28 pliko'w.")
+ "Istnieją 28 plików.")
def test_suite():
diff --git a/src/zope/i18n/translationdomain.py b/src/zope/i18n/translationdomain.py
index 684fa9b..c7f064e 100644
--- a/src/zope/i18n/translationdomain.py
+++ b/src/zope/i18n/translationdomain.py
@@ -34,11 +34,13 @@ LANGUAGE_FALLBACKS = ['en']
text_type = str if bytes is not str else unicode
+
@zope.interface.implementer(ITranslationDomain)
class TranslationDomain(object):
def __init__(self, domain, fallbacks=None):
- self.domain = domain.decode("utf-8") if isinstance(domain, bytes) else domain
+ self.domain = (
+ domain.decode("utf-8") if isinstance(domain, bytes) else domain)
# _catalogs maps (language, domain) to IMessageCatalog instances
self._catalogs = {}
# _data maps IMessageCatalog.getIdentifier() to IMessageCatalog
@@ -63,7 +65,8 @@ class TranslationDomain(object):
self._fallbacks = fallbacks
def translate(self, msgid, mapping=None, context=None,
- target_language=None, default=None):
+ target_language=None, default=None,
+ msgid_plural=None, default_plural=None, number=None):
"""See zope.i18n.interfaces.ITranslationDomain"""
# if the msgid is empty, let's save a lot of calculations and return
# an empty string.
@@ -78,37 +81,43 @@ class TranslationDomain(object):
target_language = negotiator.getLanguage(langs, context)
return self._recursive_translate(
- msgid, mapping, target_language, default, context)
+ msgid, mapping, target_language, default, context,
+ msgid_plural, default_plural, number)
def _recursive_translate(self, msgid, mapping, target_language, default,
- context, seen=None):
+ context, msgid_plural, default_plural, number,
+ seen=None):
"""Recursively translate msg."""
# MessageID attributes override arguments
if isinstance(msgid, Message):
if msgid.domain != self.domain:
- return translate(msgid, msgid.domain, mapping, context,
- target_language, default)
+ return translate(
+ msgid, msgid.domain, mapping, context, target_language,
+ default, msgid_plural, default_plural, number)
default = msgid.default
mapping = msgid.mapping
+ msgid_plural = msgid.msgid_plural
+ default_plural = msgid.default_plural
+ number = msgid.number
# Recursively translate mappings, if they are translatable
if (mapping is not None
and Message in (type(m) for m in mapping.values())):
if seen is None:
seen = set()
- seen.add(msgid)
+ seen.add((msgid, msgid_plural))
mapping = mapping.copy()
for key, value in mapping.items():
if isinstance(value, Message):
# TODO Why isn't there an IMessage interface?
# https://bugs.launchpad.net/zope3/+bug/220122
- if value in seen:
+ if (value, value.msgid_plural) in seen:
raise ValueError(
"Circular reference in mappings detected: %s" %
value)
mapping[key] = self._recursive_translate(
- value, mapping, target_language,
- default, context, seen)
+ value, mapping, target_language, default, context,
+ msgid_plural, default_plural, number, seen)
if default is None:
default = text_type(msgid)
@@ -128,12 +137,23 @@ class TranslationDomain(object):
# single catalog. More importantly, it is extremely helpful
# when testing and the test language is used, because it
# allows the test language to get the default.
- text = self._data[catalog_names[0]].queryMessage(
- msgid, default)
+ if msgid_plural is not None:
+ # This is a plural
+ text = self._data[catalog_names[0]].queryPluralMessage(
+ msgid, msgid_plural, number, default, default_plural)
+ else:
+ text = self._data[catalog_names[0]].queryMessage(
+ msgid, default)
else:
for name in catalog_names:
catalog = self._data[name]
- s = catalog.queryMessage(msgid)
+ if msgid_plural is not None:
+ # This is a plural
+ s = catalog.queryPluralMessage(
+ msgid, msgid_plural, number,
+ default, default_plural)
+ else:
+ s = catalog.queryMessage(msgid)
if s is not None:
text = s
break
diff --git a/src/zope/i18n/zcml.py b/src/zope/i18n/zcml.py
index 39a4a29..4df9ccb 100644
--- a/src/zope/i18n/zcml.py
+++ b/src/zope/i18n/zcml.py
@@ -100,7 +100,7 @@ def registerTranslations(_context, directory, domain='*'):
loaded = True
domain_file = os.path.basename(domain_path)
name = domain_file[:-3]
- if not name in domains:
+ if name not in domains:
domains[name] = {}
domains[name][language] = domain_path
if loaded: