[Zope3-checkins] CVS: Zope3/src/zope/i18n - __init__.py:1.2 domain.py:1.2 gettextmessagecatalog.py:1.2 globaltranslationservice.py:1.2 i18nobject.stx:1.2 negotiator.py:1.2 simpletranslationservice.py:1.2 translate.py:1.2

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


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

Added Files:
	__init__.py domain.py gettextmessagecatalog.py 
	globaltranslationservice.py i18nobject.stx negotiator.py 
	simpletranslationservice.py translate.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/i18n/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/__init__.py	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/i18n/domain.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/domain.py	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.interfaces.i18n import IDomain
+from zope.component import getServiceManager
+
+class Domain:
+
+    __implements__ =  IDomain
+
+    def __init__(self, domain, service):
+        self._domain = domain
+        self._translationService = service
+
+    def getDomainName(self):
+        """Return the domain name"""
+        return self._domain
+
+    # IDomain interface methods
+
+    def translate(self, msgid, mapping=None, context=None,
+                  target_language=None):
+        """See IDomain"""
+        return self._translationService.translate(
+            self._domain, msgid, mapping, context, target_language)


=== Zope3/src/zope/i18n/gettextmessagecatalog.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/gettextmessagecatalog.py	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# 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$
+"""
+
+from gettext import GNUTranslations
+from zope.interfaces.i18n import IReadMessageCatalog
+
+
+class GettextMessageCatalog:
+    """ """
+
+    __implements__ =  IReadMessageCatalog
+
+
+    def __init__(self, language, domain, path_to_file):
+        """Initialize the message catalog"""
+        self._language = language
+        self._domain = domain
+        self._path_to_file = path_to_file
+        self.__translation_object = None
+        self._prepareTranslations()
+
+
+    def _prepareTranslations(self):
+        """ """
+        if self.__translation_object is None:
+            file = open(self._path_to_file, 'r')
+            self.__translation_object = GNUTranslations(file)
+            file.close()
+
+
+    def getMessage(self, id):
+        'See IReadMessageCatalog'
+        self._prepareTranslations()
+        msg = self.__translation_object.ugettext(id)
+        if msg == id:
+            raise KeyError
+        return msg
+
+    def queryMessage(self, id, default=None):
+        'See IReadMessageCatalog'
+        self._prepareTranslations()
+        text = self.__translation_object.ugettext(id)
+        if text != id:
+            return text
+        return default
+
+    def getLanguage(self):
+        'See IReadMessageCatalog'
+        return self._language
+
+    def getDomain(self):
+        'See IReadMessageCatalog'
+        return self._domain
+
+    def getIdentifier(self):
+        'See IReadMessageCatalog'
+        return self._path_to_file


=== Zope3/src/zope/i18n/globaltranslationservice.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/globaltranslationservice.py	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Global Translation Service for providing I18n to file-based code.
+
+$Id$
+"""
+
+from zope.i18n.negotiator import negotiator
+from zope.i18n.simpletranslationservice import SimpleTranslationService
+
+# The configure.zcml file should specify a list of fallback languages for the
+# site.  If a particular catalog for a negotiated language is not available,
+# then the zcml specified order should be tried.  If that fails, then as a
+# last resort the languages in the following list are tried.  If these fail
+# too, then the msgid is returned.
+#
+# Note that these fallbacks are used only to find a catalog.  If a particular
+# message in a catalog is not translated, tough luck, you get the msgid.
+LANGUAGE_FALLBACKS = ['en']
+
+
+class GlobalTranslationService(SimpleTranslationService):
+
+    __implements__ =  SimpleTranslationService.__implements__
+
+    def __init__(self, default_domain='global', fallbacks=None):
+        # XXX We haven't specified that ITranslationServices have a default
+        # domain.  So far, we've required the domain argument to .translate()
+        self._domain = default_domain
+        # _catalogs maps (language, domain) to IMessageCatalog instances
+        self._catalogs = {}
+        # _data maps IMessageCatalog.getIdentifier() to IMessageCatalog
+        self._data = {}
+        # What languages to fallback to, if there is no catalog for the
+        # requested language (no fallback on individual messages)
+        if fallbacks is None:
+            fallbacks = LANGUAGE_FALLBACKS
+        self._fallbacks = fallbacks
+
+    def _registerMessageCatalog(self, language, domain, catalog_name):
+        key = (language, domain)
+        mc = self._catalogs.setdefault(key, [])
+        mc.append(catalog_name)
+
+    def addCatalog(self, catalog):
+        self._data[catalog.getIdentifier()] = catalog
+        self._registerMessageCatalog(catalog.getLanguage(),
+                                     catalog.getDomain(),
+                                     catalog.getIdentifier())
+
+    def setLanguageFallbacks(self, fallbacks=None):
+        if fallbacks is None:
+            fallbacks = LANGUAGE_FALLBACKS
+        self._fallbacks = fallbacks
+
+    def translate(self, domain, msgid, mapping=None, context=None,
+                  target_language=None):
+        '''See interface ITranslationService'''
+        if target_language is None:
+            if context is None:
+                raise TypeError, 'No destination language'
+            else:
+                langs = [m[0] for m in self._catalogs.keys()]
+                target_language = negotiator.getLanguage(langs, context)
+
+        # Get the translation. Use the specified fallbacks if this fails
+        catalog_names = self._catalogs.get((target_language, domain))
+        if catalog_names is None:
+            for language in self._fallbacks:
+                catalog_names = self._catalogs.get((language, domain))
+                if catalog_names is not None:
+                    break
+        # Did the fallback fail?  Sigh, use the msgid
+        if catalog_names is None:
+            catalog_names = []
+
+        text = msgid
+        for name in catalog_names:
+            catalog = self._data[name]
+            text = catalog.queryMessage(msgid)
+
+        # Now we need to do the interpolation
+        return self.interpolate(text, mapping)
+
+translationService = GlobalTranslationService()


