[Zope-Checkins] CVS: Zope3/lib/python/Zope/I18n - GettextMessageCatalog.py:1.1 GlobalTranslationService.py:1.1 SimpleTranslationService.py:1.1 i18n-meta.zcml:1.1 metaConfigure.py:1.1 Domain.py:1.4 IMessageCatalog.py:1.4 MessageCatalog.py:1.3 TranslationService.py:1.4 i18n.zcml:1.3
Stephan Richter
srichter@cbu.edu
Wed, 12 Jun 2002 14:39:27 -0400
Update of /cvs-repository/Zope3/lib/python/Zope/I18n
In directory cvs.zope.org:/tmp/cvs-serv9092/I18n
Modified Files:
Domain.py IMessageCatalog.py MessageCatalog.py
TranslationService.py i18n.zcml
Added Files:
GettextMessageCatalog.py GlobalTranslationService.py
SimpleTranslationService.py i18n-meta.zcml metaConfigure.py
Log Message:
I finally got around to finish some priliminary Translation Support. This
complements the work done recently by Barry and Fred concerning the PT i18n
namespace.
SimpleTranslationService: The possibly simplest implementation of a
Translation service. It does not do much, but provides a nice base class
for other translation services and is great for testing. This
implementation does not use message catalog for its storage.
TranslationService: This is a local, persistent flavour of the translation
service. It is used for TTW development. A first GUI for adding and
editing (delete does not work yet) messages is also there already. People
which use ZBabel will find the interface very familiar.
This class also implements many, many methods required for the editing via
the browser. These methods will be probably split up into a separate
interface called IEditableTranslationService.
GlobalTranslationService: This is a placeless version of the translation
service. It will be used to register translations via the file system and
ZCML. As default the GettextMessageCatalog is used, whcih reads in
gettext files.
MessageCatalog: A persistent implementation to accomedate the
TranslationService. It also implements a couple additional methods to
support updating and deleting of messages.
GettextMessageCatalog: Every catalog basically represents one gettext
(*.mo) file. This message catalog cannot be altered, as the file is used
read only. This message catalog is primarily used in combination with
GlobalTranslationService.
Domain: This is basically a shortcut object, so that you do not have to
specify the domain for a translation every single time. However, I think
this obejct is not quiet ready yet, as I do not have the use cases on how
to handle the placefulness well yet.
Negotiator: The language negotiator helps the translation service to
determine the target language in case it was not explicitly passed as an
argument.
gts ZCML namespace: Using the registerTranslations directive you can
specify a gettext directory as the source for additional translations.
These translation will be added to the GlobalTranslationService, where
they will be available for usage. Note that the path of the various
gettext files identify the language and domain unambiguously.
=== Added File Zope3/lib/python/Zope/I18n/GettextMessageCatalog.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""A simple implementation of a Message Catalog.
$Id: GettextMessageCatalog.py,v 1.1 2002/06/12 18:38:56 srichter Exp $
"""
from gettext import GNUTranslations
from Zope.I18n.IMessageCatalog import IMessageCatalog
class GettextMessageCatalog:
""" """
__implements__ = IMessageCatalog
def __init__(self, language, domain, path_to_file):
"""Initialize the message catalog"""
self._language = language
self._domain = domain
self._path_to_file = path_to_file
self.__translation_object = None
self._prepareTranslations()
def _prepareTranslations(self):
""" """
if self.__translation_object is None:
file = open(self._path_to_file, 'r')
self.__translation_object = GNUTranslations(file)
file.close()
############################################################
# Implementation methods for interface
# Zope.I18n.IMessageCatalog.
def getMessage(self, id):
'See Zope.I18n.IMessageCatalog.IMessageCatalog'
self._prepareTranslations()
msg = self.__translation_object.gettext(id)
if msg == id:
raise KeyError
return msg
def queryMessage(self, id, default=None):
'See Zope.I18n.IMessageCatalog.IMessageCatalog'
self._prepareTranslations()
text = self.__translation_object.gettext(id)
if text != id:
return text
if default is None:
default = id
return default
def getLanguage(self):
'See Zope.I18n.IMessageCatalog.IMessageCatalog'
return self._language
def getDomain(self):
'See Zope.I18n.IMessageCatalog.IMessageCatalog'
return self._domain
def getIdentifier(self):
'See Zope.I18n.IMessageCatalog.IMessageCatalog'
return self._path_to_file
#
############################################################
=== Added File Zope3/lib/python/Zope/I18n/GlobalTranslationService.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Global Translation Service for providing I18n to file-based code.
$Id: GlobalTranslationService.py,v 1.1 2002/06/12 18:38:56 srichter Exp $
"""
from Negotiator import negotiator
from SimpleTranslationService import SimpleTranslationService
class GlobalTranslationService(SimpleTranslationService):
''' '''
__implements__ = SimpleTranslationService.__implements__
def __init__(self, default_domain='global'):
''' '''
self._catalogs = {}
self._data = {}
def _registerMessageCatalog(self, language, domain, catalog_name):
''' '''
if (language, domain) not in self._catalogs.keys():
self._catalogs[(language, domain)] = []
mc = self._catalogs[(language, domain)]
mc.append(catalog_name)
def addCatalog(self, catalog):
''' '''
self._data[catalog.getIdentifier()] = catalog
self._registerMessageCatalog(catalog.getLanguage(),
catalog.getDomain(),
catalog.getIdentifier())
############################################################
# Implementation methods for interface
# Zope.I18n.ITranslationService.
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None):
'''See interface ITranslationService'''
if domain is None:
domain = self.default_domain
if target_language is None:
if context is None:
raise TypeError, 'No destination language'
else:
avail_langs = {}
for catalog in self._data.values():
avail_langs[catalog] = None
target_language = negotiator.getLanguage(avail_langs.keys(),
context)
# Get the translation. Default is the msgid text itself.
catalog_names = self._catalogs.get((target_language, domain), [])
text = msgid
for name in catalog_names:
catalog = self._data[name]
try:
text = catalog.getMessage(msgid)
break
except:
pass
# Now we need to do the interpolation
return self.interpolate(text, mapping)
#
############################################################
translationService = GlobalTranslationService()
=== Added File Zope3/lib/python/Zope/I18n/SimpleTranslationService.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""This is a simple implementation of the ITranslationService interface.
$Id: SimpleTranslationService.py,v 1.1 2002/06/12 18:38:56 srichter Exp $
"""
import re
from types import DictType
from Zope.ComponentArchitecture import getService
from ITranslationService import ITranslationService
from Domain import Domain
# Setting up some regular expressions for finding interpolation variables in
# the text.
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
_interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE}))
_get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE}))
class SimpleTranslationService:
"""This is the simplest implementation of the ITranslationInterface I
could come up with.
The constructor takes one optional argument 'messages', which will be
used to do the translation. The 'messages' attribute has to have the
following structure:
{('domain', 'language', 'msg_id'): 'message', ...}
Note: This Translation Service does not implemen
"""
__implements__ = ITranslationService
def __init__(self, messages=None):
"""Initializes the object. No arguments are needed."""
if messages is None:
self.messages = {}
else:
assert type(messages) == DictType
self.messages = messages
############################################################
# Implementation methods for interface
# Zope.I18n.ITranslationService.
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None):
'''See interface ITranslationService'''
# Find out what the target language should be
if target_language is None:
if context is None:
raise TypeError, 'No destination language'
else:
avail_langs = map(lambda m: m[2], self.messages.keys())
# Let's negotiate the language to translate to. :)
negotiator = getService(self, 'LanguageNegotiation')
target_language = negotiator.getLanguage(avail_langs, context)
# Make a raw translation without interpolation
text = self.messages.get((domain, target_language, msgid), msgid)
# Now we need to do the interpolation
return self.interpolate(text, mapping)
def getDomain(self, domain):
'''See interface ITranslationService'''
return Domain(self, domain)
#
############################################################
def interpolate(self, text, mapping):
"""Insert the data passed from mapping into the text"""
# If the mapping does not exist, make a "raw translation" without
# interpolation.
if mapping is None:
return text
# Find all the spots we want to substitute
to_replace = _interp_regex.findall(text)
# Now substitute with the variables in mapping
for string in to_replace:
var = _get_var_regex.findall(string)[0]
text = text.replace(string, mapping.get(var))
return text
=== Added File Zope3/lib/python/Zope/I18n/i18n-meta.zcml ===
<zopeConfigure xmlns='http://namespaces.zope.org/zope'>
<directives namespace="http://namespaces.zope.org/gts">
<directive name="registerTranslations"
attributes="directory"
handler="Zope.I18n.metaConfigure.registerTranslations" />
</directives>
</zopeConfigure>
=== Added File Zope3/lib/python/Zope/I18n/metaConfigure.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
This module handles the :startup directives.
$Id: metaConfigure.py,v 1.1 2002/06/12 18:38:56 srichter Exp $
"""
import os
from Zope.Configuration.Action import Action
from GettextMessageCatalog import GettextMessageCatalog
from GlobalTranslationService import translationService
def registerTranslations(_context, directory):
""" """
actions = []
path = _context.path(directory)
path = os.path.normpath(path)
for language in os.listdir(path):
lang_path = os.path.join(path, language)
if os.path.isdir(lang_path):
lc_messages_path = os.path.join(lang_path, 'LC_MESSAGES')
for domain_file in os.listdir(lc_messages_path):
if domain_file.endswith('.mo'):
domain_path = os.path.join(lc_messages_path, domain_file)
domain = domain_file[:-3]
catalog = GettextMessageCatalog(language, domain,
domain_path)
actions.append(Action(
discriminator = catalog.getIdentifier(),
callable = translationService.addCatalog,
args = (catalog,) ))
return actions
=== Zope3/lib/python/Zope/I18n/Domain.py 1.3 => 1.4 ===
# IDomain interface methods
- def translate(self, source, mapping=None, context=None,
+ def translate(self, msgid, mapping=None, context=None,
target_language=None):
"""See Zope.I18n.IDomain.IDomain"""
=== Zope3/lib/python/Zope/I18n/IMessageCatalog.py 1.3 => 1.4 ===
def getIdentifier():
- """Return an identifier for this message catalog.
+ """Return a identifier for this message catalog. Note that this
+ identifier does not have to be unique as several message catalog
+ could serve the same domain and language.
- Note that this identifier does not have to be unique as several
- message catalogs could be serving the same domain and language.
+ Also, there are no restrictions on the form of the identifier.
"""
=== Zope3/lib/python/Zope/I18n/MessageCatalog.py 1.2 => 1.3 ===
"""
+from Persistence.BTrees.OOBTree import OOBTree
+from Persistence import Persistent
+from Zope.ComponentArchitecture.IFactory import IFactory
+from Zope.App.Security.RegisteredObject import RegisteredObject
from Zope.I18n.IMessageCatalog import IMessageCatalog
-class MessageCatalog:
+class MessageCatalog(RegisteredObject, Persistent):
__implements__ = IMessageCatalog
+ __class_implements__ = IFactory
- def __init__(self, language, domain="global"):
+ def __init__(self, language, domain="default"):
"""Initialize the message catalog"""
+ super(MessageCatalog, self).__init__('', '', '')
+
self._language = language
self._domain = domain
- self._messages = {}
+ self._messages = OOBTree()
def setMessage(self, id, message):
"""Set a message to the catalog."""
self._messages[id] = message
+
+
+ def getMessageIds(self):
+ """ """
+ return list(self._messages.keys())
+
+
+ ############################################################
+ # Implementation methods for interface
+ # Zope/ComponentArchitecture/IFactory.py
+
+ def getInterfaces(self):
+ 'See Zope.ComponentArchitecture.IFactory.IFactory'
+ return self.__implements__
+ #
+ ############################################################
############################################################
# Implementation methods for interface
=== Zope3/lib/python/Zope/I18n/TranslationService.py 1.3 => 1.4 ===
from Persistence.BTrees.OOBTree import OOBTree
+from Zope.ComponentArchitecture import createObject
+from Zope.ComponentArchitecture import getService
+
from Zope.App.OFS.Container.BTreeContainer import BTreeContainer
from Zope.App.OFS.Container.IContainer import IContainer
from Zope.App.OFS.Container.IContainer import IHomogenousContainer
-from Zope.I18n.Negotiator import negotiator
-from Zope.I18n.IMessageCatalog import IMessageCatalog
-from Zope.I18n.ITranslationService import ITranslationService
-
-
-# Setting up some regular expressions for finding interpolation variables in
-# the text.
-NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
-_interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE}))
-_get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE}))
+from Negotiator import negotiator
+from Domain import Domain
+from IMessageCatalog import IMessageCatalog
+from ITranslationService import ITranslationService
+from SimpleTranslationService import SimpleTranslationService
class ILocalTranslationService(ITranslationService,
@@ -42,13 +40,16 @@
"""TTW manageable translation service"""
-class TranslationService(BTreeContainer):
+class TranslationService(BTreeContainer, SimpleTranslationService):
+ ''' '''
__implements__ = ILocalTranslationService
def __init__(self, default_domain='global'):
- self.__data = OOBTree()
+ ''' '''
+ super(TranslationService, self).__init__()
self._catalogs = OOBTree()
+ self.default_domain = default_domain
def _registerMessageCatalog(self, language, domain, catalog_name):
@@ -60,8 +61,7 @@
def _unregisterMessageCatalog(self, language, domain, catalog_name):
- mc = self._catalogs.get((language, domain), [])
- mc.append(catalog_name)
+ self._catalogs[(language, domain)].remove(catalog_name)
############################################################
@@ -69,21 +69,19 @@
# Zope.App.OFS.Container.IContainer.IWriteContainer
def setObject(self, name, object):
- """See Zope.App.OFS.Container.IContainer.IWriteContainer"""
- if type(name) in StringTypes and len(name)==0:
- raise ValueError
- if not self.isAddable(getattr(object,'__implements__', None)):
- raise UnaddableError (self, object, name)
- self.__data[name] = object
+ 'See Zope.App.OFS.Container.IContainer.IWriteContainer'
+ super(TranslationService, self).setObject(name, object)
self._registerMessageCatalog(object.getLanguage(), object.getDomain(),
name)
return name
def __delitem__(self, name):
- """See Zope.App.OFS.Container.IContainer.IWriteContainer"""
- del self.__data[name]
- self._unregisterMessageCatalog(object.language, object.domain, name)
-
+ 'See Zope.App.OFS.Container.IContainer.IWriteContainer'
+ object = self[name]
+ super(TranslationService, self).__delitem__(name)
+ self._unregisterMessageCatalog(object.getLanguage(),
+ object.getDomain(), name)
+
def isAddable(self, interfaces):
"""See Zope.App.OFS.Container.IContainer.IWriteContainer"""
@@ -113,14 +111,16 @@
raise TypeError, 'No destination language'
else:
avail_langs = self.getAvailableLanguages(domain)
+ # Let's negotiate the language to translate to. :)
+ negotiator = getService(self, 'LanguageNegotiation')
target_language = negotiator.getLanguage(avail_langs, context)
# Get the translation. Default is the source text itself.
- catalog_names = self._catalogs.get((target_language, domain), {})
+ catalog_names = self._catalogs.get((target_language, domain), [])
text = msgid
for name in catalog_names:
- catalog = self.__data[name]
+ catalog = super(TranslationService, self).__getitem__(name)
try:
text = catalog.getMessage(msgid)
break
@@ -134,19 +134,112 @@
############################################################
- def interpolate(self, text, mapping):
- """Insert the data passed from mapping into the text"""
-
- to_replace = _interp_regex.findall(text)
-
- for string in to_replace:
- var = _get_var_regex.findall(string)[0]
- text = text.replace(string, mapping.get(var))
-
- return text
+ def getMessageIdsOfDomain(self, domain, filter='%'):
+ """Get all the message ids of a particular domain."""
+ filter = filter.replace('%', '.*')
+ filter_re = re.compile(filter)
+
+ msg_ids = {}
+ languages = self.getAvailableLanguages(domain)
+ for language in languages:
+ for name in self._catalogs[(language, domain)]:
+ for id in self[name].getMessageIds():
+ if filter_re.match(id) >= 0:
+ msg_ids[id] = None
+ return msg_ids.keys()
+
+
+ def getAllLanguages(self):
+ """Find all the languages that are available"""
+ languages = {}
+ for key in self._catalogs.keys():
+ languages[key[0]] = None
+ return languages.keys()
+
+
+ def getAllDomains(self):
+ """Find all available domains."""
+ domains = {}
+ for key in self._catalogs.keys():
+ domains[key[1]] = None
+ return domains.keys()
def getAvailableLanguages(self, domain):
"""Find all the languages that are available for this domain"""
+ identifiers = self._catalogs.keys()
+ identifiers = filter(lambda x, d=domain: x[1] == d, identifiers)
+ languages = map(lambda x: x[0], identifiers)
+ return languages
+
+
+ def getAvailableDomains(self, language):
+ """Find all available domains."""
+ identifiers = self._catalogs.keys()
+ identifiers = filter(lambda x, l=language: x[0] == l, identifiers)
+ domains = map(lambda x: x[1], identifiers)
+ return domains
- return [x[0] for x in self._catalogs.keys() if x[1] == domain]
+
+ def addMessage(self, domain, msg_id, msg, target_language):
+ """ """
+ catalog_name = self._catalogs[(target_language, domain)][0]
+ catalog = self[catalog_name]
+ catalog.setMessage(msg_id, msg)
+
+
+ def updateMessage(self, domain, msg_id, msg, target_language):
+ """ """
+ catalog_name = self._catalogs[(target_language, domain)][0]
+ catalog = self[catalog_name]
+ catalog.setMessage(msg_id, msg)
+
+
+ def deleteMessage(self, domain, msg_id, target_language):
+ """ """
+ catalog_name = self._catalogs[(target_language, domain)][0]
+ catalog = self[catalog_name]
+ catalog.deleteMessage(msg_id)
+
+
+ def addLanguage(self, language):
+ """Add Language to Translation Service"""
+ domains = self.getAllDomains()
+ if not domains:
+ domains = [self.default_domain]
+
+ for domain in domains:
+ catalog = createObject(self, 'Message Catalog', language, domain)
+ self.setObject('%s-%s' %(domain, language), catalog)
+
+
+ def addDomain(self, domain):
+ """Add Domain to Translation Service"""
+ languages = self.getAllLanguages()
+ if not languages:
+ languages = ['en']
+
+ for language in languages:
+ catalog = createObject(self, 'Message Catalog', language, domain)
+ self.setObject('%s-%s' %(domain, language), catalog)
+
+
+ def deleteLanguage(self, language):
+ """Delete a Domain from the Translation Service."""
+ domains = self.getAvailableDomains(language)
+ for domain in domains:
+ for name in self._catalogs[(language, domain)]:
+ if self.has_key(name):
+ del self[name]
+ del self._catalogs[(language, domain)]
+
+ def deleteDomain(self, domain):
+ """Delete a Domain from the Translation Service."""
+ languages = self.getAvailableLanguages(domain)
+ for language in languages:
+ for name in self._catalogs[(language, domain)]:
+ if self.has_key(name):
+ del self[name]
+ del self._catalogs[(language, domain)]
+
+
=== Zope3/lib/python/Zope/I18n/i18n.zcml 1.2 => 1.3 ===
xmlns:browser='http://namespaces.zope.org/browser'
xmlns:service='http://namespaces.zope.org/service'
+ xmlns:gts='http://namespaces.zope.org/gts'
>
- <serviceType
+<serviceType
id="LanguageNegotiation"
interface=".INegotiator." />
- <service
+<service
serviceType="LanguageNegotiation"
component=".Negotiator.negotiator" />
-<!-- Language negotiation adapter
<adapter factory="Zope.Publisher.Browser.BrowserLanguages."
- for="Zope.Publisher.Browser.IHTTPRequest."
+ for="Zope.Publisher.Browser.IBrowserRequest."
provides="Zope.I18n.IUserPreferedLanguages"
/>
--->
+
+<!-- Register the Translation Service as a content object -->
<content class=".TranslationService.">
- <security:require permission="Zope.I18n"
+ <security:require permission="Zope.Public"
interface="Zope.I18n.ITranslationService."
/>
<security:require permission="Zope.ManageServices"
+ attributes="getMessageIdsOfDomain
+ getAllDomains getAllLanguages
+ getAvailableDomains getAvailableLanguages
+ addMessage updateMessage
+ addLanguage addDomain
+ deleteLanguage deleteDomain"
+ />
+ <security:require permission="Zope.ManageServices"
interface="Zope.App.OFS.Container.IContainer."
/>
</content>
+
+<service:factoryFromClass id="TranslationService"
+ class=".TranslationService."
+ permission="Zope.ManageServices"
+ title="Translation Service"
+ description="A Persistent Translation Service for TTW development" />
+
+
+<serviceType id="GlobalTranslationService"
+ interface=".ITranslationService." />
+
+<service serviceType="GlobalTranslationService"
+ permission="Zope.Public"
+ component=".GlobalTranslationService.translationService" />
+
+
<content class=".MessageCatalog.">
<security:require permission="Zope.Security"
interface=".IMessageCatalog."
/>
-
+ <security:require permission="Zope.ManageServices"
+ attributes="setMessage getMessageIds"
+ />
</content>
- <service:factoryFromClass id="TranslationService"
- class=".MessageCatalog."
- permission="Zope.ManageServices"
- title="Translation Service" />
-<!--factory component=".MessageCatalog." /-->
+<factory component=".MessageCatalog."
+ id="Message Catalog"/>
+
-<!--include package=".Views" file="views.zcml" /-->
+<gts:registerTranslations directory="./locale" />
+<include package=".Views" file="views.zcml" />
</zopeConfigure>