[Zope3-checkins] CVS: Zope3/src/zope/app/i18n - filters.py:1.1 interfaces.py:1.1 messagecatalog.py:1.1 translationdomain.py:1.1 configure.zcml:1.6

Stephan Richter srichter at cosmos.phy.tufts.edu
Mon Mar 8 18:35:21 EST 2004


Update of /cvs-repository/Zope3/src/zope/app/i18n
In directory cvs.zope.org:/tmp/cvs-serv4810/src/zope/app/i18n

Modified Files:
	configure.zcml 
Added Files:
	filters.py interfaces.py messagecatalog.py 
	translationdomain.py 
Log Message:


Moved local translation domain implementation to zope.app.i18n.




=== Added File Zope3/src/zope/app/i18n/filters.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.
#
##############################################################################
"""Translation Domain Message Export and Import Filters

$Id: filters.py,v 1.1 2004/03/08 23:34:50 srichter Exp $
"""
import time, re
from types import StringTypes
from zope.interface import implements

from zope.i18n.interfaces import IMessageExportFilter, IMessageImportFilter
from zope.app.i18n.interfaces import ILocalTranslationDomain


class ParseError(Exception):
    def __init__(self, state, lineno):
        Exception.__init__(self, state, lineno)
        self.state = state
        self.lineno = lineno

    def __str__(self):
        return "state %s, line %s" % (self.state, self.lineno)


class GettextExportFilter:

    implements(IMessageExportFilter)
    __used_for__ = ILocalTranslationDomain


    def __init__(self, domain):
        self.domain = domain

    def exportMessages(self, languages):
        'See IMessageExportFilter'
        domain = self.domain.domain

        if isinstance(languages, StringTypes):
            language = languages
        elif len(languages) == 1:
            language = languages[0]
        else:
            raise TypeError, \
                'Only one language at a time is supported for gettext export.'

        dt = time.time()
        dt = time.localtime(dt)
        dt = time.strftime('%Y/%m/%d %H:%M', dt)
        output = _file_header %(dt, language.encode('UTF-8'),
                                domain.encode('UTF-8'))

        for msgid in self.domain.getMessageIds():
            msgstr = self.domain.translate(msgid, target_language=language)
            msgstr = msgstr.encode('UTF-8')
            msgid = msgid.encode('UTF-8')
            output += _msg_template %(msgid, msgstr)

        return output



class GettextImportFilter:

    implements(IMessageImportFilter)
    __used_for__ = ILocalTranslationDomain


    def __init__(self, domain):
        self.domain = domain

    def importMessages(self, languages, file):
        'See IMessageImportFilter'

        if isinstance(languages, StringTypes):
            language = languages
        elif len(languages) == 1:
            language = languages[0]
        else:
            raise TypeError, \
                'Only one language at a time is supported for gettext export.'

        result = parseGetText(file.readlines())[3]
        headers = parserHeaders(''.join(result[('',)][1]))
        del result[('',)]
        charset = extractCharset(headers['content-type'])
        for msg in result.items():
            msgid = unicode(''.join(msg[0]), charset)
            msgid = msgid.replace('\\n', '\n')
            msgstr = unicode(''.join(msg[1][1]), charset)
            msgstr = msgstr.replace('\\n', '\n')
            self.domain.addMessage(msgid, msgstr, language)



def extractCharset(header):
    charset = header.split('charset=')[-1]
    return charset.lower()


def parserHeaders(headers_text):
    headers = {}
    for line in headers_text.split('\\n'):
        name = line.split(':')[0]
        value = ''.join(line.split(':')[1:])
        headers[name.lower()] = value

    return headers


