diff options
Diffstat (limited to 'src/zope/i18n')
-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: |