[Zope3-checkins] CVS: Zope3/src/zope/app/browser/services/translation - __init__.py:1.2 basetranslationserviceview.py:1.2 configure.zcml:1.2 exportimport.pt:1.2 exportimport.py:1.2 synchronize.pt:1.2 synchronize.py:1.2 translate.pt:1.2 translate.py:1.2 translatemessage.pt:1.2

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


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

Added Files:
	__init__.py basetranslationserviceview.py configure.zcml 
	exportimport.pt exportimport.py synchronize.pt synchronize.py 
	translate.pt translate.py translatemessage.pt 
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/browser/services/translation/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/__init__.py	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/browser/services/translation/basetranslationserviceview.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/basetranslationserviceview.py	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,34 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Synchronize with Foreign Translation Services
+
+$Id$
+"""
+
+from zope.publisher.browser import BrowserView
+from zope.interfaces.i18n import ITranslationService
+
+
+class BaseTranslationServiceView(BrowserView):
+
+    __used_for__ = ITranslationService
+
+
+    def getAllLanguages(self):
+        """Get all available languages from the Translation Service."""
+        return self.context.getAllLanguages()
+
+
+    def getAllDomains(self):
+        return self.context.getAllDomains()


=== Zope3/src/zope/app/browser/services/translation/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/configure.zcml	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,71 @@
+<zopeConfigure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:browser="http://namespaces.zope.org/browser">
+
+<browser:defaultView for="zope.interfaces.i18n.ITranslationService" name="index.html" />
+
+<browser:view 
+   permission="zope.ManageServices" 
+   for="zope.interfaces.i18n.ITranslationService"
+   factory="zope.app.browser.services.translation.translate.Translate">
+
+   <browser:page name="index.html" attribute="index" />
+   <browser:page name="translateMessage.html" attribute="translateMessage" />
+
+   <browser:page name="editMessages.html" attribute="editMessages" />
+   <browser:page name="editMessage.html" attribute="editMessage" />
+
+   <browser:page name="deleteMessages.html" attribute="deleteMessages" />
+
+   <browser:page name="addLanguage.html" attribute="addLanguage" />
+   <browser:page name="addDomain.html" attribute="addDomain" />
+
+   <browser:page name="changeEditLanguages.html" 
+                 attribute="changeEditLanguages" />
+   <browser:page name="changeEditDomains.html" 
+                 attribute="changeEditDomains" />
+   <browser:page name="changeFilter.html" 
+                 attribute="changeFilter" />
+
+   <browser:page name="deleteLanguages.html" attribute="deleteLanguages" />
+   <browser:page name="deleteDomains.html" attribute="deleteDomains" />
+
+</browser:view>
+
+<browser:view 
+   permission="zope.ManageServices" 
+   for="zope.interfaces.i18n.ITranslationService"
+   factory="zope.app.browser.services.translation.exportimport.ExportImport">
+
+   <browser:page name="exportImportForm.html" attribute="exportImportForm" />
+
+   <browser:page name="export.html" attribute="exportMessages" />
+   <browser:page name="import.html" attribute="importMessages" />
+
+</browser:view>
+
+<browser:view 
+   permission="zope.ManageServices" 
+   for="zope.interfaces.i18n.ITranslationService"
+   factory="zope.app.browser.services.translation.synchronize.Synchronize">
+
+   <browser:page name="synchronizeForm.html" attribute="synchronizeForm" />
+   <browser:page name="synchronize.html" attribute="synchronize" />
+   <browser:page name="synchronizeMessages.html" 
+                 attribute="synchronizeMessages" />
+   <browser:page name="saveSettings.html" attribute="saveSettings" />
+
+</browser:view>
+
+
+<browser:menuItems menu="zmi_views" for="zope.interfaces.i18n.ITranslationService">
+  <browser:menuItem title="Translate" action="@@index.html"/>
+  <browser:menuItem title="Import/Export" action="@@exportImportForm.html"/>
+  <browser:menuItem title="Synchronize" action="@@synchronizeForm.html"/>
+</browser:menuItems>
+
+<browser:menuItem menu="add_component" for="zope.app.interfaces.container.IAdding"
+     action="TranslationService"  title="Translation Service"
+     description="A Persistent Translation Service for TTW development" />
+
+</zopeConfigure>


=== Zope3/src/zope/app/browser/services/translation/exportimport.pt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/exportimport.pt	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,56 @@
+<html metal:use-macro="views/standard_macros/page">
+<head>
+  <title>Translation Service - Translate</title>
+</head>
+<body>
+
+<div metal:fill-slot="body">
+
+<h3>Import and Export Messages</h3>
+
+<p>Here you can export and import messages from your Translation Service.</p>
+
+<form action="./" method="post" enctype="multipart/form-data"
+      i18n:domain="Zope-I18n">
+  <table cols="4" width="100%" border="0">
+    <tr>
+      <td width="25%">
+        <div class="form-label" i18n:translate="">Select Languages:</div>
+        <div>
+          <select name="languages:list" size="3" style="width: 80%" multiple>
+            <option value="" 
+                    tal:attributes="value language"
+                    tal:content="language"
+                    tal:repeat="language view/getAllLanguages"></option>
+          </select>
+        </div>
+      </td>
+      <td width="25%">
+        <div class="form-label" i18n:translate="">Select Domains:</div>
+        <div>
+          <select name="domains:list" size="3" style="width: 80%" multiple>
+            <option value="" 
+                    tal:attributes="value domain"
+                    tal:content="domain"
+                    tal:repeat="domain view/getAllDomains"></option>
+          </select>
+        </div>
+      </td>
+      <td width="25%" valign="top">
+        <div class="form-label" i18n:translate="">Import File Name:</div>
+        <div>
+          <input type="file" name="file" size="20" value="">
+        </div>
+        <div>
+          <input type="submit" name="@@import.html:method" value="Import">
+          <input type="submit" name="@@export.html:method" value="Export">
+        </div>
+      </td>
+    </tr>
+  </table>
+</form>
+
+</div>
+
+</body>
+</html>


=== Zope3/src/zope/app/browser/services/translation/exportimport.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/exportimport.py	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Message Export/Import View
+
+$Id$
+"""
+from zope.component import getAdapter
+from zope.interfaces.i18n import IMessageExportFilter, IMessageImportFilter
+
+from zope.app.browser.services.translation.basetranslationserviceview \
+     import BaseTranslationServiceView
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+
+
+class ExportImport(BaseTranslationServiceView):
+
+    exportImportForm = ViewPageTemplateFile('exportimport.pt')
+
+    def exportMessages(self, domains, languages):
+        self.request.response.setHeader('content-type',
+                                             'application/x-gettext')
+        filter = getAdapter(self.context, IMessageExportFilter)
+        return filter.exportMessages(domains, languages)
+
+    def importMessages(self, domains, languages, file):
+        filter = getAdapter(self.context, IMessageImportFilter)
+        filter.importMessages(domains, languages, file)
+        return self.request.response.redirect(self.request.URL[-1])