=== Zope3/src/zope/i18n/i18nobject.stx 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/i18nobject.stx	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,63 @@
+I18n Objects
+
+  I18n objects are used to internationalize content that cannot be translated
+  via messages. The problem is three fold:
+
+    - Internationalize an object iteself. The best example is an Image. Often
+      people put text into an image that needs to be localized, but since it
+      is a picture the text cannot be retrieved as a string. You therefore
+      will need a mechanism to create a different version of the object for every
+      language. (Note: This behavior is the same as currently implemented in
+      the ZBabelObject.) 
+
+    - Internationalize fractions of the content. Let's say for example you
+      wanted to internationalize the DublinCore information of your content
+      object. In order for this to work out you need to have an
+      I18n-supportive AttributeAnnotation. Solving this use case would be
+      similar to some of the work David Juan did with
+      InternationalizedContent in Localizer.
+
+    - Formatting Objects. This problem involves converting basic or complex
+      types into a localized format. Good examples for this are decimal
+      numbers and date/time objects. In this case you would like to specify a
+      format via a templating expression and then pass the object to the
+      formatter to apply the parsed template. Initial steps for implementing
+      the template parser have been already taken, therefore we only need to
+      develop some interfaces and unittests here, so the parser developer
+      (which will use the ICU C/C++ libraries) will (choose?) what parts of the parser
+      to wrap for Python. 
+
+   
+  The first two problems are very similar and can actually share the same
+  interface for the language negotiation and redirection of the content
+  request. I would therefore propose to have a II18nContent interface that
+  somehow specifies how to get the and then redirect the call to the correct
+  local content.
+
+   
+  I18nObject
+
+    There will be an interface called II18nObject (which inherits I18nContent
+    of course), which is a cameleon-like container, as it adapts to the
+    properties of the contained object type. In order to accomplish all this,
+    you will have to implement your own traverser which looks up the correct
+    subobject.
+
+
+  I18nAttributeAnnotation
+
+    This object will basically provide an internationalized version of
+    Zope.App.OFS.AttributeAnnotations. which will be accomplished in a similar
+    manner as the I18nObject.
+
+
+  One issue left to solve is how to know the language when we need to make the
+  decision. These objects are **not** Views, therefore they do not know about
+  the request variable. 
+
+  One way to solve the issue would be that I18nAttributeAnnotation, for
+  example, would not actually implement the IAnnotations interface, but that
+  there would be an Adapter converting the II18nAnnotations to
+  IAnnotations. Since adapters are short-lived (meaning they are initialized
+  for a particular call), we can safely store some language information in
+  them in order to make the language decision.


