diff options
author | Souheil CHELFOUH <trollfot@gmail.com> | 2018-09-06 12:59:53 +0200 |
---|---|---|
committer | Souheil CHELFOUH <trollfot@gmail.com> | 2018-09-06 12:59:53 +0200 |
commit | cff01f73c0c9b30b2b32bb319deac4cbe5d1c6a6 (patch) | |
tree | 43a1fef79509fb4f79b2cec4396bed8c87aa6067 | |
parent | dafecabd80e3b9cda4fd96751479853361bb14be (diff) | |
download | zope-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__.py | 23 | ||||
-rw-r--r-- | src/zope/i18n/format.py | 8 | ||||
-rw-r--r-- | src/zope/i18n/gettextmessagecatalog.py | 6 | ||||
-rw-r--r-- | src/zope/i18n/interfaces/__init__.py | 13 | ||||
-rw-r--r-- | src/zope/i18n/negotiator.py | 4 | ||||
-rw-r--r-- | src/zope/i18n/simpletranslationdomain.py | 8 | ||||
-rw-r--r-- | src/zope/i18n/tests/pl-default.po | 2 | ||||
-rw-r--r-- | src/zope/i18n/tests/test_plurals.py | 6 | ||||
-rw-r--r-- | src/zope/i18n/translationdomain.py | 46 | ||||
-rw-r--r-- | src/zope/i18n/zcml.py | 2 |
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: |