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

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:32:27 -0500


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

Added Files:
      Tag: NameGeddon-branch
	__init__.py configure.zcml gettextexportfilter.py 
	gettextimportfilter.py i18n_service.gif messagecatalog.py 
	translationservice.py 
Log Message:
Initial renaming before debugging

=== Added File Zope3/src/zope/app/services/translation/__init__.py ===
#
# This file is necessary to make this directory a package.


=== Added File Zope3/src/zope/app/services/translation/configure.zcml ===
<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>


=== Added File Zope3/src/zope/app/services/translation/gettextexportfilter.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 Service Message Export Filter 

$Id: gettextexportfilter.py,v 1.1.2.1 2002/12/23 19:32:25 jim Exp $
"""
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

    ############################################################
    # Implementation methods for interface
    # Zope.I18n.IMessageExportFilter.IMessageExportFilter

    def exportMessages(self, domains, languages):
        'See Zope.I18n.IMessageExportFilter.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"
'''


=== Added File Zope3/src/zope/app/services/translation/gettextimportfilter.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 Service Message Import Filter 

$Id: gettextimportfilter.py,v 1.1.2.1 2002/12/23 19:32:25 jim Exp $
"""
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

    ############################################################
    # Implementation methods for interface
    # Zope.I18n.IMessageImportFilter.IMessageImportFilter

    def importMessages(self, domains, languages, file):
        'See Zope.I18n.IMessageImportFilter.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


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

=== Added File Zope3/src/zope/app/services/translation/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.2.1 2002/12/23 19:32:25 jim Exp $
"""
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()
    

    ############################################################
    # Implementation methods for interface
    # Zope.I18n.IMessageCatalog.IMessageCatalog

    ######################################
    # from: Zope.I18n.IMessageCatalog.IReadMessageCatalog

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

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

    def getLanguage(self):
        'See Zope.I18n.IMessageCatalog.IReadMessageCatalog'
        return self._language
        
    def getDomain(self):
        'See Zope.I18n.IMessageCatalog.IReadMessageCatalog'
        return self._domain

    def getIdentifier(self):
        'See Zope.I18n.IMessageCatalog.IReadMessageCatalog'
        return (self._language, self._domain)
        
    ######################################
    # from: Zope.I18n.IMessageCatalog.IWriteMessageCatalog

    def getFullMessage(self, msgid):
        'See Zope.I18n.IMessageCatalog.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 Zope.I18n.IMessageCatalog.IWriteMessageCatalog'
        if mod_time is None:
            mod_time = int(time.time())
        self._messages[msgid] = (message, mod_time)
        
    def deleteMessage(self, msgid):
        'See Zope.I18n.IMessageCatalog.IWriteMessageCatalog'
        del self._messages[msgid]

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

    def getMessages(self):
        'See Zope.I18n.IMessageCatalog.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

    #
    ############################################################

    ############################################################
    # Implementation methods for interface
    # Zope/ComponentArchitecture/IFactory.py

    def getInterfaces(self):
        'See Zope.ComponentArchitecture.IFactory.IFactory'
        return self.__implements__
        
    #
    ############################################################


=== Added File Zope3/src/zope/app/services/translation/translationservice.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 Service for TTW development.

$Id: translationservice.py,v 1.1.2.1 2002/12/23 19:32:25 jim Exp $
"""
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)


    ############################################################
    # Implementation methods for interface
    # Zope.App.OFS.Container.IContainer.IWriteContainer

    def setObject(self, 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'
        object = self[name]
        super(TranslationService, self).__delitem__(name)
        self._unregisterMessageCatalog(object.getLanguage(),
                                       object.getDomain(), name)

    # end Zope.App.OFS.Container.IContainer.IWriteContainer
    ############################################################


    ############################################################
    # Implementation methods for interface
    # Zope.I18n.ITranslationService.ITranslationService

    ######################################
    # from: Zope.I18n.ITranslationService.IReadTranslationService

    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)


    ######################################
    # from: Zope.I18n.ITranslationService.IWriteTranslationService
    
    def getMessageIdsOfDomain(self, domain, filter='%'):
        'See Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.IWriteTranslationService'
        for name in self._catalogs.get((language, domain), []):
            try:
                return self[name].getFullMessage(msgid)
            except:
                pass
        return None

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


    def getAllDomains(self):
        'See Zope.I18n.ITranslationService.IWriteTranslationService'
        domains = {}
        for key in self._catalogs.keys():
            domains[key[1]] = None
        return domains.keys()


    def getAvailableLanguages(self, domain):
        'See Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.IWriteTranslationService'
        catalog_name = self._catalogs[(language, domain)][0]
        catalog = self[catalog_name]
        catalog.deleteMessage(msgid)


    def addLanguage(self, language):
        'See Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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)]


    ######################################
    # from: Zope.I18n.ITranslationService.ISyncTranslationService

    def getMessagesMapping(self, domains, languages, foreign_messages):
        'See Zope.I18n.ITranslationService.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 Zope.I18n.ITranslationService.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'])

    #
    ############################################################