[Zope3-checkins] CVS: Zope3/src/zope/app/services/translation - __init__.py:1.2 configure.zcml:1.2 gettextexportfilter.py:1.2 gettextimportfilter.py:1.2 i18n_service.gif:1.2 messagecatalog.py:1.2 translationservice.py:1.2

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:13:52 -0500


Update of /cvs-repository/Zope3/src/zope/app/services/translation
In directory cvs.zope.org:/tmp/cvs-serv15352/src/zope/app/services/translation

Added Files:
	__init__.py configure.zcml gettextexportfilter.py 
	gettextimportfilter.py i18n_service.gif messagecatalog.py 
	translationservice.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zope/app/services/translation/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/translation/__init__.py	Wed Dec 25 09:13:21 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/services/translation/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/translation/configure.zcml	Wed Dec 25 09:13:21 2002
@@ -0,0 +1,50 @@
+<zopeConfigure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:browser="http://namespaces.zope.org/browser"
+   xmlns:service="http://namespaces.zope.org/service"
+   xmlns:gts="http://namespaces.zope.org/gts">
+
+<!-- Register the Translation Service as a content object -->
+<content class="zope.app.services.translation.translationservice.TranslationService">
+   <factory id="TranslationService" permission="zope.ManageServices" />
+   <require permission="zope.Public"
+       interface="zope.interfaces.i18n.ITranslationService" />
+   <require permission="zope.ManageServices"
+       interface="zope.app.interfaces.container.IContainer" />
+   <implements interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+</content>
+
+<browser:icon name="zmi_icon" for="zope.interfaces.i18n.ITranslationService"
+    file="./i18n_service.gif" />
+
+<!-- Setup Message Catalogs -->
+<content class="zope.app.services.translation.messagecatalog.MessageCatalog">
+
+  <require permission="zope.View" 
+      interface="zope.interfaces.i18n.IReadMessageCatalog" />
+
+  <require permission="zope.ManageServices"
+      attributes="setMessage getMessageIds" />
+  <implements interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+      interface="zope.interfaces.i18n.IWriteMessageCatalog" />
+
+
+</content>
+
+<factory component="zope.app.services.translation.messagecatalog.MessageCatalog" id="Message Catalog"/>
+
+<!-- Setup Export and Import Filters -->
+<adapter factory="zope.app.services.translation.gettextexportfilter.GettextExportFilter"
+    for="zope.interfaces.i18n.IWriteTranslationService"
+    provides="zope.interfaces.i18n.IMessageExportFilter" />
+
+<adapter factory="zope.app.services.translation.gettextimportfilter.GettextImportFilter"
+    for="zope.interfaces.i18n.IWriteTranslationService"
+    provides="zope.interfaces.i18n.IMessageImportFilter" />
+
+<gts:registerTranslations directory="./locale" />
+<gts:defaultLanguages languages="en" />
+
+<include package=".Views" />
+
+</zopeConfigure>