=== Zope3/src/zope/app/browser/services/translation/synchronize.pt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/synchronize.pt	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,144 @@
+<html metal:use-macro="views/standard_macros/page">
+  <head>
+    <title>Translation Service - Synchronize</title>
+  </head>
+<body>
+
+<div metal:fill-slot="body">
+
+<style type="text/css">
+  <!--
+  .state0 {color: green;}
+  .state1 {color: yellow;}
+  .state2 {color: yellow;}
+  .state3 {color:  red;}
+  .state4 {color:  red;}
+ -->
+</style>
+
+  <table cols="4" width="100%" border="0" cellspacing="0">
+    <form action="./" method="post">
+      <tr>
+        <td width="30%">
+          <div class="form-label">Server URL</div>
+          <div>
+            <input type="text" size="40" name="sync_url" value=""
+                   tal:attributes="value view/sync_url" />
+          </div>
+          <div>Username</div>
+          <div>
+            <input type="text" size="40" name="sync_username" value=""
+                   tal:attributes="value view/sync_username" />
+          </div>
+          <div>Password</div>
+          <div>
+            <input type="password" size="40" name="sync_password" value=""
+                   tal:attributes="value view/sync_password" />
+          </div>
+        </td>
+        <td width="25%">
+          <div>Select Domains:</div>
+          <div>
+            <select name="sync_domains:list" size="6" style="width: 80%" 
+                    multiple>
+              <tal:block repeat="domain view/getAllDomains">
+              <option value="" 
+		      tal:attributes="value domain"
+		      tal:content="domain"
+                      tal:condition="python: domain not in
+                                     view.sync_domains" ></option>
+              <option value="" selected="1"
+		      tal:attributes="value domain"
+		      tal:content="domain"
+                      tal:condition="python: domain in
+                                     view.sync_domains" ></option>
+              </tal:block>
+            </select>
+          </div>
+        </td>
+        <td width="25%">
+          <div>Select Languages:</div>
+          <div>
+            <select name="sync_languages:list" size="6" style="width: 80%" 
+                    multiple>
+              <tal:block repeat="language view/getAllLanguages">
+              <option value="" 
+		      tal:attributes="value language"
+		      tal:content="language"
+                      tal:condition="python: language not in
+                                     view.sync_languages" ></option>
+              <option value="" selected="1"
+		      tal:attributes="value language"
+		      tal:content="language"
+                      tal:condition="python: language in
+                                     view.sync_languages" ></option>
+              </tal:block>
+            </select>
+          </div>
+        </td>
+        <td width="20%">
+          <center>
+            <div><input type="submit" name="saveSettings.html:method" 
+                        value="Save Settings"></div><br>
+            <div><input type="submit" name="synchronize.html:method" 
+                        value="Synchronize"></div>
+          </center>
+        </td>
+      </tr>
+    </form>
+  </table>
+  <br>
+
+<form action="./"
+      tal:condition="view/canConnect">
+  <table cols="5" width="95%" border="0" cellpadding="2" cellspacing="0" 
+         class="listing">
+    <tr>
+      <th width="16">&nbsp;</th>
+      <th width="55%">Message Id</th>
+      <th width="15%">Domain</th>
+      <th width="10%">Language</th>
+      <th width="15%">Status</th>
+    </tr>
+    <tal:block repeat="message python: view.queryMessages().items()">
+      <tr tal:define="number repeat/message/number;
+	              oddrow repeat/message/odd"
+          tal:attributes="class python: oddrow and 'odd' or 'even'">
+        <td width="16">
+          <input type="hidden"
+      	       tal:attributes="name python: 'update-msgid-%i' %number;
+                                 value python: message[0][0]">
+          <input type="hidden"
+      	       tal:attributes="name python: 'update-domain-%i' %number;
+                                 value python: message[0][1]">
+          <input type="hidden"
+      	       tal:attributes="name python: 'update-language-%i' %number;
+                                 value python: message[0][2]">
+          <input type="checkbox" name="message_ids:list"
+      	       tal:attributes="value python: number">
+        </td>
+        <td tal:content="python: message[0][0]">Hello World!</td>
+        <td tal:content="python: message[0][1]">default</td>
+        <td tal:content="python: message[0][2]">en</td>
+        <td>
+          <b tal:content="python: view.getStatus(*message[1])"
+             tal:attributes="class python:'state%i' %
+                             view.getStatus(message[1][0], message[1][1], 0)"
+            >status</b>
+        </td>
+      </tr>
+    </tal:block>
+  </table>
+  <div><input type="submit" name="@@synchronizeMessages.html:method" 
+              value="Update"></div>
+
+</form>
+
+<p tal:condition="python: not view.canConnect()">
+No connection could be made to remote data source.
+</p>
+
+</div>
+
+</body>
+</html>
\ No newline at end of file


