Ticket #580: msgctxt.patch
| File msgctxt.patch, 33.9 KB (added by eric.oconnell@…, 10 years ago) |
|---|
-
genshi/filters/tests/i18n.py
62 62 else: 63 63 return msgid2 64 64 65 def dungettext(self, domain, singular, plural, numeral):66 return self._domain_call('ungettext', domain, singular, plural, numeral)65 def dungettext(self, domain, msgid1, msgid2, n): 66 return self._domain_call('ungettext', domain, msgid1, msgid2, n) 67 67 68 def upgettext(self, context, message): 69 try: 70 return self._catalog[(context, message)] 71 except KeyError: 72 if self._fallback: 73 return self._fallback.upgettext(context, message) 74 return unicode(message) 68 75 76 def dupgettext(self, domain, context, message): 77 return self._domain_call('upgettext', domain, context, message) 78 79 def unpgettext(self, context, msgid1, msgid2, n): 80 try: 81 return self._catalog[(context, msgid1, self.plural(n))] 82 except KeyError: 83 if self._fallback: 84 return self._fallback.unpgettext(context, msgid1, msgid2, n) 85 if n == 1: 86 return msgid1 87 else: 88 return msgid2 89 90 def dunpgettext(self, domain, context, msgid1, msgid2, n): 91 return self._domain_call('npgettext', context, msgid1, msgid2, n) 92 93 69 94 class TranslatorTestCase(unittest.TestCase): 70 95 71 96 def test_translate_included_attribute_text(self): … … 1417 1442 <p>Vohs John Doe</p> 1418 1443 </div> 1419 1444 </html>""", tmpl.generate(two=2, fname='John', lname='Doe').render()) 1420 1445 1421 1446 def test_translate_i18n_choose_and_singular_with_py_strip(self): 1422 1447 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1423 1448 xmlns:i18n="http://genshi.edgewall.org/i18n"> … … 1447 1472 </div> 1448 1473 </html>""", tmpl.generate( 1449 1474 one=1, two=2, fname='John',lname='Doe').render()) 1450 1475 1451 1476 def test_translate_i18n_choose_and_plural_with_py_strip(self): 1452 1477 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1453 1478 xmlns:i18n="http://genshi.edgewall.org/i18n"> … … 1965 1990 (34, '_', 'Update', [])], messages) 1966 1991 1967 1992 1993 class ContextDirectiveTestCase(unittest.TestCase): 1994 def test_extract_msgcontext(self): 1995 buf = StringIO("""<html xmlns:py="http://genshi.edgewall.org/" 1996 xmlns:i18n="http://genshi.edgewall.org/i18n"> 1997 <p i18n:ctxt="foo">Foo, bar.</p> 1998 <p>Foo, bar.</p> 1999 </html>""") 2000 results = list(extract(buf, ['_'], [], {})) 2001 self.assertEqual((3, 'pgettext', ('foo', 'Foo, bar.'), []), results[0]) 2002 self.assertEqual((4, None, 'Foo, bar.', []), results[1]) 2003 2004 def test_translate_msgcontext(self): 2005 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 2006 xmlns:i18n="http://genshi.edgewall.org/i18n"> 2007 <p i18n:ctxt="foo">Foo, bar.</p> 2008 <p>Foo, bar.</p> 2009 </html>""") 2010 translations = { 2011 ('foo', 'Foo, bar.'): 'Fooo! Barrr!', 2012 'Foo, bar.': 'Foo --- bar.' 2013 } 2014 translator = Translator(DummyTranslations(translations)) 2015 translator.setup(tmpl) 2016 self.assertEqual("""<html> 2017 <p>Fooo! Barrr!</p> 2018 <p>Foo --- bar.</p> 2019 </html>""", tmpl.generate().render()) 2020 2021 def test_translate_msgcontext_with_domain(self): 2022 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 2023 xmlns:i18n="http://genshi.edgewall.org/i18n"> 2024 <p i18n:domain="bar" i18n:ctxt="foo">Foo, bar. <span>foo</span></p> 2025 <p>Foo, bar.</p> 2026 </html>""") 2027 translations = DummyTranslations({ 2028 ('foo', 'Foo, bar.'): 'Fooo! Barrr!', 2029 'Foo, bar.': 'Foo --- bar.' 2030 }) 2031 translations.add_domain('bar', { 2032 ('foo', 'foo'): 'BARRR', 2033 ('foo', 'Foo, bar.'): 'Bar, bar.' 2034 }) 2035 2036 translator = Translator(translations) 2037 translator.setup(tmpl) 2038 self.assertEqual("""<html> 2039 <p>Bar, bar. <span>BARRR</span></p> 2040 <p>Foo --- bar.</p> 2041 </html>""", tmpl.generate().render()) 2042 2043 def test_translate_msgcontext_with_plurals(self): 2044 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 2045 xmlns:i18n="http://genshi.edgewall.org/i18n"> 2046 <i18n:ctxt name="foo"> 2047 <p i18n:choose="num; num"> 2048 <span i18n:singular="">There is ${num} bar</span> 2049 <span i18n:plural="">There are ${num} bars</span> 2050 </p> 2051 </i18n:ctxt> 2052 </html>""") 2053 translations = DummyTranslations({ 2054 ('foo', 'There is %(num)s bar', 0): 'Hay %(num)s barre', 2055 ('foo', 'There is %(num)s bar', 1): 'Hay %(num)s barres' 2056 }) 2057 2058 translator = Translator(translations) 2059 translator.setup(tmpl) 2060 self.assertEqual("""<html> 2061 <p> 2062 <span>Hay 1 barre</span> 2063 </p> 2064 </html>""", tmpl.generate(num=1).render()) 2065 self.assertEqual("""<html> 2066 <p> 2067 <span>Hay 2 barres</span> 2068 </p> 2069 </html>""", tmpl.generate(num=2).render()) 2070 2071 def test_translate_context_with_msg(self): 2072 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 2073 xmlns:i18n="http://genshi.edgewall.org/i18n"> 2074 <p i18n:ctxt="foo" i18n:msg="num"> 2075 Foo <span>There is ${num} bar</span> Bar 2076 </p> 2077 </html>""") 2078 translations = DummyTranslations({ 2079 ('foo', 'Foo [1:There is %(num)s bar] Bar'): 2080 'Voh [1:Hay %(num)s barre] Barre' 2081 }) 2082 translator = Translator(translations) 2083 translator.setup(tmpl) 2084 self.assertEqual("""<html> 2085 <p>Voh <span>Hay 1 barre</span> Barre</p> 2086 </html>""", tmpl.generate(num=1).render()) 2087 2088 1968 2089 def suite(): 1969 2090 suite = unittest.TestSuite() 1970 2091 suite.addTest(doctest.DocTestSuite(Translator.__module__)) … … 1973 2094 suite.addTest(unittest.makeSuite(ChooseDirectiveTestCase, 'test')) 1974 2095 suite.addTest(unittest.makeSuite(DomainDirectiveTestCase, 'test')) 1975 2096 suite.addTest(unittest.makeSuite(ExtractTestCase, 'test')) 2097 suite.addTest(unittest.makeSuite(ContextDirectiveTestCase, 'test')) 1976 2098 return suite 1977 2099 1978 2100 if __name__ == '__main__': -
genshi/filters/i18n.py
22 22 any 23 23 except NameError: 24 24 from genshi.util import any 25 from functools import partial 25 26 from gettext import NullTranslations 26 27 import os 27 28 import re … … 59 60 """Simple interface for directives to support messages extraction.""" 60 61 61 62 def extract(self, translator, stream, gettext_functions=GETTEXT_FUNCTIONS, 62 search_text=True, comment_stack=None ):63 search_text=True, comment_stack=None, context_stack=None): 63 64 raise NotImplementedError 64 65 65 66 67 contexted = { 68 None: 'pgettext', 69 'gettext': 'pgettext', 70 'ngettext': 'pngettext', 71 'dgettext': 'dpgettext', 72 'dngettext': 'dnpgettext' 73 } 74 75 76 def contextify(line, func, msg, comment, context): 77 if context: 78 context = context[0] 79 func = contexted.get(func) 80 if func not in contexted.values(): 81 raise Exception("failure, bogus extraction method") 82 if isinstance(msg, tuple): 83 msg = (context, tuple[0], tuple[1]) 84 else: 85 msg = (context, msg) 86 return line, func, msg, comment 87 88 66 89 class CommentDirective(I18NDirective): 67 90 """Implementation of the ``i18n:comment`` template directive which adds 68 91 translation comments. 69 92 70 93 >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 71 94 ... <p i18n:comment="As in Foo Bar">Foo</p> 72 95 ... </html>''') … … 86 109 class MsgDirective(ExtractableI18NDirective): 87 110 r"""Implementation of the ``i18n:msg`` directive which marks inner content 88 111 as translatable. Consider the following examples: 89 112 90 113 >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 91 114 ... <div i18n:msg=""> 92 115 ... <p>Foo</p> … … 94 117 ... </div> 95 118 ... <p i18n:msg="">Foo <em>bar</em>!</p> 96 119 ... </html>''') 97 120 98 121 >>> translator = Translator() 99 122 >>> translator.setup(tmpl) 100 123 >>> list(translator.extract(tmpl.stream)) … … 154 177 155 178 def __call__(self, stream, directives, ctxt, **vars): 156 179 gettext = ctxt.get('_i18n.gettext') 157 if ctxt.get('_i18n.domain'): 180 if ctxt.get('_i18n.domain') and ctxt.get('_i18n.context'): 181 dpgettext = ctxt.get('_i18n.dpgettext') 182 assert hasattr(dpgettext, '__call__'), \ 183 'No domain/context gettext function passed' 184 gettext = lambda msg: dpgettext(ctxt.get('_i18n.domain'), 185 ctxt.get('_i18n.context'), 186 msg) 187 elif ctxt.get('_i18n.domain'): 158 188 dgettext = ctxt.get('_i18n.dgettext') 159 189 assert hasattr(dgettext, '__call__'), \ 160 190 'No domain gettext function passed' 161 191 gettext = lambda msg: dgettext(ctxt.get('_i18n.domain'), msg) 192 elif ctxt.get('_i18n.context'): 193 pgettext = ctxt.get('_i18n.pgettext') 194 assert hasattr(pgettext, '__call__'), \ 195 'No context gettext function passed' 196 gettext = lambda msg: pgettext(ctxt.get('_i18n.context'), msg) 162 197 163 198 def _generate(): 164 199 msgbuf = MessageBuffer(self) … … 182 217 return _apply_directives(_generate(), directives, ctxt, vars) 183 218 184 219 def extract(self, translator, stream, gettext_functions=GETTEXT_FUNCTIONS, 185 search_text=True, comment_stack=None ):220 search_text=True, comment_stack=None, context_stack=None): 186 221 msgbuf = MessageBuffer(self) 187 222 strip = False 188 223 … … 206 241 if not strip: 207 242 msgbuf.append(*previous) 208 243 209 yield self.lineno, None, msgbuf.format(), comment_stack[-1:] 244 yield contextify( 245 self.lineno, None, msgbuf.format(), comment_stack[-1:], context_stack[-1:]) 210 246 211 247 212 248 class ChooseBranchDirective(I18NDirective): … … 243 279 ctxt['_i18n.choose.%s' % self.tagname] = msgbuf 244 280 245 281 def extract(self, translator, stream, gettext_functions=GETTEXT_FUNCTIONS, 246 search_text=True, comment_stack=None, msgbuf=None): 282 search_text=True, comment_stack=None, context_stack=None, 283 msgbuf=None): 247 284 stream = iter(stream) 248 285 previous = stream.next() 249 286 … … 281 318 class ChooseDirective(ExtractableI18NDirective): 282 319 """Implementation of the ``i18n:choose`` directive which provides plural 283 320 internationalisation of strings. 284 321 285 322 This directive requires at least one parameter, the one which evaluates to 286 323 an integer which will allow to choose the plural/singular form. If you also 287 324 have expressions inside the singular and plural version of the string you 288 325 also need to pass a name for those parameters. Consider the following 289 326 examples: 290 327 291 328 >>> tmpl = MarkupTemplate('''\ 292 329 <html xmlns:i18n="http://genshi.edgewall.org/i18n"> 293 330 ... <div i18n:choose="num; num"> … … 364 401 365 402 ngettext = ctxt.get('_i18n.ngettext') 366 403 assert hasattr(ngettext, '__call__'), 'No ngettext function available' 404 npgettext = ctxt.get('_i18n.npgettext') 405 if not npgettext: 406 npgettext = lambda c, s, p, n: ngettext(s, p, n) 367 407 dngettext = ctxt.get('_i18n.dngettext') 368 408 if not dngettext: 369 409 dngettext = lambda d, s, p, n: ngettext(s, p, n) 410 dnpgettext = ctxt.get('_i18n.dnpgettext') 411 if not dnpgettext: 412 dnpgettext = lambda d, c, s, p, n: dngettext(d, s, p, n) 370 413 371 414 new_stream = [] 372 415 singular_stream = None … … 397 440 else: 398 441 new_stream.append(event) 399 442 400 if ctxt.get('_i18n.domain'): 443 if ctxt.get('_i18n.context') and ctxt.get('_i18n.domain'): 444 ngettext = lambda s, p, n: dnpgettext(ctxt.get('_i18n.domain'), 445 ctxt.get('_i18n.context'), 446 s, p, n) 447 elif ctxt.get('_i18n.context'): 448 ngettext = lambda s, p, n: npgettext(ctxt.get('_i18n.context'), 449 s, p, n) 450 elif ctxt.get('_i18n.domain'): 401 451 ngettext = lambda s, p, n: dngettext(ctxt.get('_i18n.domain'), 402 452 s, p, n) 403 453 … … 426 476 ctxt.pop() 427 477 428 478 def extract(self, translator, stream, gettext_functions=GETTEXT_FUNCTIONS, 429 search_text=True, comment_stack=None ):479 search_text=True, comment_stack=None, context_stack=None): 430 480 strip = False 431 481 stream = iter(stream) 432 482 previous = stream.next() … … 450 500 if isinstance(directive, SingularDirective): 451 501 for message in directive.extract(translator, 452 502 substream, gettext_functions, search_text, 453 comment_stack, msgbuf=singular_msgbuf):503 comment_stack, context_stack, msgbuf=singular_msgbuf): 454 504 yield message 455 505 elif isinstance(directive, PluralDirective): 456 506 for message in directive.extract(translator, 457 507 substream, gettext_functions, search_text, 458 comment_stack, msgbuf=plural_msgbuf):508 comment_stack, context_stack, msgbuf=plural_msgbuf): 459 509 yield message 460 510 elif not isinstance(directive, StripDirective): 461 511 singular_msgbuf.append(*previous) … … 474 524 singular_msgbuf.append(*previous) 475 525 plural_msgbuf.append(*previous) 476 526 477 yield self.lineno, 'ngettext', \527 yield contextify(self.lineno, 'ngettext', \ 478 528 (singular_msgbuf.format(), plural_msgbuf.format()), \ 479 comment_stack[-1:]529 comment_stack[-1:], context_stack[-1:]) 480 530 481 531 def _is_plural(self, numeral, ngettext): 482 532 # XXX: should we test which form was chosen like this!?!?!? … … 490 540 class DomainDirective(I18NDirective): 491 541 """Implementation of the ``i18n:domain`` directive which allows choosing 492 542 another i18n domain(catalog) to translate from. 493 543 494 544 >>> from genshi.filters.tests.i18n import DummyTranslations 495 545 >>> tmpl = MarkupTemplate('''\ 496 546 <html xmlns:i18n="http://genshi.edgewall.org/i18n"> … … 543 593 ctxt.pop() 544 594 545 595 596 class ContextDirective(I18NDirective): 597 __slots__ = ['context'] 598 599 def __init__(self, value, template=None, namespaces=None, lineno=-1, 600 offset=-1): 601 Directive.__init__(self, None, template, namespaces, lineno, offset) 602 self.context = value 603 604 @classmethod 605 def attach(cls, template, stream, value, namespaces, pos): 606 if type(value) is dict: 607 value = value.get('name') 608 return super(ContextDirective, cls).attach(template, stream, value, 609 namespaces, pos) 610 611 def __call__(self, stream, directives, ctxt, **vars): 612 ctxt.push({'_i18n.context': self.context}) 613 for event in _apply_directives(stream, directives, ctxt, vars): 614 yield event 615 ctxt.pop() 616 617 546 618 class Translator(DirectiveFactory): 547 619 """Can extract and translate localizable strings from markup streams and 548 620 templates. 549 621 550 622 For example, assume the following template: 551 623 552 624 >>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/"> 553 625 ... <head> 554 626 ... <title>Example</title> … … 558 630 ... <p>${_("Hello, %(name)s") % dict(name=username)}</p> 559 631 ... </body> 560 632 ... </html>''', filename='example.html') 561 633 562 634 For demonstration, we define a dummy ``gettext``-style function with a 563 635 hard-coded translation table, and pass that to the `Translator` initializer: 564 636 565 637 >>> def pseudo_gettext(string): 566 638 ... return { 567 639 ... 'Example': 'Beispiel', 568 640 ... 'Hello, %(name)s': 'Hallo, %(name)s' 569 641 ... }[string] 570 642 >>> translator = Translator(pseudo_gettext) 571 643 572 644 Next, the translator needs to be prepended to any already defined filters 573 645 on the template: 574 646 575 647 >>> tmpl.filters.insert(0, translator) 576 648 577 649 When generating the template output, our hard-coded translations should be 578 650 applied as expected: 579 651 580 652 >>> print(tmpl.generate(username='Hans', _=pseudo_gettext)) 581 653 <html> 582 654 <head> … … 587 659 <p>Hallo, Hans</p> 588 660 </body> 589 661 </html> 590 662 591 663 Note that elements defining ``xml:lang`` attributes that do not contain 592 664 variable expressions are ignored by this filter. That can be used to 593 665 exclude specific parts of a template from being extracted and translated. … … 596 668 directives = [ 597 669 ('domain', DomainDirective), 598 670 ('comment', CommentDirective), 671 ('ctxt', ContextDirective), 599 672 ('msg', MsgDirective), 600 673 ('choose', ChooseDirective), 601 674 ('singular', SingularDirective), … … 614 687 def __init__(self, translate=NullTranslations(), ignore_tags=IGNORE_TAGS, 615 688 include_attrs=INCLUDE_ATTRS, extract_text=True): 616 689 """Initialize the translator. 617 690 618 691 :param translate: the translation function, for example ``gettext`` or 619 692 ``ugettext``. 620 693 :param ignore_tags: a set of tag names that should not be localized … … 622 695 :param extract_text: whether the content of text nodes should be 623 696 extracted, or only text in explicit ``gettext`` 624 697 function calls 625 698 626 699 :note: Changed in 0.6: the `translate` parameter can now be either 627 700 a ``gettext``-style function, or an object compatible with the 628 701 ``NullTransalations`` or ``GNUTranslations`` interface … … 635 708 def __call__(self, stream, ctxt=None, translate_text=True, 636 709 translate_attrs=True): 637 710 """Translate any localizable strings in the given stream. 638 711 639 712 This function shouldn't be called directly. Instead, an instance of 640 713 the `Translator` class should be registered as a filter with the 641 714 `Template` or the `TemplateLoader`, or applied as a regular stream 642 715 filter. If used as a template filter, it should be inserted in front of 643 716 all the default filters. 644 717 645 718 :param stream: the markup event stream 646 719 :param ctxt: the template context (not used) 647 720 :param translate_text: whether text nodes should be translated (used … … 671 744 except AttributeError: 672 745 dgettext = lambda _, y: gettext(y) 673 746 dngettext = lambda _, s, p, n: ngettext(s, p, n) 747 try: 748 pgettext = self.translate.upgettext 749 dpgettext = self.translate.dupgettext 750 npgettext = self.translate.unpgettext 751 dnpgettext = self.translate.dunpgettext 752 except AttributeError: 753 pgettext = lambda _, y: gettext(y) 754 dpgettext = lambda d, _, y: dgettext(d, y) 755 npgettext = lambda _, s, p, n: ngettext(s, p, n) 756 dnpgettext = lambda d, _, s, p, n: dngettext(d, s, p, n) 757 674 758 if ctxt: 675 759 ctxt['_i18n.gettext'] = gettext 676 760 ctxt['_i18n.ngettext'] = ngettext 677 761 ctxt['_i18n.dgettext'] = dgettext 678 762 ctxt['_i18n.dngettext'] = dngettext 763 ctxt['_i18n.pgettext'] = pgettext 764 ctxt['_i18n.npgettext'] = npgettext 765 ctxt['_i18n.dpgettext'] = dpgettext 766 ctxt['_i18n.dnpgettext'] = dnpgettext 679 767 680 768 if ctxt and ctxt.get('_i18n.domain'): 681 gettext = lambda msg: dgettext(ctxt.get('_i18n.domain'), msg)769 gettext = partial(dgettext, ctxt.get('_i18n.domain')) 682 770 771 if ctxt and ctxt.get('_i18n.context'): 772 if getattr(gettext, 'func', None): 773 gettext = partial(dpgettext, 774 ctxt['_i18n.domain'], 775 ctxt['_i18n.context']) 776 else: 777 gettext = partial(pgettext, ctxt['_i18n.context']) 778 683 779 for kind, data, pos in stream: 684 780 685 781 # skip chunks that should not be localized … … 730 826 elif kind is SUB: 731 827 directives, substream = data 732 828 current_domain = None 829 current_context = None 733 830 for idx, directive in enumerate(directives): 734 831 # Organize directives to make everything work 735 832 # FIXME: There's got to be a better way to do this! … … 740 837 # Put domain directive as the first one in order to 741 838 # update context before any other directives evaluation 742 839 directives.insert(0, directives.pop(idx)) 840 if isinstance(directive, ContextDirective): 841 # Grab current (msg)context and update context 842 current_context = directive.context 843 ctxt.push({'_i18n.context': current_context}) 844 # Put context directive either first in the case of 845 # no domain, or 2nd in the case there is a domain, to 846 # update context before any other directives evaluation 847 directives.insert(1 if current_domain else 0, 848 directives.pop(idx)) 743 849 744 850 # If this is an i18n directive, no need to translate text 745 851 # nodes here … … 747 853 isinstance(d, ExtractableI18NDirective) 748 854 for d in directives 749 855 ]) 856 750 857 substream = list(self(substream, ctxt, 751 858 translate_text=not is_i18n_directive, 752 859 translate_attrs=translate_attrs)) … … 754 861 755 862 if current_domain: 756 863 ctxt.pop() 864 if current_context: 865 ctxt.pop() 757 866 else: 758 867 yield kind, data, pos 759 868 760 869 def extract(self, stream, gettext_functions=GETTEXT_FUNCTIONS, 761 search_text=True, comment_stack=None ):870 search_text=True, comment_stack=None, context_stack=None): 762 871 """Extract localizable strings from the given template stream. 763 872 764 873 For every string found, this function yields a ``(lineno, function, 765 874 message, comments)`` tuple, where: 766 875 767 876 * ``lineno`` is the number of the line on which the string was found, 768 877 * ``function`` is the name of the ``gettext`` function used (if the 769 878 string was extracted from embedded Python code), and … … 772 881 arguments). 773 882 * ``comments`` is a list of comments related to the message, extracted 774 883 from ``i18n:comment`` attributes found in the markup 775 884 776 885 >>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/"> 777 886 ... <head> 778 887 ... <title>Example</title> … … 789 898 6, None, u'Example' 790 899 7, '_', u'Hello, %(name)s' 791 900 8, 'ngettext', (u'You have %d item', u'You have %d items', None) 792 901 793 902 :param stream: the event stream to extract strings from; can be a 794 903 regular stream or a template stream 795 904 :param gettext_functions: a sequence of function names that should be … … 797 906 functions 798 907 :param search_text: whether the content of text nodes should be 799 908 extracted (used internally) 800 909 801 910 :note: Changed in 0.4.1: For a function with multiple string arguments 802 911 (such as ``ngettext``), a single item with a tuple of strings is 803 912 yielded, instead an item for each string argument. … … 808 917 search_text = False 809 918 if comment_stack is None: 810 919 comment_stack = [] 920 if context_stack is None: 921 context_stack = [] 811 922 skip = 0 812 923 813 924 xml_lang = XML_NAMESPACE['lang'] … … 834 945 elif not skip and search_text and kind is TEXT: 835 946 text = data.strip() 836 947 if text and [ch for ch in text if ch.isalpha()]: 837 yield pos[1], None, text, comment_stack[-1:] 948 yield contextify(pos[1], None, text, comment_stack[-1:], 949 context_stack[-1:]) 838 950 839 951 elif kind is EXPR or kind is EXEC: 840 952 for funcname, strings in extract_from_code(data, … … 845 957 elif kind is SUB: 846 958 directives, substream = data 847 959 in_comment = False 960 in_context = False 848 961 849 962 for idx, directive in enumerate(directives): 850 963 # Do a first loop to see if there's a comment directive … … 858 971 for message in self.extract( 859 972 substream, gettext_functions, 860 973 search_text=search_text and not skip, 861 comment_stack=comment_stack): 974 comment_stack=comment_stack, 975 context_stack=context_stack): 862 976 yield message 863 977 directives.pop(idx) 978 elif isinstance(directive, ContextDirective): 979 in_context = True 980 context_stack.append(directive.context) 981 if len(directives) == 1: 982 for message in self.extract( 983 substream, gettext_functions, 984 search_text=search_text and not skip, 985 comment_stack=comment_stack, 986 context_stack=context_stack): 987 yield message 988 directives.pop(idx) 864 989 elif not isinstance(directive, I18NDirective): 865 990 # Remove all other non i18n directives from the process 866 991 directives.pop(idx) 867 992 868 if not directives and not in_comment :993 if not directives and not in_comment and not in_context: 869 994 # Extract content if there's no directives because 870 995 # strip was pop'ed and not because comment was pop'ed. 871 996 # Extraction in this case has been taken care of. … … 879 1004 for message in directive.extract(self, 880 1005 substream, gettext_functions, 881 1006 search_text=search_text and not skip, 882 comment_stack=comment_stack): 1007 comment_stack=comment_stack, 1008 context_stack=context_stack): 883 1009 yield message 884 1010 else: 885 1011 for message in self.extract( 886 1012 substream, gettext_functions, 887 1013 search_text=search_text and not skip, 888 comment_stack=comment_stack): 1014 comment_stack=comment_stack, 1015 context_stack=context_stack): 889 1016 yield message 890 1017 891 1018 if in_comment: 892 1019 comment_stack.pop() 893 1020 1021 if in_context: 1022 context_stack.pop() 1023 894 1024 def get_directive_index(self, dir_cls): 895 1025 total = len(self._dir_order) 896 1026 if dir_cls in self._dir_order: … … 900 1030 def setup(self, template): 901 1031 """Convenience function to register the `Translator` filter and the 902 1032 related directives with the given template. 903 1033 904 1034 :param template: a `Template` instance 905 1035 """ 906 1036 template.filters.insert(0, self) … … 922 1052 923 1053 class MessageBuffer(object): 924 1054 """Helper class for managing internationalized mixed content. 925 1055 926 1056 :since: version 0.5 927 1057 """ 928 1058 929 1059 def __init__(self, directive=None): 930 1060 """Initialize the message buffer. 931 1061 932 1062 :param directive: the directive owning the buffer 933 1063 :type directive: I18NDirective 934 1064 """ … … 955 1085 956 1086 def append(self, kind, data, pos): 957 1087 """Append a stream event to the buffer. 958 1088 959 1089 :param kind: the stream event kind 960 1090 :param data: the event data 961 1091 :param pos: the position of the event in the source … … 987 1117 params = "(%s)" % params 988 1118 raise IndexError("%d parameters%s given to 'i18n:%s' but " 989 1119 "%d or more expressions used in '%s', line %s" 990 % (len(self.orig_params), params, 1120 % (len(self.orig_params), params, 991 1121 self.directive.tagname, 992 1122 len(self.orig_params) + 1, 993 1123 os.path.basename(pos[0] or … … 997 1127 self._add_event(self.stack[-1], (kind, data, pos)) 998 1128 self.values[param] = (kind, data, pos) 999 1129 else: 1000 if kind is START: 1130 if kind is START: 1001 1131 self.string.append('[%d:' % self.order) 1002 1132 self.stack.append(self.order) 1003 1133 self._add_event(self.stack[-1], (kind, data, pos)) … … 1019 1149 def translate(self, string, regex=re.compile(r'%\((\w+)\)s')): 1020 1150 """Interpolate the given message translation with the events in the 1021 1151 buffer and return the translated stream. 1022 1152 1023 1153 :param string: the translated message string 1024 1154 """ 1025 1155 substream = None … … 1108 1238 def parse_msg(string, regex=re.compile(r'(?:\[(\d+)\:)|(?<!\\)\]')): 1109 1239 """Parse a translated message using Genshi mixed content message 1110 1240 formatting. 1111 1241 1112 1242 >>> parse_msg("See [1:Help].") 1113 1243 [(0, 'See '), (1, 'Help'), (0, '.')] 1114 1244 1115 1245 >>> parse_msg("See [1:our [2:Help] page] for details.") 1116 1246 [(0, 'See '), (1, 'our '), (2, 'Help'), (1, ' page'), (0, ' for details.')] 1117 1247 1118 1248 >>> parse_msg("[2:Details] finden Sie in [1:Hilfe].") 1119 1249 [(2, 'Details'), (0, ' finden Sie in '), (1, 'Hilfe'), (0, '.')] 1120 1250 1121 1251 >>> parse_msg("[1:] Bilder pro Seite anzeigen.") 1122 1252 [(1, ''), (0, ' Bilder pro Seite anzeigen.')] 1123 1253 1124 1254 :param string: the translated message string 1125 1255 :return: a list of ``(order, string)`` tuples 1126 1256 :rtype: `list` … … 1152 1282 1153 1283 def extract_from_code(code, gettext_functions): 1154 1284 """Extract strings from Python bytecode. 1155 1285 1156 1286 >>> from genshi.template.eval import Expression 1157 1287 >>> expr = Expression('_("Hello")') 1158 1288 >>> list(extract_from_code(expr, GETTEXT_FUNCTIONS)) 1159 1289 [('_', u'Hello')] 1160 1290 1161 1291 >>> expr = Expression('ngettext("You have %(num)s item", ' 1162 1292 ... '"You have %(num)s items", num)') 1163 1293 >>> list(extract_from_code(expr, GETTEXT_FUNCTIONS)) 1164 1294 [('ngettext', (u'You have %(num)s item', u'You have %(num)s items', None))] 1165 1295 1166 1296 :param code: the `Code` object 1167 1297 :type code: `genshi.template.eval.Code` 1168 1298 :param gettext_functions: a sequence of function names … … 1202 1332 1203 1333 def extract(fileobj, keywords, comment_tags, options): 1204 1334 """Babel extraction method for Genshi templates. 1205 1335 1206 1336 :param fileobj: the file-like object the messages should be extracted from 1207 1337 :param keywords: a list of keywords (i.e. function names) that should be 1208 1338 recognized as translation functions -
examples/bench/bigtable.py
56 56 </table> 57 57 """) 58 58 59 genshi_tmpl_i18n = MarkupTemplate(""" 60 <table xmlns:py="http://genshi.edgewall.org/" 61 xmlns:i18n="http://genshi.edgewall.org/i18n"> 62 <tr py:for="row in table"> 63 <td py:for="c in row.values()" i18n:ctxt="foo" py:content="c"/> 64 </tr> 65 </table> 66 """) 67 59 68 genshi_tmpl2 = MarkupTemplate(""" 60 69 <table xmlns:py="http://genshi.edgewall.org/">$table</table> 61 70 """) … … 103 112 stream = genshi_tmpl.generate(table=table) 104 113 stream.render('html', strip_whitespace=False) 105 114 115 def test_genshi_i18n(): 116 """Genshi template w/ i18n""" 117 stream = genshi_tmpl_i18n.generate(table=table) 118 stream.render('html', strip_whitespace=False) 119 106 120 def test_genshi_text(): 107 121 """Genshi text template""" 108 122 stream = genshi_text_tmpl.generate(table=table) … … 167 181 et.tostring(_table) 168 182 169 183 if cet: 170 def test_cet(): 184 def test_cet(): 171 185 """cElementTree""" 172 186 _table = cet.Element('table') 173 187 for row in table: … … 196 210 197 211 198 212 def run(which=None, number=10): 199 tests = ['test_builder', 'test_genshi', 'test_genshi_ text',213 tests = ['test_builder', 'test_genshi', 'test_genshi_i18n', 'test_genshi_text', 200 214 'test_genshi_builder', 'test_mako', 'test_kid', 'test_kid_et', 201 215 'test_et', 'test_cet', 'test_clearsilver', 'test_django'] 202 216