=== Zope3/src/zope/app/services/translation/gettextexportfilter.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/translation/gettextexportfilter.py	Wed Dec 25 09:13:21 2002
@@ -0,0 +1,90 @@
+##############################################################################
+#
+# 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 Service Message Export Filter
+
+$Id$
+"""
+import time
+from types import StringTypes
+
+from zope.interfaces.i18n import IMessageExportFilter
+from zope.interfaces.i18n import IWriteTranslationService
+
+
+class GettextExportFilter:
+
+    __implements__ =  IMessageExportFilter
+    __used_for__ = IWriteTranslationService
+
+
+    def __init__(self, service):
+        self.service = service
+
+    def exportMessages(self, domains, languages):
+        'See IMessageExportFilter'
+
+        if isinstance(domains, StringTypes):
+            domain = domains
+        elif len(domains) == 1:
+            domain = domains[0]
+        else:
+            raise TypeError, \
+                  'Only one domain at a time is supported for gettext export.'
+
+        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'))
+        service = self.service
+
+        for msgid in service.getMessageIdsOfDomain(domain):
+            msgstr = service.translate(domain, msgid,
+                                       target_language=language)
+            msgstr = msgstr.encode('UTF-8')
+            msgid = msgid.encode('UTF-8')
+            output += _msg_template %(msgid, msgstr)
+
+        return output
+
+    #
+    ############################################################
+
+
+
+_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"
+'''


=== Zope3/src/zope/app/services/translation/gettextimportfilter.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/translation/gettextimportfilter.py	Wed Dec 25 09:13:21 2002
@@ -0,0 +1,164 @@
+##############################################################################
+#
+# 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 Service Message Import Filter
+
+$Id$
+"""
+import time, re
+from types import StringTypes
+
+from zope.interfaces.i18n import IMessageImportFilter
+from zope.interfaces.i18n import IWriteTranslationService
+
+
+class GettextImportFilter:
+
+    __implements__ =  IMessageImportFilter
+    __used_for__ = IWriteTranslationService
+
+
+    def __init__(self, service):
+        self.service = service
+
+    def importMessages(self, domains, languages, file):
+        'See IMessageImportFilter'
+
+        if isinstance(domains, StringTypes):
+            domain = domains
+        elif len(domains) == 1:
+            domain = domains[0]
+        else:
+            raise TypeError, \
+                  'Only one domain at a time is supported for gettext export.'
+
+        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'])
+        service = self.service
+        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')
+            service.addMessage(domain, 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', 'state 0, line %d\n' % (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', 'state 1, line %d\n' % (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', 'state 2, line %d\n' % (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', 'state 3, line %d\n' % (pointer + 1)
+
+    # the last also goes in
+    if tuple(MSGID):
+        trans[tuple(MSGID)] = (COM, MSGSTR)
+
+    return COM, MSGID, MSGSTR, trans


=== Zope3/src/zope/app/services/translation/i18n_service.gif 1.1 => 1.2 ===
  <Binary-ish file>

=== Zope3/src/zope/app/services/translation/messagecatalog.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/translation/messagecatalog.py	Wed Dec 25 09:13:21 2002
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# 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$
+"""
+import time
+
+from zodb.btrees.OOBTree import OOBTree
+from persistence import Persistent
+from zope.proxy.introspection import removeAllProxies
+from zope.component.interfaces import IFactory
+from zope.app.security.registries.registeredobject import RegisteredObject
+from zope.interfaces.i18n import IMessageCatalog
+
+
+class MessageCatalog(RegisteredObject, Persistent):
+
+    __implements__ =  IMessageCatalog
+    __class_implements__ = IFactory
+
+    def __init__(self, language, domain="default"):
+        """Initialize the message catalog"""
+        super(MessageCatalog, self).__init__('', '', '')
+        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 getLanguage(self):
+        'See IReadMessageCatalog'
+        return self._language
+
+    def getDomain(self):
+        'See IReadMessageCatalog'
+        return self._domain
+
+    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 self.__implements__


=== Zope3/src/zope/app/services/translation/translationservice.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/translation/translationservice.py	Wed Dec 25 09:13:21 2002
@@ -0,0 +1,292 @@
+##############################################################################
+#
+# 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 Service for TTW development.
+
+$Id$
+"""
+import re
+from types import StringTypes, TupleType
+
+import persistence
+from zodb.btrees.OOBTree import OOBTree
+
+from zope.component import createObject, getService
+from zope.app.component.nextservice import queryNextService
+
+from zope.app.container.btree import BTreeContainer
+from zope.app.interfaces.container import IContainer
+
+from zope.i18n.negotiator import negotiator
+from zope.i18n.domain import Domain
+from zope.interfaces.i18n import IMessageCatalog
+from zope.interfaces.i18n import ITranslationService
+from zope.i18n.simpletranslationservice import SimpleTranslationService
+
+
+class ILocalTranslationService(ITranslationService, IContainer):
+    """TTW manageable translation service"""
+
+
+class TranslationService(BTreeContainer, SimpleTranslationService):
+
+    __implements__ =  ILocalTranslationService
+
+    def __init__(self, default_domain='global'):
+        super(TranslationService, self).__init__()
+        self._catalogs = OOBTree()
+        self.default_domain = default_domain
+
+
+    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 _unregisterMessageCatalog(self, language, domain, catalog_name):
+        self._catalogs[(language, domain)].remove(catalog_name)
+
+
+    def setObject(self, name, object):
+        'See IWriteContainer'
+        super(TranslationService, self).setObject(name, object)
+        self._registerMessageCatalog(object.getLanguage(), object.getDomain(),
+                                     name)
+        return name
+
+    def __delitem__(self, name):
+        'See IWriteContainer'
+        object = self[name]
+        super(TranslationService, self).__delitem__(name)
+        self._unregisterMessageCatalog(object.getLanguage(),
+                                       object.getDomain(), name)
+
+    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 = 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), [])
+
+        text = msgid
+        for name in catalog_names:
+            catalog = super(TranslationService, self).__getitem__(name)
+            text = catalog.queryMessage(msgid)
+
+        # If the message id equals the returned text, then we should look up
+        # a translation server higher up the tree.
+        if text == msgid:
+            ts = queryNextService(self, 'Translation')
+            if ts is not None:
+                return ts.translate(domain, msgid, mapping, context,
+                                    target_language)
+            else:
+                return text
+
+        # Now we need to do the interpolation
+        return self.interpolate(text, mapping)
+
+    def getMessageIdsOfDomain(self, domain, filter='%'):
+        'See IWriteTranslationService'
+        filter = filter.replace('%', '.*')
+        filter_re = re.compile(filter)
+
+        msgids = {}
+        languages = self.getAvailableLanguages(domain)
+        for language in languages:
+            for name in self._catalogs[(language, domain)]:
+                for msgid in self[name].getMessageIds():
+                    if filter_re.match(msgid) >= 0:
+                        msgids[msgid] = None
+        return msgids.keys()
+
+
+    def getMessagesOfDomain(self, domain):
+        'See IWriteTranslationService'
+        messages = []
+        languages = self.getAvailableLanguages(domain)
+        for language in languages:
+            for name in self._catalogs[(language, domain)]:
+                messages += self[name].getMessages()
+        return messages
+
+
+    def getMessage(self, msgid, domain, language):
+        'See IWriteTranslationService'
+        for name in self._catalogs.get((language, domain), []):
+            try:
+                return self[name].getFullMessage(msgid)
+            except:
+                pass
+        return None
+
+    def getAllLanguages(self):
+        'See IWriteTranslationService'
+        languages = {}
+        for key in self._catalogs.keys():
+            languages[key[0]] = None
+        return languages.keys()
+
+
+    def getAllDomains(self):
+        'See IWriteTranslationService'
+        domains = {}
+        for key in self._catalogs.keys():
+            domains[key[1]] = None
+        return domains.keys()
+
+
+    def getAvailableLanguages(self, domain):
+        'See IWriteTranslationService'
+        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):
+        'See IWriteTranslationService'
+        identifiers = self._catalogs.keys()
+        identifiers = filter(lambda x, l=language: x[0] == l, identifiers)
+        domains = map(lambda x: x[1], identifiers)
+        return domains
+
+
+    def addMessage(self, domain, msgid, msg, language, mod_time=None):
+        'See IWriteTranslationService'
+        if not self._catalogs.has_key((language, domain)):
+            if language not in self.getAllLanguages():
+                self.addLanguage(language)
+            if domain not in self.getAllDomains():
+                self.addDomain(domain)
+
+        catalog_name = self._catalogs[(language, domain)][0]
+        catalog = self[catalog_name]
+        catalog.setMessage(msgid, msg, mod_time)
+
+
+    def updateMessage(self, domain, msgid, msg, language, mod_time=None):
+        'See IWriteTranslationService'
+        catalog_name = self._catalogs[(language, domain)][0]
+        catalog = self[catalog_name]
+        catalog.setMessage(msgid, msg, mod_time)
+
+
+    def deleteMessage(self, domain, msgid, language):
+        'See IWriteTranslationService'
+        catalog_name = self._catalogs[(language, domain)][0]
+        catalog = self[catalog_name]
+        catalog.deleteMessage(msgid)
+
+
+    def addLanguage(self, language):
+        'See IWriteTranslationService'
+        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):
+        'See IWriteTranslationService'
+        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):
+        'See IWriteTranslationService'
+        domains = self.getAvailableDomains(language)
+        for domain in domains:
+            # Delete all catalogs from the data storage
+            for name in self._catalogs[(language, domain)]:
+                if self.has_key(name):
+                    del self[name]
+            # Now delete the specifc catalog registry for this lang/domain
+            del self._catalogs[(language, domain)]
+
+    def deleteDomain(self, domain):
+        'See IWriteTranslationService'
+        languages = self.getAvailableLanguages(domain)
+        for language in languages:
+            # Delete all catalogs from the data storage
+            for name in self._catalogs[(language, domain)]:
+                if self.has_key(name):
+                    del self[name]
+            # Now delete the specifc catalog registry for this lang/domain
+            del self._catalogs[(language, domain)]
+
+    def getMessagesMapping(self, domains, languages, foreign_messages):
+        'See ISyncTranslationService'
+        mapping = {}
+        # Get all relevant local messages
+        local_messages = []
+        for domain in domains:
+            for language in languages:
+                for name in self._catalogs.get((language, domain), []):
+                    local_messages += self[name].getMessages()
+
+
+        for fmsg in foreign_messages:
+            ident = (fmsg['msgid'], fmsg['domain'], fmsg['language'])
+            mapping[ident] = (fmsg, self.getMessage(*ident))
+
+        for lmsg in local_messages:
+            ident = (lmsg['msgid'], lmsg['domain'], 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['domain'], lmsg['msgid'],
+                                   lmsg['language'])
+            elif lmsg is None:
+                self.addMessage(fmsg['domain'], fmsg['msgid'],
+                                fmsg['msgstr'], fmsg['language'],
+                                fmsg['mod_time'])
+            elif fmsg['mod_time'] > lmsg['mod_time']:
+                self.updateMessage(fmsg['domain'], fmsg['msgid'],
+                                   fmsg['msgstr'], fmsg['language'],
+                                   fmsg['mod_time'])
+
+    #
+    ############################################################