[Checkins] SVN: zope.i18n/branches/lazy-tds/ The ZCML directive handler for a translation domain registration now

Malthe Borch cvs-admin at zope.org
Wed Oct 10 09:25:21 UTC 2012


Log message for revision 127957:
  The ZCML directive handler for a translation domain registration now
  loads message catalogs lazily.
  
  If the `zope_i18n_allowed_languages` environment variable is not
  provided, the allowed languages are determined at runtime. To
  participate in this, a translation domain implementation must now
  expose a set of languages in a ``languages`` attribute.
  
  The difficulty in this patch is that the ``INegotiator`` interface
  relies on an "allowed languages" set, but this set is not readily
  available. In the current definition of a translation domain, we can't
  know until we attempt a translation whether a language is supported.
  
  The solution in this patch is to require that a translation domain
  exposes the defined languages and then query all translation domains
  to determine the allowed languages. It would be nice if we could just
  provide a value of ``None`` (meaning any language), but there's
  already existing implementations that expect a set.
  

Changed:
  U   zope.i18n/branches/lazy-tds/CHANGES.txt
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/__init__.py
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/compile.py
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/interfaces/__init__.py
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/simpletranslationdomain.py
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/tests/test_zcml.py
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/translationdomain.py
  U   zope.i18n/branches/lazy-tds/src/zope/i18n/zcml.py

-=-
Modified: zope.i18n/branches/lazy-tds/CHANGES.txt
===================================================================
--- zope.i18n/branches/lazy-tds/CHANGES.txt	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/CHANGES.txt	2012-10-10 09:25:17 UTC (rev 127957)
@@ -5,6 +5,16 @@
 4.0.0 (unreleased)
 ------------------
 
+- The ZCML directive handler for a translation domain registration now
+  loads message catalogs lazily.
+
+- If the `zope_i18n_allowed_languages` environment variable is not
+  provided, the allowed languages are determined at runtime. To
+  participate in this, a translation domain implementation must now
+  expose a set of languages in a ``languages`` attribute.
+
+  This has been implemented for the default implementations.
+
 - log DEBUG when loading translations from directories.
 
 - Replaced deprecated ``zope.interface.implements`` usage with equivalent

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/__init__.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/__init__.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/__init__.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -15,10 +15,10 @@
 """
 import re
 
-from zope.component import queryUtility
+from zope.component import queryUtility, getAllUtilitiesRegisteredFor
 from zope.i18nmessageid import MessageFactory, Message
 
-from zope.i18n.config import ALLOWED_LANGUAGES
+from zope.i18n import config
 from zope.i18n.interfaces import INegotiator
 from zope.i18n.interfaces import ITranslationDomain
 from zope.i18n.interfaces import IFallbackTranslationDomainFactory
@@ -33,17 +33,23 @@
 
 
 def negotiate(context):
-    """Negotiate language.
+    """Negotiate language."""
 
-    This only works if the languages are set globally, otherwise each message
-    catalog needs to do the language negotiation.
-    """
-    if ALLOWED_LANGUAGES is not None:
-        negotiator = queryUtility(INegotiator)
-        if negotiator is not None:
-            return negotiator.getLanguage(ALLOWED_LANGUAGES, context)
-    return None
+    negotiator = queryUtility(INegotiator)
+    if negotiator is not None:
+        languages = config.ALLOWED_LANGUAGES
+        if languages is None:
+            languages = set()
+            for domain in getAllUtilitiesRegisteredFor(ITranslationDomain):
+                try:
+                    languages |= domain.languages
+                except AttributeError:
+                    continue
 
+        if languages:
+            return negotiator.getLanguage(languages, context)
+
+
 def translate(msgid, domain=None, mapping=None, context=None,
                target_language=None, default=None):
     """Translate text.

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/compile.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/compile.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/compile.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -45,3 +45,5 @@
             fd.close()
         except (IOError, OSError, PoSyntaxError):
             logger.warn('Error while compiling %s' % pofile)
+
+    return mofile

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/interfaces/__init__.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/interfaces/__init__.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/interfaces/__init__.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -132,6 +132,10 @@
         description=u"The name of the domain this object represents.",
         required=True)
 