=== Zope3/src/zope/i18n/negotiator.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/negotiator.py	Wed Dec 25 09:13:39 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.interfaces.i18n import INegotiator
+from zope.interfaces.i18n import IUserPreferredLanguages
+from zope.interfaces.i18n import ILanguageAvailability
+from zope.component import getAdapter
+
+class Negotiator:
+
+    __implements__ = INegotiator
+
+    def getLanguage(self, langs, env):
+        envadaptor = getAdapter(env, IUserPreferredLanguages)
+        userlangs = envadaptor.getPreferredLanguages()
+        # Prioritize on the user preferred languages.  Return the first user
+        # preferred language that the object has available.
+        for lang in userlangs:
+            if lang in langs:
+                return lang
+        return None
+
+
+negotiator = Negotiator()


=== Zope3/src/zope/i18n/simpletranslationservice.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/simpletranslationservice.py	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,102 @@
+##############################################################################
+#
+# 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 a simple implementation of the ITranslationService interface.
+
+$Id$
+"""
+
+import re
+from types import DictType
+from zope.component import getService
+from zope.interfaces.i18n import ITranslationService
+from zope.i18n.domain import Domain
+
+
+# Setting up some regular expressions for finding interpolation variables in
+# the text.
+NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
+_interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE}))
+_get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE}))
+
+
+class SimpleTranslationService:
+    """This is the simplest implementation of the ITranslationInterface I
+       could come up with.
+
+       The constructor takes one optional argument 'messages', which will be
+       used to do the translation. The 'messages' attribute has to have the
+       following structure:
+
+       {('domain', 'language', 'msg_id'): 'message', ...}
+
+       Note: This Translation Service does not implemen
+    """
+
+    __implements__ =  ITranslationService
+
+
+    def __init__(self, messages=None):
+        """Initializes the object. No arguments are needed."""
+        if messages is None:
+            self.messages = {}
+        else:
+            assert type(messages) == DictType
+            self.messages = messages
+
+
+    def translate(self, domain, msgid, mapping=None, context=None,
+                  target_language=None):
+        '''See interface ITranslationService'''
+        # Find out what the target language should be
+        if target_language is None:
+            if context is None:
+                raise TypeError, 'No destination language'
+            else:
+                langs = [m[1] for m in self.messages.keys()]
+                # Let's negotiate the language to translate to. :)
+                negotiator = getService(self, 'LanguageNegotiation')
+                target_language = negotiator.getLanguage(langs, context)
+
+        # Make a raw translation without interpolation
+        text = self.messages.get((domain, target_language, msgid))
+
+        # Now we need to do the interpolation
+        return self.interpolate(text, mapping)
+
+
+    def getDomain(self, domain):
+        '''See interface ITranslationService'''
+        return Domain(domain, self)
+
+    def interpolate(self, text, mapping):
+        """Insert the data passed from mapping into the text"""
+
+        # If no translation was found, there is nothing to do.
+        if text is None:
+            return None
+
+        # If the mapping does not exist, make a "raw translation" without
+        # interpolation.
+        if mapping is None:
+            return text
+
+        # Find all the spots we want to substitute
+        to_replace = _interp_regex.findall(text)
+
+        # Now substitute with the variables in mapping
+        for string in to_replace:
+            var = _get_var_regex.findall(string)[0]
+            text = text.replace(string, mapping.get(var))
+
+        return text


=== Zope3/src/zope/i18n/translate.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:10 2002
+++ Zope3/src/zope/i18n/translate.py	Wed Dec 25 09:13:39 2002
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+
+def translate(place, domain, source, mapping=None, context=None,
+              target_language=None):
+    """Translates a source text based on a location in the Zope architecture
+       and a domain."""
+
+    # Lookup service...
+    service = None
+
+    return service.translate(domain, source, mapping, context, target_language)