def parseGetText(content):
    # The regular expressions
    com = re.compile('^#.*')
    msgid = re.compile(r'^ *msgid *"(.*?[^\\]*)"')
    msgstr = re.compile(r'^ *msgstr *"(.*?[^\\]*)"')
    re_str = re.compile(r'^ *"(.*?[^\\])"')
    blank = re.compile(r'^\s*$')

    trans = {}
    pointer = 0
    state = 0
    COM, MSGID, MSGSTR = [], [], []
    while pointer < len(content):
        line = content[pointer]
        #print 'STATE:', state
        #print 'LINE:', line, content[pointer].strip()
        if state == 0:
            COM, MSGID, MSGSTR = [], [], []
            if com.match(line):
                COM.append(line.strip())
                state = 1
                pointer = pointer + 1
            elif msgid.match(line):
                MSGID.append(msgid.match(line).group(1))
                state = 2
                pointer = pointer + 1
            elif blank.match(line):
                pointer = pointer + 1
            else:
                raise ParseError(0, pointer + 1)
        elif state == 1:
            if com.match(line):
                COM.append(line.strip())
                state = 1
                pointer = pointer + 1
            elif msgid.match(line):
                MSGID.append(msgid.match(line).group(1))
                state = 2
                pointer = pointer + 1
            elif blank.match(line):
                pointer = pointer + 1
            else:
                raise ParseError(1, pointer + 1)

        elif state == 2:
            if com.match(line):
                COM.append(line.strip())
                state = 2
                pointer = pointer + 1
            elif re_str.match(line):
                MSGID.append(re_str.match(line).group(1))
                state = 2
                pointer = pointer + 1
            elif msgstr.match(line):
                MSGSTR.append(msgstr.match(line).group(1))
                state = 3
                pointer = pointer + 1
            elif blank.match(line):
                pointer = pointer + 1
            else:
                raise ParseError(2, pointer + 1)

        elif state == 3:
            if com.match(line) or msgid.match(line):
                # print "\nEn", language, "detected", MSGID
                trans[tuple(MSGID)] = (COM, MSGSTR)
                state = 0
            elif re_str.match(line):
                MSGSTR.append(re_str.match(line).group(1))
                state = 3
                pointer = pointer + 1
            elif blank.match(line):
                pointer = pointer + 1
            else:
                raise ParseError(3, pointer + 1)

    # the last also goes in
    if tuple(MSGID):
        trans[tuple(MSGID)] = (COM, MSGSTR)

    return COM, MSGID, MSGSTR, trans


_file_header = '''
msgid ""
msgstr ""
"Project-Id-Version: Zope 3\\n"
"PO-Revision-Date: %s\\n"
"Last-Translator: Zope 3 Gettext Export Filter\\n"
"Zope-Language: %s\\n"
"Zope-Domain: %s\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
'''

_msg_template = '''
msgid "%s"
msgstr "%s"
'''


=== Added File Zope3/src/zope/app/i18n/interfaces.py ===
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Placeful internationalization of content objects.

$Id: interfaces.py,v 1.1 2004/03/08 23:34:50 srichter Exp $
"""
from zope.interface import Interface
from zope.i18n.interfaces import ITranslationDomain, IMessageCatalog
from zope.app.container.interfaces import IContainer


class IWriteTranslationDomain(Interface):
    """This interface describes the methods that are necessary for an editable
    Translation Domain to work.

    For a translation domain to be editable its 'messages' have to support
    the following information: id, string, domain, language, date

    Most of the information will be natural, since they are required by the
    translation domain, but especially the date is not a necessary info
    (in fact, it is meta data)
    """

    def getMessage(msgid, langauge):
        """Get the full message of a particular language."""

    def getMessageIds(filter='%'):
        """Get all the message ids of this domain."""

    def getMessages():
        """Get all the messages of this domain."""

    def getAllLanguages():
        """Find all the languages that are available"""

    def getAvailableLanguages():
        """Find all the languages that are available."""

    def addMessage(msgid, msg, language, mod_time=None):
        """Add a message to the translation domain.

        If mod_time is None, then the current time should be inserted.
        """

    def updateMessage(msgid, msg, language, mod_time=None):
        """Update a message in the translation domain.

        If mod_time is None, then the current time should be inserted.
        """

    def deleteMessage(domain, msgid, language):
        """Delete a messahe in the translation domain."""

    def addLanguage(language):
        """Add Language to Translation Domain"""

    def deleteLanguage(language):
        """Delete a Domain from the Translation Domain."""


class ISyncTranslationDomain(Interface):
    """This interface allows translation domains to be synchronized. The
       following four synchronization states can exist:

       0 - uptodate: The two messages are in sync.
                Default Action: Do nothing.

       1 - new: The message exists on the foreign TS, but is locally unknown.
                Default Action: Add the message to the local catalog.

       2 - older: The local version of the message is older than the one on
                the server.
                Default Action: Update the local message.

       3 - newer: The local version is newer than the foreign version.
                Default Action: Do nothing.

       4 - deleted: The message does not exist in the foreign TS.
                Default Action: Delete local version of message/
    """

    def getMessagesMapping(languages, foreign_messages):
        """Creates a mapping of the passed foreign messages and the local ones.
        Returns a status report in a dictionary with keys of the form
        (msgid, domain, language) and values being a tuple of:

        foreign_mod_date, local_mod_date
        """

    def synchronize(messages_mapping):
        """Update the local message catalogs based on the foreign data.
        """


class ILocalTranslationDomain(ITranslationDomain,
                              IWriteTranslationDomain,
                              ISyncTranslationDomain,
                              IContainer):
    """This is the common and full-features translation domain. Almost all
    translation domain implementations will use this interface.

    An exception to this is the GlobalMessageCatalog as it will be read-only.
    """


class ILocalMessageCatalog(IMessageCatalog):
    """If this interfaces is implemented by a message catalog, then we will be
    able to update our messages.

    Note that not all methods here require write access, but they should
    not be required for an IReadMEssageCatalog and are used for editing
    only. Therefore this is the more suitable interface to put them.
    """

    def getFullMessage(msgid):
        """Get the message data and meta data as a nice dictionary. More
        advanced implementation might choose to return an object with
        the data, but the object should then implement IEnumerableMapping.

        An exception is raised if the message id is not found.
        """

    def setMessage(msgid, message, mod_time=None):
        """Set a message to the catalog. If mod_time is None use the current
           time instead as modification time."""

    def deleteMessage(msgid):
        """Delete a message from the catalog."""

    def getMessageIds():
        """Get a list of all the message ids."""

    def getMessages():
        """Get a list of all the messages."""



=== Added File Zope3/src/zope/app/i18n/messagecatalog.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: messagecatalog.py,v 1.1 2004/03/08 23:34:50 srichter Exp $
"""
from zope.interface import classProvides, providedBy, implements
import time