+    languages = Attribute(
+        """Return the set of languages available in this domain."""
+        )
+
     def translate(msgid, mapping=None, context=None, target_language=None,
                   default=None):
         """Return the translation for the message referred to by msgid.

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/simpletranslationdomain.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/simpletranslationdomain.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/simpletranslationdomain.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -45,7 +45,14 @@
             assert isinstance(messages, dict)
             self.messages = messages
 
+    @property
+    def languages(self):
+        langs = set()
+        for (lang, msgid) in self.messages:
+            langs.add(lang)
 
+        return langs
+
     def translate(self, msgid, mapping=None, context=None,
                   target_language=None, default=None):
         '''See interface ITranslationDomain'''

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/tests/test_zcml.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/tests/test_zcml.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/tests/test_zcml.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -43,13 +43,7 @@
         from zope.configuration import xmlconfig
         super(DirectivesTest, self).setUp()
         self.context = xmlconfig.file('meta.zcml', zope.i18n)
-        self.allowed = config.ALLOWED_LANGUAGES
-        config.ALLOWED_LANGUAGES = None
 
-    def tearDown(self):
-        super(DirectivesTest, self).tearDown()
-        config.ALLOWED_LANGUAGES = self.allowed
-
     def testRegisterTranslations(self):
         from zope.configuration import xmlconfig
         self.assert_(queryUtility(ITranslationDomain) is None)
@@ -59,27 +53,13 @@
             <i18n:registerTranslations directory="locale" />
             </configure>
             ''', self.context)
-        path = os.path.join(os.path.dirname(zope.i18n.tests.__file__),
-                            'locale', 'en', 'LC_MESSAGES', 'zope-i18n.mo')
         util = getUtility(ITranslationDomain, 'zope-i18n')
-        self.assertEquals(util._catalogs.get('test'), ['test'])
-        self.assertEquals(util._catalogs.get('en'), [unicode(path)])
 
-    def testAllowedTranslations(self):
-        from zope.configuration import xmlconfig
-        self.assert_(queryUtility(ITranslationDomain) is None)
-        config.ALLOWED_LANGUAGES = ('de', 'fr')
-        xmlconfig.string(
-            template % '''
-            <configure package="zope.i18n.tests">
-            <i18n:registerTranslations directory="locale" />
-            </configure>
-            ''', self.context)
-        path = os.path.join(os.path.dirname(zope.i18n.tests.__file__),
-                            'locale', 'de', 'LC_MESSAGES', 'zope-i18n.mo')
-        util = getUtility(ITranslationDomain, 'zope-i18n')
-        self.assertEquals(util._catalogs,
-                          {'test': ['test'], 'de': [unicode(path)]})
+        self.assertEquals(util.translate('test', target_language='test'),
+                          u'[[zope-i18n][test]]')
+        self.assertEquals(util.translate('test', target_language='en'), u'test')
+        self.assertEquals(util.translate('New Domain', target_language='en'),
+                          u'New Domain translated')
 
     def testRegisterDistributedTranslations(self):
         from zope.configuration import xmlconfig
@@ -96,14 +76,7 @@
             <i18n:registerTranslations directory="locale2" />
             </configure>
             ''', self.context)
-        path1 = os.path.join(os.path.dirname(zope.i18n.tests.__file__),
-                             'locale', 'en', 'LC_MESSAGES', 'zope-i18n.mo')
-        path2 = os.path.join(os.path.dirname(zope.i18n.tests.__file__),
-                             'locale2', 'en', 'LC_MESSAGES', 'zope-i18n.mo')
         util = getUtility(ITranslationDomain, 'zope-i18n')
-        self.assertEquals(util._catalogs.get('test'), ['test', 'test'])
-        self.assertEquals(util._catalogs.get('en'),
-                          [unicode(path1), unicode(path2)])
 
         msg = util.translate(u'Additional message', target_language='en')
         self.assertEquals(msg, u'Additional message translated')
@@ -140,8 +113,6 @@
             </configure>
             ''', self.context)
         util = getUtility(ITranslationDomain, 'zope-i18n')
-        self.assertEquals(util._catalogs,
-                          {'test': ['test'], 'en': [unicode(path)]})
 
         msg = util.translate(u"I'm a newer file", target_language='en')
         self.assertEquals(msg, u"I'm a newer file translated")
@@ -163,12 +134,9 @@
             <i18n:registerTranslations directory="locale3" domain="zope-i18n" />
             </configure>
             ''', self.context)
-        path = os.path.join(os.path.dirname(zope.i18n.tests.__file__),
-                            'locale3', 'en', 'LC_MESSAGES', 'zope-i18n.mo')
         util = getUtility(ITranslationDomain, 'zope-i18n')
-        self.assertEquals(util._catalogs,
-                          {'test': ['test'], 'en': [unicode(path)]})
-
+        self.assertEquals(util.translate('test', target_language='test'),
+                          u'[[zope-i18n][test]]')
         self.assert_(queryUtility(ITranslationDomain, 'zope-i18n2') is None)
 
 

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/translationdomain.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/translationdomain.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/translationdomain.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -17,7 +17,9 @@
 from zope.i18nmessageid import Message
 from zope.i18n import translate, interpolate
 from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
-from zope.i18n.interfaces import ITranslationDomain, INegotiator
+from zope.i18n.interfaces import INegotiator
+from zope.i18n.compile import compile_mo_file
+from zope.i18n.gettextmessagecatalog import GettextMessageCatalog
 
 # The configuration should specify a list of fallback languages for the
 # site.  If a particular catalog for a negotiated language is not available,
@@ -31,6 +33,7 @@
 
 
 class TranslationDomain(SimpleTranslationDomain):
+    languages = ()
 
     def __init__(self, domain, fallbacks=None):
         self.domain = domain
@@ -43,22 +46,34 @@
         if fallbacks is None:
             fallbacks = LANGUAGE_FALLBACKS
         self._fallbacks = fallbacks
+        self._pending = {}
+        self.languages = set()
 
     def _registerMessageCatalog(self, language, catalog_name):
         key = language
         mc = self._catalogs.setdefault(key, [])
         mc.append(catalog_name)
+        self.languages.add(language)
 
     def addCatalog(self, catalog):
         self._data[catalog.getIdentifier()] = catalog
         self._registerMessageCatalog(catalog.language,
                                      catalog.getIdentifier())
 
+    def addLanguage(self, lang, path):
+        self._pending.setdefault(lang, []).append(path)
+        self.languages.add(lang)
+
     def setLanguageFallbacks(self, fallbacks=None):
         if fallbacks is None:
             fallbacks = LANGUAGE_FALLBACKS
         self._fallbacks = fallbacks
 
+    def importCatalog(self, lang, lc_messages_path):
+        path = compile_mo_file(self.domain, lc_messages_path)
+        catalog = GettextMessageCatalog(lang, self.domain, path)
+        self.addCatalog(catalog)
+
     def translate(self, msgid, mapping=None, context=None,
                   target_language=None, default=None):
         """See zope.i18n.interfaces.ITranslationDomain"""
@@ -74,6 +89,10 @@
             # try to determine target language from negotiator utility
             target_language = negotiator.getLanguage(langs, context)
 
+        paths = self._pending.pop(target_language, ())
+        for path in paths:
+            self.importCatalog(target_language, path)
+
         return self._recursive_translate(
             msgid, mapping, target_language, default, context)
 
@@ -124,7 +143,7 @@
                 # this is a slight optimization for the case when there is a
                 # single catalog. More importantly, it is extremely helpful
                 # when testing and the test language is used, because it
-                # allows the test language to get the default. 
+                # allows the test language to get the default.
                 text = self._data[catalog_names[0]].queryMessage(
                     msgid, default)
             else:

Modified: zope.i18n/branches/lazy-tds/src/zope/i18n/zcml.py
===================================================================
--- zope.i18n/branches/lazy-tds/src/zope/i18n/zcml.py	2012-10-10 08:02:21 UTC (rev 127956)
+++ zope.i18n/branches/lazy-tds/src/zope/i18n/zcml.py	2012-10-10 09:25:17 UTC (rev 127957)
@@ -28,8 +28,6 @@
 from zope.schema import TextLine
 
 from zope.i18n import config
-from zope.i18n.compile import compile_mo_file
-from zope.i18n.gettextmessagecatalog import GettextMessageCatalog
 from zope.i18n.testmessagecatalog import TestMessageCatalog
 from zope.i18n.translationdomain import TranslationDomain
 from zope.i18n.interfaces import ITranslationDomain
@@ -61,7 +59,7 @@
     return lang in config.ALLOWED_LANGUAGES
 
 
-def handler(catalogs, name):
+def handler(name, langs):
     """ special handler handling the merging of two message catalogs """
     gsm = getSiteManager()
     # Try to get an existing domain and add the given catalogs to it
@@ -69,8 +67,8 @@
     if domain is None:
         domain = TranslationDomain(name)
         gsm.registerUtility(domain, ITranslationDomain, name=name)
-    for catalog in catalogs:
-        domain.addCatalog(catalog)
+    for lang, path in langs.items():
+        domain.addLanguage(lang, path)
     # make sure we have a TEST catalog for each domain:
     domain.addCatalog(TestMessageCatalog(name))
 
@@ -86,38 +84,29 @@
     for language in os.listdir(path):
         if not allow_language(language):
             continue
+
         lc_messages_path = os.path.join(path, language, 'LC_MESSAGES')
         if os.path.isdir(lc_messages_path):
-            # Preprocess files and update or compile the mo files
-            if config.COMPILE_MO_FILES:
-                for domain_path in glob(os.path.join(lc_messages_path,
-                                                     '%s.po' % domain)):
-                    domain_file = os.path.basename(domain_path)
-                    name = domain_file[:-3]
-                    compile_mo_file(name, lc_messages_path)
-            for domain_path in glob(os.path.join(lc_messages_path,
-                                                 '%s.mo' % domain)):
+            query = os.path.join(lc_messages_path, '%s.[pm]o' % domain)
+            for domain_path in glob(query):
                 loaded = True
-                domain_file = os.path.basename(domain_path)
-                name = domain_file[:-3]
+                base, ext = os.path.splitext(domain_path)
+                name = os.path.basename(base)
                 if not name in domains:
                     domains[name] = {}
-                domains[name][language] = domain_path
+                domains[name][language] = lc_messages_path
     if loaded:
         logger.debug('register directory %s' % directory)
 
     # Now create TranslationDomain objects and add them as utilities
     for name, langs in domains.items():
-        catalogs = []
-        for lang, file in langs.items():
-            catalogs.append(GettextMessageCatalog(lang, name, file))
         # register the necessary actions directly (as opposed to using
         # `zope.component.zcml.utility`) since we need the actual utilities
         # in place before the merging can be done...
         _context.action(
             discriminator = None,
             callable = handler,
-            args = (catalogs, name))
+            args = (name, langs))
 
     # also register the interface for the translation utilities
     provides = ITranslationDomain



More information about the checkins mailing list