# HG changeset patch # User cmlenz # Date 1218150911 0 # Node ID 422a9dd01e9f1a4112544d7f42d1127cc0175af4 # Parent d30a27266b45ac0e9700c670872a8e71b2def4e8 Add support for supplying comments on localizable messages in the i18n filter. Based on patch by Pedro Algarvio on #129. diff --git a/genshi/filters/i18n.py b/genshi/filters/i18n.py --- a/genshi/filters/i18n.py +++ b/genshi/filters/i18n.py @@ -129,9 +129,11 @@ translate = self.translate if not self.extract_text: search_text = False + + ns_prefixes = [] skip = 0 + i18n_comment = I18N_NAMESPACE['comment'] i18n_msg = I18N_NAMESPACE['msg'] - ns_prefixes = [] xml_lang = XML_NAMESPACE['lang'] for kind, data, pos in stream: @@ -180,7 +182,7 @@ if params and type(params) is list: # event tuple params = params[0][1] msgbuf = MessageBuffer(params) - attrs -= i18n_msg + attrs -= (i18n_comment, i18n_msg) yield kind, (tag, attrs), pos @@ -226,13 +228,16 @@ """Extract localizable strings from the given template stream. For every string found, this function yields a ``(lineno, function, - message)`` tuple, where: + message, comments)`` tuple, where: * ``lineno`` is the number of the line on which the string was found, * ``function`` is the name of the ``gettext`` function used (if the string was extracted from embedded Python code), and * ``message`` is the string itself (a ``unicode`` object, or a tuple - of ``unicode`` objects for functions with multiple string arguments). + of ``unicode`` objects for functions with multiple string + arguments). + * ``comments`` is a list of comments related to the message, extracted + from ``i18n:comment`` attributes found in the markup >>> from genshi.template import MarkupTemplate >>> @@ -247,8 +252,8 @@ ... ... ''', filename='example.html') >>> - >>> for lineno, funcname, message in Translator().extract(tmpl.stream): - ... print "%d, %r, %r" % (lineno, funcname, message) + >>> for line, func, msg, comments in Translator().extract(tmpl.stream): + ... print "%d, %r, %r" % (line, func, msg) 3, None, u'Example' 6, None, u'Example' 7, '_', u'Hello, %(name)s' @@ -265,10 +270,13 @@ :note: Changed in 0.4.1: For a function with multiple string arguments (such as ``ngettext``), a single item with a tuple of strings is yielded, instead an item for each string argument. + :note: Changed in 0.6: The returned tuples now include a 4th element, + which is a list of comments for the translator """ if not self.extract_text: search_text = False skip = 0 + i18n_comment = I18N_NAMESPACE['comment'] i18n_msg = I18N_NAMESPACE['msg'] xml_lang = XML_NAMESPACE['lang'] @@ -293,33 +301,37 @@ if name in self.include_attrs: text = value.strip() if text: - yield pos[1], None, text + yield pos[1], None, text, [] else: - for lineno, funcname, text in self.extract( + for lineno, funcname, text, comments in self.extract( _ensure(value), gettext_functions, search_text=False): - yield lineno, funcname, text + yield lineno, funcname, text, comments if msgbuf: msgbuf.append(kind, data, pos) - elif i18n_msg in attrs: - params = attrs.get(i18n_msg) - if params and type(params) is list: # event tuple - params = params[0][1] - msgbuf = MessageBuffer(params, pos[1]) + else: + msg_params = attrs.get(i18n_msg) + if msg_params is not None: + if type(msg_params) is list: # event tuple + msg_params = msg_params[0][1] + msgbuf = MessageBuffer( + msg_params, attrs.get(i18n_comment), pos[1] + ) elif not skip and search_text and kind is TEXT: if not msgbuf: text = data.strip() if text and filter(None, [ch.isalpha() for ch in text]): - yield pos[1], None, text + yield pos[1], None, text, [] else: msgbuf.append(kind, data, pos) elif not skip and msgbuf and kind is END: msgbuf.append(kind, data, pos) if not msgbuf.depth: - yield msgbuf.lineno, None, msgbuf.format() + yield msgbuf.lineno, None, msgbuf.format(), \ + filter(None, [msgbuf.comment]) msgbuf = None elif kind is EXPR or kind is EXEC: @@ -327,15 +339,15 @@ msgbuf.append(kind, data, pos) for funcname, strings in extract_from_code(data, gettext_functions): - yield pos[1], funcname, strings + yield pos[1], funcname, strings, [] elif kind is SUB: subkind, substream = data messages = self.extract(substream, gettext_functions, search_text=search_text and not skip, msgbuf=msgbuf) - for lineno, funcname, text in messages: - yield lineno, funcname, text + for lineno, funcname, text, comments in messages: + yield lineno, funcname, text, comments class MessageBuffer(object): @@ -344,7 +356,7 @@ :since: version 0.5 """ - def __init__(self, params=u'', lineno=-1): + def __init__(self, params=u'', comment=None, lineno=-1): """Initialize the message buffer. :param params: comma-separated list of parameter names @@ -353,6 +365,7 @@ belonging to the message was found """ self.params = [name.strip() for name in params.split(',')] + self.comment = comment self.lineno = lineno self.string = [] self.events = {} @@ -545,6 +558,5 @@ tmpl = template_class(fileobj, filename=getattr(fileobj, 'name', None), encoding=encoding) translator = Translator(None, ignore_tags, include_attrs, extract_text) - for lineno, func, message in translator.extract(tmpl.stream, - gettext_functions=keywords): - yield lineno, func, message, [] + for message in translator.extract(tmpl.stream, gettext_functions=keywords): + yield message diff --git a/genshi/filters/tests/i18n.py b/genshi/filters/tests/i18n.py --- a/genshi/filters/tests/i18n.py +++ b/genshi/filters/tests/i18n.py @@ -44,7 +44,7 @@ translator = Translator(extract_text=False) messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((3, 'ngettext', (u'Singular', u'Plural', None)), + self.assertEqual((3, 'ngettext', (u'Singular', u'Plural', None), []), messages[0]) def test_extract_plural_form(self): @@ -54,7 +54,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, 'ngettext', (u'Singular', u'Plural', None)), + self.assertEqual((2, 'ngettext', (u'Singular', u'Plural', None), []), messages[0]) def test_extract_funky_plural_form(self): @@ -64,7 +64,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, 'ngettext', (None, None)), messages[0]) + self.assertEqual((2, 'ngettext', (None, None), []), messages[0]) def test_extract_gettext_with_unicode_string(self): tmpl = MarkupTemplate(""" @@ -73,7 +73,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, 'gettext', u'Gr\xfc\xdfe'), messages[0]) + self.assertEqual((2, 'gettext', u'Gr\xfc\xdfe', []), messages[0]) def test_extract_included_attribute_text(self): tmpl = MarkupTemplate(""" @@ -82,7 +82,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, None, u'Foo'), messages[0]) + self.assertEqual((2, None, u'Foo', []), messages[0]) def test_extract_attribute_expr(self): tmpl = MarkupTemplate(""" @@ -91,7 +91,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, '_', u'Save'), messages[0]) + self.assertEqual((2, '_', u'Save', []), messages[0]) def test_extract_non_included_attribute_interpolated(self): tmpl = MarkupTemplate(""" @@ -100,7 +100,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, None, u'Foo'), messages[0]) + self.assertEqual((2, None, u'Foo', []), messages[0]) def test_extract_text_from_sub(self): tmpl = MarkupTemplate(""" @@ -109,7 +109,7 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, None, u'Foo'), messages[0]) + self.assertEqual((2, None, u'Foo', []), messages[0]) def test_ignore_tag_with_fixed_xml_lang(self): tmpl = MarkupTemplate(""" @@ -126,7 +126,8 @@ translator = Translator() messages = list(translator.extract(tmpl.stream)) self.assertEqual(1, len(messages)) - self.assertEqual((2, None, u'(c) 2007 Edgewall Software'), messages[0]) + self.assertEqual((2, None, u'(c) 2007 Edgewall Software', []), + messages[0]) def test_ignore_attribute_with_expression(self): tmpl = MarkupTemplate(""" @@ -369,6 +370,27 @@ #

Einträge pro Seite anzeigen.

# """, tmpl.generate().render()) + def test_extract_i18n_msg_with_comment(self): + tmpl = MarkupTemplate(""" +

Foo

+ """) + translator = Translator() + messages = list(translator.extract(tmpl.stream)) + self.assertEqual(1, len(messages)) + self.assertEqual((3, None, u'Foo', ['As in foo bar']), messages[0]) + + def test_translate_i18n_msg_with_comment(self): + tmpl = MarkupTemplate(""" +

Foo

+ """) + gettext = lambda s: u"Voh" + tmpl.filters.insert(0, Translator(gettext)) + self.assertEqual(""" +

Voh

+ """, tmpl.generate().render()) + class ExtractTestCase(unittest.TestCase):