Index: tests/test_translationdomain.py =================================================================== --- tests/test_translationdomain.py (Revision 85056) +++ tests/test_translationdomain.py (Arbeitskopie) @@ -92,8 +92,36 @@ 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") + + def testMessageIDTranslateForDifferentDomain(self): domain = TranslationDomain('other') path = testdir() Index: translationdomain.py =================================================================== --- translationdomain.py (Revision 85056) +++ translationdomain.py (Arbeitskopie) @@ -62,7 +62,7 @@ self._fallbacks = fallbacks def translate(self, msgid, mapping=None, context=None, - target_language=None, default=None): + target_language=None, default=None, to_translate = None): """See zope.i18n.interfaces.ITranslationDomain""" # if the msgid is empty, let's save a lot of calculations and return @@ -79,10 +79,27 @@ # MessageID attributes override arguments if isinstance(msgid, Message): + if to_translate == None: + to_translate = [] + to_translate.append(msgid) if msgid.domain != self.domain: util = getUtility(ITranslationDomain, msgid.domain) + default = msgid.default + # Recursively translate mappings, if they are translatable mapping = msgid.mapping - default = msgid.default + if mapping is not None and ( + Message in [type(m) for m in mapping.values()]): + mapping = mapping.copy() + for key, value in mapping.items(): + if isinstance(value, Message): + if value in to_translate: + raise RuntimeError( + "Circular Reference in Mappings detected") + else: + to_translate.append(value) + mapping[key]=self.translate(value, mapping, context, + target_language, + default, to_translate) if default is None: default = unicode(msgid)