Index: setup.py
===================================================================
--- setup.py	(revision 7678)
+++ setup.py	(working copy)
@@ -73,7 +73,7 @@
         'Genshi>=0.5'
     ],
     extras_require = {
-        'Babel': ['Babel>=0.9.3'],
+        'Babel': ['Babel>=0.9.4'],
         'Pygments': ['Pygments>=0.6'],
         'reST': ['docutils>=0.3'],
         'SilverCity': ['SilverCity>=0.9.4'],
Index: trac/web/api.py
===================================================================
--- trac/web/api.py	(revision 7678)
+++ trac/web/api.py	(working copy)
@@ -369,7 +369,7 @@
                     from trac.web.chrome import Chrome
                     from trac.util import translation
                     if hasattr(self, 'locale'):
-                        translation.activate(self.locale)
+                        translation.activate(self.locale, env.path)
                     try:
                         data = Chrome(env).render_template(self, template,
                                                            data, 'text/html')
Index: trac/web/chrome.py
===================================================================
--- trac/web/chrome.py	(revision 7678)
+++ trac/web/chrome.py	(working copy)
@@ -26,7 +26,7 @@
 
 from genshi import Markup
 from genshi.builder import tag, Element
-from genshi.filters import Translator
+from genshi.filters import Translator, setup_i18n
 from genshi.input import HTML, ParseError
 from genshi.core import Attrs, START
 from genshi.output import DocType
@@ -293,6 +293,8 @@
         'classes': presentation.classes,
         'date': datetime.date,
         'datetime': datetime.datetime,
+        'dgettext': translation.dgettext,
+        'dngettext': translation.dngettext,
         'first_last': presentation.first_last,
         'get_reporter_id': get_reporter_id,
         'gettext': translation.gettext,
@@ -660,7 +662,8 @@
         """
         if not self.templates:
             def _template_loaded(template):
-                template.filters.insert(0, Translator(translation.gettext))
+                translator = Translator(translation.get_translations())
+                setup_i18n(template, translator)
 
             self.templates = TemplateLoader(self.get_all_templates_dirs(),
                                             auto_reload=self.auto_reload,
Index: trac/web/main.py
===================================================================
--- trac/web/main.py	(revision 7678)
+++ trac/web/main.py	(working copy)
@@ -164,7 +164,7 @@
         try:
             try:
                 try:
-                    translation.activate(req.locale)
+                    translation.activate(req.locale, self.env.path)
 
                     # Select the component that should handle the request
                     chosen_handler = None
Index: trac/util/translation.py
===================================================================
--- trac/util/translation.py	(revision 7678)
+++ trac/util/translation.py	(working copy)
@@ -14,6 +14,7 @@
 """Utilities for text translation with gettext."""
 
 import re
+import sys 
 try:
     import threading
 except ImportError:
@@ -57,58 +58,164 @@
     from babel.support import LazyProxy, Translations
     from gettext import NullTranslations
 
-    _current = threading.local()
+    class TranslationsProxy(object):
+        """Delegate Translations calls to the currently active Translations.
 
-    def gettext(string, **kwargs):
-        def _gettext():
-            trans = get_translations().ugettext(string)
-            return kwargs and trans % kwargs or trans
-        if not hasattr(_current, 'translations'):
-            return LazyProxy(_gettext)
-        return _gettext()
-    _ = gettext
+        If there's none, wrap those calls in LazyProxy objects.
+        """
 
-    def ngettext(singular, plural, num, **kwargs):
-        kwargs = kwargs.copy()
-        kwargs.setdefault('num', num)
-        def _ngettext():
-            trans = get_translations().ungettext(singular, plural, num)
-            return trans % kwargs
-        if not hasattr(_current, 'translations'):
-            return LazyProxy(_ngettext)
-        return _ngettext()
+        def __init__(self):
+            self._current = threading.local()
+            self._null_translations = NullTranslations()
+            self._plugin_domains = {}
 
-    def tgettext(string, **kwargs):
-        def _tgettext():
-            trans = get_translations().ugettext(string)
-            return kwargs and _tag_kwargs(trans, kwargs) or trans
-        if not hasattr(_current, 'translations'):
-            return LazyProxy(_tgettext)
-        return _tgettext()
-    tag_ = tgettext
+        # Public API
 
-    def tngettext(singular, plural, num, **kwargs):
-        kwargs = kwargs.copy()
-        kwargs.setdefault('num', num)
-        def _tngettext():
-            trans = get_translations().ungettext(singular, plural, num)
-            return _tag_kwargs(trans, kwargs)
-        if not hasattr(_current, 'translations'):
-            return LazyProxy(_tngettext)
-        return _tngettext()
+        def add_domain(self, domain, env_path, locales_dir):
+            if env_path not in self._plugin_domains:
+                self._plugin_domains[env_path] = []
+            self._plugin_domains[env_path].append((domain, locales_dir))
 
-    def activate(locale):
-        locale_dir = pkg_resources.resource_filename(__name__, '../locale')
-        _current.translations = Translations.load(locale_dir, locale)
+        def activate(self, locale, env_path=None):
+            locale_dir = pkg_resources.resource_filename(__name__, '../locale')
+            t = Translations.load(locale_dir, locale)
+            if env_path:
+                for domain, dirname in self._plugin_domains.get(env_path, []):
+                    t.add(Translations.load(dirname, locale, domain))
+            self._current.translations = t
+         
+        def deactivate(self):
+            del self._current.translations
+    
+        @property
+        def active(self):
+            return getattr(self._current, 'translations', 
+                           self._null_translations)
 
-    _null_translations = NullTranslations()
+        @property
+        def isactive(self):
+            return hasattr(self._current, 'translations')
 
-    def get_translations():
-        return getattr(_current, 'translations', _null_translations)
+        # Delegated methods
 
+        def __getattr__(self, name):
+            return getattr(self.active, name)
+
+        def gettext(self, string, **kwargs):
+            def _gettext():
+                trans = self.active.ugettext(string)
+                return kwargs and trans % kwargs or trans
+            if not self.isactive:
+                return LazyProxy(_gettext)
+            return _gettext()
+
+        def dgettext(self, domain, string, **kwargs):
+            def _dgettext():
+                trans = self.active.dugettext(domain, string)
+                return kwargs and trans % kwargs or trans
+            if not self.isactive:
+                return LazyProxy(_dgettext)
+            return _dgettext()
+
+        def ngettext(self, singular, plural, num, **kwargs):
+            kwargs = kwargs.copy()
+            kwargs.setdefault('num', num)
+            def _ngettext():
+                trans = self.active.ungettext(singular, plural, num)
+                return trans % kwargs
+            if not self.isactive:
+                return LazyProxy(_ngettext)
+            return _ngettext()
+
+        def dngettext(self, domain, singular, plural, num, **kwargs):
+            kwargs = kwargs.copy()
+            kwargs.setdefault('num', num)
+            def _dngettext():
+                trans = self.active.dungettext(domain, singular, plural, num)
+                return trans % kwargs
+            if not self.isactive:
+                return LazyProxy(_dngettext)
+            return _dngettext()
+
+        def tgettext(self, string, **kwargs):
+            def _tgettext():
+                trans = self.active.ugettext(string)
+                return kwargs and _tag_kwargs(trans, kwargs) or trans
+            if not self.isactive:
+                return LazyProxy(_tgettext)
+            return _tgettext()
+
+        def dtgettext(self, domain, string, **kwargs):
+            def _dtgettext():
+                trans = self.active.dugettext(domain, string)
+                return kwargs and _tag_kwargs(trans, kwargs) or trans
+            if not self.isactive:
+                return LazyProxy(_dtgettext)
+            return _dtgettext()
+
+        def tngettext(self, singular, plural, num, **kwargs):
+            kwargs = kwargs.copy()
+            kwargs.setdefault('num', num)
+            def _tngettext():
+                trans = self.active.ungettext(singular, plural, num)
+                return _tag_kwargs(trans, kwargs)
+            if not self.isactive:
+                return LazyProxy(_tngettext)
+            return _tngettext()
+
+        def dtngettext(self, domain, singular, plural, num, **kwargs):
+            kwargs = kwargs.copy()
+            def _dtngettext():
+                trans = self.active.dungettext(domain, singular, plural, num)
+                if '%(num)' in trans:
+                    kwargs.update(num=num)
+                return kwargs and _tag_kwargs(trans, kwargs) or trans
+            if not self.isactive:
+                return LazyProxy(_dtngettext)
+            return _dtngettext()
+
+    
+    translations = TranslationsProxy()
+
+    def domain_functions(domain, *symbols):
+        _symbols = {
+          'gettext': translations.dgettext,
+          '_': translations.dgettext,
+          'ngettext': translations.dngettext,
+          'tgettext': translations.dtgettext,
+          'tag_': translations.dtgettext,
+          'tngettext': translations.dtngettext,
+          'add_domain': translations.add_domain,
+          }
+        def wrapdomain(symbol):
+            if symbol == 'N_':
+                return gettext_noop
+            return lambda *args, **kw: _symbols[symbol](domain, *args, **kw)
+        return [wrapdomain(s) for s in symbols]
+
+    gettext = translations.gettext 
+    _ = gettext 
+    dgettext = translations.dgettext 
+    ngettext = translations.ngettext 
+    dngettext = translations.dngettext 
+    tgettext = translations.tgettext 
+    tag_ = tgettext 
+    dtgettext = translations.dtgettext 
+    tngettext = translations.tngettext 
+    dtngettext = translations.dtngettext 
+    
     def deactivate():
-        del _current.translations
+        translations.deactivate()
 
+    def activate(locale, env_path=None):
+        translations.activate(locale, env_path)
+
+    def add_domain(domain, env_path, locale_dir):
+        translations.add_domain(domain, env_path, locale_dir)
+
+    def get_translations():
+        return translations
+
     def get_available_locales():
         """Return a list of locale identifiers of the locales for which
         translations are available.
@@ -129,6 +236,9 @@
     def deactivate():
         pass
 
+    def add_domain(domain, env_path, locale_dir):
+        pass
+
     def get_translations():
         return []
 

