summaryrefslogtreecommitdiff
path: root/src/zope/i18n
diff options
context:
space:
mode:
Diffstat (limited to 'src/zope/i18n')
-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: