diff --git a/trac/util/translation.py b/trac/util/translation.py
--- a/trac/util/translation.py
+++ b/trac/util/translation.py
@@ -14,6 +14,7 @@
 """Utilities for text translation with gettext."""
 
 import re
+import sys 
 try:
     import threading
 except ImportError:
@@ -65,10 +66,22 @@
         def _gettext():
             trans = get_translations().ugettext(string)
             return kwargs and trans % kwargs or trans
+        domain = sys._getframe(1).f_globals.get('domain')
+        if domain:
+            # It has, use that domain
+            return dgettext(domain, string, **kwargs)
         if not hasattr(_current, 'translations'):
             return LazyProxy(_gettext)
         return _gettext()
     _ = gettext
+    
+    def dgettext(domain, string, **kwargs):
+        def _dgettext():
+            trans = get_translations().dugettext(domain, string)
+            return kwargs and trans % kwargs or trans
+        if not hasattr(_current, 'translations'):
+            return LazyProxy(_dgettext)
+        return _dgettext()
 
     def ngettext(singular, plural, num, **kwargs):
         kwargs = kwargs.copy()
@@ -77,18 +90,47 @@
             if '%(num)' in trans:
                 kwargs.update(num=num)
             return kwargs and trans % kwargs or trans
+        # The caller is a plugin with domain variable defined ???
+        domain = sys._getframe(1).f_globals.get('domain')
+        if domain:
+            # It has, use that domain
+            return dngettext(domain, singular, plural, num, **kwargs)
         if not hasattr(_current, 'translations'):
             return LazyProxy(_ngettext)
         return _ngettext()
+    
+    def dngettext(domain, singular, plural, num, **kwargs):
+        kwargs = kwargs.copy()
+        def _dngettext():
+            trans = get_translations().dungettext(domain, singular, plural, num)
+            if '%(num)' in trans:
+                kwargs.update(num=num)
+            return kwargs and trans % kwargs or trans
+        if not hasattr(_current, 'translations'):
+            return LazyProxy(_dngettext)
+        return _dngettext()
 
     def tgettext(string, **kwargs):
         def _tgettext():
             trans = get_translations().ugettext(string)
             return kwargs and _tag_kwargs(trans, kwargs) or trans
+        # The caller is a plugin with domain variable defined ???
+        domain = sys._getframe(1).f_globals.get('domain')
+        if domain:
+            # It has, use that domain
+            return dtgettext(domain, string, **kwargs)
         if not hasattr(_current, 'translations'):
             return LazyProxy(_tgettext)
         return _tgettext()
     tag_ = tgettext
+    
+    def dtgettext(domain, string, **kwargs):
+        def _dtgettext():
+            trans = get_translations().dugettext(domain, string)
+            return kwargs and _tag_kwargs(trans, kwargs) or trans
+        if not hasattr(_current, 'translations'):
+            return LazyProxy(_dtgettext)
+        return _dtgettext()
 
     def tngettext(singular, plural, num, **kwargs):
         kwargs = kwargs.copy()
@@ -97,14 +139,37 @@
             if '%(num)' in trans:
                 kwargs.update(num=num)
             return kwargs and _tag_kwargs(trans, kwargs) or trans
+        # The caller is a plugin with domain variable defined ???
+        domain = sys._getframe(1).f_globals.get('domain')
+        if domain:
+            # It has, use that domain
+            return dtngettext(domain, singular, plural, num, **kwargs)
         if not hasattr(_current, 'translations'):
             return LazyProxy(_tngettext)
         return _tngettext()
+    
+    def dtngettext(domain, singular, plural, num, **kwargs):
+        kwargs = kwargs.copy()
+        def _dtngettext():
+            trans = get_translations().dungettext(domain, singular, plural, num)
+            if '%(num)' in trans:
+                kwargs.update(num=num)
+            return kwargs and _tag_kwargs(trans, kwargs) or trans
+        if not hasattr(_current, 'translations'):
+            return LazyProxy(_dtngettext)
+        return _dtngettext()
+    
+    _plugin_domains = {}
 
-    def activate(locale):
+    def activate(locale, env_path=None):
         locale_dir = pkg_resources.resource_filename(__name__, '../locale')
         _current.translations = Translations.load(locale_dir, locale)
-
+        if env_path:
+            plugin_domains = _plugin_domains.get(env_path)
+            if plugin_domains:
+                for domain, dirname in plugin_domains:
+                    _current.translations.add_domain(domain, dirname)
+                
     _null_translations = NullTranslations()
 
     def get_translations():
@@ -112,6 +177,12 @@
 
     def deactivate():
         del _current.translations
+
+    
+    def add_domain(env_path, domain, locales_dir):
+        if env_path not in _plugin_domains:
+            _plugin_domains[env_path] = []
+        _plugin_domains[env_path].append((domain, locales_dir))
 
     def get_available_locales():
         """Return a list of locale identifiers of the locales for which
diff --git a/trac/web/api.py b/trac/web/api.py
--- a/trac/web/api.py
+++ b/trac/web/api.py
@@ -339,7 +339,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')
diff --git a/trac/web/chrome.py b/trac/web/chrome.py
--- a/trac/web/chrome.py
+++ b/trac/web/chrome.py
@@ -27,7 +27,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
@@ -290,6 +290,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,
@@ -657,9 +659,8 @@
         """
         if not self.templates:
             def _template_loaded(template):
-                template.filters.insert(
-                    0, Translator(translation.get_translations())
-                )
+                translator = Translator(translation.get_translations())
+                setup_i18n(template, translator)
 
             self.templates = TemplateLoader(self.get_all_templates_dirs(),
                                             auto_reload=self.auto_reload,
diff --git a/trac/web/main.py b/trac/web/main.py
--- a/trac/web/main.py
+++ b/trac/web/main.py
@@ -163,7 +163,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

