[Zope-Checkins] CVS: Zope3/lib/python/Zope/App/OFS/Services/TranslationService - GettextExportFilter.py:1.1 GettextImportFilter.py:1.1 MessageCatalog.py:1.1 TranslationService.py:1.1 __init__.py:1.1 configure.zcml:1.1 i18n_service.gif:1.1
Stephan Richter
srichter@cbu.edu
Thu, 11 Jul 2002 03:12:43 -0400
Update of /cvs-repository/Zope3/lib/python/Zope/App/OFS/Services/TranslationService
In directory cvs.zope.org:/tmp/cvs-serv24977/lib/python/Zope/App/OFS/Services/TranslationService
Added Files:
GettextExportFilter.py GettextImportFilter.py
MessageCatalog.py TranslationService.py __init__.py
configure.zcml i18n_service.gif
Log Message:
I moved the OFS-specific parts of the Translation Service to
Zope.App.OFS.Services, which is the way all the other local/placeful
service impolementations do it.
=== Added File Zope3/lib/python/Zope/App/OFS/Services/TranslationService/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 2002/07/11 07:12:41 srichter Exp $
"""
import time
from types import StringTypes
from Zope.I18n.IMessageExportFilter import IMessageExportFilter
from Zope.I18n.ITranslationService 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/lib/python/Zope/App/OFS/Services/TranslationService/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 2002/07/11 07:12:41 srichter Exp $
"""
import time, re
from types import StringTypes
from Zope.I18n.IMessageImportFilter import IMessageImportFilter
from Zope.I18n.ITranslationService 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(strip(line))
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(strip(line))
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(strip(line))
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/lib/python/Zope/App/OFS/Services/TranslationService/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 2002/07/11 07:12:41 srichter Exp $
"""
import time
from Persistence.BTrees.OOBTree import OOBTree
from Persistence import Persistent
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from Zope.ComponentArchitecture.IFactory import IFactory
from Zope.App.Security.Registries.RegisteredObject import RegisteredObject
from Zope.I18n.IMessageCatalog 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'
if default is None:
default = id
result = removeAllProxies(self._messages.get(id, default))
if result != default: result = result[0]
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/lib/python/Zope/App/OFS/Services/TranslationService/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 2002/07/11 07:12:41 srichter Exp $
"""
import re
from types import StringTypes, TupleType
import Persistence
from Persistence.BTrees.OOBTree import OOBTree
from Zope.ComponentArchitecture import createObject
from Zope.ComponentArchitecture import getService, queryNextService
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.Domain import Domain
from Zope.I18n.IMessageCatalog import IMessageCatalog
from Zope.I18n.ITranslationService import ITranslationService
from Zope.I18n.SimpleTranslationService import SimpleTranslationService
class ILocalTranslationService(ITranslationService,
IContainer, IHomogenousContainer):
"""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)
def isAddable(self, interfaces):
"""See Zope.App.OFS.Container.IContainer.IWriteContainer"""
if type(interfaces) != TupleType:
interfaces = (interfaces,)
if IMessageCatalog in interfaces:
return 1
return 0
# 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, 'TranslationService')
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'])
#
############################################################
=== Added File Zope3/lib/python/Zope/App/OFS/Services/TranslationService/__init__.py ===
=== Added File Zope3/lib/python/Zope/App/OFS/Services/TranslationService/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=".TranslationService.">
<factory id="TranslationService" permission="Zope.ManageServices" />
<require permission="Zope.Public"
interface="Zope.I18n.ITranslationService." />
<require permission="Zope.ManageServices"
interface="Zope.App.OFS.Container.IContainer." />
</content>
<browser:icon name="zmi_icon" for="Zope.I18n.ITranslationService."
file="./i18n_service.gif" />
<!-- Setup Message Catalogs -->
<content class=".MessageCatalog.">
<require permission="Zope.Security" interface="Zope.I18n.IMessageCatalog." />
<require permission="Zope.ManageServices"
attributes="setMessage getMessageIds" />
</content>
<factory component=".MessageCatalog." id="Message Catalog"/>
<!-- Setup Export and Import Filters -->
<adapter factory=".GettextExportFilter."
for="Zope.I18n.ITranslationService.IWriteTranslationService."
provides="Zope.I18n.IMessageExportFilter." />
<adapter factory=".GettextImportFilter."
for="Zope.I18n.ITranslationService.IWriteTranslationService."
provides="Zope.I18n.IMessageImportFilter." />
<gts:registerTranslations directory="./locale" />
<gts:defaultLanguages languages="en" />
<include package=".Views" />
</zopeConfigure>
=== Added File Zope3/lib/python/Zope/App/OFS/Services/TranslationService/i18n_service.gif ===
<Binary-ish file>