from BTrees.OOBTree import OOBTree
from persistent import Persistent
from zope.proxy import removeAllProxies
from zope.component.interfaces import IFactory
from zope.app.i18n.interfaces import ILocalMessageCatalog


class MessageCatalog(Persistent):

    implements(ILocalMessageCatalog)
    classProvides(IFactory)

    def __init__(self, language, domain="default"):
        """Initialize the message catalog"""
        self.id  = ''
        self.title = ''
        self.description = ''
        self.language = language
        self.domain = domain
        self._messages = OOBTree()

    def getMessage(self, id):
        'See IReadMessageCatalog'
        return removeAllProxies(self._messages[id][0])

    def queryMessage(self, id, default=None):
        'See IReadMessageCatalog'
        result = removeAllProxies(self._messages.get(id))
        if result is not None:
            result = result[0]
        else:
            result = default
        return result

    def getIdentifier(self):
        'See IReadMessageCatalog'
        return (self.language, self.domain)

    def getFullMessage(self, msgid):
        'See IWriteMessageCatalog'
        message = removeAllProxies(self._messages[msgid])
        return {'domain'   : self.domain,
                'language' : self.language,
                'msgid'    : msgid,
                'msgstr'   : message[0],
                'mod_time' : message[1]}

    def setMessage(self, msgid, message, mod_time=None):
        'See IWriteMessageCatalog'
        if mod_time is None:
            mod_time = int(time.time())
        self._messages[msgid] = (message, mod_time)

    def deleteMessage(self, msgid):
        'See IWriteMessageCatalog'
        del self._messages[msgid]

    def getMessageIds(self):
        'See IWriteMessageCatalog'
        return list(self._messages.keys())

    def getMessages(self):
        'See IWriteMessageCatalog'
        messages = []
        for message in self._messages.items():
            messages.append({'domain'   : self.domain,
                             'language' : self.language,
                             'msgid'    : message[0],
                             'msgstr'   : message[1][0],
                             'mod_time' : message[1][1]})
        return messages

    def getInterfaces(self):
        'See IFactory'
        return tuple(providedBy(self))

    getInterfaces = classmethod(getInterfaces)


=== Added File Zope3/src/zope/app/i18n/translationdomain.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 the standard, placeful Translation Domain for TTW development.

