| 1476 | === Internationalize The Application (I18N) === |
| 1477 | To internationalize our application we'll use [http://babel.edgewall.org Babel]. |
| 1478 | {{{ |
| 1479 | easy_install Babel |
| 1480 | }}} |
| 1481 | |
| 1482 | To get the basic info on how to work with message catalog using Babel [http://babel.edgewall.org/wiki/Documentation/0.9/messages.html read this], I'll just follow you through the basic procedures. |
| 1483 | |
| 1484 | First we'll create a `locale` directory under `geddit` which will hold the message catalogs. Next, we'll create a mapping file which will tell Babel how to handle the several type of files. Create a `mappings.cfg` on the directory above the geddit one and add inside: |
| 1485 | {{{ |
| 1486 | # Extraction from Python source files |
| 1487 | |
| 1488 | [python: **.py] |
| 1489 | |
| 1490 | # Extraction from Genshi HTML and text templates |
| 1491 | |
| 1492 | [genshi: **/templates/**.html |
| 1493 | }}} |
| 1494 | The above will tell Babel that `.py` files should be handled by the python extractor and that the `.html` files should be handled by the Genshi extractor. |
| 1495 | |
| 1496 | Now let's extract some messages to be translated: |
| 1497 | {{{ |
| 1498 | pybabel extract -o geddit/locale/geddit.pot -F ./mapping.cfg geddit |
| 1499 | }}} |
| 1500 | |
| 1501 | The output should be similar to: |
| 1502 | {{{ |
| 1503 | extracting messages from geddit/__init__.py |
| 1504 | extracting messages from geddit/controller.py |
| 1505 | extracting messages from geddit/form.py |
| 1506 | extracting messages from geddit/model.py |
| 1507 | extracting messages from geddit/translator.py |
| 1508 | extracting messages from geddit/lib/__init__.py |
| 1509 | extracting messages from geddit/lib/ajax.py |
| 1510 | extracting messages from geddit/lib/template.py |
| 1511 | extracting messages from geddit/templates/_comment.html |
| 1512 | extracting messages from geddit/templates/_form.html |
| 1513 | extracting messages from geddit/templates/comment.html |
| 1514 | extracting messages from geddit/templates/index.html |
| 1515 | extracting messages from geddit/templates/info.html |
| 1516 | extracting messages from geddit/templates/layout.html |
| 1517 | extracting messages from geddit/templates/submit.html |
| 1518 | writing PO template file to geddit/locale/geddit.pot |
| 1519 | }}} |
| 1520 | |
| 1521 | Now let's create the English catalog which normally isn't translated: |
| 1522 | {{{ |
| 1523 | pybabel init -D geddit -i geddit/locale/geddit.pot -d geddit/locale/ -l en |
| 1524 | }}} |
| 1525 | |
| 1526 | And, since I'm Portuguese, we'll create a Portuguese catalog to serve as an example: |
| 1527 | {{{ |
| 1528 | pybabel init -D geddit -i geddit/locale/geddit.pot -d geddit/locale/ -l pt_PT |
| 1529 | }}} |
| 1530 | On this step you can create a catalog in your mother language if not english so that you can see Geddit translated. |
| 1531 | |
| 1532 | Edit the contents of the resulting `geddit.po` for the locale you created, translate it, save and exit. |
| 1533 | |
| 1534 | Next step will involve compiling these catalogs so that they can be used by python's gettext module: |
| 1535 | {{{ |
| 1536 | pybabel compile -D geddit -d geddit/locale/ -f --statistics |
| 1537 | }}} |
| 1538 | |
| 1539 | Now, our Geddit application needs to use and know how to use these translated catalogs. |
| 1540 | |
| 1541 | Let's modify `geddit/controller.py` with(this is a diff): |
| 1542 | {{{ |
| 1543 | #!diff |
| 1544 | Index: geddit/controller.py |
| 1545 | =================================================================== |
| 1546 | --- geddit/controller.py (revision 766) |
| 1547 | +++ geddit/controller.py (working copy) |
| 1548 | @@ -92,8 +92,40 @@ |
| 1549 | links = sorted(self.data.values(), key=operator.attrgetter('time')) |
| 1550 | return template.render(links=links) |
| 1551 | |
| 1552 | + @cherrypy.expose |
| 1553 | + def set_lang(self, language): |
| 1554 | + import gettext |
| 1555 | + import formencode |
| 1556 | + import __builtin__ |
| 1557 | + locale_dir = os.path.join(os.path.dirname(__file__), 'locale') |
| 1558 | + domain = 'geddit' |
| 1559 | + codeset= 'utf-8' |
| 1560 | + |
| 1561 | + gettext.bindtextdomain(domain, locale_dir) |
| 1562 | + gettext.textdomain(domain) |
| 1563 | + |
| 1564 | + try: |
| 1565 | + translator = gettext.translation(domain, |
| 1566 | + locale_dir, |
| 1567 | + languages=[language], |
| 1568 | + codeset=codeset) |
| 1569 | + except IOError, error: |
| 1570 | + language=['en'] |
| 1571 | + translator = gettext.translation(domain, |
| 1572 | + locale_dir, |
| 1573 | + languages=language, |
| 1574 | + codeset=codeset) |
| 1575 | + formencode.api.set_stdtranslation(languages=[language]) |
| 1576 | + __builtin__._ = translator.ugettext |
| 1577 | + raise cherrypy.HTTPRedirect('/') |
| 1578 | + |
| 1579 | + |
| 1580 | |
| 1581 | def main(filename): |
| 1582 | + import __builtin__ |
| 1583 | + from gettext import NullTranslations |
| 1584 | + |
| 1585 | + __builtin__._ = NullTranslations().ugettext |
| 1586 | # load data from the pickle file, or initialize it to an empty list |
| 1587 | if os.path.exists(filename): |
| 1588 | fileobj = open(filename, 'rb') |
| 1589 | }}} |
| 1590 | |
| 1591 | Our `geddit/lib/template.py` with(also diff): |
| 1592 | {{{ |
| 1593 | !#diff |
| 1594 | Index: geddit/lib/template.py |
| 1595 | =================================================================== |
| 1596 | --- geddit/lib/template.py (revision 766) |
| 1597 | +++ geddit/lib/template.py (working copy) |
| 1598 | @@ -4,6 +4,7 @@ |
| 1599 | from genshi.core import Stream |
| 1600 | from genshi.output import encode, get_serializer |
| 1601 | from genshi.template import Context, TemplateLoader |
| 1602 | +from genshi.filters import Translator |
| 1603 | |
| 1604 | from geddit.lib import ajax |
| 1605 | |
| 1606 | @@ -42,6 +43,7 @@ |
| 1607 | template = loader.load(args[0]) |
| 1608 | else: |
| 1609 | template = cherrypy.thread_data.template |
| 1610 | + template.filters.insert(0, Translator(_)) |
| 1611 | ctxt = Context(url=cherrypy.url) |
| 1612 | ctxt.push(kwargs) |
| 1613 | return template.generate(ctxt) |
| 1614 | }}} |
| 1615 | |
| 1616 | And finaly our `geddit/templates/layout.html` with(also diff): |
| 1617 | {{{ |
| 1618 | #!diff |
| 1619 | Index: geddit/templates/layout.html |
| 1620 | =================================================================== |
| 1621 | --- geddit/templates/layout.html (revision 766) |
| 1622 | +++ geddit/templates/layout.html (working copy) |
| 1623 | @@ -19,6 +19,16 @@ |
| 1624 | <a href="/"><img src="${url('/media/logo.gif')}" width="201" height="79" alt="geddit?" /></a> |
| 1625 | </div> |
| 1626 | <div id="content"> |
| 1627 | + <form method="post" id="lang_choose" name="lang_choose" action="${url('/set_lang')}"> |
| 1628 | + <label for="language">Language:</label> |
| 1629 | + <select id="language" name="language" onChange="$('#lang_choose').submit()"> |
| 1630 | + <option value="en">English</option> |
| 1631 | + <option value="pt_PT">Portuguese</option> |
| 1632 | + </select> |
| 1633 | + </form> |
| 1634 | + <noscript> |
| 1635 | + <input type="submit" name="submit" value="Update"/> |
| 1636 | + </noscript> |
| 1637 | ${select('*|text()')} |
| 1638 | </div> |
| 1639 | <div id="footer"> |
| 1640 | }}} |
| 1641 | |
| 1642 | This will allow the user to select the language. |
| 1643 | |
| 1644 | And that wraps it up! |
| 1645 | |