=== Zope3/src/zope/app/browser/services/translation/synchronize.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/synchronize.py	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Synchronize with Foreign Translation Services
+
+$Id$
+"""
+import httplib
+import urllib
+import xmlrpclib
+
+from base64 import encodestring
+
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+from zope.app.browser.services.translation.basetranslationserviceview \
+     import BaseTranslationServiceView
+
+
+class Synchronize(BaseTranslationServiceView):
+
+    synchronizeForm = ViewPageTemplateFile('synchronize.pt')
+
+    messageStatus = ['Up to Date', 'New Remote', 'Out of Date', 'Newer Local',
+                     'Does not exist']
+
+    def __init__(self, context, request):
+        super(Synchronize, self).__init__(context, request)
+
+        self.sync_url = self.request.cookies.get('sync_url',
+                                'http://localhost:8081/++etc++Services/ts/')
+        self.sync_url = urllib.unquote(self.sync_url)
+        self.sync_username = self.request.cookies.get('sync_username', 'admin')
+        self.sync_password = self.request.cookies.get('sync_password', 'admin')
+        self.sync_domains = filter(None, self.request.cookies.get(
+            'sync_domains', '').split(','))
+        self.sync_languages = filter(None, self.request.cookies.get(
+            'sync_languages', '').split(','))
+
+
+    def _connect(self):
+        '''Connect to the remote server via XML-RPC HTTP; return status'''
+        # make sure the URL contains the http:// prefix
+        if not self.sync_url.startswith('http://'):
+            url = 'http://' + self.sync_url
+        else:
+            url = self.sync_url
+
+        # Now try to connect
+        self._connection = xmlrpclib.Server(
+            url, transport = BasicAuthTransport(self.sync_username,
+                                                self.sync_password))
+
+        # check whether the connection was made and the Master Babel Tower
+        # exists
+        try:
+            self._connection.getAllDomains()
+            return 1
+        except:
+            self._connection = None
+            return 0
+
+
+    def _disconnect(self):
+        '''Disconnect from the sever; return None'''
+        if hasattr(self, '_connection') and self._connection is not None:
+            self._connection = None
+
+
+    def _isConnected(self):
+        '''Check whether we are currently connected to the server; return
+        boolean'''
+        if not hasattr(self, '_connection'):
+            self._connection = None
+
+        if not self._connection is None and self._connection.getAllDomains():
+            return 1
+        else:
+            return 0
+
+
+    def canConnect(self):
+        '''Checks whether we can connect using this server and user data;
+        return boolean'''
+        if self._isConnected():
+            return 1
+        else:
+            try:
+                return self._connect()
+            except:
+                return 0
+
+
+    def getAllDomains(self):
+        connected = self._isConnected()
+        if not connected: connected = self._connect()
+
+        if connected:
+
+            return self._connection.getAllDomains()
+        else:
+            return []
+
+
+    def getAllLanguages(self):
+        connected = self._isConnected()
+        if not connected: connected = self._connect()
+
+        if connected:
+            return self._connection.getAllLanguages()
+        else:
+            return []
+
+
+
+    def queryMessages(self):
+        connected = self._isConnected()
+        if not connected: connected = self._connect()
+
+        if connected:
+            fmsgs = self._connection.getMessagesFor(self.sync_domains,
+                                                    self.sync_languages)
+        else:
+            fmdgs = []
+
+        return self.context.getMessagesMapping(self.sync_domains,
+                                               self.sync_languages,
+                                               fmsgs)
+
+
+    def getStatus(self, fmsg, lmsg, verbose=1):
+        state = 0
+        if fmsg is None:
+            state = 4
+        elif lmsg is None:
+            state = 1
+        elif fmsg['mod_time'] > lmsg['mod_time']:
+            state = 2
+        elif fmsg['mod_time'] < lmsg['mod_time']:
+            state = 3
+        elif fmsg['mod_time'] == lmsg['mod_time']:
+            state = 0
+
+        if verbose:
+            return self.messageStatus[state]
+        return state
+
+
+    def saveSettings(self):
+        self.sync_domains = self.request.form.get('sync_domains', [])
+        self.sync_languages = self.request.form.get('sync_languages', [])
+        self.request.response.setCookie('sync_domains',
+                                             ','.join(self.sync_domains))
+        self.request.response.setCookie('sync_languages',
+                                             ','.join(self.sync_languages))
+        self.request.response.setCookie('sync_url',
+                            urllib.quote(self.request['sync_url']).strip())
+        self.request.response.setCookie('sync_username',
+                                             self.request['sync_username'])
+        self.request.response.setCookie('sync_password',
+                                             self.request['sync_password'])
+
+        return self.request.response.redirect(self.request.URL[-1]+
+                                                   '/@@synchronizeForm.html')
+
+
+    def synchronize(self):
+        mapping = self.queryMessages()
+        self.context.synchronize(mapping)
+        return self.request.response.redirect(self.request.URL[-1]+
+                                                   '/@@synchronizeForm.html')
+
+
+    def synchronizeMessages(self):
+        idents = []
+        for id in self.request.form['message_ids']:
+            msgid = self.request.form['update-msgid-'+id]
+            domain = self.request.form['update-domain-'+id]
+            language = self.request.form['update-language-'+id]
+            idents.append((msgid, domain, language))
+
+        mapping = self.queryMessages()
+        new_mapping = {}
+        for item in mapping.items():
+            if item[0] in idents:
+                new_mapping[item[0]] = item[1]
+
+        self.context.synchronize(new_mapping)
+        return self.request.response.redirect(self.request.URL[-1]+
+                                                   '/@@synchronizeForm.html')
+
+
+
+class BasicAuthTransport(xmlrpclib.Transport):
+    def __init__(self, username=None, password=None, verbose=0):
+        self.username=username
+        self.password=password
+        self.verbose=verbose
+
+    def request(self, host, handler, request_body, verbose=0):
+        # issue XML-RPC request
+
+        self.verbose = verbose
+
+        h = httplib.HTTP(host)
+        h.putrequest("POST", handler)
+
+        # required by HTTP/1.1
+        h.putheader("Host", host)
+
+        # required by XML-RPC
+        h.putheader("User-Agent", self.user_agent)
+        h.putheader("Content-Type", "text/xml")
+        h.putheader("Content-Length", str(len(request_body)))
+
+        # basic auth
+        if self.username is not None and self.password is not None:
+            h.putheader("AUTHORIZATION", "Basic %s" %
+                        encodestring("%s:%s" % (self.username, self.password)
+                                     ).replace("\012", ""))
+        h.endheaders()
+
+        if request_body:
+            h.send(request_body)
+
+        errcode, errmsg, headers = h.getreply()
+
+        if errcode != 200:
+            raise xmlrpclib.ProtocolError(host + handler,
+                                          errcode, errmsg, headers)
+
+        return self.parse_response(h.getfile())


=== Zope3/src/zope/app/browser/services/translation/translate.pt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/browser/services/translation/translate.pt	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,188 @@
+<html metal:use-macro="views/standard_macros/page">
+<head>
+  <title>Translation Service - Translate</title>
+</head>
+<body>
+
+<div metal:fill-slot="body">
+
+<span i18n:domain="Zope-I18n">
+
+<table cols="4" width="100%" border="0">
+  <tr>
+    <td width="25%">
+      <form action="./" method="post">
+        <div class="form-label" i18n:translate="">Select Languages:</div>
+        <div>
+          <select name="languages:list" size="3" style="width: 80%" multiple>
+            <tal:block repeat="language view/getAllLanguages">
+              <option value="" 
+		      tal:attributes="value language"
+		      tal:content="language"
+                      tal:condition="python: language not in
+                                     view.getEditLanguages()" ></option>
+              <option value="" selected="1"
+		      tal:attributes="value language"
+		      tal:content="language"
+                      tal:condition="python: language in
+                                     view.getEditLanguages()" ></option>
+            </tal:block>
+          </select>
+        </div>
+        <div>
+          <input class="form-element" type="submit" 
+                 name="@@changeEditLanguages.html:method" value="Edit"
+		 i18n:attributes="value">
+          <input class="form-element" type="submit" 
+                 name="@@deleteLanguages.html:method" value="Delete"
+		 i18n:attributes="value">
+        </div>
+      </form>
+    </td>
+    <td width="25%">
+      <form action="./" method="post">
+        <div class="form-label" i18n:translate="">Select Domains:</div>
+        <div>
+          <select name="domains:list" size="3" style="width: 80%" multiple>
+            <tal:block repeat="domain view/getAllDomains">
+              <option value="" 
+		      tal:attributes="value domain"
+		      tal:content="domain"
+                      tal:condition="python: domain not in
+                                     view.getEditDomains()" ></option>
+              <option value="" selected="1"
+		      tal:attributes="value domain"
+		      tal:content="domain"
+                      tal:condition="python: domain in
+                                     view.getEditDomains()" ></option>
+            </tal:block>
+            </select>
+          </div>
+          <div>
+            <input class="form-element" type="submit" 
+                   name="@@changeEditDomains.html:method" value="Edit"
+                   i18n:attributes="value">
+            <input class="form-element" type="submit" 
+                   name="@@deleteDomains.html:method" value="Delete"
+                   i18n:attributes="value">
+          </div>
+        </form>
+      </td>
+      <td width="25%" align="right" valign="top">
+        <form action="." method="post">
+          <div class="form-label" i18n:translate="">New Language:</div>
+          <div>
+            <input type="text" name="language" size="20" value="">
+            <input type="submit" name="@@addLanguage.html:method" value="Add">
+          </div>
+          <div class="form-label" i18n:translate="">New Domain:</div>
+          <div>
+            <input type="text" name="domain" size="20" value="">
+            <input type="submit" name="@@addDomain.html:method" value="Add">
+          </div>
+        </form>
+      </td>
+      <td width="25%" align="right" valign="top">
+        <form action="./" method="post">
+          <div class="form-label" 
+               i18n:translate="">Filter (% - wildcard):</div>
+          <div>
+            <input type="text" name="filter" size="25" value=""
+                   tal:attributes="value request/filter|default" />
+          </div>
+          <div>
+            <input type="submit" name="@@changeFilter.html:method" 
+                   value="Filter" 
+                   i18n:attributes="value"/>
+          </div>
+        </form>
+      </td>
+    </tr>
+  </table>
+
+  <form action="./" method="post">
+  <table width="100%" cellspacing="0" cellpadding="3" border="0" 
+         class="listing">
+    <tr class="list-header" align="left">
+        <th width="16">&nbsp;</th>
+        <th i18n:translate="">Message Id</th>
+        <th i18n:translate="">Domain</th>
+        <th tal:repeat="language python:view.getEditLanguages()"
+            tal:content="language">de</th>
+    </tr>    
+    <tal:block repeat="message python: view.getMessages()">
+      <tr tal:define="oddrow repeat/message/odd"
+          tal:attributes="class python: oddrow and 'odd' or 'even'">
+      	<td>
+      	  <input type="hidden"
+                 tal:attributes="name python: 'edit-msg_id-%i' %message[2];
+                                 value python: message[0]">
+          <input type="hidden"
+                 tal:attributes="name python: 'edit-domain-%i' %message[2];
+                                 value python: message[1]">
+          <input type="checkbox" name="message_ids:list"
+                 tal:attributes="value python: message[2]">
+        </td>
+        <td>
+          <a href=""
+             tal:content="python: message[0]"
+             tal:attributes="
+              href python:'translateMessage.html?msgid=%s&domain=%s' %(
+                          message[0], message[1])">message_id</a>
+        </td>
+        <td tal:content="python: message[1]">
+          default
+        </td>
+        <td tal:repeat="language python:view.getEditLanguages()">
+          <textarea cols="20" rows="2"
+             tal:attributes="name python: 'edit-%s-%i' %(language, message[2])"
+            tal:content="python: view.getTranslation(message[1], 
+                                 message[0], language)"></textarea>
+        </td>
+      </tr>
+    </tal:block>
+    <tr><th colspan="3"
+         tal:attributes="colspan python:len(view.getEditLanguages())+3">
+      Add new messages
+    </th></tr>
+    
+    <tal:block repeat="count python: range(5)">
+      <tr tal:define="oddrow repeat/count/odd"
+          tal:attributes="class python: oddrow and 'odd' or 'even'">
+      	<td width="16">&nbsp;</td>
+      	<td>
+      	  <textarea cols="20" rows="2" name=""
+      	        tal:attributes="name string:new-msg_id-${count}"></textarea> 
+      	</td>
+      	<td>
+      	  <select name=""
+      	          tal:attributes="name string:new-domain-${count}">
+      	    <option value=""
+      		          tal:repeat="domain python: view.getEditDomains()"
+      	            tal:content="domain"
+      			  tal:attributes="value domain">default</option>
+      	  </select>
+      	</td>
+      	<td tal:repeat="language python:view.getEditLanguages()">
+      	  <textarea cols="20" rows="2" name=""
+      	    tal:attributes="name string:new-${language}-${count}"></textarea> 
+      	</td>
+      </tr>
+    </tal:block>
+  </table>
+    
+  <div>
+    <input class="form-element" type="submit" 
+           name="@@editMessages.html:method" value="Edit Messages"
+           i18n:attributes="value">&nbsp;
+    <input class="form-element" type="submit" 
+           name="@@deleteMessages.html:method" value="Delete Messages"
+           i18n:attributes="value">
+  </div>
+  </form>
+
+</span>
+</div>
+
+</body>
+</html>


=== Zope3/src/zope/app/browser/services/translation/translate.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/app/browser/services/translation/translate.py	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,144 @@
+##############################################################################
+#
+# 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 GUI
+
+$Id$
+"""
+from zope.app.browser.services.translation.basetranslationserviceview \
+     import BaseTranslationServiceView
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+
+
+class Translate(BaseTranslationServiceView):
+
+    index = ViewPageTemplateFile('translate.pt')
+    translateMessage = ViewPageTemplateFile('translatemessage.pt')
+
+    def getMessages(self):
+        """Get messages based on the domain selection"""
+        filter = self.request.get('filter', '%')
+        domains = self.getEditDomains()
+        messages = []
+        for domain in domains:
+            for msg_id in self.context.getMessageIdsOfDomain(domain, filter):
+                messages.append((msg_id, domain, len(messages)))
+
+        return messages
+
+
+    def getTranslation(self, domain, msgid, target_lang):
+        return self.context.translate(domain, msgid,
+                                      target_language=target_lang)
+
+
+    def getEditLanguages(self):
+        '''get the languages that are selected for editing'''
+        languages = self.request.cookies.get('edit_languages', '')
+        return filter(None, languages.split(','))
+
+
+    def getEditDomains(self):
+        '''get the languages that are selected for editing'''
+        domains = self.request.cookies.get('edit_domains', '')
+        return filter(None, domains.split(','))
+
+
+    def editMessage(self):
+        domain = self.request['msg_domain']
+        msg_id = self.request['msg_id']
+        for language in self.getEditLanguages():
+            msg = self.request['msg_lang_%s' %language]
+            if msg != self.context.translate(domain, msg_id,
+                                             target_language=language):
+                self.context.updateMessage(domain, msg_id, msg, language)
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def editMessages(self):
+        # Handle new Messages
+        for count in range(5):
+            msg_id = self.request.get('new-msg_id-%i' %count, '')
+            if msg_id:
+                domain = self.request.get('new-domain-%i' %count, 'default')
+                for language in self.getEditLanguages():
+                    msg = self.request.get('new-%s-%i' %(language, count),
+                                           msg_id)
+                    self.context.addMessage(domain, msg_id, msg, language)
+
+        # Handle edited Messages
+        keys = filter(lambda k: k.startswith('edit-msg_id-'),
+                      self.request.keys())
+        keys = map(lambda k: k[12:], keys)
+        for key in keys:
+            msg_id = self.request['edit-msg_id-'+key]
+            domain = self.request['edit-domain-'+key]
+            for language in self.getEditLanguages():
+                msg = self.request['edit-%s-%s' %(language, key)]
+                if msg != self.context.translate(domain, msg_id,
+                                                 target_language=language):
+                    self.context.updateMessage(domain, msg_id, msg, language)
+
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def deleteMessages(self, message_ids):
+        for id in message_ids:
+            domain = self.request.form['edit-domain-%s' %id]
+            msgid = self.request.form['edit-msg_id-%s' %id]
+            for language in self.context.getAvailableLanguages(domain):
+                # Some we edit a language, but no translation exists...
+                try:
+                    self.context.deleteMessage(domain, msgid, language)
+                except KeyError:
+                    pass
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def addLanguage(self, language):
+        self.context.addLanguage(language)
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def addDomain(self, domain):
+        self.context.addDomain(domain)
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def changeEditLanguages(self, languages=[]):
+        self.request.response.setCookie('edit_languages',
+                                        ','.join(languages))
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def changeEditDomains(self, domains=[]):
+        self.request.response.setCookie('edit_domains', ','.join(domains))
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def changeFilter(self):
+        filter = self.request.get('filter', '%')
+        self.request.response.setCookie('filter', filter)
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def deleteLanguages(self, languages):
+        for language in languages:
+            self.context.deleteLanguage(language)
+        return self.request.response.redirect(self.request.URL[-1])
+
+
+    def deleteDomains(self, domains):
+        for domain in domains:
+            self.context.deleteDomain(domain)
+        return self.request.response.redirect(self.request.URL[-1])


