Edgewall Software

Ticket #284: genshi_i18n_custom_format.patch

File genshi_i18n_custom_format.patch, 36.4 KB (added by palgarvio, 3 years ago)
  • genshi/filters/i18n.py

    diff --git a/genshi/filters/i18n.py b/genshi/filters/i18n.py
    a b  
    1919""" 
    2020 
    2121from gettext import NullTranslations 
    22 import os 
    2322import re 
    2423from types import FunctionType 
    2524 
     25try: 
     26    from babel.messages.catalog import PYTHON_FORMAT 
     27except ImportError: 
     28    PYTHON_FORMAT = None 
     29 
    2630from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \ 
    2731                        END_NS, XML_NAMESPACE, _ensure, StreamEventKind 
    2832from genshi.template.eval import _ast 
     
    6872    >>> translator = Translator() 
    6973    >>> translator.setup(tmpl) 
    7074    >>> list(translator.extract(tmpl.stream)) 
    71     [(2, None, u'Foo', [u'As in Foo Bar'])] 
     75    [(2, None, u'Foo', {'comments': [u'As in Foo Bar']})] 
    7276    >>> 
    7377    """ 
    7478 
     
    9599 
    96100    >>> translator = Translator() 
    97101    >>> translator.setup(tmpl) 
    98     >>> list(translator.extract(tmpl.stream)) 
    99     [(2, None, u'[1:Foo]\n    [2:Bar]', []), (6, None, u'Foo [1:bar]!', [])] 
     102    >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 
     103    [(2, None, u'[1:Foo]\n    [2:Bar]', {'flags': ['genshi-format'], 
     104                                         'comments': []}), 
     105     (6, None, u'Foo [1:bar]!', {'flags': ['genshi-format'], 
     106                                 'comments': []})] 
    100107    >>> print tmpl.generate().render() 
    101108    <html> 
    102109      <div><p>Foo</p> 
     
    113120    ... </html>''') 
    114121    >>> translator.setup(tmpl) 
    115122    >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 
    116     [(2, None, u'[1:First Name: %(fname)s]\n    [2:Last Name: %(lname)s]', []), 
    117     (6, None, u'Foo [1:bar]!', [])] 
     123    [(2, None, u'[1:First Name: %(fname)s]\n    [2:Last Name: %(lname)s]', 
     124     {'flags': ['genshi-format', 'python-format'], 'comments': []}), 
     125    (6, None, u'Foo [1:bar]!', {'flags': ['genshi-format'], 'comments': []})] 
    118126    >>> 
    119127    >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 
    120128    ...   <div i18n:msg="fname, lname"> 
     
    193201            msgbuf.append(*previous) 
    194202 
    195203        if msgbuf.valid: 
    196             yield (None, msgbuf.format(), 
    197                    filter(None, [ctxt.get('_i18n.comment')])) 
     204            message = msgbuf.format() 
     205            info = { 
     206                'comments': filter(None, [ctxt.get('_i18n.comment')]) 
     207            } 
     208            if len(parse_msg(message)) > 1: 
     209                info['flags'] = ['genshi-format'] 
     210            if PYTHON_FORMAT and bool(filter(None, 
     211                                             [PYTHON_FORMAT.search(id) for id in 
     212                                              [message] if id])): 
     213                info.setdefault('flags', []).append('python-format') 
     214            yield None, message, info 
    198215 
    199216 
    200217class InnerChooseDirective(I18NDirective): 
     
    264281    >>> translator.setup(tmpl) 
    265282    >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 
    266283    [(2, 'ngettext', (u'There is %(num)s coin', 
    267                       u'There are %(num)s coins'), [])] 
     284                      u'There are %(num)s coins'), 
     285                      {'flags': ['python-format'], 'comments': []})] 
    268286    >>> 
    269287    >>> tmpl = MarkupTemplate('''\ 
    270288        <html xmlns:i18n="http://genshi.edgewall.org/i18n"> 
     
    299317    >>> translator.setup(tmpl) 
    300318    >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 
    301319    [(2, 'ngettext', (u'There is %(num)s coin', 
    302                       u'There are %(num)s coins'), [])] 
     320                      u'There are %(num)s coins'), 
     321                      {'flags': ['python-format'], 'comments': []})] 
    303322    >>> 
    304323    """ 
    305324 
     
    410429                singular_msgbuf.append(kind, event, pos) 
    411430                plural_msgbuf.append(kind, event, pos) 
    412431        if singular_msgbuf.valid and plural_msgbuf.valid: 
    413             yield 'ngettext', \ 
    414                 (singular_msgbuf.format(), plural_msgbuf.format()), \ 
    415                 filter(None, [ctxt.get('_i18n.comment')]) 
     432            singular = singular_msgbuf.format() 
     433            plural = plural_msgbuf.format() 
     434            info = {'comments': filter(None, [ctxt.get('_i18n.comment')])} 
     435            if (len(parse_msg(singular)) or len(parse_msg(plural))) > 1: 
     436                info.setdefault('flags', []).append('genshi-format') 
     437            if PYTHON_FORMAT and bool(filter(None, 
     438                                             [PYTHON_FORMAT.search(id) for id in 
     439                                              [singular, plural] if id])): 
     440                info.setdefault('flags', []).append('python-format') 
     441            yield 'ngettext', (singular, plural), info 
    416442 
    417443 
    418444class DomainDirective(I18NDirective): 
     
    728754        """Extract localizable strings from the given template stream. 
    729755 
    730756        For every string found, this function yields a ``(lineno, function, 
    731         message, comments)`` tuple, where: 
     757        message, info)`` tuple, where: 
    732758 
    733759        * ``lineno`` is the number of the line on which the string was found, 
    734760        * ``function`` is the name of the ``gettext`` function used (if the 
    735761          string was extracted from embedded Python code), and 
    736         *  ``message`` is the string itself (a ``unicode`` object, or a tuple 
    737            of ``unicode`` objects for functions with multiple string 
    738            arguments). 
    739         *  ``comments`` is a list of comments related to the message, extracted 
    740            from ``i18n:comment`` attributes found in the markup 
     762        * ``message`` is the string itself (a ``unicode`` object, or a tuple 
     763          of ``unicode`` objects for functions with multiple string 
     764          arguments). 
     765        * `info`` is a dictionary that contains additional information about the 
     766          message being extracted like: 
     767              `comments`: A list of comments embedded in the source code; 
     768                          With genshi you define the comments to be extracted 
     769                          from the ``i18n:comment`` attributes found in the 
     770                          markup; 
     771              `context`: A string containing the message context; Not yet used 
     772                         in genshi. 
     773               `flags`: A list of flags used on the message, ie, 
     774                       ``python-format``, etc, or even a custom flag, 
     775                       ``genshi-format`` for genshi nested markup; 
    741776 
    742777        >>> from genshi.template import MarkupTemplate 
    743778        >>> 
     
    752787        ...   </body> 
    753788        ... </html>''', filename='example.html') 
    754789        >>> 
    755         >>> for line, func, msg, comments in Translator().extract(tmpl.stream): 
    756         ...    print "%d, %r, %r" % (line, func, msg) 
    757         3, None, u'Example' 
    758         6, None, u'Example' 
    759         7, '_', u'Hello, %(name)s' 
    760         8, 'ngettext', (u'You have %d item', u'You have %d items', None) 
     790        >>> for line, func, msg, info in Translator().extract(tmpl.stream): 
     791        ...    print "%d, %r, %r, %r" % ( 
     792        ...             line, func,msg, info) #doctest: +NORMALIZE_WHITESPACE 
     793        3, None, u'Example', {'comments': []} 
     794        6, None, u'Example', {'comments': []} 
     795        7, '_', u'Hello, %(name)s', {'flags': ['python-format'], 'comments': []} 
     796        8, 'ngettext', (u'You have %d item', u'You have %d items', None), 
     797                                    {'flags': ['python-format'], 'comments': []} 
    761798 
    762799        :param stream: the event stream to extract strings from; can be a 
    763800                       regular stream or a template stream 
     
    807844                            text = value.strip() 
    808845                            if text: 
    809846                                # XXX: Do we need to grab i18n:comment from ctxt ??? 
    810                                 yield pos[1], None, text, [] 
     847                                yield pos[1], None, text, {'comments': []} 
    811848                    else: 
    812                         for lineno, funcname, text, comments in self.extract( 
    813                                             _ensure(value), gettext_functions, 
    814                                             search_text=False, 
    815                                             error_callback=error_callback): 
    816                             yield lineno, funcname, text, comments 
     849                        for lineno, funcname, text, info in \ 
     850                            self.extract(_ensure(value), gettext_functions, 
     851                                        search_text=False, 
     852                                        error_callback=error_callback): 
     853                            yield lineno, funcname, text, info 
    817854 
    818855                if msgbuf: 
    819856                    msgbuf.append(kind, data, pos) 
     
    822859                if not msgbuf: 
    823860                    text = data.strip() 
    824861                    if text and filter(None, [ch.isalpha() for ch in text]): 
    825                         yield pos[1], None, text, \ 
    826                                     filter(None, [ctxt.get('_i18n.comment')]) 
     862                        yield pos[1], None, text, { 
     863                            'comments': filter(None, 
     864                                               [ctxt.get('_i18n.comment')])} 
    827865                else: 
    828866                    msgbuf.append(kind, data, pos) 
    829867 
    830868            elif not skip and msgbuf and kind is END: 
    831869                msgbuf.append(kind, data, pos) 
    832870                if not msgbuf.depth and msgbuf.valid: 
    833                     yield msgbuf.lineno, None, msgbuf.format(), \ 
    834                                                   filter(None, [msgbuf.comment]) 
     871                    message = msgbuf.format() 
     872                    info = {'comments': filter(None, [msgbuf.comment])} 
     873                    if len(message) > 1: 
     874                        singular, plural = message 
     875                        if (len(parse_msg(singular)) or 
     876                                                    len(parse_msg(plural))) > 1: 
     877                            info.setdefault('flags', []).append('genshi-format') 
     878                        if PYTHON_FORMAT and bool(filter( 
     879                            None, [PYTHON_FORMAT.search(id) 
     880                                   for id in [message] if id])): 
     881                            info.setdefault('flags', []).append('python-format') 
     882                    elif len(parse_msg(message)) > 1: 
     883                        info.setdefault('flags', []).append('genshi-format') 
     884                        if PYTHON_FORMAT and bool(filter( 
     885                            None, [PYTHON_FORMAT.search(id) 
     886                                   for id in [message] if id])): 
     887                            info.setdefault('flags', []).append('python-format') 
     888                    yield msgbuf.lineno, None, message, info 
    835889                    msgbuf = None 
    836890 
    837891            elif kind is EXPR or kind is EXEC: 
    838892                if msgbuf: 
    839893                    msgbuf.append(kind, data, pos) 
    840                 for funcname, strings in extract_from_code(data, 
    841                                                            gettext_functions): 
    842                     # XXX: Do we need to grab i18n:comment from ctxt ??? 
    843                     yield pos[1], funcname, strings, [] 
     894                for funcname, strings, info in extract_from_code( 
     895                                                    data, gettext_functions): 
     896                    yield pos[1], funcname, strings, info 
    844897 
    845898            elif kind is SUB: 
    846899                directives, substream = data 
     
    860913                                search_text=search_text and not skip, 
    861914                                msgbuf=msgbuf, ctxt=ctxt, 
    862915                                error_callback=error_callback) 
    863                             for lineno, funcname, text, comments in messages: 
    864                                 yield lineno, funcname, text, comments 
     916                            for lineno, funcname, text, info in messages: 
     917                                yield lineno, funcname, text, info 
    865918                        directives.pop(idx) 
    866919                    elif not isinstance(directive, I18NDirective): 
    867920                        # Remove all other non i18n directives from the process 
     
    874927                    messages = self.extract( 
    875928                        substream, gettext_functions, 
    876929                        search_text=search_text and not skip, msgbuf=msgbuf) 
    877                     for lineno, funcname, text, comments in messages: 
    878                         yield lineno, funcname, text, comments 
     930                    for lineno, funcname, text, info in messages: 
     931                        yield lineno, funcname, text, info 
    879932 
    880933                for directive in directives: 
    881934                    if isinstance(directive, I18NDirectiveExtract): 
    882935                        messages = directive.extract( 
    883936                                substream, ctxt, error_callback=error_callback 
    884937                        ) 
    885                         for funcname, text, comments in messages: 
    886                             yield pos[1], funcname, text, comments 
     938                        for funcname, text, info in messages: 
     939                            yield pos[1], funcname, text, info 
    887940                    else: 
    888941                        messages = self.extract( 
    889942                            substream, gettext_functions, 
    890943                            search_text=search_text and not skip, msgbuf=msgbuf, 
    891944                            error_callback=error_callback) 
    892                         for lineno, funcname, text, comments in messages: 
    893                             yield lineno, funcname, text, comments 
     945                        for lineno, funcname, text, info in messages: 
     946                            yield lineno, funcname, text, info 
    894947                if comment: 
    895948                    ctxt.pop() 
    896949 
     
    11501203 
    11511204    >>> expr = Expression('_("Hello")') 
    11521205    >>> list(extract_from_code(expr, Translator.GETTEXT_FUNCTIONS)) 
    1153     [('_', u'Hello')] 
     1206    [('_', u'Hello', {'comments': []})] 
    11541207 
    11551208    >>> expr = Expression('ngettext("You have %(num)s item", ' 
    11561209    ...                            '"You have %(num)s items", num)') 
    1157     >>> list(extract_from_code(expr, Translator.GETTEXT_FUNCTIONS)) 
    1158     [('ngettext', (u'You have %(num)s item', u'You have %(num)s items', None))] 
     1210    >>> list(extract_from_code( 
     1211    ...      expr, Translator.GETTEXT_FUNCTIONS)) #doctest: +NORMALIZE_WHITESPACE 
     1212    [('ngettext', (u'You have %(num)s item', u'You have %(num)s items', None), 
     1213      {'flags': ['python-format'], 'comments': []})] 
    11591214 
    11601215    :param code: the `Code` object 
    11611216    :type code: `genshi.template.eval.Code` 
     
    11741229            [_add(arg) for arg in node.args] 
    11751230            _add(node.starargs) 
    11761231            _add(node.kwargs) 
     1232            info = {'comments': []} 
    11771233            if len(strings) == 1: 
    11781234                strings = strings[0] 
     1235                if PYTHON_FORMAT and bool(filter(None, [PYTHON_FORMAT.search(id) 
     1236                                                        for id in [strings] 
     1237                                                        if id])): 
     1238                    info.setdefault('flags', []).append('python-format') 
    11791239            else: 
    11801240                strings = tuple(strings) 
    1181             yield node.func.id, strings 
     1241                if PYTHON_FORMAT and bool(filter(None, [PYTHON_FORMAT.search(id) 
     1242                                                        for id in strings 
     1243                                                        if id])): 
     1244                    info.setdefault('flags', []).append('python-format') 
     1245            yield node.func.id, strings, info 
    11821246        elif node._fields: 
    11831247            children = [] 
    11841248            for field in node._fields: 
     
    11891253                elif isinstance(child, _ast.AST): 
    11901254                    children.append(child) 
    11911255            for child in children: 
    1192                 for funcname, strings in _walk(child): 
    1193                     yield funcname, strings 
     1256                for funcname, strings, info in _walk(child): 
     1257                    yield funcname, strings, info 
    11941258    return _walk(code.ast) 
    11951259 
    11961260 
    1197 def extract(fileobj, keywords, comment_tags, options): 
     1261def extract(fileobj, keywords, comment_tags, options, error_callback): 
    11981262    """Babel extraction method for Genshi templates. 
    11991263 
     1264    This function generates tuples of the form: 
     1265 
     1266        ``(lineno, funcname, message, info)`` 
     1267 
     1268    * `info`` is a dictionary that contains additional information about the 
     1269      message being extracted like: 
     1270          `comments`: A list of comments embedded in the source code; 
     1271                      With genshi you define the comments to be extracted 
     1272                      from the ``i18n:comment`` attributes found in the 
     1273                      markup; 
     1274          `context`: A string containing the message context; Not yet used 
     1275                     in genshi. 
     1276           `flags`: A list of flags used on the message, ie, 
     1277                   ``python-format``, etc, or even a custom flag, 
     1278                   ``genshi-format`` for genshi nested markup; 
     1279 
    12001280    :param fileobj: the file-like object the messages should be extracted from 
    12011281    :param keywords: a list of keywords (i.e. function names) that should be 
    12021282                     recognized as translation functions 
    12031283    :param comment_tags: a list of translator tags to search for and include 
    12041284                         in the results 
    12051285    :param options: a dictionary of additional options (optional) 
    1206     :return: an iterator over ``(lineno, funcname, message, comments)`` tuples 
     1286    :param error_callback: an error function called if defined, and, in case 
     1287                           of errors, to send them to the user. 
     1288                           Accepts three arguments; 
     1289                           ``filename``, ``line_number``, ``error_message`` 
     1290    :return: an iterator over ``(lineno, funcname, message, info)`` tuples 
    12071291    :rtype: ``iterator`` 
    12081292    """ 
    12091293    template_class = options.get('template_class', MarkupTemplate) 
     
    12291313    tmpl = template_class(fileobj, filename=getattr(fileobj, 'name', None), 
    12301314                          encoding=encoding) 
    12311315 
    1232     error_callback = options.get('error_callback') 
    1233  
    12341316    translator = Translator(None, ignore_tags, include_attrs, extract_text) 
    12351317    if hasattr(tmpl, 'add_directives'): 
    12361318        tmpl.add_directives(Translator.NAMESPACE, translator) 
    1237     for message in translator.extract(tmpl.stream, gettext_functions=keywords, 
    1238                                       error_callback=error_callback): 
    1239         yield message 
     1319    for lineno, funcname, messages, info in translator.extract( 
     1320        tmpl.stream, gettext_functions=keywords, error_callback=error_callback): 
     1321        yield lineno, funcname, messages, info 
    12401322 
    12411323 
    12421324def setup_i18n(tmpl, translator): 
  • genshi/filters/tests/i18n.py

    diff --git a/genshi/filters/tests/i18n.py b/genshi/filters/tests/i18n.py
    a b  
    8888        translator = Translator(extract_text=False) 
    8989        messages = list(translator.extract(tmpl.stream)) 
    9090        self.assertEqual(1, len(messages)) 
    91         self.assertEqual((3, 'ngettext', (u'Singular', u'Plural', None), []), 
     91        self.assertEqual((3, 'ngettext', (u'Singular', u'Plural', None), 
     92                          {'comments': []}), 
    9293                         messages[0]) 
    9394 
    9495    def test_extract_plural_form(self): 
     
    9899        translator = Translator() 
    99100        messages = list(translator.extract(tmpl.stream)) 
    100101        self.assertEqual(1, len(messages)) 
    101         self.assertEqual((2, 'ngettext', (u'Singular', u'Plural', None), []), 
     102        self.assertEqual((2, 'ngettext', (u'Singular', u'Plural', None), 
     103                          {'comments': []}), 
    102104                         messages[0]) 
    103105 
    104106    def test_extract_funky_plural_form(self): 
     
    108110        translator = Translator() 
    109111        messages = list(translator.extract(tmpl.stream)) 
    110112        self.assertEqual(1, len(messages)) 
    111         self.assertEqual((2, 'ngettext', (None, None), []), messages[0]) 
     113        self.assertEqual((2, 'ngettext', (None, None), {'comments': []}), messages[0]) 
    112114 
    113115    def test_extract_gettext_with_unicode_string(self): 
    114116        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    117119        translator = Translator() 
    118120        messages = list(translator.extract(tmpl.stream)) 
    119121        self.assertEqual(1, len(messages)) 
    120         self.assertEqual((2, 'gettext', u'Gr\xfc\xdfe', []), messages[0]) 
     122        self.assertEqual((2, 'gettext', u'Gr\xfc\xdfe', {'comments': []}), messages[0]) 
    121123 
    122124    def test_extract_included_attribute_text(self): 
    123125        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    126128        translator = Translator() 
    127129        messages = list(translator.extract(tmpl.stream)) 
    128130        self.assertEqual(1, len(messages)) 
    129         self.assertEqual((2, None, u'Foo', []), messages[0]) 
     131        self.assertEqual((2, None, u'Foo', {'comments': []}), messages[0]) 
    130132 
    131133    def test_extract_attribute_expr(self): 
    132134        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    135137        translator = Translator() 
    136138        messages = list(translator.extract(tmpl.stream)) 
    137139        self.assertEqual(1, len(messages)) 
    138         self.assertEqual((2, '_', u'Save', []), messages[0]) 
     140        self.assertEqual((2, '_', u'Save', {'comments': []}), messages[0]) 
    139141 
    140142    def test_extract_non_included_attribute_interpolated(self): 
    141143        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    144146        translator = Translator() 
    145147        messages = list(translator.extract(tmpl.stream)) 
    146148        self.assertEqual(1, len(messages)) 
    147         self.assertEqual((2, None, u'Foo', []), messages[0]) 
     149        self.assertEqual((2, None, u'Foo', {'comments': []}), messages[0]) 
    148150 
    149151    def test_extract_text_from_sub(self): 
    150152        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    153155        translator = Translator() 
    154156        messages = list(translator.extract(tmpl.stream)) 
    155157        self.assertEqual(1, len(messages)) 
    156         self.assertEqual((2, None, u'Foo', []), messages[0]) 
     158        self.assertEqual((2, None, u'Foo', {'comments': []}), messages[0]) 
    157159 
    158160    def test_ignore_tag_with_fixed_xml_lang(self): 
    159161        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    170172        translator = Translator() 
    171173        messages = list(translator.extract(tmpl.stream)) 
    172174        self.assertEqual(1, len(messages)) 
    173         self.assertEqual((2, None, u'(c) 2007 Edgewall Software', []), 
     175        self.assertEqual((2, None, u'(c) 2007 Edgewall Software', {'comments': []}), 
    174176                         messages[0]) 
    175177 
    176178    def test_ignore_attribute_with_expression(self): 
     
    468470        tmpl.add_directives(Translator.NAMESPACE, translator) 
    469471        messages = list(translator.extract(tmpl.stream)) 
    470472        self.assertEqual(1, len(messages)) 
    471         self.assertEqual((3, None, u'Foo', ['As in foo bar']), messages[0]) 
     473        self.assertEqual((3, None, u'Foo', {'comments': [u'As in foo bar']}), 
     474                         messages[0]) 
    472475        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
    473476            xmlns:i18n="http://genshi.edgewall.org/i18n"> 
    474477          <p i18n:msg="" i18n:comment="As in foo bar">Foo</p> 
     
    477480        tmpl.add_directives(Translator.NAMESPACE, translator) 
    478481        messages = list(translator.extract(tmpl.stream)) 
    479482        self.assertEqual(1, len(messages)) 
    480         self.assertEqual((3, None, u'Foo', ['As in foo bar']), messages[0]) 
     483        self.assertEqual((3, None, u'Foo', {'comments': [u'As in foo bar']}), 
     484                         messages[0]) 
    481485 
    482486    def test_translate_i18n_msg_with_comment(self): 
    483487        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    499503        translator = Translator() 
    500504        messages = list(translator.extract(tmpl.stream)) 
    501505        self.assertEqual(2, len(messages)) 
    502         self.assertEqual((3, None, u'Foo bar', []), messages[0]) 
    503         self.assertEqual((3, None, u'Foo', []), messages[1]) 
     506        self.assertEqual((3, None, u'Foo bar', {'comments': []}), messages[0]) 
     507        self.assertEqual((3, None, u'Foo', {'comments': []}), messages[1]) 
    504508 
    505509    def test_translate_i18n_msg_with_attr(self): 
    506510        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    554558        messages = list(translator.extract(tmpl.stream)) 
    555559        self.assertEqual(1, len(messages)) 
    556560        self.assertEqual( 
    557             (3, None, u'Changed %(date)s ago by %(author)s', []), messages[0] 
     561            (3, None, u'Changed %(date)s ago by %(author)s', 
     562             {'flags': ['python-format'], 'comments': []}), messages[0] 
    558563        ) 
    559564 
    560565    def test_i18n_msg_ticket_300_translate(self): 
     
    584589        messages = list(translator.extract(tmpl.stream)) 
    585590        self.assertEqual(1, len(messages)) 
    586591        self.assertEqual( 
    587             (3, None, u'[1:[2:Translation\\[\xa00\xa0\\]]: [3:One coin]]', []), messages[0] 
     592            (3, None, u'[1:[2:Translation\\[\xa00\xa0\\]]: [3:One coin]]', 
     593             {'flags': ['genshi-format'], 'comments': []}), messages[0] 
    588594        ) 
    589595 
    590596    def test_i18n_msg_ticket_251_translate(self): 
     
    10031009        tmpl.add_directives(Translator.NAMESPACE, translator) 
    10041010        messages = list(translator.extract(tmpl.stream)) 
    10051011        self.assertEqual(2, len(messages)) 
    1006         self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), []), messages[0]) 
    1007         self.assertEqual((7, 'ngettext', (u'FooBar', u'FooBars'), []), messages[1]) 
     1012        self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), {'comments': []}), messages[0]) 
     1013        self.assertEqual((7, 'ngettext', (u'FooBar', u'FooBars'), {'comments': []}), messages[1]) 
    10081014 
    10091015    def test_extract_i18n_choose_as_directive(self): 
    10101016        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    10221028        tmpl.add_directives(Translator.NAMESPACE, translator) 
    10231029        messages = list(translator.extract(tmpl.stream)) 
    10241030        self.assertEqual(2, len(messages)) 
    1025         self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), []), messages[0]) 
    1026         self.assertEqual((7, 'ngettext', (u'FooBar', u'FooBars'), []), messages[1]) 
     1031        self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), {'comments': []}), messages[0]) 
     1032        self.assertEqual((7, 'ngettext', (u'FooBar', u'FooBars'), {'comments': []}), messages[1]) 
    10271033 
    10281034    def test_extract_i18n_choose_as_attribute_with_params(self): 
    10291035        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    10381044        messages = list(translator.extract(tmpl.stream)) 
    10391045        self.assertEqual(1, len(messages)) 
    10401046        self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    1041                                           u'Foos %(fname)s %(lname)s'), []), 
     1047                                          u'Foos %(fname)s %(lname)s'), 
     1048                          {'flags': ['python-format'], 'comments': []}), 
    10421049                         messages[0]) 
    10431050 
    10441051    def test_extract_i18n_choose_as_attribute_with_params_and_domain_as_param(self): 
     
    10551062        messages = list(translator.extract(tmpl.stream)) 
    10561063        self.assertEqual(1, len(messages)) 
    10571064        self.assertEqual((4, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    1058                                           u'Foos %(fname)s %(lname)s'), []), 
     1065                                          u'Foos %(fname)s %(lname)s'), 
     1066                          {'flags': ['python-format'], 'comments': []}), 
    10591067                         messages[0]) 
    10601068 
    10611069    def test_extract_i18n_choose_as_directive_with_params(self): 
     
    10751083        messages = list(translator.extract(tmpl.stream)) 
    10761084        self.assertEqual(2, len(messages)) 
    10771085        self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    1078                                           u'Foos %(fname)s %(lname)s'), []), 
     1086                                          u'Foos %(fname)s %(lname)s'), 
     1087                          {'flags': ['python-format'], 'comments': []}), 
    10791088                         messages[0]) 
    10801089        self.assertEqual((7, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    1081                                           u'Foos %(fname)s %(lname)s'), []), 
     1090                                          u'Foos %(fname)s %(lname)s'), 
     1091                          {'flags': ['python-format'], 'comments': []}), 
    10821092                         messages[1]) 
    10831093 
    10841094    def test_extract_i18n_choose_as_directive_with_params_and_domain_as_directive(self): 
     
    11001110        messages = list(translator.extract(tmpl.stream)) 
    11011111        self.assertEqual(2, len(messages)) 
    11021112        self.assertEqual((4, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    1103                                           u'Foos %(fname)s %(lname)s'), []), 
     1113                                          u'Foos %(fname)s %(lname)s'), 
     1114                          {'flags': ['python-format'], 'comments': []}), 
    11041115                         messages[0]) 
    11051116        self.assertEqual((9, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    1106                                           u'Foos %(fname)s %(lname)s'), []), 
     1117                                          u'Foos %(fname)s %(lname)s'), 
     1118                          {'flags': ['python-format'], 'comments': []}), 
    11071119                         messages[1]) 
    11081120 
    11091121    def test_extract_i18n_choose_as_attribute_with_params_and_comment(self): 
     
    11201132        self.assertEqual(1, len(messages)) 
    11211133        self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    11221134                                          u'Foos %(fname)s %(lname)s'), 
    1123                           [u'As in Foo Bar']), 
     1135                          {'flags': ['python-format'], 
     1136                           'comments': [u'As in Foo Bar']}), 
    11241137                         messages[0]) 
    11251138 
    11261139    def test_extract_i18n_choose_as_directive_with_params_and_comment(self): 
     
    11371150        self.assertEqual(1, len(messages)) 
    11381151        self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 
    11391152                                          u'Foos %(fname)s %(lname)s'), 
    1140                           [u'As in Foo Bar']), 
     1153                          {'flags': ['python-format'], 
     1154                           'comments': [u'As in Foo Bar']}), 
    11411155                         messages[0]) 
    11421156 
    11431157    def test_translate_i18n_domain_with_nested_inlcudes(self): 
     
    13691383        tmpl.add_directives(Translator.NAMESPACE, translator) 
    13701384        messages = list(translator.extract(tmpl.stream)) 
    13711385        self.assertEqual(1, len(messages)) 
    1372         self.assertEqual((3, None, u'Please see [1:Help] for details.', []), 
     1386        self.assertEqual((3, None, u'Please see [1:Help] for details.', 
     1387                          {'flags': ['genshi-format'], 'comments': []}), 
    13731388                         messages[0]) 
    13741389 
    13751390    def test_extract_i18n_msg_with_py_strip_and_comment(self): 
     
    13841399        messages = list(translator.extract(tmpl.stream)) 
    13851400        self.assertEqual(1, len(messages)) 
    13861401        self.assertEqual((3, None, u'Please see [1:Help] for details.', 
    1387                           ['Foo']), messages[0]) 
     1402                          {'flags': ['genshi-format'], 'comments': [u'Foo']}), 
     1403                          messages[0]) 
    13881404 
    13891405    def test_extract_i18n_choose_as_attribute_and_py_strip(self): 
    13901406        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    13981414        tmpl.add_directives(Translator.NAMESPACE, translator) 
    13991415        messages = list(translator.extract(tmpl.stream)) 
    14001416        self.assertEqual(1, len(messages)) 
    1401         self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), []), messages[0]) 
     1417        self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), {'comments': []}), messages[0]) 
    14021418 
    14031419    def test_translate_i18n_domain_with_inline_directive_on_START_NS(self): 
    14041420        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    14721488            <p>${ngettext("You have %d item", "You have %d items", num)}</p> 
    14731489          </body> 
    14741490        </html>""") 
    1475         results = list(extract(buf, ['_', 'ngettext'], [], {})) 
     1491        results = list(extract(buf, ['_', 'ngettext'], [], {}, None)) 
    14761492        self.assertEqual([ 
    1477             (3, None, u'Example', []), 
    1478             (6, None, u'Example', []), 
    1479             (7, '_', u'Hello, %(name)s', []), 
     1493            (3, None, u'Example', {'comments': []}), 
     1494            (6, None, u'Example', {'comments': []}), 
     1495            (7, '_', u'Hello, %(name)s', {'flags': ['python-format'], 'comments': []}), 
    14801496            (8, 'ngettext', (u'You have %d item', u'You have %d items', None), 
    1481                              []), 
     1497                             {'flags': ['python-format'], 'comments': []}), 
    14821498        ], results) 
    14831499 
    14841500    def test_extraction_without_text(self): 
     
    14881504        </html>""") 
    14891505        results = list(extract(buf, ['_', 'ngettext'], [], { 
    14901506            'extract_text': 'no' 
    1491         })) 
     1507        }, None)) 
    14921508        self.assertEqual([ 
    1493             (3, 'ngettext', (u'Singular', u'Plural', None), []), 
     1509            (3, 'ngettext', (u'Singular', u'Plural', None), {'comments': []}), 
    14941510        ], results) 
    14951511 
    14961512    def test_text_template_extraction(self): 
     
    15051521        Foobar""") 
    15061522        results = list(extract(buf, ['_', 'ngettext'], [], { 
    15071523            'template_class': 'genshi.template:TextTemplate' 
    1508         })) 
     1524        }, None)) 
    15091525        self.assertEqual([ 
    1510             (1, '_', u'Dear %(name)s', []), 
    1511             (3, 'ngettext', (u'Your item:', u'Your items', None), []), 
    1512             (7, None, u'All the best,\n        Foobar', []) 
     1526            (1, '_', u'Dear %(name)s', {'flags': ['python-format'], 'comments': []}), 
     1527            (3, 'ngettext', (u'Your item:', u'Your items', None), 
     1528             {'comments': []}), 
     1529            (7, None, u'All the best,\n        Foobar', {'comments': []}) 
    15131530        ], results) 
    15141531 
    15151532    def test_extraction_with_keyword_arg(self): 
    15161533        buf = StringIO("""<html xmlns:py="http://genshi.edgewall.org/"> 
    15171534          ${gettext('Foobar', foo='bar')} 
    15181535        </html>""") 
    1519         results = list(extract(buf, ['gettext'], [], {})) 
     1536        results = list(extract(buf, ['gettext'], [], {}, None)) 
    15201537        self.assertEqual([ 
    1521             (2, 'gettext', (u'Foobar'), []), 
     1538            (2, 'gettext', (u'Foobar'), {'comments': []}), 
    15221539        ], results) 
    15231540 
    15241541    def test_extraction_with_nonstring_arg(self): 
    15251542        buf = StringIO("""<html xmlns:py="http://genshi.edgewall.org/"> 
    15261543          ${dgettext(curdomain, 'Foobar')} 
    15271544        </html>""") 
    1528         results = list(extract(buf, ['dgettext'], [], {})) 
     1545        results = list(extract(buf, ['dgettext'], [], {}, None)) 
    15291546        self.assertEqual([ 
    1530             (2, 'dgettext', (None, u'Foobar'), []), 
     1547            (2, 'dgettext', (None, u'Foobar'), {'comments': []}), 
    15311548        ], results) 
    15321549 
    15331550    def test_extraction_inside_ignored_tags(self): 
     
    15391556            }); 
    15401557          </script> 
    15411558        </html>""") 
    1542         results = list(extract(buf, ['_'], [], {})) 
    1543         self.assertEqual([ 
    1544             (5, '_', u'Please wait...', []), 
    1545         ], results) 
     1559        results = list(extract(buf, ['_'], [], {}, None)) 
     1560        self.assertEqual([(5, '_', u'Please wait...', {'comments': []})], 
     1561                         results) 
    15461562 
    15471563    def test_extraction_inside_ignored_tags_with_directives(self): 
    15481564        buf = StringIO("""<html xmlns:py="http://genshi.edgewall.org/"> 
     
    15521568            </py:if> 
    15531569          </script> 
    15541570        </html>""") 
    1555         self.assertEqual([], list(extract(buf, ['_'], [], {}))) 
     1571        self.assertEqual([], list(extract(buf, ['_'], [], {}, None))) 
    15561572 
    15571573    def test_extract_py_def_directive_with_py_strip(self): 
    15581574        # Failed extraction from Trac 
     
    15971613        messages = list(translator.extract(tmpl.stream)) 
    15981614        self.assertEqual(10, len(messages)) 
    15991615        self.assertEqual([ 
    1600             (3, None, u'View differences', []), 
    1601             (6, None, u'inline', []), 
    1602             (8, None, u'side by side', []), 
    1603             (10, None, u'Show', []), 
    1604             (13, None, u'lines around each change', []), 
    1605             (16, None, u'Ignore:', []), 
    1606             (20, None, u'Blank lines', []), 
    1607             (25, None, u'Case changes',[]), 
    1608             (30, None, u'White space changes', []), 
    1609             (34, '_', u'Update', [])], messages) 
     1616            (3, None, u'View differences', {'comments': []}), 
     1617            (6, None, u'inline', {'comments': []}), 
     1618            (8, None, u'side by side', {'comments': []}), 
     1619            (10, None, u'Show', {'comments': []}), 
     1620            (13, None, u'lines around each change', {'comments': []}), 
     1621            (16, None, u'Ignore:', {'comments': []}), 
     1622            (20, None, u'Blank lines', {'comments': []}), 
     1623            (25, None, u'Case changes', {'comments': []}), 
     1624            (30, None, u'White space changes', {'comments': []}), 
     1625            (34, '_', u'Update', {'comments': []}) 
     1626        ], messages) 
    16101627 
    16111628 
    16121629def suite():