$Id: translationdomain.py,v 1.1 2004/03/08 23:34:50 srichter Exp $
"""
import re
from BTrees.OOBTree import OOBTree
from zope.interface import implements

from zope.app import zapi
from zope.app.container.btree import BTreeContainer
from zope.app.i18n.interfaces import ILocalTranslationDomain
from zope.i18n import interpolate
from zope.i18n.negotiator import negotiator
from zope.i18n.interfaces import INegotiator, ITranslationDomain
from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
from zope.app.container.contained import Contained
from zope.app.component.nextservice import getNextService
from zope.component.servicenames import Utilities
from zope.app.services.utility import UtilityRegistration


class TranslationDomain(BTreeContainer, SimpleTranslationDomain, Contained):

    implements(ILocalTranslationDomain)

    def __init__(self):
        super(TranslationDomain, self).__init__()
        self._catalogs = OOBTree()
        self.domain = None

    def _registerMessageCatalog(self, language, catalog_name):
        if language not in self._catalogs.keys():
            self._catalogs[language] = []

        mc = self._catalogs[language]
        mc.append(catalog_name)

    def _unregisterMessageCatalog(self, language, catalog_name):
        self._catalogs[language].remove(catalog_name)

    def __setitem__(self, name, object):
        'See IWriteContainer'
        super(TranslationDomain, self).__setitem__(name, object)
        self._registerMessageCatalog(object.language, name)

    def __delitem__(self, name):
        'See IWriteContainer'
        object = self[name]
        super(TranslationDomain, self).__delitem__(name)
        self._unregisterMessageCatalog(object.language, name)

    def translate(self, msgid, mapping=None, context=None,
                  target_language=None, default=None):
        """See interface ITranslationService"""
        if target_language is None and context is not None:
            avail_langs = self.getAvailableLanguages()
            # Let's negotiate the language to translate to. :)
            negotiator = zapi.getUtility(self, INegotiator)
            target_language = negotiator.getLanguage(avail_langs, context)

        # Get the translation. Default is the source text itself.
        catalog_names = self._catalogs.get(target_language, [])

        for name in catalog_names:
            catalog = super(TranslationDomain, self).__getitem__(name)
            text = catalog.queryMessage(msgid)
            if text is not None:
                break
        else:
            # If nothing found, delegate to a translation server higher up the
            # tree.
            utils = getNextService(self, Utilities)
            domain = utils.queryUtility(ITranslationDomain, name=self.domain)
            if domain is not None:
                return domain.translate(msgid, mapping, context,
                                        target_language, default=default)
            else:
                text = default

        # Now we need to do the interpolation
        return interpolate(text, mapping)


    def getMessageIds(self, filter='%'):
        'See IWriteTranslationDomain'
        filter = filter.replace('%', '.*')
        filter_re = re.compile(filter)

        msgids = {}
        for language in self.getAvailableLanguages():
            for name in self._catalogs[language]:
                for msgid in self[name].getMessageIds():
                    if filter_re.match(msgid) >= 0:
                        msgids[msgid] = None
        return msgids.keys()

    def getMessages(self):
        'See IWriteTranslationDomain'
        messages = []
        languages = self.getAvailableLanguages()
        for language in languages:
            for name in self._catalogs[language]:
                messages += self[name].getMessages()
        return messages


    def getMessage(self, msgid, language):
        'See IWriteTranslationService'
        for name in self._catalogs.get(language, []):
            try:
                return self[name].getFullMessage(msgid)
            except:
                pass
        return None

    def getAllLanguages(self):
        'See IWriteTranslationService'
        languages = {}
        for key in self._catalogs.keys():
            languages[key] = None
        return languages.keys()


    def getAvailableLanguages(self):
        'See IWriteTranslationService'
        return list(self._catalogs.keys())


    def addMessage(self, msgid, msg, language, mod_time=None):
        'See IWriteTranslationService'
        if not self._catalogs.has_key(language):
            if language not in self.getAllLanguages():
                self.addLanguage(language)

        catalog_name = self._catalogs[language][0]
        catalog = self[catalog_name]
        catalog.setMessage(msgid, msg, mod_time)


    def updateMessage(self, msgid, msg, language, mod_time=None):
        'See IWriteTranslationService'
        catalog_name = self._catalogs[language][0]
        catalog = self[catalog_name]
        catalog.setMessage(msgid, msg, mod_time)


    def deleteMessage(self, msgid, language):
        'See IWriteTranslationService'
        catalog_name = self._catalogs[language][0]
        catalog = self[catalog_name]
        catalog.deleteMessage(msgid)


    def addLanguage(self, language):
        'See IWriteTranslationService'
        catalog = zapi.createObject(self, 'Message Catalog', language)
        self[language] = catalog


    def deleteLanguage(self, language):
        'See IWriteTranslationService'
        # Delete all catalogs from the data storage
        for name in self._catalogs[language]:
            if self.has_key(name):
                del self[name]
        # Now delete the specifc catalog registry for this language
        del self._catalogs[language]


    def getMessagesMapping(self, languages, foreign_messages):
        'See ISyncTranslationService'
        mapping = {}
        # Get all relevant local messages
        local_messages = []
        for language in languages:
            for name in self._catalogs.get(language, []):
                local_messages += self[name].getMessages()


        for fmsg in foreign_messages:
            ident = (fmsg['msgid'], fmsg['language'])
            mapping[ident] = (fmsg, self.getMessage(*ident))

        for lmsg in local_messages:
            ident = (lmsg['msgid'], lmsg['language'])
            if ident not in mapping.keys():
                mapping[ident] = (None, lmsg)

        return mapping


    def synchronize(self, messages_mapping):
        'See ISyncTranslationService'

        for value in messages_mapping.values():
            fmsg = value[0]
            lmsg = value[1]
            if fmsg is None:
                self.deleteMessage(lmsg['msgid'], lmsg['language'])
            elif lmsg is None:
                self.addMessage(fmsg['msgid'],
                                fmsg['msgstr'], fmsg['language'],
                                fmsg['mod_time'])
            elif fmsg['mod_time'] > lmsg['mod_time']:
                self.updateMessage(fmsg['msgid'],
                                   fmsg['msgstr'], fmsg['language'],
                                   fmsg['mod_time'])



class DomainRegistration(UtilityRegistration):
    """Domain Registration

    We have a custom registration here, since we want active registrations to
    set the domain of the TranslationDomain.
    """
    def activated(self):
        domain = self.getComponent()
        domain.domain = self.name

    def deactivated(self):
        domain = self.getComponent()
        domain.domain = '<domain not activated>'


=== Zope3/src/zope/app/i18n/configure.zcml 1.5 => 1.6 ===
--- Zope3/src/zope/app/i18n/configure.zcml:1.5	Tue Mar  2 12:49:12 2004
+++ Zope3/src/zope/app/i18n/configure.zcml	Mon Mar  8 18:34:50 2004
@@ -1,7 +1,5 @@
 <configure
-   xmlns="http://namespaces.zope.org/zope"
-   package="zope.i18n"
-   >
+   xmlns="http://namespaces.zope.org/zope">
 
 <!-- Setup language negotiation -->
 <utility 
@@ -17,12 +15,71 @@
     for="zope.publisher.interfaces.http.IHTTPRequest"
     provides="zope.i18n.interfaces.IUserPreferredCharsets" />
 
-<!-- Setup Translation Service -->
-<serviceType id="Translation" 
-             interface="zope.i18n.interfaces.ITranslationService" />
-
-<service serviceType="Translation"
-    permission="zope.Public"
-    component="zope.i18n.globaltranslationservice.translationService" />
+<!-- Register the Translation Domain as a content object -->
+<content 
+    class=".translationdomain.TranslationDomain">
+   <factory 
+        id="TranslationService" 
+        permission="zope.ManageServices" 
+        />
+    <implements
+        interface="zope.app.interfaces.services.utility.ILocalUtility" 
+        />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" 
+        />
+   <allow interface="zope.i18n.interfaces.ITranslationDomain" 
+       />
+   <require permission="zope.ManageServices"
+       interface="zope.app.container.interfaces.IContainer" 
+       />
+   <require permission="zope.ManageServices"
+       interface=".interfaces.IWriteTranslationDomain" 
+       />
+   <require permission="zope.ManageServices"
+       interface=".interfaces.ISyncTranslationDomain" 
+       />
+</content>
+
+<content class=".translationdomain.DomainRegistration">
+  <require
+    permission="zope.ManageServices"
+    interface="zope.app.interfaces.services.utility.IUtilityRegistration
+               zope.app.container.interfaces.IAddNotifiable
+               zope.app.container.interfaces.IRemoveNotifiable"
+    set_schema="zope.app.interfaces.services.utility.IUtilityRegistration" />
+</content>
+
+<!-- Setup Message Catalogs -->
+<content class=".messagecatalog.MessageCatalog">
+
+  <require permission="zope.View" 
+      interface="zope.i18n.interfaces.IMessageCatalog" />
+
+  <require permission="zope.ManageServices"
+      attributes="setMessage getMessageIds" />
+  <implements 
+      interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+</content>
+
+<factory 
+     component="zope.app.i18n.messagecatalog.MessageCatalog" 
+     id="Message Catalog"/>
+
+
+<!-- Setup Export and Import Filters -->
+<adapter 
+    factory=".filters.GettextExportFilter"
+    for=".interfaces.ILocalTranslationDomain"
+    provides="zope.i18n.interfaces.IMessageExportFilter" />
+
+<adapter 
+    factory=".filters.GettextImportFilter"
+    for=".interfaces.ILocalTranslationDomain"
+    provides="zope.i18n.interfaces.IMessageImportFilter" />
+
+<include package=".xmlrpc" />
+<include package=".browser" />
+
 
 </configure>




More information about the Zope3-Checkins mailing list