Personal tools
You are here: Home Random Bits Zope3 hints Zope3 pagetemplates with i18n standalone

Zope3 pagetemplates with i18n standalone

Shows how to use pagetemplates with most features in a python script without using Zope at all

Why

I love the concept of Zope Page Templates. They force you to separate logic from content and always generate valid xml or xhtml. It limits the danger of cross-site scripting vulnerabilities because everything that's dynamically inserted is escaped.

There are interesting implementations like simpletal or phptal which I used so far. Problem with phptal - it's for PHP ;). Other than that it's fully featured including internationalisation (i18n). And it is pretty fast, phptal generates php code from the templates, eliminating the parsing overhead. Problem with simpletal - no i18n and weird bugs when i18n is patched into it, like msgids within a macro aren't translated.

Why would I need to run it standalone whithout using Zope3? Well sometimes I have to quickly create scripts that churn out xhtml/xml, using a templating engine helps a lot. Also I like to use mod_python for smaller projects, having the cool templating engine without the overhead of Zope3 development sometimes makes sense.

The code:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys

# python stuff
import gettext
# don't use cStringIO - it is unicode unaware!
from StringIO import StringIO
import os, re, datetime

# ZOPE 3 stuff
sys.path.insert(0,'/usr/lib/zope-3.3.0/lib/python/')
from zope.tal.talinterpreter import TALInterpreter
from zope.tales.tales import ExpressionEngine, Context
from zope.pagetemplate.pagetemplate import PageTemplate

# Documentation:
# http://wiki.zope.org/zope3/ZPTInternationalizationSupport

TranslationBasePath = '/path/to/translations'
# context object for translation
class TranslationContext(Context):
  def __init__(self, language, engine, contexts):
        Context.__init__(self, engine, contexts)
        self.translation_file = os.path.join(TranslationBasePath, language + "/LC_MESSAGES/messages.mo")

  def translate(self, msgid, domain=None, mapping=None, default=None):
        #print msgid, domain, mapping, default
        trans = gettext.GNUTranslations(open(self.translation_file))
        translated = trans.ugettext(msgid)
        def repl(m):
          return unicode(mapping[m.group(m.lastindex).lower()])
        cre = re.compile(r'\$(?:([_A-Za-z][-\w]*)|\{([_A-Za-z][-\w]*)\})')
        return cre.sub(repl, translated)

class FSPageTemplateFolder:
         def __init__(self, basepath):
                 self._basepath = basepath
         def __getitem__(self, name):
                 fn = os.path.join(self._basepath, name)
                 if os.path.isdir(fn):
                         return FSPageTemplateFolder(fn)
                 txt = open(fn).read()
                 pt = PageTemplate()
                 pt.write(txt)
                 return pt


data = {
                 'test': 'Testing text',
                 'here': {'name': 'sepp', 'country_of_birth': 'lala', 'greeting': 'first name'},
                 'dt': datetime.datetime(2007, 5, 23),
                 'has_banana': True,
                 'container': FSPageTemplateFolder('/path/to/template')
                 }
t1 = u'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xlmns="http://www.w3.org/1999/xhtml"
   xmlns:tal="http://xml.zope.org/namespaces/tal"
   xmlns:i18n="http://xml.zope.org/namespaces/i18n"
   i18n:domain="startpage"
   i18n:source="en"
   >
   <h1 tal:content="test"></h1>
<p i18n:translate="">without limits</p>
<p tal:content="python: dt.today()"></p>
<span i18n:translate=''>
  <span tal:replace='here/name' i18n:name='name' /> was born in
  <span tal:replace='here/country_of_birth' i18n:name='country' />.
</span>
<div metal:use-macro="container/master.html/macros/thismonth">
  <div metal:fill-slot="additional-notes">
        <h1 tal:content="test"></h1>
  </div>
</div>
<img src="http://foo.com/logo" alt="Visit us"
   tal:attributes="alt here/greeting"
   i18n:attributes="alt" />
<select>
  <option tal:attributes="selected has_banana">banana</option>
</select>
<!-- <div i18n:data="dt" i18n:translate="msgid"></div> -->
</html>'''

context = TranslationContext('de', ExpressionEngine(),data)
_ = context.translate
print _('without limits')
buffer = StringIO()
pt = PageTemplate()
pt.pt_edit(t1, 'text/html')
pt._cook()
TALInterpreter(pt._v_program, pt._v_macros, context, buffer)()
macro = buffer.getvalue()
print "----"
print macro

master.html:

<html i18n:domain="CalendarService">
  <!-- really hairy TAL code here ;-) -->
<p metal:define-macro="copyright">
         Copyright 2001, <em>Foobar</em> Inc.
        </p>
  <div metal:define-macro="thismonth">
  <p i18n:translate="">without limits</p>
  <div metal:define-slot="additional-notes">
    Place for the application to add additional notes if desired.
  </div>
  </div>
</html>

output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xlmns="http://www.w3.org/1999/xhtml">
   <h1>Testing text</h1>
<p>keine Einschränkung</p>
<p>2007-06-17 13:10:22.690563</p>
<span>sepp was born in lala.</span>
<div>
  <p>keine Einschränkung</p>
  <div>
    <h1>Testing text</h1>
  </div>
  </div>
<img src="http://foo.com/logo" alt="Vorname" />
<select>
  <option selected="selected">banana</option>
</select>
<!-- <div i18n:data="dt" i18n:translate="msgid"></div> -->
</html>

What happens here

  1. The stuff from zope3 gets imported. If you have a setup like me, multiple zope versions might be installed, they are not in the search_path, that's why i insert the path to my desired zope version at the beginning.
  2. A new class TranslationContext for my translation stuff, based on zope.tales.tales.Context - I used the easy approach with the gettext.GNUTranslations internally. More sophisticated solutions, with domains and whatnot are possible, but this is just a quick and dirty intro.
  1. A new class for the container object within the templates context. In Zope this is pointing somewhere inside the ZODB, we only have a filesystem, here's a quick implementation of a filesystem with templates FSPageTemplateFolder.
  2. data holds the information that goes into the template
  3. t1 is the template, it references to macros from master.html
  4. the transformation of the template, i use pt_edit instead of simply write because there I can specify if it is text/html or text/xml I want

What does not work

  • i18n:data is somehow broken, I don't need it but it still would be interesting how to enable this feature.
  • i18n:source and i18n:target don't seem to have any effect
Document Actions
« February 2012 »
February
MoTuWeThFrSaSu
12345
6789101112
13141516171819
20212223242526
272829