| 454 | === i18n support for plugins #i18n |
| 455 | |
| 456 | The basics remain the same, we use Babel for extracting the source strings from Jinja2 templates and for performing the translations at runtime. One notable differences though is how the //domain// used by the plugin specific catalogs is specified. With Genshi, the templates themselves contained a directive which specified the name of the domain, while Jinja2 doesn't provide this facility. It is therefore up to the controller to do this job, and we use for that the //metadata// `dict` returned by [#IRequestHandler IRequestHandler.process_request]. |
| 457 | |
| 458 | The specification for the extraction is slightly more verbose than it use to be with Genshi. As we use a syntax for the Jinja2 templates which is different than the default ([#TheJinja2syntaxusedbyTrac see below]), it has to be specified for the extractor as well. |
| 459 | |
| 460 | In what follows, we'll take the example of the SpamFilter plugin. |
| 461 | |
| 462 | In the `setup.cfg`, we added the `mapping_file` parameter to the `[extract_messages]` section. |
| 463 | {{{#!ini |
| 464 | [extract_messages] |
| 465 | add_comments = TRANSLATOR: |
| 466 | msgid_bugs_address = trac@dstoecker.de |
| 467 | output_file = tracspamfilter/locale/messages.pot |
| 468 | keywords = _ ngettext:1,2 N_ tag_ cleandoc_ Option:4 BoolOption:4 IntOption:4 ChoiceOption:4 ListOption:6 ExtensionOption:5 ConfigSection:2 |
| 469 | width = 72 |
| 470 | mapping_file = messages.cfg |
| 471 | }}} |
| 472 | The other i18n sections are unchanged. |
| 473 | |
| 474 | This makes it possible to specify all the extraction details in a specific configuration file, `messages.cfg`: |
| 475 | {{{#!ini |
| 476 | # mapping file for extracting messages from Jinja2 templates into |
| 477 | # trac/locale/messages.pot (see setup.cfg) |
| 478 | |
| 479 | [extractors] |
| 480 | python = trac.dist:extract_python |
| 481 | html = trac.dist:extract_html |
| 482 | text = trac.dist:extract_text |
| 483 | |
| 484 | [python: **.py] |
| 485 | |
| 486 | [html: **/templates/**.html] |
| 487 | extensions = jinja2.ext.with_ |
| 488 | variable_start_string = ${ |
| 489 | variable_end_string = } |
| 490 | line_statement_prefix = # |
| 491 | line_comment_prefix = ## |
| 492 | trim_blocks = yes |
| 493 | lstrip_blocks = yes |
| 494 | newstyle_gettext = yes |
| 495 | |
| 496 | [text: **/templates/**.txt] |
| 497 | extensions = jinja2.ext.with_ |
| 498 | variable_start_string = ${ |
| 499 | variable_end_string = } |
| 500 | line_statement_prefix = # |
| 501 | line_comment_prefix = ## |
| 502 | trim_blocks = yes |
| 503 | lstrip_blocks = yes |
| 504 | newstyle_gettext = yes |
| 505 | }}} |
| 506 | The `html` extractor (i.e. the `trac.dist.extract_html` function) will be applied to all HTML files `**/templates/**.html`, while the `text` extractor (the `trac.dist.extract_text` function). wil be applied on the "text" files `**/templates/**.txt`. |
| 507 | |
| 508 | The HTML extractor "auto-detects" in a simple but effective way the presence of Genshi i18n directives, and will use the legacy Genshi extractor in that case. It is therefore safe to use this new way even before the integrality of the templates has been migrated to Jinja2. |
| 509 | |
| 510 | Note that as we have a dedicated mapping file anyway, we specify the extractors directly there, so we no longer needs to do that in the setup.py file: |
| 511 | {{{#!diff |
| 512 | Index: setup.py |
| 513 | =================================================================== |
| 514 | --- setup.py (revision 15351) |
| 515 | +++ setup.py (revision 15352) |
| 516 | @@ -23,13 +23,6 @@ |
| 517 | cmdclass = get_l10n_cmdclass() |
| 518 | if cmdclass: |
| 519 | extra['cmdclass'] = cmdclass |
| 520 | - extractors = [ |
| 521 | - ('**.py', 'trac.dist:extract_python', None), |
| 522 | - ('**/templates/**.html', 'genshi', None) |
| 523 | - ] |
| 524 | - extra['message_extractors'] = { |
| 525 | - 'tracspamfilter': extractors, |
| 526 | - } |
| 527 | except ImportError: |
| 528 | pass |
| 529 | }}} |
| 530 | |
| 531 | See r15352 for the full changeset. |
| 532 | |
| 533 | Finally, as mentioned earlier, we need to specify the domain from the Python code: |
| 534 | {{{#!diff |
| 535 | Index: tracspamfilter/admin.py |
| 536 | =================================================================== |
| 537 | --- tracspamfilter/admin.py (revision 15352) |
| 538 | +++ tracspamfilter/admin.py (revision 15353) |
| 539 | @@ -113,6 +113,8 @@ |
| 540 | |
| 541 | add_stylesheet(req, 'spamfilter/admin.css') |
| 542 | data['accmgr'] = 'ACCTMGR_USER_ADMIN' in req.perm |
| 543 | + if page == 'config': |
| 544 | + return 'admin_spamconfig.html', data, {'domain': 'tracspamfilter'} |
| 545 | return 'admin_spam%s.html' % page, data, None |
| 546 | |
| 547 | # ITemplateProvider methods |
| 548 | }}} |
| 549 | In that case, only the `'admin_spamconfig.html'` template has been converted to Jinja2, the other templates remain Genshi templates, hence the value of `None` as third member of the return tuple for them. |
| 550 | |
| 551 | See r15353 for the full changeset. |
| 552 | |