=== Zope3/src/zope/app/browser/services/translation/translatemessage.pt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/app/browser/services/translation/translatemessage.pt	Wed Dec 25 09:12:38 2002
@@ -0,0 +1,41 @@
+<html metal:use-macro="views/standard_macros/page">
+<head>
+  <title>Translation Service - Translate</title>
+</head>
+<body>
+
+<div metal:fill-slot="body">
+
+<span i18n:domain="Zope-I18n">
+
+<form action="./" method="post">
+<input type="hidden" name="msg_domain" value=""
+       tal:attributes="value request/domain" />
+<input type="hidden" name="msg_id" value=""
+       tal:attributes="value request/msgid" />
+<table>
+  <tr>
+    <th i18n:translate="">Message Id</th>
+    <td tal:content="request/msgid">Message Id of the message.</td>
+  </tr>
+  <tr tal:repeat="language view/getEditLanguages">
+    <th tal:content="language">Language</th>
+    <td>
+      <textarea cols="80" rows="10" name=""
+                tal:attributes="name string:msg_lang_${language}" 
+                tal:content="python: view.getTranslation(request['domain'], 
+                             request['msgid'], language)"
+       >Translation of Message</textarea>
+    </td>
+  </tr>
+</table>
+<input class="form-element" type="submit" 
+       name="@@editMessage.html:method" value="Edit Message"
+       i18n:attributes="value">
+</form>
+
+</span>
+</div>
+
+</body>
+</html>