Ticket #129: i18n_directives_full_with_py_strip.patch
| File i18n_directives_full_with_py_strip.patch, 85.2 KB (added by palgarvio, 15 years ago) |
|---|
-
genshi/filters/tests/i18n.py
13 13 14 14 from datetime import datetime 15 15 import doctest 16 from gettext import NullTranslations 16 from gettext import NullTranslations, c2py 17 17 from StringIO import StringIO 18 18 import unittest 19 19 20 20 from genshi.core import Attrs 21 21 from genshi.template import MarkupTemplate 22 from genshi.filters.i18n import Translator, extract 22 from genshi.filters.i18n import Translator, extract, setup_i18n 23 23 from genshi.input import HTML 24 24 25 25 26 26 class DummyTranslations(NullTranslations): 27 _domains = {} 27 28 28 def __init__(self, catalog ):29 def __init__(self, catalog=()): 29 30 NullTranslations.__init__(self) 30 self._catalog = catalog 31 self._catalog = catalog or {} 32 self.plural = c2py('(n != 1)') 33 34 def add_domain(self, domain, catalog): 35 translation = DummyTranslations(catalog) 36 translation.add_fallback(self) 37 self._domains[domain] = translation 38 39 def _domain_call(self, func, domain, *args, **kwargs): 40 return getattr(self._domains.get(domain, self), func)(*args, **kwargs) 31 41 32 42 def ugettext(self, message): 33 43 missing = object() … … 37 47 return self._fallback.ugettext(message) 38 48 return unicode(message) 39 49 return tmsg 50 51 def dugettext(self, domain, message): 52 return self._domain_call('ugettext', domain, message) 53 54 def ungettext(self, msgid1, msgid2, n): 55 try: 56 return self._catalog[(msgid1, self.plural(n))] 57 except KeyError: 58 if self._fallback: 59 return self._fallback.ngettext(msgid1, msgid2, n) 60 if n == 1: 61 return msgid1 62 else: 63 return msgid2 64 65 def dungettext(self, domain, singular, plural, numeral): 66 return self._domain_call('ungettext', domain, singular, plural, numeral) 40 67 41 68 42 69 class TranslatorTestCase(unittest.TestCase): … … 162 189 </p> 163 190 </html>""") 164 191 translator = Translator() 192 tmpl.add_directives(Translator.NAMESPACE, translator) 165 193 messages = list(translator.extract(tmpl.stream)) 166 194 self.assertEqual(1, len(messages)) 167 195 self.assertEqual('Please see [1:Help] for details.', messages[0][2]) … … 175 203 </html>""") 176 204 gettext = lambda s: u"Für Details siehe bitte [1:Hilfe]." 177 205 translator = Translator(gettext) 178 tmpl.filters.insert(0, translator) 179 tmpl.add_directives(Translator.NAMESPACE, translator) 206 setup_i18n(tmpl, translator) 180 207 self.assertEqual("""<html> 181 208 <p>Für Details siehe bitte <a href="help.html">Hilfe</a>.</p> 182 209 </html>""", tmpl.generate().render()) … … 189 216 </p> 190 217 </html>""") 191 218 translator = Translator() 219 tmpl.add_directives(Translator.NAMESPACE, translator) 192 220 messages = list(translator.extract(tmpl.stream)) 193 221 self.assertEqual(1, len(messages)) 194 222 self.assertEqual('Please see [1:[2:Help] page] for details.', … … 203 231 </html>""") 204 232 gettext = lambda s: u"Für Details siehe bitte [1:[2:Hilfeseite]]." 205 233 translator = Translator(gettext) 206 tmpl.filters.insert(0, translator) 207 tmpl.add_directives(Translator.NAMESPACE, translator) 234 setup_i18n(tmpl, translator) 208 235 self.assertEqual("""<html> 209 236 <p>Für Details siehe bitte <a href="help.html"><em>Hilfeseite</em></a>.</p> 210 237 </html>""", tmpl.generate().render()) … … 217 244 </p> 218 245 </html>""") 219 246 translator = Translator() 247 tmpl.add_directives(Translator.NAMESPACE, translator) 220 248 messages = list(translator.extract(tmpl.stream)) 221 249 self.assertEqual(1, len(messages)) 222 250 self.assertEqual('Show me [1:] entries per page.', messages[0][2]) … … 230 258 </html>""") 231 259 gettext = lambda s: u"[1:] Einträge pro Seite anzeigen." 232 260 translator = Translator(gettext) 233 tmpl.filters.insert(0, translator) 234 tmpl.add_directives(Translator.NAMESPACE, translator) 261 setup_i18n(tmpl, translator) 235 262 self.assertEqual("""<html> 236 263 <p><input type="text" name="num"/> Einträge pro Seite anzeigen.</p> 237 264 </html>""", tmpl.generate().render()) … … 244 271 </p> 245 272 </html>""") 246 273 translator = Translator() 274 tmpl.add_directives(Translator.NAMESPACE, translator) 247 275 messages = list(translator.extract(tmpl.stream)) 248 276 self.assertEqual(1, len(messages)) 249 277 self.assertEqual('Please see [1:Help] for [2:details].', messages[0][2]) … … 257 285 </html>""") 258 286 gettext = lambda s: u"Für [2:Details] siehe bitte [1:Hilfe]." 259 287 translator = Translator(gettext) 260 tmpl.filters.insert(0, translator) 261 tmpl.add_directives(Translator.NAMESPACE, translator) 288 setup_i18n(tmpl, translator) 262 289 self.assertEqual("""<html> 263 290 <p>Für <em>Details</em> siehe bitte <a href="help.html">Hilfe</a>.</p> 264 291 </html>""", tmpl.generate().render()) … … 271 298 </p> 272 299 </html>""") 273 300 translator = Translator() 301 tmpl.add_directives(Translator.NAMESPACE, translator) 274 302 messages = list(translator.extract(tmpl.stream)) 275 303 self.assertEqual(1, len(messages)) 276 304 self.assertEqual('Show me [1:] entries per page, starting at page [2:].', … … 285 313 </html>""") 286 314 gettext = lambda s: u"[1:] Einträge pro Seite, beginnend auf Seite [2:]." 287 315 translator = Translator(gettext) 288 tmpl.filters.insert(0, translator) 289 tmpl.add_directives(Translator.NAMESPACE, translator) 316 setup_i18n(tmpl, translator) 290 317 self.assertEqual("""<html> 291 318 <p><input type="text" name="num"/> Eintr\xc3\xa4ge pro Seite, beginnend auf Seite <input type="text" name="num"/>.</p> 292 319 </html>""", tmpl.generate().render()) … … 299 326 </p> 300 327 </html>""") 301 328 translator = Translator() 329 tmpl.add_directives(Translator.NAMESPACE, translator) 302 330 messages = list(translator.extract(tmpl.stream)) 303 331 self.assertEqual(1, len(messages)) 304 332 self.assertEqual('Hello, %(name)s!', messages[0][2]) … … 312 340 </html>""") 313 341 gettext = lambda s: u"Hallo, %(name)s!" 314 342 translator = Translator(gettext) 315 tmpl.filters.insert(0, translator) 316 tmpl.add_directives(Translator.NAMESPACE, translator) 343 setup_i18n(tmpl, translator) 317 344 self.assertEqual("""<html> 318 345 <p>Hallo, Jim!</p> 319 346 </html>""", tmpl.generate(user=dict(name='Jim')).render()) … … 327 354 </html>""") 328 355 gettext = lambda s: u"%(name)s, sei gegrüßt!" 329 356 translator = Translator(gettext) 330 tmpl.filters.insert(0, translator) 331 tmpl.add_directives(Translator.NAMESPACE, translator) 357 setup_i18n(tmpl, translator) 332 358 self.assertEqual("""<html> 333 359 <p>Jim, sei gegrüßt!</p> 334 360 </html>""", tmpl.generate(user=dict(name='Jim')).render()) … … 342 368 </html>""") 343 369 gettext = lambda s: u"Sei gegrüßt, [1:Alter]!" 344 370 translator = Translator(gettext) 345 tmpl.filters.insert(0, translator) 346 tmpl.add_directives(Translator.NAMESPACE, translator) 371 setup_i18n(tmpl, translator) 347 372 self.assertEqual("""<html> 348 373 <p>Sei gegrüßt, <a href="#42">Alter</a>!</p> 349 374 </html>""", tmpl.generate(anchor='42').render()) … … 356 381 </p> 357 382 </html>""") 358 383 translator = Translator() 384 tmpl.add_directives(Translator.NAMESPACE, translator) 359 385 messages = list(translator.extract(tmpl.stream)) 360 386 self.assertEqual(1, len(messages)) 361 387 self.assertEqual('Posted by %(name)s at %(time)s', messages[0][2]) … … 369 395 </html>""") 370 396 gettext = lambda s: u"%(name)s schrieb dies um %(time)s" 371 397 translator = Translator(gettext) 372 tmpl.filters.insert(0, translator) 373 tmpl.add_directives(Translator.NAMESPACE, translator) 398 setup_i18n(tmpl, translator) 374 399 entry = { 375 400 'author': 'Jim', 376 401 'time': datetime(2008, 4, 1, 14, 30) … … 387 412 </p> 388 413 </html>""") 389 414 translator = Translator() 415 tmpl.add_directives(Translator.NAMESPACE, translator) 390 416 messages = list(translator.extract(tmpl.stream)) 391 417 self.assertEqual(1, len(messages)) 392 418 self.assertEqual('Show me [1:] entries per page.', messages[0][2]) 393 419 394 # FIXME: this currently fails :-/395 # def test_translate_i18n_msg_with_directive(self): 396 # tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 397 # xmlns:i18n="http://genshi.edgewall.org/i18n">398 # <p i18n:msg=""> 399 # Show me <input type="text" name="num" py:attrs="{'value': x}" /> entries per page. 400 # </p> 401 # </html>""") 402 # gettext = lambda s: u"[1:] Einträge pro Seite anzeigen." 403 # tmpl.filters.insert(0, Translator(gettext))404 #self.assertEqual("""<html>405 #<p><input type="text" name="num" value="x"/> Einträge pro Seite anzeigen.</p>406 #</html>""", tmpl.generate().render())420 def test_translate_i18n_msg_with_directive(self): 421 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 422 xmlns:i18n="http://genshi.edgewall.org/i18n"> 423 <p i18n:msg=""> 424 Show me <input type="text" name="num" py:attrs="{'value': 'x'}" /> entries per page. 425 </p> 426 </html>""") 427 gettext = lambda s: u"[1:] Einträge pro Seite anzeigen." 428 translator = Translator(gettext) 429 setup_i18n(tmpl, translator) 430 self.assertEqual("""<html> 431 <p><input type="text" name="num" value="x"/> Einträge pro Seite anzeigen.</p> 432 </html>""", tmpl.generate().render()) 407 433 408 434 def test_extract_i18n_msg_with_comment(self): 409 435 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 410 436 xmlns:i18n="http://genshi.edgewall.org/i18n"> 437 <p i18n:comment="As in foo bar" i18n:msg="">Foo</p> 438 </html>""") 439 translator = Translator() 440 tmpl.add_directives(Translator.NAMESPACE, translator) 441 messages = list(translator.extract(tmpl.stream)) 442 self.assertEqual(1, len(messages)) 443 self.assertEqual((3, None, u'Foo', ['As in foo bar']), messages[0]) 444 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 445 xmlns:i18n="http://genshi.edgewall.org/i18n"> 411 446 <p i18n:msg="" i18n:comment="As in foo bar">Foo</p> 412 447 </html>""") 413 448 translator = Translator() 449 tmpl.add_directives(Translator.NAMESPACE, translator) 414 450 messages = list(translator.extract(tmpl.stream)) 415 451 self.assertEqual(1, len(messages)) 416 452 self.assertEqual((3, None, u'Foo', ['As in foo bar']), messages[0]) … … 422 458 </html>""") 423 459 gettext = lambda s: u"Voh" 424 460 translator = Translator(gettext) 425 tmpl.filters.insert(0, translator) 426 tmpl.add_directives(Translator.NAMESPACE, translator) 461 setup_i18n(tmpl, translator) 427 462 self.assertEqual("""<html> 428 463 <p>Voh</p> 429 464 </html>""", tmpl.generate().render()) … … 461 496 <p i18n:msg="" i18n:comment="As in foo bar">Foo</p> 462 497 </html>""") 463 498 translator = Translator(DummyTranslations({'Foo': 'Voh'})) 464 tmpl.filters.insert(0, translator) 465 tmpl.add_directives(Translator.NAMESPACE, translator) 499 setup_i18n(tmpl, translator) 466 500 self.assertEqual("""<html> 467 501 <p>Voh</p> 468 502 </html>""", tmpl.generate().render()) 503 504 def test_translate_mixing_py_and_i18n_directives(self): 505 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 506 xmlns:i18n="http://genshi.edgewall.org/i18n"> 507 <p i18n:msg="" i18n:comment="As in foo bar" py:strip="">Foo</p> 508 <p py:strip="" i18n:msg="" i18n:comment="As in foo bar">Foo</p> 509 </html>""") 510 translator = Translator(DummyTranslations({'Foo': 'Voh'})) 511 setup_i18n(tmpl, translator) 512 self.assertEqual("""<html> 513 Voh 514 Voh 515 </html>""", tmpl.generate().render()) 516 517 def test_translate_i18n_domain_with_msg_directives(self): 518 #"""translate with i18n:domain and nested i18n:msg directives """ 469 519 520 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 521 xmlns:i18n="http://genshi.edgewall.org/i18n"> 522 <div i18n:domain="foo"> 523 <p i18n:msg="">FooBar</p> 524 <p i18n:msg="">Bar</p> 525 </div> 526 </html>""") 527 translations = DummyTranslations({'Bar': 'Voh'}) 528 translations.add_domain('foo', {'FooBar': 'BarFoo', 'Bar': 'PT_Foo'}) 529 translator = Translator(translations) 530 setup_i18n(tmpl, translator) 531 self.assertEqual("""<html> 532 <div> 533 <p>BarFoo</p> 534 <p>PT_Foo</p> 535 </div> 536 </html>""", tmpl.generate().render()) 537 538 def test_translate_i18n_domain_with_inline_directives(self): 539 #"""translate with inlined i18n:domain and i18n:msg directives""" 540 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 541 xmlns:i18n="http://genshi.edgewall.org/i18n"> 542 <p i18n:msg="" i18n:domain="foo">FooBar</p> 543 </html>""") 544 translations = DummyTranslations({'Bar': 'Voh'}) 545 translations.add_domain('foo', {'FooBar': 'BarFoo'}) 546 translator = Translator(translations) 547 setup_i18n(tmpl, translator) 548 self.assertEqual("""<html> 549 <p>BarFoo</p> 550 </html>""", tmpl.generate().render()) 551 552 def test_translate_i18n_domain_without_msg_directives(self): 553 #"""translate domain call without i18n:msg directives still uses current domain""" 554 555 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 556 xmlns:i18n="http://genshi.edgewall.org/i18n"> 557 <p i18n:msg="">Bar</p> 558 <div i18n:domain="foo"> 559 <p i18n:msg="">FooBar</p> 560 <p i18n:msg="">Bar</p> 561 <p>Bar</p> 562 </div> 563 <p>Bar</p> 564 </html>""") 565 translations = DummyTranslations({'Bar': 'Voh'}) 566 translations.add_domain('foo', {'FooBar': 'BarFoo', 'Bar': 'PT_Foo'}) 567 translator = Translator(translations) 568 setup_i18n(tmpl, translator) 569 self.assertEqual("""<html> 570 <p>Voh</p> 571 <div> 572 <p>BarFoo</p> 573 <p>PT_Foo</p> 574 <p>PT_Foo</p> 575 </div> 576 <p>Voh</p> 577 </html>""", tmpl.generate().render()) 578 579 def test_translate_i18n_domain_as_directive_not_attribute(self): 580 #"""translate with domain as directive""" 581 582 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 583 xmlns:i18n="http://genshi.edgewall.org/i18n"> 584 <i18n:domain name="foo"> 585 <p i18n:msg="">FooBar</p> 586 <p i18n:msg="">Bar</p> 587 <p>Bar</p> 588 </i18n:domain> 589 <p>Bar</p> 590 </html>""") 591 translations = DummyTranslations({'Bar': 'Voh'}) 592 translations.add_domain('foo', {'FooBar': 'BarFoo', 'Bar': 'PT_Foo'}) 593 translator = Translator(translations) 594 setup_i18n(tmpl, translator) 595 self.assertEqual("""<html> 596 <p>BarFoo</p> 597 <p>PT_Foo</p> 598 <p>PT_Foo</p> 599 <p>Voh</p> 600 </html>""", tmpl.generate().render()) 601 602 def test_translate_i18n_domain_nested_directives(self): 603 #"""translate with nested i18n:domain directives""" 604 605 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 606 xmlns:i18n="http://genshi.edgewall.org/i18n"> 607 <p i18n:msg="">Bar</p> 608 <div i18n:domain="foo"> 609 <p i18n:msg="">FooBar</p> 610 <p i18n:domain="bar" i18n:msg="">Bar</p> 611 <p>Bar</p> 612 </div> 613 <p>Bar</p> 614 </html>""") 615 translations = DummyTranslations({'Bar': 'Voh'}) 616 translations.add_domain('foo', {'FooBar': 'BarFoo', 'Bar': 'foo_Bar'}) 617 translations.add_domain('bar', {'Bar': 'bar_Bar'}) 618 translator = Translator(translations) 619 setup_i18n(tmpl, translator) 620 self.assertEqual("""<html> 621 <p>Voh</p> 622 <div> 623 <p>BarFoo</p> 624 <p>bar_Bar</p> 625 <p>foo_Bar</p> 626 </div> 627 <p>Voh</p> 628 </html>""", tmpl.generate().render()) 629 630 def test_translate_i18n_domain_with_empty_nested_domain_directive(self): 631 #"""translate with empty nested i18n:domain directive does not use dngettext""" 632 633 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 634 xmlns:i18n="http://genshi.edgewall.org/i18n"> 635 <p i18n:msg="">Bar</p> 636 <div i18n:domain="foo"> 637 <p i18n:msg="">FooBar</p> 638 <p i18n:domain="" i18n:msg="">Bar</p> 639 <p>Bar</p> 640 </div> 641 <p>Bar</p> 642 </html>""") 643 translations = DummyTranslations({'Bar': 'Voh'}) 644 translations.add_domain('foo', {'FooBar': 'BarFoo', 'Bar': 'foo_Bar'}) 645 translations.add_domain('bar', {'Bar': 'bar_Bar'}) 646 translator = Translator(translations) 647 setup_i18n(tmpl, translator) 648 self.assertEqual("""<html> 649 <p>Voh</p> 650 <div> 651 <p>BarFoo</p> 652 <p>Voh</p> 653 <p>foo_Bar</p> 654 </div> 655 <p>Voh</p> 656 </html>""", tmpl.generate().render()) 657 658 def test_translate_i18n_choose_as_attribute(self): 659 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 660 xmlns:i18n="http://genshi.edgewall.org/i18n"> 661 <div i18n:choose="one"> 662 <p i18n:singular="">FooBar</p> 663 <p i18n:plural="">FooBars</p> 664 </div> 665 <div i18n:choose="two"> 666 <p i18n:singular="">FooBar</p> 667 <p i18n:plural="">FooBars</p> 668 </div> 669 </html>""") 670 translations = DummyTranslations() 671 translator = Translator(translations) 672 setup_i18n(tmpl, translator) 673 self.assertEqual("""<html> 674 <div> 675 <p>FooBar</p> 676 </div> 677 <div> 678 <p>FooBars</p> 679 </div> 680 </html>""", tmpl.generate(one=1, two=2).render()) 681 682 def test_translate_i18n_choose_as_directive(self): 683 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 684 xmlns:i18n="http://genshi.edgewall.org/i18n"> 685 <i18n:choose numeral="two"> 686 <p i18n:singular="">FooBar</p> 687 <p i18n:plural="">FooBars</p> 688 </i18n:choose> 689 <i18n:choose numeral="one"> 690 <p i18n:singular="">FooBar</p> 691 <p i18n:plural="">FooBars</p> 692 </i18n:choose> 693 </html>""") 694 translations = DummyTranslations() 695 translator = Translator(translations) 696 setup_i18n(tmpl, translator) 697 self.assertEqual("""<html> 698 <p>FooBars</p> 699 <p>FooBar</p> 700 </html>""", tmpl.generate(one=1, two=2).render()) 701 702 def test_translate_i18n_choose_as_attribute_with_params(self): 703 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 704 xmlns:i18n="http://genshi.edgewall.org/i18n"> 705 <div i18n:choose="two; fname, lname"> 706 <p i18n:singular="">Foo $fname $lname</p> 707 <p i18n:plural="">Foos $fname $lname</p> 708 </div> 709 </html>""") 710 translations = DummyTranslations({ 711 ('Foo %(fname)s %(lname)s', 0): 'Voh %(fname)s %(lname)s', 712 ('Foo %(fname)s %(lname)s', 1): 'Vohs %(fname)s %(lname)s', 713 'Foo %(fname)s %(lname)s': 'Voh %(fname)s %(lname)s', 714 'Foos %(fname)s %(lname)s': 'Vohs %(fname)s %(lname)s', 715 }) 716 translator = Translator(translations) 717 setup_i18n(tmpl, translator) 718 self.assertEqual("""<html> 719 <div> 720 <p>Vohs John Doe</p> 721 </div> 722 </html>""", tmpl.generate(two=2, fname='John', lname='Doe').render()) 723 724 def test_translate_i18n_choose_as_attribute_with_params_and_domain_as_param(self): 725 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 726 xmlns:i18n="http://genshi.edgewall.org/i18n" 727 i18n:domain="foo"> 728 <div i18n:choose="two; fname, lname"> 729 <p i18n:singular="">Foo $fname $lname</p> 730 <p i18n:plural="">Foos $fname $lname</p> 731 </div> 732 </html>""") 733 translations = DummyTranslations() 734 translations.add_domain('foo', { 735 ('Foo %(fname)s %(lname)s', 0): 'Voh %(fname)s %(lname)s', 736 ('Foo %(fname)s %(lname)s', 1): 'Vohs %(fname)s %(lname)s', 737 'Foo %(fname)s %(lname)s': 'Voh %(fname)s %(lname)s', 738 'Foos %(fname)s %(lname)s': 'Vohs %(fname)s %(lname)s', 739 }) 740 translator = Translator(translations) 741 setup_i18n(tmpl, translator) 742 self.assertEqual("""<html> 743 <div> 744 <p>Vohs John Doe</p> 745 </div> 746 </html>""", tmpl.generate(two=2, fname='John', lname='Doe').render()) 747 748 def test_translate_i18n_choose_as_directive_with_params(self): 749 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 750 xmlns:i18n="http://genshi.edgewall.org/i18n"> 751 <i18n:choose numeral="two" params="fname, lname"> 752 <p i18n:singular="">Foo ${fname} ${lname}</p> 753 <p i18n:plural="">Foos ${fname} ${lname}</p> 754 </i18n:choose> 755 <i18n:choose numeral="one" params="fname, lname"> 756 <p i18n:singular="">Foo ${fname} ${lname}</p> 757 <p i18n:plural="">Foos ${fname} ${lname}</p> 758 </i18n:choose> 759 </html>""") 760 translations = DummyTranslations({ 761 ('Foo %(fname)s %(lname)s', 0): 'Voh %(fname)s %(lname)s', 762 ('Foo %(fname)s %(lname)s', 1): 'Vohs %(fname)s %(lname)s', 763 'Foo %(fname)s %(lname)s': 'Voh %(fname)s %(lname)s', 764 'Foos %(fname)s %(lname)s': 'Vohs %(fname)s %(lname)s', 765 }) 766 translator = Translator(translations) 767 setup_i18n(tmpl, translator) 768 self.assertEqual("""<html> 769 <p>Vohs John Doe</p> 770 <p>Voh John Doe</p> 771 </html>""", tmpl.generate(one=1, two=2, 772 fname='John', lname='Doe').render()) 773 774 def test_translate_i18n_choose_as_directive_with_params_and_domain_as_directive(self): 775 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 776 xmlns:i18n="http://genshi.edgewall.org/i18n"> 777 <i18n:domain name="foo"> 778 <i18n:choose numeral="two" params="fname, lname"> 779 <p i18n:singular="">Foo ${fname} ${lname}</p> 780 <p i18n:plural="">Foos ${fname} ${lname}</p> 781 </i18n:choose> 782 </i18n:domain> 783 <i18n:choose numeral="one" params="fname, lname"> 784 <p i18n:singular="">Foo ${fname} ${lname}</p> 785 <p i18n:plural="">Foos ${fname} ${lname}</p> 786 </i18n:choose> 787 </html>""") 788 translations = DummyTranslations() 789 translations.add_domain('foo', { 790 ('Foo %(fname)s %(lname)s', 0): 'Voh %(fname)s %(lname)s', 791 ('Foo %(fname)s %(lname)s', 1): 'Vohs %(fname)s %(lname)s', 792 'Foo %(fname)s %(lname)s': 'Voh %(fname)s %(lname)s', 793 'Foos %(fname)s %(lname)s': 'Vohs %(fname)s %(lname)s', 794 }) 795 translator = Translator(translations) 796 setup_i18n(tmpl, translator) 797 self.assertEqual("""<html> 798 <p>Vohs John Doe</p> 799 <p>Foo John Doe</p> 800 </html>""", tmpl.generate(one=1, two=2, 801 fname='John', lname='Doe').render()) 802 803 def test_extract_i18n_choose_as_attribute(self): 804 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 805 xmlns:i18n="http://genshi.edgewall.org/i18n"> 806 <div i18n:choose="one"> 807 <p i18n:singular="">FooBar</p> 808 <p i18n:plural="">FooBars</p> 809 </div> 810 <div i18n:choose="two"> 811 <p i18n:singular="">FooBar</p> 812 <p i18n:plural="">FooBars</p> 813 </div> 814 </html>""") 815 translator = Translator() 816 tmpl.add_directives(Translator.NAMESPACE, translator) 817 messages = list(translator.extract(tmpl.stream)) 818 self.assertEqual(2, len(messages)) 819 self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), []), messages[0]) 820 self.assertEqual((7, 'ngettext', (u'FooBar', u'FooBars'), []), messages[1]) 821 822 def test_extract_i18n_choose_as_directive(self): 823 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 824 xmlns:i18n="http://genshi.edgewall.org/i18n"> 825 <i18n:choose numeral="two"> 826 <p i18n:singular="">FooBar</p> 827 <p i18n:plural="">FooBars</p> 828 </i18n:choose> 829 <i18n:choose numeral="one"> 830 <p i18n:singular="">FooBar</p> 831 <p i18n:plural="">FooBars</p> 832 </i18n:choose> 833 </html>""") 834 translator = Translator() 835 tmpl.add_directives(Translator.NAMESPACE, translator) 836 messages = list(translator.extract(tmpl.stream)) 837 self.assertEqual(2, len(messages)) 838 self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), []), messages[0]) 839 self.assertEqual((7, 'ngettext', (u'FooBar', u'FooBars'), []), messages[1]) 840 841 def test_extract_i18n_choose_as_attribute_with_params(self): 842 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 843 xmlns:i18n="http://genshi.edgewall.org/i18n"> 844 <div i18n:choose="two; fname, lname"> 845 <p i18n:singular="">Foo $fname $lname</p> 846 <p i18n:plural="">Foos $fname $lname</p> 847 </div> 848 </html>""") 849 translator = Translator() 850 tmpl.add_directives(Translator.NAMESPACE, translator) 851 messages = list(translator.extract(tmpl.stream)) 852 self.assertEqual(1, len(messages)) 853 self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 854 u'Foos %(fname)s %(lname)s'), []), 855 messages[0]) 856 857 def test_extract_i18n_choose_as_attribute_with_params_and_domain_as_param(self): 858 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 859 xmlns:i18n="http://genshi.edgewall.org/i18n" 860 i18n:domain="foo"> 861 <div i18n:choose="two; fname, lname"> 862 <p i18n:singular="">Foo $fname $lname</p> 863 <p i18n:plural="">Foos $fname $lname</p> 864 </div> 865 </html>""") 866 translator = Translator() 867 tmpl.add_directives(Translator.NAMESPACE, translator) 868 messages = list(translator.extract(tmpl.stream)) 869 self.assertEqual(1, len(messages)) 870 self.assertEqual((4, 'ngettext', (u'Foo %(fname)s %(lname)s', 871 u'Foos %(fname)s %(lname)s'), []), 872 messages[0]) 873 874 def test_extract_i18n_choose_as_directive_with_params(self): 875 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 876 xmlns:i18n="http://genshi.edgewall.org/i18n"> 877 <i18n:choose numeral="two" params="fname, lname"> 878 <p i18n:singular="">Foo ${fname} ${lname}</p> 879 <p i18n:plural="">Foos ${fname} ${lname}</p> 880 </i18n:choose> 881 <i18n:choose numeral="one" params="fname, lname"> 882 <p i18n:singular="">Foo ${fname} ${lname}</p> 883 <p i18n:plural="">Foos ${fname} ${lname}</p> 884 </i18n:choose> 885 </html>""") 886 translator = Translator() 887 tmpl.add_directives(Translator.NAMESPACE, translator) 888 messages = list(translator.extract(tmpl.stream)) 889 self.assertEqual(2, len(messages)) 890 self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 891 u'Foos %(fname)s %(lname)s'), []), 892 messages[0]) 893 self.assertEqual((7, 'ngettext', (u'Foo %(fname)s %(lname)s', 894 u'Foos %(fname)s %(lname)s'), []), 895 messages[1]) 896 897 def test_extract_i18n_choose_as_directive_with_params_and_domain_as_directive(self): 898 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 899 xmlns:i18n="http://genshi.edgewall.org/i18n"> 900 <i18n:domain name="foo"> 901 <i18n:choose numeral="two" params="fname, lname"> 902 <p i18n:singular="">Foo ${fname} ${lname}</p> 903 <p i18n:plural="">Foos ${fname} ${lname}</p> 904 </i18n:choose> 905 </i18n:domain> 906 <i18n:choose numeral="one" params="fname, lname"> 907 <p i18n:singular="">Foo ${fname} ${lname}</p> 908 <p i18n:plural="">Foos ${fname} ${lname}</p> 909 </i18n:choose> 910 </html>""") 911 translator = Translator() 912 tmpl.add_directives(Translator.NAMESPACE, translator) 913 messages = list(translator.extract(tmpl.stream)) 914 self.assertEqual(2, len(messages)) 915 self.assertEqual((4, 'ngettext', (u'Foo %(fname)s %(lname)s', 916 u'Foos %(fname)s %(lname)s'), []), 917 messages[0]) 918 self.assertEqual((9, 'ngettext', (u'Foo %(fname)s %(lname)s', 919 u'Foos %(fname)s %(lname)s'), []), 920 messages[1]) 921 922 def test_extract_i18n_choose_as_attribute_with_params_and_comment(self): 923 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 924 xmlns:i18n="http://genshi.edgewall.org/i18n"> 925 <div i18n:choose="two; fname, lname" i18n:comment="As in Foo Bar"> 926 <p i18n:singular="">Foo $fname $lname</p> 927 <p i18n:plural="">Foos $fname $lname</p> 928 </div> 929 </html>""") 930 translator = Translator() 931 tmpl.add_directives(Translator.NAMESPACE, translator) 932 messages = list(translator.extract(tmpl.stream)) 933 self.assertEqual(1, len(messages)) 934 self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 935 u'Foos %(fname)s %(lname)s'), 936 [u'As in Foo Bar']), 937 messages[0]) 938 939 def test_extract_i18n_choose_as_directive_with_params_and_comment(self): 940 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 941 xmlns:i18n="http://genshi.edgewall.org/i18n"> 942 <i18n:choose numeral="two" params="fname, lname" i18n:comment="As in Foo Bar"> 943 <p i18n:singular="">Foo ${fname} ${lname}</p> 944 <p i18n:plural="">Foos ${fname} ${lname}</p> 945 </i18n:choose> 946 </html>""") 947 translator = Translator() 948 tmpl.add_directives(Translator.NAMESPACE, translator) 949 messages = list(translator.extract(tmpl.stream)) 950 self.assertEqual(1, len(messages)) 951 self.assertEqual((3, 'ngettext', (u'Foo %(fname)s %(lname)s', 952 u'Foos %(fname)s %(lname)s'), 953 [u'As in Foo Bar']), 954 messages[0]) 955 956 def test_translate_i18n_domain_with_nested_inlcudes(self): 957 import os, shutil, tempfile 958 from genshi.template.loader import TemplateLoader 959 dirname = tempfile.mkdtemp(suffix='genshi_test') 960 try: 961 for idx in range(7): 962 file1 = open(os.path.join(dirname, 'tmpl%d.html' % idx), 'w') 963 try: 964 file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 965 xmlns:py="http://genshi.edgewall.org/" 966 xmlns:i18n="http://genshi.edgewall.org/i18n" py:strip=""> 967 <div>Included tmpl$idx</div> 968 <p i18n:msg="idx">Bar $idx</p> 969 <p i18n:domain="bar">Bar</p> 970 <p i18n:msg="idx" i18n:domain="">Bar $idx</p> 971 <p i18n:domain="" i18n:msg="idx">Bar $idx</p> 972 <py:if test="idx < 6"> 973 <xi:include href="tmpl${idx}.html" py:with="idx = idx+1"/> 974 </py:if> 975 </html>""") 976 finally: 977 file1.close() 978 979 file2 = open(os.path.join(dirname, 'tmpl10.html'), 'w') 980 try: 981 file2.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 982 xmlns:py="http://genshi.edgewall.org/" 983 xmlns:i18n="http://genshi.edgewall.org/i18n" 984 i18n:domain="foo"> 985 <xi:include href="tmpl${idx}.html" py:with="idx = idx+1"/> 986 </html>""") 987 finally: 988 file2.close() 989 990 def callback(template): 991 translations = DummyTranslations({'Bar %(idx)s': 'Voh %(idx)s'}) 992 translations.add_domain('foo', {'Bar %(idx)s': 'foo_Bar %(idx)s'}) 993 translations.add_domain('bar', {'Bar': 'bar_Bar'}) 994 translator = Translator(translations) 995 setup_i18n(template, translator) 996 loader = TemplateLoader([dirname], callback=callback) 997 tmpl = loader.load('tmpl10.html') 998 999 self.assertEqual("""<html> 1000 <div>Included tmpl0</div> 1001 <p>foo_Bar 0</p> 1002 <p>bar_Bar</p> 1003 <p>Voh 0</p> 1004 <p>Voh 0</p> 1005 <div>Included tmpl1</div> 1006 <p>foo_Bar 1</p> 1007 <p>bar_Bar</p> 1008 <p>Voh 1</p> 1009 <p>Voh 1</p> 1010 <div>Included tmpl2</div> 1011 <p>foo_Bar 2</p> 1012 <p>bar_Bar</p> 1013 <p>Voh 2</p> 1014 <p>Voh 2</p> 1015 <div>Included tmpl3</div> 1016 <p>foo_Bar 3</p> 1017 <p>bar_Bar</p> 1018 <p>Voh 3</p> 1019 <p>Voh 3</p> 1020 <div>Included tmpl4</div> 1021 <p>foo_Bar 4</p> 1022 <p>bar_Bar</p> 1023 <p>Voh 4</p> 1024 <p>Voh 4</p> 1025 <div>Included tmpl5</div> 1026 <p>foo_Bar 5</p> 1027 <p>bar_Bar</p> 1028 <p>Voh 5</p> 1029 <p>Voh 5</p> 1030 <div>Included tmpl6</div> 1031 <p>foo_Bar 6</p> 1032 <p>bar_Bar</p> 1033 <p>Voh 6</p> 1034 <p>Voh 6</p> 1035 </html>""", tmpl.generate(idx=-1).render()) 1036 finally: 1037 shutil.rmtree(dirname) 1038 1039 def test_translate_i18n_domain_with_nested_inlcudes_with_translatable_attrs(self): 1040 import os, shutil, tempfile 1041 from genshi.template.loader import TemplateLoader 1042 dirname = tempfile.mkdtemp(suffix='genshi_test') 1043 try: 1044 for idx in range(4): 1045 file1 = open(os.path.join(dirname, 'tmpl%d.html' % idx), 'w') 1046 try: 1047 file1.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 1048 xmlns:py="http://genshi.edgewall.org/" 1049 xmlns:i18n="http://genshi.edgewall.org/i18n" py:strip=""> 1050 <div>Included tmpl$idx</div> 1051 <p title="${dg('foo', 'Bar %(idx)s') % dict(idx=idx)}" i18n:msg="idx">Bar $idx</p> 1052 <p title="Bar" i18n:domain="bar">Bar</p> 1053 <p title="Bar" i18n:msg="idx" i18n:domain="">Bar $idx</p> 1054 <p i18n:msg="idx" i18n:domain="" title="Bar">Bar $idx</p> 1055 <p i18n:domain="" i18n:msg="idx" title="Bar">Bar $idx</p> 1056 <py:if test="idx < 3"> 1057 <xi:include href="tmpl${idx}.html" py:with="idx = idx+1"/> 1058 </py:if> 1059 </html>""") 1060 finally: 1061 file1.close() 1062 1063 file2 = open(os.path.join(dirname, 'tmpl10.html'), 'w') 1064 try: 1065 file2.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude" 1066 xmlns:py="http://genshi.edgewall.org/" 1067 xmlns:i18n="http://genshi.edgewall.org/i18n" 1068 i18n:domain="foo"> 1069 <xi:include href="tmpl${idx}.html" py:with="idx = idx+1"/> 1070 </html>""") 1071 finally: 1072 file2.close() 1073 1074 translations = DummyTranslations({'Bar %(idx)s': 'Voh %(idx)s', 1075 'Bar': 'Voh'}) 1076 translations.add_domain('foo', {'Bar %(idx)s': 'foo_Bar %(idx)s'}) 1077 translations.add_domain('bar', {'Bar': 'bar_Bar'}) 1078 translator = Translator(translations) 1079 1080 def callback(template): 1081 setup_i18n(template, translator) 1082 loader = TemplateLoader([dirname], callback=callback) 1083 tmpl = loader.load('tmpl10.html') 1084 1085 self.assertEqual("""<html> 1086 <div>Included tmpl0</div> 1087 <p title="foo_Bar 0">foo_Bar 0</p> 1088 <p title="bar_Bar">bar_Bar</p> 1089 <p title="Voh">Voh 0</p> 1090 <p title="Voh">Voh 0</p> 1091 <p title="Voh">Voh 0</p> 1092 <div>Included tmpl1</div> 1093 <p title="foo_Bar 1">foo_Bar 1</p> 1094 <p title="bar_Bar">bar_Bar</p> 1095 <p title="Voh">Voh 1</p> 1096 <p title="Voh">Voh 1</p> 1097 <p title="Voh">Voh 1</p> 1098 <div>Included tmpl2</div> 1099 <p title="foo_Bar 2">foo_Bar 2</p> 1100 <p title="bar_Bar">bar_Bar</p> 1101 <p title="Voh">Voh 2</p> 1102 <p title="Voh">Voh 2</p> 1103 <p title="Voh">Voh 2</p> 1104 <div>Included tmpl3</div> 1105 <p title="foo_Bar 3">foo_Bar 3</p> 1106 <p title="bar_Bar">bar_Bar</p> 1107 <p title="Voh">Voh 3</p> 1108 <p title="Voh">Voh 3</p> 1109 <p title="Voh">Voh 3</p> 1110 </html>""", tmpl.generate(idx=-1, 1111 dg=translations.dugettext).render()) 1112 finally: 1113 shutil.rmtree(dirname) 1114 1115 def test_translate_with_i18n_msg_and_py_strip_directives(self): 1116 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1117 xmlns:i18n="http://genshi.edgewall.org/i18n"> 1118 <p i18n:msg="" i18n:comment="As in foo bar" py:strip="">Foo</p> 1119 <p py:strip="" i18n:msg="" i18n:comment="As in foo bar">Foo</p> 1120 </html>""") 1121 translator = Translator(DummyTranslations({'Foo': 'Voh'})) 1122 setup_i18n(tmpl, translator) 1123 self.assertEqual("""<html> 1124 Voh 1125 Voh 1126 </html>""", tmpl.generate().render()) 1127 1128 def test_translate_i18n_choose_and_py_strip(self): 1129 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1130 xmlns:i18n="http://genshi.edgewall.org/i18n"> 1131 <div i18n:choose="two; fname, lname"> 1132 <p i18n:singular="">Foo $fname $lname</p> 1133 <p i18n:plural="">Foos $fname $lname</p> 1134 </div> 1135 </html>""") 1136 translations = DummyTranslations({ 1137 ('Foo %(fname)s %(lname)s', 0): 'Voh %(fname)s %(lname)s', 1138 ('Foo %(fname)s %(lname)s', 1): 'Vohs %(fname)s %(lname)s', 1139 'Foo %(fname)s %(lname)s': 'Voh %(fname)s %(lname)s', 1140 'Foos %(fname)s %(lname)s': 'Vohs %(fname)s %(lname)s', 1141 }) 1142 translator = Translator(translations) 1143 setup_i18n(tmpl, translator) 1144 self.assertEqual("""<html> 1145 <div> 1146 <p>Vohs John Doe</p> 1147 </div> 1148 </html>""", tmpl.generate(two=2, fname='John', lname='Doe').render()) 1149 1150 def test_translate_i18n_choose_and_domain_and_py_strip(self): 1151 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1152 xmlns:i18n="http://genshi.edgewall.org/i18n" 1153 i18n:domain="foo"> 1154 <div i18n:choose="two; fname, lname"> 1155 <p i18n:singular="">Foo $fname $lname</p> 1156 <p i18n:plural="">Foos $fname $lname</p> 1157 </div> 1158 </html>""") 1159 translations = DummyTranslations() 1160 translations.add_domain('foo', { 1161 ('Foo %(fname)s %(lname)s', 0): 'Voh %(fname)s %(lname)s', 1162 ('Foo %(fname)s %(lname)s', 1): 'Vohs %(fname)s %(lname)s', 1163 'Foo %(fname)s %(lname)s': 'Voh %(fname)s %(lname)s', 1164 'Foos %(fname)s %(lname)s': 'Vohs %(fname)s %(lname)s', 1165 }) 1166 translator = Translator(translations) 1167 setup_i18n(tmpl, translator) 1168 self.assertEqual("""<html> 1169 <div> 1170 <p>Vohs John Doe</p> 1171 </div> 1172 </html>""", tmpl.generate(two=2, fname='John', lname='Doe').render()) 1173 1174 def test_extract_i18n_msg_with_py_strip(self): 1175 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1176 xmlns:i18n="http://genshi.edgewall.org/i18n"> 1177 <p i18n:msg="" py:strip=""> 1178 Please see <a href="help.html">Help</a> for details. 1179 </p> 1180 </html>""") 1181 translator = Translator() 1182 tmpl.add_directives(Translator.NAMESPACE, translator) 1183 messages = list(translator.extract(tmpl.stream)) 1184 self.assertEqual(1, len(messages)) 1185 self.assertEqual((3, None, u'Please see [1:Help] for details.', []), 1186 messages[0]) 1187 1188 def test_extract_i18n_msg_with_py_strip_and_comment(self): 1189 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1190 xmlns:i18n="http://genshi.edgewall.org/i18n"> 1191 <p i18n:msg="" py:strip="" i18n:comment="Foo"> 1192 Please see <a href="help.html">Help</a> for details. 1193 </p> 1194 </html>""") 1195 translator = Translator() 1196 tmpl.add_directives(Translator.NAMESPACE, translator) 1197 messages = list(translator.extract(tmpl.stream)) 1198 self.assertEqual(1, len(messages)) 1199 self.assertEqual((3, None, u'Please see [1:Help] for details.', 1200 ['Foo']), messages[0]) 1201 1202 def test_extract_i18n_choose_as_attribute_and_py_strip(self): 1203 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1204 xmlns:i18n="http://genshi.edgewall.org/i18n"> 1205 <div i18n:choose="one" py:strip=""> 1206 <p i18n:singular="" py:strip="">FooBar</p> 1207 <p i18n:plural="" py:strip="">FooBars</p> 1208 </div> 1209 </html>""") 1210 translator = Translator() 1211 tmpl.add_directives(Translator.NAMESPACE, translator) 1212 messages = list(translator.extract(tmpl.stream)) 1213 self.assertEqual(1, len(messages)) 1214 self.assertEqual((3, 'ngettext', (u'FooBar', u'FooBars'), []), messages[0]) 1215 1216 def test_translate_i18n_domain_with_inline_directive_on_START_NS(self): 1217 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1218 xmlns:i18n="http://genshi.edgewall.org/i18n" i18n:domain="foo"> 1219 <p i18n:msg="">FooBar</p> 1220 </html>""") 1221 translations = DummyTranslations({'Bar': 'Voh'}) 1222 translations.add_domain('foo', {'FooBar': 'BarFoo'}) 1223 translator = Translator(translations) 1224 setup_i18n(tmpl, translator) 1225 self.assertEqual("""<html> 1226 <p>BarFoo</p> 1227 </html>""", tmpl.generate().render()) 1228 1229 def test_translate_i18n_domain_with_inline_directive_on_START_NS_with_py_strip(self): 1230 tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 1231 xmlns:i18n="http://genshi.edgewall.org/i18n" 1232 i18n:domain="foo" py:strip=""> 1233 <p i18n:msg="">FooBar</p> 1234 </html>""") 1235 translations = DummyTranslations({'Bar': 'Voh'}) 1236 translations.add_domain('foo', {'FooBar': 'BarFoo'}) 1237 translator = Translator(translations) 1238 setup_i18n(tmpl, translator) 1239 self.assertEqual(""" 1240 <p>BarFoo</p> 1241 """, tmpl.generate().render()) 470 1242 471 1243 class ExtractTestCase(unittest.TestCase): 472 1244 -
genshi/filters/__init__.py
14 14 """Implementation of a number of stream filters.""" 15 15 16 16 from genshi.filters.html import HTMLFormFiller, HTMLSanitizer 17 from genshi.filters.i18n import Translator 17 from genshi.filters.i18n import Translator, setup_i18n 18 18 from genshi.filters.transform import Transformer 19 19 20 20 __docformat__ = 'restructuredtext en' -
genshi/filters/i18n.py
11 11 # individuals. For the exact contribution history, see the revision 12 12 # history and logs, available at http://genshi.edgewall.org/log/. 13 13 14 """Utilities for internationalization and localization of templates. 14 """Directives and utilities for internationalization and localization of 15 templates. 15 16 16 17 :since: version 0.4 18 :note: Directives support added since version 0.6 17 19 """ 18 20 19 21 from compiler import ast 20 22 from gettext import NullTranslations 23 import os 21 24 import re 22 25 from types import FunctionType 23 26 24 from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \ 25 END_NS, XML_NAMESPACE, _ensure 26 from genshi.template.base import DirectiveFactory, EXPR, SUB, _apply_directives 27 from genshi.template.directives import Directive 27 from genshi.core import Attrs, Namespace, QName, START, END, TEXT, \ 28 XML_NAMESPACE, _ensure, StreamEventKind 29 from genshi.template.base import Context, DirectiveFactory, EXPR, SUB, \ 30 _apply_directives 31 from genshi.template.directives import Directive, StripDirective 28 32 from genshi.template.markup import MarkupTemplate, EXEC 29 33 30 34 __all__ = ['Translator', 'extract'] … … 32 36 33 37 I18N_NAMESPACE = Namespace('http://genshi.edgewall.org/i18n') 34 38 39 MSGBUF = StreamEventKind('MSGBUF') 40 41 class DirectiveExtract(object): 42 """Simple interface for directives to support messages extraction""" 43 44 def extract(self, stream, ctxt): 45 raise NotImplementedError 35 46 36 47 class CommentDirective(Directive): 48 """Implementation of the ``i18n:comment`` template directive which adds 49 translation comments. 50 51 >>> from genshi.filters.i18n import Translator, setup_i18n 52 >>> from genshi.template import MarkupTemplate 53 >>> 54 >>> translator = Translator() 55 >>> 56 >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 57 ... <p i18n:comment="As in Foo Bar">Foo</p> 58 ... </html>''') 59 >>> 60 >>> setup_i18n(tmpl, translator) 61 >>> list(translator.extract(tmpl.stream)) 62 [(2, None, u'Foo', [u'As in Foo Bar'])] 63 >>> 64 """ 37 65 38 __slots__ = [ ]66 __slots__ = ['comment'] 39 67 40 @classmethod 41 def attach(cls, template, stream, value, namespaces, pos): 42 return None, stream 68 def __init__(self, value, template, hints=None, namespaces=None, 69 lineno=-1, offset=-1): 70 Directive.__init__(self, None, template, namespaces, lineno, offset) 71 self.comment = value 43 72 73 def __call__(self, stream, directives, ctxt, **vars): 74 return stream 44 75 45 class MsgDirective(Directive): 76 class MsgDirective(Directive, DirectiveExtract): 77 r"""Implementation of the ``i18n:msg`` directive which marks inner content 78 as translatable. Consider the following examples: 79 80 >>> from genshi.filters.i18n import Translator, setup_i18n 81 >>> from genshi.template import MarkupTemplate 82 >>> 83 >>> translator = Translator() 84 >>> 85 >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 86 ... <div i18n:msg=""> 87 ... <p>Foo</p> 88 ... <p>Bar</p> 89 ... </div> 90 ... <p i18n:msg="">Foo <em>bar</em>!</p> 91 ... </html>''') 92 >>> 93 >>> setup_i18n(tmpl, translator) 94 >>> 95 >>> list(translator.extract(tmpl.stream)) 96 [(2, None, u'[1:Foo]\n [2:Bar]', []), (6, None, u'Foo [1:bar]!', [])] 97 >>> print tmpl.generate().render() 98 <html> 99 <div><p>Foo</p> 100 <p>Bar</p></div> 101 <p>Foo <em>bar</em>!</p> 102 </html> 103 >>> 104 >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 105 ... <div i18n:msg="fname, lname"> 106 ... <p>First Name: ${fname}</p> 107 ... <p>Last Name: ${lname}</p> 108 ... </div> 109 ... <p i18n:msg="">Foo <em>bar</em>!</p> 110 ... </html>''') 111 >>> setup_i18n(tmpl, translator) 112 >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 113 [(2, None, u'[1:First Name: %(fname)s]\n [2:Last Name: %(lname)s]', []), 114 (6, None, u'Foo [1:bar]!', [])] 115 >>> 116 >>> tmpl = MarkupTemplate('''<html xmlns:i18n="http://genshi.edgewall.org/i18n"> 117 ... <div i18n:msg="fname, lname"> 118 ... <p>First Name: ${fname}</p> 119 ... <p>Last Name: ${lname}</p> 120 ... </div> 121 ... <p i18n:msg="">Foo <em>bar</em>!</p> 122 ... </html>''') 123 >>> setup_i18n(tmpl, translator) 124 >>> print tmpl.generate(fname='John', lname='Doe').render() 125 <html> 126 <div><p>First Name: John 127 <p>Last Name: Doe</div> 128 <p>Foo <em>bar</em>!</p> 129 </html> 130 >>> 131 132 Starting and ending white-space is stripped of to make it simpler for 133 translators. Stripping it is not that important since it's on the html 134 source, the rendered output will remain the same. 135 """ 46 136 47 137 __slots__ = ['params'] 48 138 49 139 def __init__(self, value, template, hints=None, namespaces=None, 50 140 lineno=-1, offset=-1): 51 141 Directive.__init__(self, None, template, namespaces, lineno, offset) 52 self.params = [name.strip() for name in value.split(',')] 142 self.params = [param.strip() for param in value.split(',') if param] 143 144 @classmethod 145 def attach(cls, template, stream, value, namespaces, pos): 146 if type(value) is dict: 147 value = value.get('params', '').strip() 148 return super(MsgDirective, cls).attach(template, stream, value.strip(), 149 namespaces, pos) 53 150 54 151 def __call__(self, stream, directives, ctxt, **vars): 152 153 gettext = ctxt.get('_i18n.gettext') 154 dgettext = ctxt.get('_i18n.dgettext') 155 if ctxt.get('_i18n.domain'): 156 assert callable(dgettext), "No domain gettext function passed" 157 gettext = lambda msg: dgettext(ctxt.get('_i18n.domain'), msg) 158 159 msgbuf = MessageBuffer(self.params) 160 161 new_stream = [] 162 stream = iter(_apply_directives(stream, directives, ctxt)) 163 strip_directive = [d for d in directives if 164 isinstance(d, StripDirective)] 165 new_stream.append(stream.next()) 166 previous = stream.next() 167 for kind, data, pos in stream: 168 if kind is SUB and not strip_directive: 169 # py:attrs for example 170 subdirectives, substream = data 171 for skind, sdata, spos in _apply_directives(substream, 172 subdirectives, 173 ctxt): 174 try: 175 msgbuf.append(*previous) 176 previous = skind, sdata, spos 177 except IndexError: 178 raise IndexError("Not enough parameters passed to '%s' " 179 "on '%s', line number %s: %s" % 180 (type(self).__name__, 181 os.path.basename(spos[0]), spos[1], 182 self.params)) 183 try: 184 msgbuf.append(*previous) 185 except IndexError: 186 raise IndexError("Not enough parameters passed to '%s' on '%s'," 187 " line number %s: %s" % 188 (type(self).__name__, 189 os.path.basename(previous[2][0]), 190 previous[2][1], self.params), previous[1]) 191 192 previous = kind, data, pos 193 194 for event in msgbuf.translate(gettext(msgbuf.format())): 195 new_stream.append(event) 196 new_stream.append(previous) 197 if strip_directive: 198 return _apply_directives(new_stream, strip_directive, ctxt) 199 return new_stream 200 201 def extract(self, stream, ctxt): 55 202 msgbuf = MessageBuffer(self.params) 56 203 57 204 stream = iter(stream) 205 stream.next() # the outer start tag 206 previous = stream.next() 207 for event in stream: 208 try: 209 msgbuf.append(*previous) 210 except IndexError: 211 raise IndexError("Not enough parameters passed to '%s' on '%s'," 212 " line number %s: %s" % 213 (type(self).__name__, 214 os.path.basename(previous[2][0]), 215 previous[2][1], self.params)) 216 previous = event 217 218 yield None, msgbuf.format(), filter(None, [ctxt.get('_i18n.comment')]) 219 220 class InnerChooseDirective(Directive): 221 __slots__ = [] 222 223 def __call__(self, stream, directives, ctxt, **vars): 224 225 msgbuf = MessageBuffer(ctxt.get('_i18n.choose.params', [])[:]) 226 227 stream = iter(_apply_directives(stream, directives, ctxt)) 58 228 yield stream.next() # the outer start tag 59 229 previous = stream.next() 230 # if previous[0] is TEXT and not previous[1].strip(): 231 # yield previous # white space and newlines 232 for kind, data, pos in stream: 233 234 msgbuf.append(*previous) 235 previous = kind, data, pos 236 # if event[0] is TEXT and not event[1].strip(): 237 # yield event # white space and newlines 238 yield MSGBUF, (), -1 # the place holder for msgbuf output 239 yield previous # the outer end tag 240 ctxt['_i18n.choose.%s' % type(self).__name__] = msgbuf 241 242 243 def extract(self, stream, ctxt, msgbuf): 244 245 stream = iter(stream) 246 stream.next() # the outer start tag 247 previous = stream.next() 60 248 for event in stream: 61 249 msgbuf.append(*previous) 62 250 previous = event 251 return msgbuf 63 252 64 gettext = ctxt.get('_i18n.gettext')65 for event in msgbuf.translate(gettext(msgbuf.format())):66 yield event67 253 68 yield previous # the outer end tag 254 class SingularDirective(InnerChooseDirective): 255 """Implementation of the ``i18n:singular`` directive to be used with the 256 ``i18n:choose`` directive.""" 257 258 259 class PluralDirective(InnerChooseDirective): 260 """Implementation of the ``i18n:plural`` directive to be used with the 261 ``i18n:choose`` directive.""" 262 263 264 class ChooseDirective(Directive, DirectiveExtract): 265 """Implementation of the ``i18n:choose`` directive which provides plural 266 internationalisation of strings. 267 268 This directive requires at least one parameter, the one which evaluates to 269 an integer which will allow to choose the plural/singular form. If you also 270 have expressions inside the singular and plural version of the string you 271 also need to pass a name for those parameters. Consider the following 272 examples: 273 274 >>> from genshi.filters.i18n import Translator, setup_i18n 275 >>> from genshi.template import MarkupTemplate 276 >>> 277 >>> translator = Translator() 278 >>> 279 >>> tmpl = MarkupTemplate('''\ 280 <html xmlns:i18n="http://genshi.edgewall.org/i18n"> 281 ... <div i18n:choose="num; num"> 282 ... <p i18n:singular="">There is $num coin</p> 283 ... <p i18n:plural="">There are $num coins</p> 284 ... </div> 285 ... </html>''') 286 >>> setup_i18n(tmpl, translator) 287 >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 288 [(2, 'ngettext', (u'There is %(num)s coin', 289 u'There are %(num)s coins'), [])] 290 >>> 291 >>> tmpl = MarkupTemplate('''\ 292 <html xmlns:i18n="http://genshi.edgewall.org/i18n"> 293 ... <div i18n:choose="num; num"> 294 ... <p i18n:singular="">There is $num coin</p> 295 ... <p i18n:plural="">There are $num coins</p> 296 ... </div> 297 ... </html>''') 298 >>> setup_i18n(tmpl, translator) 299 >>> print tmpl.generate(num=1).render() 300 <html> 301 <div> 302 <p>There is 1 coin</p> 303 </div> 304 </html> 305 >>> print tmpl.generate(num=2).render() 306 <html> 307 <div> 308 <p>There are 2 coins</p> 309 </div> 310 </html> 311 >>> 312 313 When used as a directive and not as an attribute: 314 >>> tmpl = MarkupTemplate('''\ 315 <html xmlns:i18n="http://genshi.edgewall.org/i18n"> 316 ... <i18n:choose numeral="num" params="num"> 317 ... <p i18n:singular="">There is $num coin</p> 318 ... <p i18n:plural="">There are $num coins</p> 319 ... </i18n:choose> 320 ... </html>''') 321 >>> setup_i18n(tmpl, translator) 322 >>> list(translator.extract(tmpl.stream)) #doctest: +NORMALIZE_WHITESPACE 323 [(2, 'ngettext', (u'There is %(num)s coin', 324 u'There are %(num)s coins'), [])] 325 >>> 326 """ 327 328 __slots__ = ['numeral', 'params'] 329 330 def __init__(self, value, template, hints=None, namespaces=None, 331 lineno=-1, offset=-1): 332 Directive.__init__(self, None, template, namespaces, lineno, offset) 333 params = [v.strip() for v in value.split(';')] 334 self.numeral = self._parse_expr(params.pop(0), template, lineno, offset) 335 self.params = params and [name.strip() for name in 336 params[0].split(',') if name] or [] 337 338 @classmethod 339 def attach(cls, template, stream, value, namespaces, pos): 340 if type(value) is dict: 341 numeral = value.get('numeral', '').strip() 342 assert numeral is not '', "at least pass the numeral param" 343 params = [v.strip() for v in value.get('params', '').split(',')] 344 value = '%s; ' % numeral + ', '.join(params) 345 return super(ChooseDirective, cls).attach(template, stream, value, 346 namespaces, pos) 347 348 def __call__(self, stream, directives, ctxt, **vars): 349 350 ctxt.push({'_i18n.choose.params': self.params, 351 '_i18n.choose.SingularDirective': None, 352 '_i18n.choose.PluralDirective': None}) 353 354 new_stream = [] 355 singular_stream = None 356 singular_msgbuf = None 357 plural_stream = None 358 plural_msgbuf = None 359 360 ngettext = ctxt.get('_i18n.ungettext') 361 assert callable(ngettext), "No ngettext function available" 362 dngettext = ctxt.get('_i18n.dngettext') 363 if not dngettext: 364 dngettext = lambda d, s, p, n: ngettext(s, p, n) 365 366 for kind, event, pos in stream: 367 if kind is SUB: 368 subdirectives, substream = event 369 if isinstance(subdirectives[0], 370 SingularDirective) and not singular_stream: 371 # Apply directives to update context 372 singular_stream = list(_apply_directives(substream, 373 subdirectives, 374 ctxt)) 375 new_stream.append((MSGBUF, (), -1)) # msgbuf place holder 376 singular_msgbuf = ctxt.get('_i18n.choose.SingularDirective') 377 elif isinstance(subdirectives[0], 378 PluralDirective) and not plural_stream: 379 # Apply directives to update context 380 plural_stream = list(_apply_directives(substream, 381 subdirectives, ctxt)) 382 plural_msgbuf = ctxt.get('_i18n.choose.PluralDirective') 383 else: 384 new_stream.append((kind, event, pos)) 385 else: 386 new_stream.append((kind, event, pos)) 387 388 if ctxt.get('_i18n.domain'): 389 ngettext = lambda s, p, n: dngettext(ctxt.get('_i18n.domain'), 390 s, p, n) 391 392 for kind, data, pos in new_stream: 393 if kind is MSGBUF: 394 for skind, sdata, spos in singular_stream: 395 if skind is MSGBUF: 396 translation = ngettext(singular_msgbuf.format(), 397 plural_msgbuf.format(), 398 self.numeral.evaluate(ctxt)) 399 for event in singular_msgbuf.translate(translation): 400 yield event 401 else: 402 yield skind, sdata, spos 403 else: 404 yield kind, data, pos 405 406 ctxt.pop() 407 408 def extract(self, stream, ctxt): 409 410 stream = iter(stream) 411 previous = stream.next() 412 if previous is START: 413 stream.next() 414 415 singular_msgbuf = MessageBuffer(self.params[:]) 416 plural_msgbuf = MessageBuffer(self.params[:]) 417 418 for kind, event, pos in stream: 419 if kind is SUB: 420 subdirectives, substream = event 421 for subdirective in subdirectives: 422 if isinstance(subdirective, SingularDirective): 423 singular_msgbuf = subdirective.extract(substream, ctxt, 424 singular_msgbuf) 425 elif isinstance(subdirective, PluralDirective): 426 plural_msgbuf = subdirective.extract(substream, ctxt, 427 plural_msgbuf) 428 elif not isinstance(subdirective, StripDirective): 429 try: 430 singular_msgbuf.append(kind, event, pos) 431 plural_msgbuf.append(kind, event, pos) 432 except IndexError: 433 raise IndexError("Not enough parameters passed to " 434 "'%s' on '%s', line number %s: " 435 "%s" % (type(self).__name__, 436 os.path.basename(pos[0]), 437 pos[1], self.params)) 438 else: 439 try: 440 singular_msgbuf.append(kind, event, pos) 441 plural_msgbuf.append(kind, event, pos) 442 except IndexError: 443 raise IndexError("Not enough parameters passed to '%s' on " 444 "'%s', line number %s: %s" % 445 (type(self).__name__, 446 os.path.basename(pos[0]), pos[1], 447 self.params)) 448 449 yield 'ngettext', \ 450 (singular_msgbuf.format(), plural_msgbuf.format()), \ 451 filter(None, [ctxt.get('_i18n.comment')]) 452 453 class DomainDirective(Directive): 454 """Implementation of the ``i18n:domain`` directive which allows choosing 455 another i18n domain(catalog) to translate from. 456 457 >>> from gettext import NullTranslations 458 >>> from genshi.filters.i18n import Translator, setup_i18n 459 >>> from genshi.template.markup import MarkupTemplate 460 >>> 461 >>> class DummyTranslations(NullTranslations): 462 ... _domains = {} 463 ... def __init__(self, catalog): 464 ... NullTranslations.__init__(self) 465 ... self._catalog = catalog 466 ... def add_domain(self, domain, catalog): 467 ... translation = DummyTranslations(catalog) 468 ... translation.add_fallback(self) 469 ... self._domains[domain] = translation 470 ... def _domain_call(self, func, domain, *args, **kwargs): 471 ... return getattr(self._domains.get(domain, self), func)(*args, 472 ... **kwargs) 473 ... def ugettext(self, message): 474 ... missing = object() 475 ... tmsg = self._catalog.get(message, missing) 476 ... if tmsg is missing: 477 ... if self._fallback: 478 ... return self._fallback.ugettext(message) 479 ... return unicode(message) 480 ... return tmsg 481 ... def dugettext(self, domain, message): 482 ... return self._domain_call('ugettext', domain, message) 483 ... 484 >>> 485 >>> tmpl = MarkupTemplate('''\ 486 <html xmlns:i18n="http://genshi.edgewall.org/i18n"> 487 ... <p i18n:msg="">Bar</p> 488 ... <div i18n:domain="foo"> 489 ... <p i18n:msg="">FooBar</p> 490 ... <p>Bar</p> 491 ... <p i18n:domain="bar" i18n:msg="">Bar</p> 492 ... <p i18n:domain="">Bar</p> 493 ... </div> 494 ... <p>Bar</p> 495 ... </html>''') 496 >>> 497 >>> translations = DummyTranslations({'Bar': 'Voh'}) 498 >>> translations.add_domain('foo', {'FooBar': 'BarFoo', 'Bar': 'foo_Bar'}) 499 >>> translations.add_domain('bar', {'Bar': 'bar_Bar'}) 500 >>> translator = Translator(translations) 501 >>> setup_i18n(tmpl, translator) 502 >>> 503 >>> print tmpl.generate().render() 504 <html> 505 <p>Voh</p> 506 <div> 507 <p>BarFoo</p> 508 <p>foo_Bar</p> 509 <p>bar_Bar</p> 510 <p>Voh</p> 511 </div> 512 <p>Voh</p> 513 </html> 514 >>> 515 """ 516 517 __slots__ = ['domain'] 518 519 def __init__(self, value, template, hints=None, namespaces=None, 520 lineno=-1, offset=-1): 521 Directive.__init__(self, None, template, namespaces, lineno, offset) 522 self.domain = value and value.strip() or '__DEFAULT__' 523 524 @classmethod 525 def attach(cls, template, stream, value, namespaces, pos): 526 if type(value) is dict: 527 value = value.get('name') 528 return super(DomainDirective, cls).attach(template, stream, value, 529 namespaces, pos) 530 531 def __call__(self, stream, directives, ctxt, **vars): 532 ctxt.push({'_i18n.domain': self.domain}) 533 for event in _apply_directives(stream, directives, ctxt): 534 yield event 535 ctxt.pop() 69 536 70 537 71 538 class Translator(DirectiveFactory): 72 539 """Can extract and translate localizable strings from markup streams and 73 540 templates. 74 541 75 For example, assume the follow ng template:542 For example, assume the following template: 76 543 77 544 >>> from genshi.template import MarkupTemplate 78 >>> 545 >>> 79 546 >>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/"> 80 547 ... <head> 81 548 ... <title>Example</title> … … 94 561 ... 'Example': 'Beispiel', 95 562 ... 'Hello, %(name)s': 'Hallo, %(name)s' 96 563 ... }[string] 97 >>> 564 >>> 98 565 >>> translator = Translator(pseudo_gettext) 99 566 100 567 Next, the translator needs to be prepended to any already defined filters … … 115 582 <p>Hallo, Hans</p> 116 583 </body> 117 584 </html> 118 585 119 586 Note that elements defining ``xml:lang`` attributes that do not contain 120 587 variable expressions are ignored by this filter. That can be used to 121 588 exclude specific parts of a template from being extracted and translated. 122 589 """ 123 590 124 591 directives = [ 592 ('domain', DomainDirective), 125 593 ('comment', CommentDirective), 126 ('msg', MsgDirective) 594 ('msg', MsgDirective), 595 ('choose', ChooseDirective), 596 ('singular', SingularDirective), 597 ('plural', PluralDirective) 127 598 ] 128 599 129 600 IGNORE_TAGS = frozenset([ 130 601 QName('script'), QName('http://www.w3.org/1999/xhtml}script'), 131 602 QName('style'), QName('http://www.w3.org/1999/xhtml}style') 132 603 ]) 133 INCLUDE_ATTRS = frozenset(['abbr', 'alt', 'label', 'prompt', 'standby', 134 'summary', 'title']) 604 INCLUDE_ATTRS = frozenset([ 605 'abbr', 'alt', 'label', 'prompt', 'standby', 'summary', 'title' 606 ]) 135 607 NAMESPACE = I18N_NAMESPACE 136 608 137 609 def __init__(self, translate=NullTranslations(), ignore_tags=IGNORE_TAGS, … … 145 617 :param extract_text: whether the content of text nodes should be 146 618 extracted, or only text in explicit ``gettext`` 147 619 function calls 148 620 149 621 :note: Changed in 0.6: the `translate` parameter can now be either 150 622 a ``gettext``-style function, or an object compatible with the 151 623 ``NullTransalations`` or ``GNUTranslations`` interface … … 177 649 178 650 if type(self.translate) is FunctionType: 179 651 gettext = self.translate 652 if ctxt: 653 ctxt['_i18n.gettext'] = gettext 180 654 else: 181 655 gettext = self.translate.ugettext 182 if ctxt: 183 ctxt['_i18n.gettext'] = gettext 656 try: 657 dgettext = self.translate.dugettext 658 except AttributeError: 659 dgettext = lambda x, y: gettext(y) 660 ngettext = self.translate.ungettext 661 try: 662 dngettext = self.translate.dungettext 663 except AttributeError: 664 dngettext = lambda d, s, p, n: ngettext(s, p, n) 665 666 if ctxt: 667 ctxt['_i18n.gettext'] = gettext 668 ctxt['_i18n.ugettext'] = gettext 669 ctxt['_i18n.dgettext'] = dgettext 670 ctxt['_i18n.ngettext'] = ngettext 671 ctxt['_i18n.ungettext'] = ngettext 672 ctxt['_i18n.dngettext'] = dngettext 184 673 185 674 extract_text = self.extract_text 186 675 if not extract_text: 187 676 search_text = False 188 677 678 if ctxt and ctxt.get('_i18n.domain'): 679 old_gettext = gettext 680 gettext = lambda msg: dgettext(ctxt.get('_i18n.domain'), msg) 681 189 682 for kind, data, pos in stream: 190 683 191 684 # skip chunks that should not be localized … … 208 701 209 702 new_attrs = [] 210 703 changed = False 704 211 705 for name, value in attrs: 212 706 newval = value 213 707 if extract_text and isinstance(value, basestring): 214 708 if name in include_attrs: 215 709 newval = gettext(value) 216 710 else: 217 newval = list( self(_ensure(value), ctxt,218 se arch_text=False)711 newval = list( 712 self(_ensure(value), ctxt, search_text=False) 219 713 ) 220 714 if newval != value: 221 715 value = newval … … 234 728 235 729 elif kind is SUB: 236 730 directives, substream = data 237 # If this is an i18n:msg directive, no need to translate text 731 current_domain = None 732 for idx, directive in enumerate(directives): 733 # Organize directives to make everything work 734 if isinstance(directive, StripDirective): 735 # Push stripping to last 736 directives.append(directives.pop(idx)) 737 elif isinstance(directive, DomainDirective): 738 # Grab current domain and update context 739 current_domain = directive.domain 740 ctxt.push({'_i18n.domain': current_domain}) 741 # Put domain directive as the first one in order to 742 # update context before any other directives evaluation 743 directives.insert(0, directives.pop(idx)) 744 745 # If this is an i18n directive, no need to translate text 238 746 # nodes here 239 is_msg = filter(None, [isinstance(d, MsgDirective) 240 for d in directives]) 747 is_i18n_directive = filter(None, 748 [isinstance(d, DirectiveExtract) 749 for d in directives]) 241 750 substream = list(self(substream, ctxt, 242 search_text=not is_ msg))751 search_text=not is_i18n_directive)) 243 752 yield kind, (directives, substream), pos 244 753 754 if current_domain: 755 ctxt.pop() 245 756 else: 246 757 yield kind, data, pos 247 758 … … 249 760 'ugettext', 'ungettext') 250 761 251 762 def extract(self, stream, gettext_functions=GETTEXT_FUNCTIONS, 252 search_text=True, msgbuf=None ):763 search_text=True, msgbuf=None, ctxt=Context()): 253 764 """Extract localizable strings from the given template stream. 254 765 255 766 For every string found, this function yields a ``(lineno, function, … … 265 776 from ``i18n:comment`` attributes found in the markup 266 777 267 778 >>> from genshi.template import MarkupTemplate 268 >>> 779 >>> 269 780 >>> tmpl = MarkupTemplate('''<html xmlns:py="http://genshi.edgewall.org/"> 270 781 ... <head> 271 782 ... <title>Example</title> … … 276 787 ... <p>${ngettext("You have %d item", "You have %d items", num)}</p> 277 788 ... </body> 278 789 ... </html>''', filename='example.html') 279 >>> 790 >>> 280 791 >>> for line, func, msg, comments in Translator().extract(tmpl.stream): 281 792 ... print "%d, %r, %r" % (line, func, msg) 282 793 3, None, u'Example' … … 291 802 functions 292 803 :param search_text: whether the content of text nodes should be 293 804 extracted (used internally) 805 :param ctxt: the current extraction context (used internaly) 294 806 295 807 :note: Changed in 0.4.1: For a function with multiple string arguments 296 808 (such as ``ngettext``), a single item with a tuple of strings is 297 809 yielded, instead an item for each string argument. 298 810 :note: Changed in 0.6: The returned tuples now include a 4th element, 299 which is a list of comments for the translator 811 which is a list of comments for the translator. Added an ``ctxt`` 812 argument which is used to pass arround the current extraction 813 context. 300 814 """ 301 815 if not self.extract_text: 302 816 search_text = False 303 817 skip = 0 304 i18n_comment = I18N_NAMESPACE['comment'] 305 i18n_msg = I18N_NAMESPACE['msg'] 818 819 # Un-comment bellow to extract messages without adding directives 820 # i18n_comment = I18N_NAMESPACE['comment'] 821 # i18n_msg = I18N_NAMESPACE['msg'] 306 822 xml_lang = XML_NAMESPACE['lang'] 307 823 308 824 for kind, data, pos in stream: 309 310 825 if skip: 311 826 if kind is START: 312 827 skip += 1 … … 326 841 if name in self.include_attrs: 327 842 text = value.strip() 328 843 if text: 844 # XXX: Do we need to grab i18n:comment from ctxt ??? 329 845 yield pos[1], None, text, [] 330 846 else: 331 847 for lineno, funcname, text, comments in self.extract( … … 335 851 336 852 if msgbuf: 337 853 msgbuf.append(kind, data, pos) 338 else: 339 msg_params = attrs.get(i18n_msg) 340 if msg_params is not None: 341 if type(msg_params) is list: # event tuple 342 msg_params = msg_params[0][1] 343 msgbuf = MessageBuffer( 344 msg_params, attrs.get(i18n_comment), pos[1] 345 ) 854 # Un-comment bellow to extract messages without adding 855 # directives 856 # else: 857 # msg_params = attrs.get(i18n_msg) 858 # if msg_params is not None: 859 # print kind, data, pos 860 # if type(msg_params) is list: # event tuple 861 # msg_params = msg_params[0][1] 862 # msgbuf = MessageBuffer( 863 # msg_params, attrs.get(i18n_comment), pos[1] 864 # ) 346 865 347 866 elif not skip and search_text and kind is TEXT: 348 867 if not msgbuf: 349 868 text = data.strip() 350 869 if text and filter(None, [ch.isalpha() for ch in text]): 351 yield pos[1], None, text, [] 870 yield pos[1], None, text, \ 871 filter(None, [ctxt.get('_i18n.comment')]) 352 872 else: 353 873 msgbuf.append(kind, data, pos) 354 874 … … 356 876 msgbuf.append(kind, data, pos) 357 877 if not msgbuf.depth: 358 878 yield msgbuf.lineno, None, msgbuf.format(), \ 359 filter(None, [msgbuf.comment])879 filter(None, [msgbuf.comment]) 360 880 msgbuf = None 361 881 362 882 elif kind is EXPR or kind is EXEC: … … 364 884 msgbuf.append(kind, data, pos) 365 885 for funcname, strings in extract_from_code(data, 366 886 gettext_functions): 887 # XXX: Do we need to grab i18n:comment from ctxt ??? 367 888 yield pos[1], funcname, strings, [] 368 889 369 890 elif kind is SUB: 370 subkind, substream = data 371 messages = self.extract(substream, gettext_functions, 372 search_text=search_text and not skip, 373 msgbuf=msgbuf) 374 for lineno, funcname, text, comments in messages: 375 yield lineno, funcname, text, comments 891 directives, substream = data 376 892 893 comment = None 894 for idx, directive in enumerate(directives): 895 # Do a first loop to see if there's a comment directive 896 # If there is update context and pop it from directives 897 if isinstance(directive, CommentDirective): 898 comment = directive.comment 899 ctxt.push({'_i18n.comment': comment}) 900 if len(directives) == 1: 901 # in case we're in the presence of something like: 902 # <p i18n:comment="foo">Foo</p> 903 messages = self.extract( 904 substream, gettext_functions, 905 search_text=search_text and not skip, 906 msgbuf=msgbuf, ctxt=ctxt) 907 for lineno, funcname, text, comments in messages: 908 yield lineno, funcname, text, comments 909 directives.pop(idx) 910 elif isinstance(directive, StripDirective): 911 # Previously we didn't evaluate py:strip directives 912 # in extraction, let's continue not to 913 directives.pop(idx) 914 915 for directive in directives: 916 if isinstance(directive, DirectiveExtract): 917 messages = directive.extract(substream, ctxt) 918 for funcname, text, comments in messages: 919 yield pos[1], funcname, text, comments 920 else: 921 messages = self.extract( 922 substream, gettext_functions, 923 search_text=search_text and not skip, msgbuf=msgbuf) 924 for lineno, funcname, text, comments in messages: 925 yield lineno, funcname, text, comments 926 if comment: 927 ctxt.pop() 377 928 378 929 class MessageBuffer(object): 379 930 """Helper class for managing internationalized mixed content. … … 391 942 """ 392 943 if isinstance(params, basestring): 393 944 params = [name.strip() for name in params.split(',')] 394 self.params = params 945 # params list needs to be copied so that directives can be evaluated 946 # more than once 947 self.params = params[:] 395 948 self.comment = comment 396 949 self.lineno = lineno 397 950 self.string = [] … … 408 961 :param data: the event data 409 962 :param pos: the position of the event in the source 410 963 """ 964 if kind is SUB: 965 # py:attrs for example 966 for skind, sdata, spos in data[1]: 967 self.append(skind, sdata, spos) 411 968 if kind is TEXT: 412 969 self.string.append(data) 413 970 self.events.setdefault(self.stack[-1], []).append(None) … … 464 1021 def parse_msg(string, regex=re.compile(r'(?:\[(\d+)\:)|\]')): 465 1022 """Parse a translated message using Genshi mixed content message 466 1023 formatting. 467 1024 468 1025 >>> parse_msg("See [1:Help].") 469 1026 [(0, 'See '), (1, 'Help'), (0, '.')] 470 1027 471 1028 >>> parse_msg("See [1:our [2:Help] page] for details.") 472 1029 [(0, 'See '), (1, 'our '), (2, 'Help'), (1, ' page'), (0, ' for details.')] 473 1030 474 1031 >>> parse_msg("[2:Details] finden Sie in [1:Hilfe].") 475 1032 [(2, 'Details'), (0, ' finden Sie in '), (1, 'Hilfe'), (0, '.')] 476 1033 477 1034 >>> parse_msg("[1:] Bilder pro Seite anzeigen.") 478 1035 [(1, ''), (0, ' Bilder pro Seite anzeigen.')] 479 1036 480 1037 :param string: the translated message string 481 1038 :return: a list of ``(order, string)`` tuples 482 1039 :rtype: `list` … … 514 1071 >>> expr = Expression('_("Hello")') 515 1072 >>> list(extract_from_code(expr, Translator.GETTEXT_FUNCTIONS)) 516 1073 [('_', u'Hello')] 517 1074 518 1075 >>> expr = Expression('ngettext("You have %(num)s item", ' 519 1076 ... '"You have %(num)s items", num)') 520 1077 >>> list(extract_from_code(expr, Translator.GETTEXT_FUNCTIONS)) … … 584 1141 585 1142 tmpl = template_class(fileobj, filename=getattr(fileobj, 'name', None), 586 1143 encoding=encoding) 1144 587 1145 translator = Translator(None, ignore_tags, include_attrs, extract_text) 1146 if hasattr(tmpl, 'add_directives'): 1147 tmpl.add_directives(Translator.NAMESPACE, translator) 588 1148 for message in translator.extract(tmpl.stream, gettext_functions=keywords): 589 1149 yield message 1150 1151 def setup_i18n(template, translator): 1152 """Convinience function to setup both the i18n filter and the i18n 1153 directives. 1154 1155 :param template: an instance of a genshi template 1156 :param translator: an instance of ``Translator`` 1157 """ 1158 template.filters.insert(0, translator) 1159 if hasattr(template, 'add_directives'): 1160 template.add_directives(Translator.NAMESPACE, translator) -
genshi/template/directives.py
530 530 531 531 def __call__(self, stream, directives, ctxt, **vars): 532 532 def _generate(): 533 if _eval_expr(self.expr, ctxt, **vars): 533 if not self.expr: 534 for event in list(stream)[1:-1]: 535 yield event 536 elif _eval_expr(self.expr, ctxt, **vars): 534 537 stream.next() # skip start tag 535 538 previous = stream.next() 536 539 for event in stream: … … 539 542 else: 540 543 for event in stream: 541 544 yield event 542 return _apply_directives(_generate(), directives, ctxt, **vars) 543 544 def attach(cls, template, stream, value, namespaces, pos): 545 if not value: 546 return None, stream[1:-1] 547 return super(StripDirective, cls).attach(template, stream, value, 548 namespaces, pos) 549 attach = classmethod(attach) 545 return _apply_directives(_generate(), directives, ctxt) 550 546 551 547 552 548 class ChooseDirective(Directive):
