[Checkins] SVN: zope.i18n/trunk/ Feature: Recursive translations of message strings with mappings

Christian Zagrodnick cz at gocept.com
Sun Apr 20 13:22:50 EDT 2008


Log message for revision 85508:
  Feature: Recursive translations of message strings with mappings
  (https://bugs.launchpad.net/zope3/+bug/210177), thanks to Hermann Himmelbauer
  for the inital patch.
  
  

Changed:
  U   zope.i18n/trunk/CHANGES.txt
  U   zope.i18n/trunk/src/zope/i18n/interfaces/__init__.py
  U   zope.i18n/trunk/src/zope/i18n/tests/test_translationdomain.py
  U   zope.i18n/trunk/src/zope/i18n/translationdomain.py

-=-
Modified: zope.i18n/trunk/CHANGES.txt
===================================================================
--- zope.i18n/trunk/CHANGES.txt	2008-04-20 17:15:56 UTC (rev 85507)
+++ zope.i18n/trunk/CHANGES.txt	2008-04-20 17:22:49 UTC (rev 85508)
@@ -2,6 +2,13 @@
 CHANGES
 =======
 
+3.4.1 (unreleased)
+------------------
+
+- Feature: Recursive translations of message strings with mappings
+  (https://bugs.launchpad.net/zope3/+bug/210177), thanks to Hermann
+  Himmelbauer for the inital patch.
+
 3.4.0 (2007-10-02)
 ------------------
 

Modified: zope.i18n/trunk/src/zope/i18n/interfaces/__init__.py
===================================================================
--- zope.i18n/trunk/src/zope/i18n/interfaces/__init__.py	2008-04-20 17:15:56 UTC (rev 85507)
+++ zope.i18n/trunk/src/zope/i18n/interfaces/__init__.py	2008-04-20 17:22:49 UTC (rev 85508)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# Copyright (c) 2001-2008 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -143,7 +143,10 @@
         However, the method does a little more than a vanilla translation.
         The method also looks for a possible language to translate to.
         After a translation it also replaces any $name variable variables
-        inside the post-translation string.
+        inside the post-translation string with data from `mapping`. If a
+        value of `mapping` is a Message it is also translated before
+        interpolation.
+
         """
 
 class IFallbackTranslationDomainFactory(Interface):

Modified: zope.i18n/trunk/src/zope/i18n/tests/test_translationdomain.py
===================================================================
--- zope.i18n/trunk/src/zope/i18n/tests/test_translationdomain.py	2008-04-20 17:15:56 UTC (rev 85507)
+++ zope.i18n/trunk/src/zope/i18n/tests/test_translationdomain.py	2008-04-20 17:22:49 UTC (rev 85508)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# Copyright (c) 2001-2008 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -92,8 +92,42 @@
                         mapping={'that': 'THAT'})
         self.assertEqual(
             translate(msgid, target_language='en', default="default",
-                         mapping={"that": "that"}), "this THAT the other")
+                      mapping={"that": "that"}), "this THAT the other")
 
+    def testMessageIDRecursiveTranslate(self):
+        factory = MessageFactory('default')
+        translate = self._domain.translate
+        msgid_sub1 = factory(u'44-not-there', '${blue}',
+                            mapping = {'blue': 'BLUE'})
+        msgid_sub2 = factory(u'45-not-there', '${yellow}',
+                            mapping = {'yellow': 'YELLOW'})
+        mapping = {'color1': msgid_sub1,
+                   'color2': msgid_sub2}
+        msgid = factory(u'46-not-there', 'Color: ${color1}/${color2}',
+                        mapping=mapping)
+        self.assertEqual(
+            translate(msgid, target_language='en', default="default"),
+            "Color: BLUE/YELLOW")
+        # The recursive translation must not change the mappings
+        self.assertEqual(msgid.mapping, {'color1': msgid_sub1,
+                                         'color2': msgid_sub2})
+        # A circular reference should not lead to crashes
+        msgid1 = factory(u'47-not-there', 'Message 1 and $msg2',
+                         mapping = {})
+        msgid2 = factory(u'48-not-there', 'Message 2 and $msg1',
+                         mapping = {})
+        msgid1.mapping['msg2'] = msgid2
+        msgid2.mapping['msg1'] = msgid1
+        self.assertRaises(RuntimeError,
+                          translate, msgid1, None, None, 'en',"default")
+        # Recusrive translations also work if the original message id wasn't a
+        # message id but a unicode with a directly passed mapping
+        self.assertEqual("Color: BLUE/YELLOW",
+                         translate(u'Color: ${color1}/${color2}', mapping=mapping,
+                                   target_language='en'))
+
+
+
     def testMessageIDTranslateForDifferentDomain(self):
         domain = TranslationDomain('other')
         path = testdir()

Modified: zope.i18n/trunk/src/zope/i18n/translationdomain.py
===================================================================
--- zope.i18n/trunk/src/zope/i18n/translationdomain.py	2008-04-20 17:15:56 UTC (rev 85507)
+++ zope.i18n/trunk/src/zope/i18n/translationdomain.py	2008-04-20 17:22:49 UTC (rev 85508)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# Copyright (c) 2001-2008 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -15,7 +15,7 @@
 
 $Id$
 """
-from zope.component import getUtility
+import zope.component
 from zope.i18nmessageid import Message
 from zope.i18n import interpolate
 from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
@@ -64,7 +64,6 @@
     def translate(self, msgid, mapping=None, context=None,
                   target_language=None, default=None):
         """See zope.i18n.interfaces.ITranslationDomain"""
-
         # if the msgid is empty, let's save a lot of calculations and return
         # an empty string.
         if msgid == u'':
@@ -73,17 +72,41 @@
         if target_language is None and context is not None:
             langs = self._catalogs.keys()
             # invoke local or global unnamed 'INegotiator' utilities
-            negotiator = getUtility(INegotiator)
+            negotiator = zope.component.getUtility(INegotiator)
             # try to determine target language from negotiator utility
             target_language = negotiator.getLanguage(langs, context)
 
+        return self._recursive_translate(
+            msgid, mapping, target_language, default)
+
+    def _recursive_translate(self, msgid, mapping, target_language, default,
+                             seen=None):
+        """Recurivly translate msg."""
         # MessageID attributes override arguments
         if isinstance(msgid, Message):
             if msgid.domain != self.domain:
-                util = getUtility(ITranslationDomain, msgid.domain)
+                util = zope.component.getUtility(
+                    ITranslationDomain, msgid.domain)
+            default = msgid.default
             mapping = msgid.mapping
-            default = msgid.default
 
+        # 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)
+            mapping = mapping.copy()
+            for key, value in mapping.items():
+                if isinstance(value, Message):
+                    # XXX why isn't there an IMessage interface?
+                    if value in seen:
+                        raise RuntimeError(  # XXX ValueError
+                            "Circular Reference in Mappings detected")
+                    mapping[key]=self._recursive_translate(
+                        value, mapping, target_language,
+                        default, seen)
+
         if default is None:
             default = unicode(msgid)
 



More information about the Checkins mailing list