Index: trac/core.py
===================================================================
--- trac/core.py	(revision 4233)
+++ trac/core.py	(working copy)
@@ -16,6 +16,8 @@
 # Author: Jonas Borgström <jonas@edgewall.com>
 #         Christopher Lenz <cmlenz@gmx.de>
 
+import trac.l10n
+
 from trac.util import TracError
 
 __all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface',
@@ -162,7 +164,7 @@
         cls = self.__class__.__name__
         if hasattr(self, '__module__'):
             cls = '.'.join((self.__module__, cls))
-        raise AttributeError, "'%s' object has no attribute '%s'" % (cls, name)
+        raise AttributeError, _("'%s' object has no attribute '%s'") % (cls, name)
 
 
 class ComponentManager(object):
@@ -186,11 +188,11 @@
             if not self.is_component_enabled(cls):
                 return None
             if cls not in ComponentMeta._components:
-                raise TracError, 'Component "%s" not registered' % cls.__name__
+                raise TracError, _('Component "%s" not registered') % cls.__name__
             try:
                 component = cls(self)
             except TypeError, e:
-                raise TracError, 'Unable to instantiate component "%s" (%s)' \
+                raise TracError, _('Unable to instantiate component "%s" (%s)') \
                                  % (cls.__name__, e)
         return component
 
Index: trac/env.py
===================================================================
--- trac/env.py	(revision 4233)
+++ trac/env.py	(working copy)
@@ -18,6 +18,8 @@
 
 import os
 
+import trac.l10n
+
 from trac import db, db_default, util
 from trac.config import Configuration
 from trac.core import Component, ComponentManager, implements, Interface, \
@@ -155,7 +157,7 @@
         from trac.versioncontrol.svn_fs import SubversionRepository
         repos_dir = self.config.get('trac', 'repository_dir')
         if not repos_dir:
-            raise EnvironmentError, 'Path to repository not configured'
+            raise EnvironmentError, _('Path to repository not configured')
         authz = None
         if authname:
             authz = SubversionAuthorizer(self, authname)
@@ -268,7 +270,7 @@
 
         db_str = self.config.get('trac', 'database')
         if not db_str.startswith('sqlite:'):
-            raise EnvironmentError, 'Can only backup sqlite databases'
+            raise EnvironmentError, _('Can only backup sqlite databases')
         db_name = os.path.join(self.path, db_str[7:])
         if not dest:
             dest = '%s.%i.bak' % (db_name, self.get_version())
@@ -335,7 +337,7 @@
         if dbver == db_default.db_version:
             return False
         elif dbver > db_default.db_version:
-            raise TracError, 'Database newer than Trac version'
+            raise TracError, _('Database newer than Trac version')
         return True
 
     def upgrade_environment(self, db):
@@ -347,7 +349,7 @@
                 upgrades = __import__('upgrades', globals(), locals(), [name])
                 script = getattr(upgrades, name)
             except AttributeError:
-                err = 'No upgrade module for version %i (%s.py)' % (i, name)
+                err = _('No upgrade module for version %i (%s.py)') % (i, name)
                 raise TracError, err
             script.do_upgrade(self.env, i, cursor)
         cursor.execute("UPDATE system SET value=%s WHERE "
@@ -367,12 +369,12 @@
     if not env_path:
         env_path = os.getenv('TRAC_ENV')
     if not env_path:
-        raise TracError, 'Missing environment variable "TRAC_ENV". Trac ' \
+        raise TracError, _('Missing environment variable "TRAC_ENV". Trac ' \
                          'requires this variable to point to a valid Trac ' \
-                         'environment.'
+                         'environment.')
 
     env = Environment(env_path)
     if env.needs_upgrade():
-        raise TracError, 'The Trac Environment needs to be upgraded. Run ' \
-                         'trac-admin %s upgrade"' % env_path
+        raise TracError, _('The Trac Environment needs to be upgraded. Run ' \
+                         'trac-admin %s upgrade"') % env_path
     return env
Index: trac/attachment.py
===================================================================
--- trac/attachment.py	(revision 4233)
+++ trac/attachment.py	(working copy)
@@ -23,6 +23,8 @@
 import time
 import urllib
 
+import trac.l10n
+
 from trac import perm, util
 from trac.core import *
 from trac.env import IEnvironmentSetupParticipant
@@ -60,8 +62,8 @@
         cursor.close()
         if not row:
             self.filename = filename
-            raise TracError('Attachment %s does not exist.' % (self.title),
-                            'Invalid Attachment')
+            raise TracError(_('Attachment %s does not exist.') % (self.title),
+                            _('Invalid Attachment'))
         self.filename = row[0]
         self.description = row[1]
         self.size = row[2] and int(row[2]) or 0
@@ -110,7 +112,7 @@
                                    self.path, exc_info=True)
                 if handle_ta:
                     db.rollback()
-                raise TracError, 'Could not delete attachment'
+                raise TracError, _('Could not delete attachment')
 
         self.env.log.info('Attachment removed: %s' % self.title)
         if handle_ta:
@@ -126,8 +128,8 @@
         # Maximum attachment size (in bytes)
         max_size = int(self.env.config.get('attachment', 'max_size'))
         if max_size >= 0 and size > max_size:
-            raise TracError('Maximum attachment size: %d bytes' % max_size,
-                            'Upload failed')
+            raise TracError(_('Maximum attachment size: %d bytes') % max_size,
+                            _('Upload failed'))
         self.size = size
         self.time = t or time.time()
 
@@ -186,7 +188,7 @@
         try:
             fd = open(self.path, 'rb')
         except IOError:
-            raise TracError('Attachment %s not found' % self.filename)
+            raise TracError(_('Attachment %s not found') % self.filename)
         return fd
 
 
@@ -247,9 +249,9 @@
         parent_type = req.args.get('type')
         path = req.args.get('path')
         if not parent_type or not path:
-            raise TracError('Bad request')
+            raise TracError(_('Bad request'))
         if not parent_type in ['ticket', 'wiki']:
-            raise TracError('Unknown attachment type')
+            raise TracError(_('Unknown attachment type'))
 
         action = req.args.get('action', 'view')
         if action == 'new':
@@ -294,17 +296,17 @@
 
         upload = req.args['attachment']
         if not upload.filename:
-            raise TracError, 'No file uploaded'
+            raise TracError, _('No file uploaded')
         if hasattr(upload.file, 'fileno'):
             size = os.fstat(upload.file.fileno())[6]
         else:
             size = upload.file.len
         if size == 0:
-            raise TracError, 'No file uploaded'
+            raise TracError, _('No file uploaded')
 
         filename = upload.filename.replace('\\', '/').replace(':', '/')
         filename = os.path.basename(filename)
-        assert filename, 'No file uploaded'
+        assert filename, _('No file uploaded')
 
         # We try to normalize the filename to utf-8 NFC if we can.
         # Files uploaded from OS X might be in NFD.
@@ -425,7 +427,7 @@
             
             if not is_binary(data):
                 add_link(req, 'alternate', attachment.href(format='txt'),
-                         'Plain Text', mimetype)
+                         _('Plain Text'), mimetype)
 
             hdf = mimeview.preview_to_hdf(req, mimetype, None, data,
                                           attachment.filename, None,
@@ -457,7 +459,7 @@
                 params = link[idx:]
         try:
             attachment = Attachment(self.env, parent_type, parent_id, filename)
-            return '<a class="attachment" title="Attachment %s" href="%s">%s</a>' \
+            return _('<a class="attachment" title="Attachment %s" href="%s">%s</a>') \
                    % (util.escape(attachment.title),
                       util.escape(attachment.href() + params),
                       util.escape(label))
Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(revision 4233)
+++ trac/db_default.py	(working copy)
@@ -14,6 +14,8 @@
 #
 # Author: Daniel Lundin <daniel@edgewall.com>
 
+import trac.l10n
+
 from trac.config import default_dir
 from trac.db import Table, Column, Index
 
@@ -158,12 +160,12 @@
 ##
 
 reports = (
-('Active Tickets',
-"""
+(_('Active Tickets'),
+_("""
  * List all active tickets by priority.
  * Color each row based on priority.
  * If a ticket has been accepted, a '*' is appended after the owner's name
-""",
+"""),
 """
 SELECT p.value AS __color__,
    id AS ticket, summary, component, version, milestone, t.type AS type, 
@@ -177,14 +179,14 @@
   ORDER BY p.value, milestone, t.type, time
 """),
 #----------------------------------------------------------------------------
- ('Active Tickets by Version',
-"""
+ (_('Active Tickets by Version'),
+_("""
 This report shows how to color results by priority,
 while grouping results by version.
 
 Last modification time, description and reporter are included as hidden fields
 for useful RSS export.
-""",
+"""),
 """
 SELECT p.value AS __color__,
    version AS __group__,
@@ -199,14 +201,14 @@
   ORDER BY (version IS NULL),version, p.value, t.type, time
 """),
 #----------------------------------------------------------------------------
-('All Tickets by Milestone',
-"""
+(_('All Tickets by Milestone'),
+_("""
 This report shows how to color results by priority,
 while grouping results by milestone.
 
 Last modification time, description and reporter are included as hidden fields
 for useful RSS export.
-""",
+"""),
 """
 SELECT p.value AS __color__,
    milestone||' Release' AS __group__,
@@ -221,10 +223,10 @@
   ORDER BY (milestone IS NULL),milestone, p.value, t.type, time
 """),
 #----------------------------------------------------------------------------
-('Assigned, Active Tickets by Owner',
-"""
+(_('Assigned, Active Tickets by Owner'),
+_("""
 List assigned tickets, group by ticket owner, sorted by priority.
-""",
+"""),
 """
 
 SELECT p.value AS __color__,
@@ -238,11 +240,11 @@
   ORDER BY owner, p.value, t.type, time
 """),
 #----------------------------------------------------------------------------
-('Assigned, Active Tickets by Owner (Full Description)',
-"""
+(_('Assigned, Active Tickets by Owner (Full Description)'),
+_("""
 List tickets assigned, group by ticket owner.
 This report demonstrates the use of full-row display.
-""",
+"""),
 """
 SELECT p.value AS __color__,
    owner AS __group__,
@@ -255,10 +257,10 @@
   ORDER BY owner, p.value, t.type, time
 """),
 #----------------------------------------------------------------------------
-('All Tickets By Milestone  (Including closed)',
-"""
+(_('All Tickets By Milestone  (Including closed)'),
+_("""
 A more complex example to show how to make advanced reports.
-""",
+"""),
 """
 SELECT p.value AS __color__,
    t.milestone AS __group__,
@@ -277,12 +279,12 @@
         (CASE status WHEN 'closed' THEN modified ELSE (-1)*p.value END) DESC
 """),
 #----------------------------------------------------------------------------
-('My Tickets',
-"""
+(_('My Tickets'),
+_("""
 This report demonstrates the use of the automatically set 
 $USER dynamic variable, replaced with the username of the
 logged in user when executed.
-""",
+"""),
 """
 SELECT p.value AS __color__,
    (CASE status WHEN 'assigned' THEN 'Assigned' ELSE 'Owned' END) AS __group__,
@@ -296,11 +298,11 @@
   ORDER BY (status = 'assigned') DESC, p.value, milestone, t.type, time
 """),
 #----------------------------------------------------------------------------
-('Active Tickets, Mine first',
-"""
+(_('Active Tickets, Mine first'),
+_("""
  * List all active tickets by priority.
  * Show all tickets owned by the logged in user in a group first.
-""",
+"""),
 """
 SELECT p.value AS __color__,
    (CASE owner 
Index: trac/mimeview/rst.py
===================================================================
--- trac/mimeview/rst.py	(revision 4233)
+++ trac/mimeview/rst.py	(working copy)
@@ -81,9 +81,9 @@
             from docutils.parsers import rst
             from docutils import __version__
         except ImportError:
-            raise TracError, 'Docutils not found'
+            raise TracError, _('Docutils not found')
         if StrictVersion(__version__) < StrictVersion('0.3.3'):
-            raise TracError, 'Docutils version >= %s required, %s found' \
+            raise TracError, _('Docutils version >= %s required, %s found') \
                              % ('0.3.3', __version__)
 
         def trac_get_reference(rawtext, link, text):
Index: trac/mimeview/api.py
===================================================================
--- trac/mimeview/api.py	(revision 4233)
+++ trac/mimeview/api.py	(working copy)
@@ -24,6 +24,8 @@
 except ImportError:
     from StringIO import StringIO
 
+import trac.l10n
+
 from trac.core import *
 from trac.util import enum, escape, to_utf8
 
@@ -339,7 +341,7 @@
     # ITextAnnotator methods
 
     def get_annotation_type(self):
-        return 'lineno', 'Line', 'Line numbers'
+        return 'lineno', _('Line'), _('Line numbers')
 
     def annotate_line(self, number, content):
         return '<th id="L%s"><a href="#L%s">%s</a></th>' % (number, number,
Index: trac/mimeview/silvercity.py
===================================================================
--- trac/mimeview/silvercity.py	(revision 4233)
+++ trac/mimeview/silvercity.py	(working copy)
@@ -86,7 +86,7 @@
             except IndexError:
                 pass
         except (KeyError, AttributeError):
-            err = "No SilverCity lexer found for mime-type '%s'." % mimetype
+            err = _("No SilverCity lexer found for mime-type '%s'.") % mimetype
             raise Exception, err
 
         # SilverCity generates extra empty line against some types of
Index: trac/mimeview/patch.py
===================================================================
--- trac/mimeview/patch.py	(revision 4233)
+++ trac/mimeview/patch.py	(working copy)
@@ -15,6 +15,8 @@
 # Author: Christopher Lenz <cmlenz@gmx.de>
 #         Ludvig Strigeus
 
+import trac.l10n
+
 from trac.core import *
 from trac.mimeview.api import IHTMLPreviewRenderer
 from trac.util import escape
@@ -31,7 +33,7 @@
 
     implements(IHTMLPreviewRenderer)
 
-    diff_cs = """
+    diff_cs = _("""
 <?cs include:'macros.cs' ?>
 <div class="diff"><ul class="entries"><?cs
  each:file = diff.files ?><li class="entry">
@@ -54,7 +56,7 @@
   </table>
  </li><?cs /each ?>
 </ul></div>
-""" # diff_cs
+""") # diff_cs
 
     # IHTMLPreviewRenderer methods
 
@@ -69,7 +71,7 @@
         tabwidth = int(self.config.get('diff', 'tab_width'))
         d = self._diff_to_hdf(content.splitlines(), tabwidth)
         if not d:
-            raise TracError, 'Invalid unified diff content'
+            raise TracError, _('Invalid unified diff content')
         hdf = HDFWrapper(loadpaths=[self.env.get_templates_dir(),
                                     self.config.get('trac', 'templates_dir')])
         hdf['diff.files'] = d
Index: trac/mimeview/enscript.py
===================================================================
--- trac/mimeview/enscript.py	(revision 4233)
+++ trac/mimeview/enscript.py	(working copy)
@@ -17,6 +17,8 @@
 
 from __future__ import generators
 
+import trac.l10n
+
 from trac.core import *
 from trac.mimeview.api import IHTMLPreviewRenderer
 from trac.util import escape, NaivePopen, Deuglifier
@@ -110,7 +112,7 @@
 
         np = NaivePopen(cmdline, content, capturestderr=1)
         if np.errorlevel or np.err:
-            err = 'Running (%s) failed: %s, %s.' % (cmdline, np.errorlevel,
+            err = _('Running (%s) failed: %s, %s.') % (cmdline, np.errorlevel,
                                                     np.err)
             raise Exception, err
         odata = np.out
Index: trac/mimeview/php.py
===================================================================
--- trac/mimeview/php.py	(revision 4233)
+++ trac/mimeview/php.py	(working copy)
@@ -18,6 +18,8 @@
 
 from __future__ import generators
 
+import trac.l10n
+
 from trac.core import *
 from trac.mimeview.api import IHTMLPreviewRenderer
 from trac.util import Deuglifier, NaivePopen
@@ -61,14 +63,14 @@
 
         np = NaivePopen(cmdline, content, capturestderr=1)
         if np.errorlevel or np.err:
-            err = 'Running (%s) failed: %s, %s.' % (cmdline, np.errorlevel,
+            err = _('Running (%s) failed: %s, %s.') % (cmdline, np.errorlevel,
                                                     np.err)
             raise Exception, err
         odata = ''.join(np.out.splitlines()[1:-1])
         if odata.startswith('X-Powered-By'):
-            raise TracError, 'You appear to be using the PHP CGI binary.  ' \
+            raise TracError, _('You appear to be using the PHP CGI binary.  ' \
                              'Trac requires the CLI version for syntax ' \
-                             'highlighting.'
+                             'highlighting.')
 
         html = PhpDeuglifier().format(odata)
         for line in html.split('<br />'):
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py	(revision 4233)
+++ trac/ticket/api.py	(working copy)
@@ -16,6 +16,8 @@
 
 from __future__ import generators
 
+import trac.l10n
+
 from trac import util
 from trac.core import *
 from trac.perm import IPermissionRequestor
@@ -49,12 +51,13 @@
         fields = []
 
         # Basic text fields
+	bflabel = { 'summary': _('Summary'), 'reporter': _('Reporter') }
         for name in ('summary', 'reporter'):
-            field = {'name': name, 'type': 'text', 'label': name.title()}
+            field = {'name': name, 'type': 'text', 'label': bflabel[name] }
             fields.append(field)
 
         # Owner field, can be text or drop-down depending on configuration
-        field = {'name': 'owner', 'label': 'Owner'}
+        field = {'name': 'owner', 'label': _('Owner') }
         if self.config.get('ticket', 'restrict_owner').lower() in util.TRUE:
             field['type'] = 'select'
             users = []
@@ -68,9 +71,15 @@
 
         # Description
         fields.append({'name': 'description', 'type': 'textarea',
-                       'label': 'Description'})
+                       'label': _('Description')})
 
         # Default select and radio fields
+	slabel = {
+		'type': _('Type'), 'status': _('Status'),
+	        'priority': _('Priority'), 'milestone': _('Milestone'),
+		'component': _('Component'), 'version': _('Version'),
+		'severity': _('Severity'), 'resolution': _('Resolution'),
+	}
         selects = [('type', model.Type), ('status', model.Status),
                    ('priority', model.Priority), ('milestone', model.Milestone),
                    ('component', model.Component), ('version', model.Version),
@@ -81,7 +90,7 @@
                 # Fields without possible values are treated as if they didn't
                 # exist
                 continue
-            field = {'name': name, 'type': 'select', 'label': name.title(),
+            field = {'name': name, 'type': 'select', 'label': slabel[name],
                      'value': self.config.get('ticket', 'default_' + name),
                      'options': options}
             if name in ('status', 'resolution'):
@@ -91,8 +100,9 @@
             fields.append(field)
 
         # Advanced text fields
+	aflabel = { 'keywords': _('Keywords'), 'cc': _('Cc')}
         for name in ('keywords', 'cc', ):
-            field = {'name': name, 'type': 'text', 'label': name.title()}
+            field = {'name': name, 'type': 'text', 'label': aflabel[name]}
             fields.append(field)
 
         custom_fields = self.get_custom_fields()
@@ -162,7 +172,7 @@
 
     def get_search_filters(self, req):
         if req.perm.has_permission('TICKET_VIEW'):
-            yield ('ticket', 'Tickets')
+            yield ('ticket', _('Tickets'))
 
     def get_search_results(self, req, query, filters):
         if not 'ticket' in filters:
Index: trac/ticket/web_ui.py
===================================================================
--- trac/ticket/web_ui.py	(revision 4233)
+++ trac/ticket/web_ui.py	(working copy)
@@ -19,6 +19,8 @@
 import re
 import time
 
+import trac.l10n
+
 from trac import util
 from trac.attachment import attachment_to_hdf, Attachment
 from trac.core import *
@@ -67,7 +69,7 @@
         if not req.perm.has_permission('TICKET_CREATE'):
             return
         yield 'mainnav', 'newticket', \
-              '<a href="%s" accesskey="7">New Ticket</a>' \
+              _('<a href="%s" accesskey="7">New Ticket</a>') \
               % (self.env.href.newticket())
 
     # IRequestHandler methods
@@ -91,7 +93,7 @@
             description = wiki_to_html(ticket['description'], self.env, req, db)
             req.hdf['newticket.description_preview'] = description
 
-        req.hdf['title'] = 'New Ticket'
+        req.hdf['title'] = _('New Ticket')
         req.hdf['newticket'] = dict(zip(ticket.values.keys(),
                                         [util.escape(value) for value
                                          in ticket.values.values()]))
@@ -115,7 +117,7 @@
                         'resolution'):
                 field['skip'] = True
             elif name == 'owner':
-                field['label'] = 'Assign to'
+                field['label'] = _('Assign to')
             elif name == 'milestone':
                 # Don't make completed milestones available for selection
                 options = field['options'][:]
@@ -133,7 +135,7 @@
 
     def _do_create(self, req, db):
         if not req.args.get('summary'):
-            raise TracError('Tickets must contain a summary.')
+            raise TracError(_('Tickets must contain a summary.'))
 
         ticket = Ticket(self.env, db=db)
         ticket.values.setdefault('reporter', util.get_reporter_id(req))
@@ -220,16 +222,16 @@
             if str(id) in tickets:
                 idx = tickets.index(str(ticket.id))
                 if idx > 0:
-                    add_link(req, 'first', self.env.href.ticket(tickets[0]),
-                             'Ticket #%s' % tickets[0])
-                    add_link(req, 'prev', self.env.href.ticket(tickets[idx - 1]),
-                             'Ticket #%s' % tickets[idx - 1])
+                    add_link(req, _('first'), self.env.href.ticket(tickets[0]),
+                             _('Ticket #%s') % tickets[0])
+                    add_link(req, _('prev'), self.env.href.ticket(tickets[idx - 1]),
+                             _('Ticket #%s') % tickets[idx - 1])
                 if idx < len(tickets) - 1:
-                    add_link(req, 'next', self.env.href.ticket(tickets[idx + 1]),
-                             'Ticket #%s' % tickets[idx + 1])
-                    add_link(req, 'last', self.env.href.ticket(tickets[-1]),
-                             'Ticket #%s' % tickets[-1])
-                add_link(req, 'up', req.session['query_href'])
+                    add_link(req, _('next'), self.env.href.ticket(tickets[idx + 1]),
+                             _('Ticket #%s') % tickets[idx + 1])
+                    add_link(req, _('last'), self.env.href.ticket(tickets[-1]),
+                             _('Ticket #%s') % tickets[-1])
+                add_link(req, _('up'), req.session['query_href'])
 
         add_stylesheet(req, 'common/css/ticket.css')
         return 'ticket.cs', None
@@ -238,7 +240,7 @@
 
     def get_timeline_filters(self, req):
         if req.perm.has_permission('TICKET_VIEW'):
-            yield ('ticket', 'Ticket changes')
+            yield ('ticket', _('Ticket changes'))
 
     def get_timeline_events(self, req, start, stop, filters):
         if 'ticket' in filters:
@@ -276,13 +278,13 @@
             cursor = db.cursor()
             cursor.execute(" UNION ALL ".join(sql), (start, stop, start, stop,
                            start, stop))
-            kinds = {'new': 'newticket', 'reopened': 'newticket',
-                     'closed': 'closedticket'}
-            verbs = {'new': 'created', 'reopened': 'reopened',
-                     'closed': 'closed'}
+            kinds = {'new': _('newticket'), 'reopened': _('newticket'),
+                     'closed': _('closedticket') }
+            verbs = {'new': _('created'), 'reopened': _('reopened'),
+                     'closed': _('closed')}
             for t, id, resolution, status, type, message, author, summary \
                     in cursor:
-                title = 'Ticket <em title="%s">#%s</em> (%s) %s by %s' % (
+                title = _('Ticket <em title="%s">#%s</em> (%s) %s by %s') % (
                         util.escape(summary), id, type, verbs[status],
                         util.escape(author))
                 if format == 'rss':
@@ -310,7 +312,7 @@
         if req.perm.has_permission('TICKET_CHGPROP'):
             # TICKET_CHGPROP gives permission to edit the ticket
             if not req.args.get('summary'):
-                raise TracError('Tickets must contain summary.')
+                raise TracError(_('Tickets must contain summary.'))
 
             if req.args.has_key('description') or req.args.has_key('reporter'):
                 req.perm.assert_permission('TICKET_ADMIN')
@@ -321,15 +323,15 @@
 
         # Mid air collision?
         if int(req.args.get('ts')) != ticket.time_changed:
-            raise TracError("Sorry, can not save your changes. "
+            raise TracError(_("Sorry, can not save your changes. "
                             "This ticket has been modified by someone else "
-                            "since you started", 'Mid Air Collision')
+                            "since you started"), _('Mid Air Collision'))
 
         # Do any action on the ticket?
         action = req.args.get('action')
         actions = TicketSystem(self.env).get_available_actions(ticket, req.perm)
         if action not in actions:
-            raise TracError('Invalid action')
+            raise TracError(_('Invalid action'))
 
         # TODO: this should not be hard-coded like this
         if action == 'accept':
@@ -443,7 +445,7 @@
         if self.config.get('timeline', 'ticket_show_details') in util.FALSE:
             return
         if req.perm.has_permission('TICKET_VIEW'):
-            yield ('ticket_details', 'Ticket details', False)
+            yield ('ticket_details', _('Ticket details'), False)
 
     def get_timeline_events(self, req, start, stop, filters):
         if 'ticket_details' in filters:
@@ -481,11 +483,11 @@
                     href = self.env.abs_href.ticket(id)
                 else:
                     href = self.env.href.ticket(id) 
-                title = 'Ticket <em title="%s">#%s</em> (%s) updated by %s' \
+                title = _('Ticket <em title="%s">#%s</em> (%s) updated by %s') \
                         % (util.escape(summary), id, type, util.escape(author))
                 message = ''
                 if len(field_changes) > 0:
-                    message = ', '.join(field_changes) + ' changed.<br />'
+                    message = ', '.join(field_changes) + _(' changed.<br />')
                 message += wiki_to_oneliner(comment, self.env, db,
                                             shorten=True, absurls=absurls)
                 yield 'editedticket', href, title, t, author, message
Index: trac/ticket/report.py
===================================================================
--- trac/ticket/report.py	(revision 4233)
+++ trac/ticket/report.py	(working copy)
@@ -19,6 +19,8 @@
 import types
 import urllib
 
+import trac.l10n
+
 from trac import util
 from trac.core import *
 from trac.perm import IPermissionRequestor
@@ -73,7 +75,7 @@
     def get_navigation_items(self, req):
         if not req.perm.has_permission('REPORT_VIEW'):
             return
-        yield 'mainnav', 'tickets', '<a href="%s">View Tickets</a>' \
+        yield 'mainnav', 'tickets', _('<a href="%s">View Tickets</a>') \
               % util.escape(self.env.href.report())
 
     # IPermissionRequestor methods  
@@ -121,7 +123,7 @@
                return resp
 
         if id != -1 or action == 'new':
-            add_link(req, 'up', self.env.href.report(), 'Available Reports')
+            add_link(req, 'up', self.env.href.report(), _('Available Reports'))
 
             # Kludge: Reset session vars created by query module so that the
             # query navigation links on the ticket page don't confuse the user
@@ -191,9 +193,9 @@
         cursor.execute("SELECT title FROM report WHERE id = %s", (id,))
         row = cursor.fetchone()
         if not row:
-            raise util.TracError('Report %s does not exist.' % id,
-                                 'Invalid Report Number')
-        req.hdf['title'] = 'Delete Report {%s} %s' % (id, row[0])
+            raise util.TracError(_('Report %s does not exist.') % id,
+                                 _('Invalid Report Number'))
+        req.hdf['title'] = _('Delete Report {%s} %s') % (id, row[0])
         req.hdf['report'] = {
             'id': id,
             'mode': 'delete',
@@ -222,11 +224,11 @@
             title += ' (copy)'
 
         if copy or id == -1:
-            req.hdf['title'] = 'Create New Report'
+            req.hdf['title'] = _('Create New Report')
             req.hdf['report.href'] = self.env.href.report()
             req.hdf['report.action'] = 'new'
         else:
-            req.hdf['title'] = 'Edit Report {%d} %s' % (id, title)
+            req.hdf['title'] = _('Edit Report {%d} %s') % (id, title)
             req.hdf['report.href'] = self.env.href.report(id)
             req.hdf['report.action'] = 'edit'
 
@@ -272,7 +274,7 @@
         try:
             cols, rows = self.execute_report(req, db, id, sql, args)
         except Exception, e:
-            req.hdf['report.message'] = 'Report execution failed: %s' % e
+            req.hdf['report.message'] = _('Report execution failed: %s') % e
             return 'report.cs', None
 
         # Convert the header info to HDF-format
@@ -386,14 +388,14 @@
         href = ''
         if params:
             href = '&' + urllib.urlencode(params)
-        add_link(req, 'alternate', '?format=rss' + href, 'RSS Feed',
+        add_link(req, 'alternate', '?format=rss' + href, _('RSS Feed'),
                  'application/rss+xml', 'rss')
         add_link(req, 'alternate', '?format=csv' + href,
-                 'Comma-delimited Text', 'text/plain')
+                 _('Comma-delimited Text'), 'text/plain')
         add_link(req, 'alternate', '?format=tab' + href,
-                 'Tab-delimited Text', 'text/plain')
+                 _('Tab-delimited Text'), 'text/plain')
         if req.perm.has_permission('REPORT_SQL_VIEW'):
-            add_link(req, 'alternate', '?format=sql', 'SQL Query',
+            add_link(req, 'alternate', '?format=sql', _('SQL Query'),
                      'text/plain')
 
     def execute_report(self, req, db, id, sql, args):
@@ -418,9 +420,9 @@
         if id == -1:
             # If no particular report was requested, display
             # a list of available reports instead
-            title = 'Available Reports'
+            title = _('Available Reports')
             sql = 'SELECT id AS report, title FROM report ORDER BY report'
-            description = 'This is a list of reports available.'
+            description = _('This is a list of reports available.')
         else:
             cursor = db.cursor()
             cursor.execute("SELECT title,sql,description from report "
Index: trac/ticket/model.py
===================================================================
--- trac/ticket/model.py	(revision 4233)
+++ trac/ticket/model.py	(working copy)
@@ -74,8 +74,8 @@
                        % ','.join(std_fields), (tkt_id,))
         row = cursor.fetchone()
         if not row:
-            raise TracError('Ticket %d does not exist.' % tkt_id,
-                            'Invalid Ticket Number')
+            raise TracError(_('Ticket %d does not exist.') % tkt_id,
+                            _('Invalid Ticket Number'))
 
         self.id = tkt_id
         for i in range(len(std_fields)):
@@ -410,7 +410,7 @@
                            "WHERE name=%s", (name,))
             row = cursor.fetchone()
             if not row:
-                raise TracError, 'Component %s does not exist.' % name
+                raise TracError, _('Component %s does not exist.') % name
             self.name = self._old_name = name
             self.owner = row[0] or None
             self.description = row[1] or ''
@@ -517,8 +517,8 @@
                        "FROM milestone WHERE name=%s", (name,))
         row = cursor.fetchone()
         if not row:
-            raise TracError('Milestone %s does not exist.' % name,
-                            'Invalid Milestone Name')
+            raise TracError(_('Milestone %s does not exist.') % name,
+                            _('Invalid Milestone Name'))
         self.name = row[0]
         self.due = row[1] and int(row[1]) or 0
         self.completed = row[2] and int(row[2]) or 0
@@ -625,7 +625,7 @@
                            "WHERE name=%s", (name,))
             row = cursor.fetchone()
             if not row:
-                raise TracError, 'Version %s does not exist.' % name
+                raise TracError, _('Version %s does not exist.') % name
             self.name = self._old_name = name
             self.time = row[0] and int(row[0]) or None
             self.description = row[1] or ''
Index: trac/ticket/roadmap.py
===================================================================
--- trac/ticket/roadmap.py	(revision 4233)
+++ trac/ticket/roadmap.py	(working copy)
@@ -18,6 +18,8 @@
 import re
 from time import localtime, strftime, time
 
+import trac.l10n
+
 from trac import __version__
 from trac.core import *
 from trac.perm import IPermissionRequestor
@@ -128,7 +130,7 @@
     def get_navigation_items(self, req):
         if not req.perm.has_permission('ROADMAP_VIEW'):
             return
-        yield 'mainnav', 'roadmap', '<a href="%s" accesskey="3">Roadmap</a>' \
+        yield 'mainnav', 'roadmap', _('<a href="%s" accesskey="3">Roadmap</a>') \
                                     % self.env.href.roadmap()
 
     # IPermissionRequestor methods
@@ -143,7 +145,7 @@
 
     def process_request(self, req):
         req.perm.assert_permission('ROADMAP_VIEW')
-        req.hdf['title'] = 'Roadmap'
+        req.hdf['title'] = _('Roadmap')
 
         showall = req.args.get('show') == 'all'
         req.hdf['roadmap.showall'] = showall
@@ -301,7 +303,7 @@
 
     def get_timeline_filters(self, req):
         if req.perm.has_permission('MILESTONE_VIEW'):
-            yield ('milestone', 'Milestones')
+            yield ('milestone', _('Milestones'))
 
     def get_timeline_events(self, req, start, stop, filters):
         if 'milestone' in filters:
@@ -312,7 +314,7 @@
                            "WHERE completed>=%s AND completed<=%s",
                            (start, stop,))
             for completed,name,description in cursor:
-                title = 'Milestone <em>%s</em> completed' % escape(name)
+                title = _('Milestone <em>%s</em> completed') % escape(name)
                 if format == 'rss':
                     href = self.env.abs_href.milestone(name)
                     message = wiki_to_html(description or '--', self.env, db,
@@ -382,24 +384,24 @@
             req.perm.assert_permission('MILESTONE_CREATE')
 
         if not req.args.has_key('name'):
-            raise TracError('You must provide a name for the milestone.',
-                            'Required Field Missing')
+            raise TracError(_('You must provide a name for the milestone.'),
+                            _('Required Field Missing'))
         milestone.name = req.args.get('name')
 
         due = req.args.get('duedate', '')
         try:
             milestone.due = due and parse_date(due) or 0
         except ValueError, e:
-            raise TracError(e, 'Invalid Date Format')
+            raise TracError(e, _('Invalid Date Format'))
         if req.args.has_key('completed'):
             completed = req.args.get('completeddate', '')
             try:
                 milestone.completed = completed and parse_date(completed) or 0
             except ValueError, e:
-                raise TracError(e, 'Invalid Date Format')
+                raise TracError(e, _('Invalid Date Format'))
             if milestone.completed > time():
-                raise TracError('Completion date may not be in the future',
-                                'Invalid Completion Date')
+                raise TracError(_('Completion date may not be in the future'),
+                                _('Invalid Completion Date'))
         else:
             milestone.completed = 0
 
@@ -417,7 +419,7 @@
     def _render_confirm(self, req, db, milestone):
         req.perm.assert_permission('MILESTONE_DELETE')
 
-        req.hdf['title'] = 'Milestone %s' % milestone.name
+        req.hdf['title'] = _('Milestone %s') % milestone.name
         req.hdf['milestone'] = milestone_to_hdf(self.env, db, req, milestone)
         req.hdf['milestone.mode'] = 'delete'
 
@@ -429,11 +431,11 @@
     def _render_editor(self, req, db, milestone):
         if milestone.exists:
             req.perm.assert_permission('MILESTONE_MODIFY')
-            req.hdf['title'] = 'Milestone %s' % milestone.name
+            req.hdf['title'] = _('Milestone %s') % milestone.name
             req.hdf['milestone.mode'] = 'edit'
         else:
             req.perm.assert_permission('MILESTONE_CREATE')
-            req.hdf['title'] = 'New Milestone'
+            req.hdf['title'] = _('New Milestone')
             req.hdf['milestone.mode'] = 'new'
 
         from trac.util import get_date_format_hint, get_datetime_format_hint
@@ -443,7 +445,7 @@
         req.hdf['milestone.datetime_now'] = format_datetime()
 
     def _render_view(self, req, db, milestone):
-        req.hdf['title'] = 'Milestone %s' % milestone.name
+        req.hdf['title'] = _('Milestone %s') % milestone.name
         req.hdf['milestone.mode'] = 'view'
 
         req.hdf['milestone'] = milestone_to_hdf(self.env, db, req, milestone)
Index: trac/ticket/query.py
===================================================================
--- trac/ticket/query.py	(revision 4233)
+++ trac/ticket/query.py	(working copy)
@@ -18,6 +18,8 @@
 import re
 import time
 
+import trac.l10n
+
 from trac.core import *
 from trac.perm import IPermissionRequestor
 from trac.ticket import Ticket, TicketSystem
@@ -58,11 +60,11 @@
         for filter in filters:
             filter = filter.split('=')
             if len(filter) != 2:
-                raise QuerySyntaxError, 'Query filter requires field and ' \
-                                        'constraints separated by a "="'
+                raise QuerySyntaxError, _('Query filter requires field and ' \
+                                        'constraints separated by a "="')
             field,values = filter
             if not field:
-                raise QuerySyntaxError, 'Query filter requires field name'
+                raise QuerySyntaxError, _('Query filter requires field name')
             values = values.split('|')
             mode, neg = '', ''
             if field[-1] in ('~', '^', '$'):
@@ -322,7 +324,7 @@
         from trac.ticket.report import ReportModule
         if req.perm.has_permission('TICKET_VIEW') and \
            not self.env.is_component_enabled(ReportModule):
-            yield 'mainnav', 'tickets', '<a href="%s">View Tickets</a>' \
+            yield 'mainnav', 'tickets', _('<a href="%s">View Tickets</a>') \
                   % escape(self.env.href.query())
 
     # IRequestHandler methods
@@ -359,11 +361,11 @@
                     del req.session[var]
             req.redirect(query.get_href())
 
-        add_link(req, 'alternate', query.get_href('rss'), 'RSS Feed',
+        add_link(req, 'alternate', query.get_href('rss'), _('RSS Feed'),
                  'application/rss+xml', 'rss')
         add_link(req, 'alternate', query.get_href('csv'),
-                 'Comma-delimited Text', 'text/plain')
-        add_link(req, 'alternate', query.get_href('tab'), 'Tab-delimited Text',
+                 _('Comma-delimited Text'), 'text/plain')
+        add_link(req, 'alternate', query.get_href('tab'), _('Tab-delimited Text'),
                  'text/plain')
 
         constraints = {}
@@ -436,21 +438,21 @@
     def _get_constraint_modes(self):
         modes = {}
         modes['text'] = [
-            {'name': "contains", 'value': "~"},
-            {'name': "doesn't contain", 'value': "!~"},
-            {'name': "begins with", 'value': "^"},
-            {'name': "ends with", 'value': "$"},
-            {'name': "is", 'value': ""},
-            {'name': "is not", 'value': "!"}
+            {'name': _("contains"), 'value': "~"},
+            {'name': _("doesn't contain"), 'value': "!~"},
+            {'name': _("begins with"), 'value': "^"},
+            {'name': _("ends with"), 'value': "$"},
+            {'name': _("is"), 'value': ""},
+            {'name': _("is not"), 'value': "!"}
         ]
         modes['select'] = [
-            {'name': "is", 'value': ""},
-            {'name': "is not", 'value': "!"}
+            {'name': _("is"), 'value': ""},
+            {'name': _("is not"), 'value': "!"}
         ]
         return modes
 
     def display_html(self, req, query):
-        req.hdf['title'] = 'Custom Query'
+        req.hdf['title'] = _('Custom Query')
         add_stylesheet(req, 'common/css/report.css')
 
         db = self.env.get_db_cnx()
@@ -619,7 +621,7 @@
     implements(IWikiMacroProvider)
 
     def get_macros(self):
-        yield 'TicketQuery'
+        yield _('TicketQuery')
 
     def get_macro_description(self, name):
         import inspect
Index: trac/versioncontrol/svn_fs.py
===================================================================
--- trac/versioncontrol/svn_fs.py	(revision 4233)
+++ trac/versioncontrol/svn_fs.py	(working copy)
@@ -16,6 +16,8 @@
 
 from __future__ import generators
 
+import trac.l10n
+
 from trac.util import TracError
 from trac.versioncontrol import Changeset, Node, Repository
 
@@ -179,7 +181,7 @@
     def __init__(self, path, authz, log):
         if core.SVN_VER_MAJOR < 1:
             raise TracError, \
-                  "Subversion >= 1.0 required: Found %d.%d.%d" % \
+                  _("Subversion >= 1.0 required: Found %d.%d.%d") % \
                   (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO)
 
         self.pool = Pool()
@@ -190,7 +192,7 @@
         self.path = repos.svn_repos_find_root_path(path, self.pool())
         if self.path is None:
             raise TracError, \
-                  "%s does not appear to be a Subversion repository." % path
+                  _("%s does not appear to be a Subversion repository.") % path
 
         self.repos = repos.svn_repos_open(self.path, self.pool())
         self.fs_ptr = repos.svn_repos_fs(self.repos)
@@ -239,7 +241,7 @@
         if rev is None:
             rev = self.youngest_rev
         elif rev > self.youngest_rev:
-            raise TracError, "Revision %s doesn't exist yet" % rev
+            raise TracError, _("Revision %s doesn't exist yet") % rev
         return rev
 
     def close(self):
@@ -366,7 +368,7 @@
         self.root = fs.revision_root(fs_ptr, rev, self.pool())
         node_type = fs.check_path(self.root, self.scoped_path, self.pool())
         if not node_type in _kindmap:
-            raise TracError, "No node at %s in revision %s" % (path, rev)
+            raise TracError, _("No node at %s in revision %s") % (path, rev)
         self.created_rev = fs.node_created_rev(self.root, self.scoped_path,
                                                self.pool())
         self.created_path = fs.node_created_path(self.root, self.scoped_path,
Index: trac/versioncontrol/cache.py
===================================================================
--- trac/versioncontrol/cache.py	(revision 4233)
+++ trac/versioncontrol/cache.py	(working copy)
@@ -16,6 +16,8 @@
 
 from __future__ import generators
 
+import trac.l10n
+
 from trac.util import TracError
 from trac.versioncontrol import Changeset, Node, Repository, Authorizer
 
@@ -57,8 +59,8 @@
             previous_repository_dir = self.name
 
         if self.name != previous_repository_dir:
-            raise TracError, ("The 'repository_dir' has changed, "
-                              "a 'trac-admin resync' operation is needed.")
+            raise TracError, (_("The 'repository_dir' has changed, "
+                              "a 'trac-admin resync' operation is needed."))
 
         youngest_stored = self.repos.get_youngest_rev_in_cache(self.db)
 
@@ -139,7 +141,7 @@
             date, author, message = row
             Changeset.__init__(self, rev, message, author, int(date))
         else:
-            raise TracError, "No changeset %s in the repository" % rev
+            raise TracError, _("No changeset %s in the repository") % rev
 
     def get_changes(self):
         cursor = self.db.cursor()
Index: trac/versioncontrol/web_ui/util.py
===================================================================
--- trac/versioncontrol/web_ui/util.py	(revision 4233)
+++ trac/versioncontrol/web_ui/util.py	(working copy)
@@ -17,6 +17,8 @@
 import re
 import urllib
 
+import trac.l10n
+
 from trac.util import escape, format_datetime, pretty_timedelta, shorten_line, \
                       TracError
 from trac.wiki import wiki_to_html, wiki_to_oneliner
@@ -86,8 +88,8 @@
     try: 
         return repos.get_node(path, rev) 
     except TracError, e: 
-        raise TracError(e.message + '<br><p>You can <a href="%s">search</a> ' 
+        raise TracError(e.message + _('<br><p>You can <a href="%s">search</a> ' 
                         'in the repository history to see if that path '
-                        'existed but was later removed.</p>'
+                        'existed but was later removed.</p>')
                         % escape(env.href.log(path, rev=rev,
                                               mode='path_history')))
Index: trac/versioncontrol/web_ui/changeset.py
===================================================================
--- trac/versioncontrol/web_ui/changeset.py	(revision 4233)
+++ trac/versioncontrol/web_ui/changeset.py	(working copy)
@@ -20,6 +20,8 @@
 import time
 import re
 
+import trac.l10n
+
 from trac import mimeview, util
 from trac.core import *
 from trac.perm import IPermissionRequestor
@@ -97,7 +99,7 @@
 
     def get_timeline_filters(self, req):
         if req.perm.has_permission('CHANGESET_VIEW'):
-            yield ('changeset', 'Repository checkins')
+            yield ('changeset', _('Repository checkins'))
 
     def get_timeline_events(self, req, start, stop, filters):
         if 'changeset' in filters:
@@ -119,14 +121,14 @@
                 if chgset.date < stop:
                     message = chgset.message or '--'
                     if format == 'rss':
-                        title = 'Changeset <em>[%s]</em>: %s' \
+                        title = _('Changeset <em>[%s]</em>: %s') \
                                 % (util.escape(chgset.rev),
                                    util.escape(util.shorten_line(message)))
                         href = self.env.abs_href.changeset(chgset.rev)
                         message = wiki_to_html(message, self.env, db,
                                                absurls=True)
                     else:
-                        title = 'Changeset <em>[%s]</em> by %s' \
+                        title = _('Changeset <em>[%s]</em> by %s') \
                                 % (util.escape(chgset.rev),
                                    util.escape(chgset.author))
                         href = self.env.href.changeset(chgset.rev)
@@ -378,7 +380,7 @@
 
     def get_search_filters(self, req):
         if req.perm.has_permission('CHANGESET_VIEW'):
-            yield ('changeset', 'Changesets')
+            yield ('changeset', _('Changesets'))
 
     def get_search_results(self, req, query, filters):
         if not 'changeset' in filters:
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 4233)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -19,6 +19,8 @@
 import re
 import urllib
 
+import trac.l10n
+
 from trac import util
 from trac.core import *
 from trac.perm import IPermissionRequestor
@@ -124,9 +126,9 @@
             previous_path = old_path
         if info == []:
             # FIXME: we should send a 404 error here
-            raise TracError("The file or directory '%s' doesn't exist "
-                            "at revision %s or at any previous revision."
-                            % (path, rev), 'Nonexistent path')
+            raise TracError(_("The file or directory '%s' doesn't exist "
+                            "at revision %s or at any previous revision.")
+                            % (path, rev), _('Nonexistent path'))
 
         def make_log_href(path, **args):
             link_rev = rev
Index: trac/versioncontrol/web_ui/browser.py
===================================================================
--- trac/versioncontrol/web_ui/browser.py	(revision 4233)
+++ trac/versioncontrol/web_ui/browser.py	(working copy)
@@ -18,6 +18,8 @@
 import re
 import urllib
 
+import trac.l10n
+
 from trac import util
 from trac.core import *
 from trac.mimeview import get_mimetype, is_binary, detect_unicode, Mimeview
@@ -61,7 +63,7 @@
     def get_navigation_items(self, req):
         if not req.perm.has_permission('BROWSER_VIEW'):
             return
-        yield 'mainnav', 'browser', '<a href="%s">Browse Source</a>' \
+        yield 'mainnav', 'browser', _('<a href="%s">Browse Source</a>') \
               % util.escape(self.env.href.browser())
 
     # IPermissionRequestor methods
Index: trac/Settings.py
===================================================================
--- trac/Settings.py	(revision 4233)
+++ trac/Settings.py	(working copy)
@@ -16,6 +16,8 @@
 
 from __future__ import generators
 
+import trac.l10n
+
 from trac.core import *
 from trac.util import escape
 from trac.web import IRequestHandler
@@ -33,7 +35,7 @@
         return 'settings'
 
     def get_navigation_items(self, req):
-        yield 'metanav', 'settings', '<a href="%s">Settings</a>' \
+        yield 'metanav', 'settings', _('<a href="%s">Settings</a>') \
               % escape(self.env.href.settings())
 
     # IRequestHandler methods
@@ -50,7 +52,7 @@
             elif action == 'load':
                 self._do_load(req)
 
-        req.hdf['title'] = 'Settings'
+        req.hdf['title'] = _('Settings')
         req.hdf['settings'] = req.session
         if req.authname == 'anonymous':
             req.hdf['settings.session_id'] = req.session.sid
Index: trac/scripts/admin.py
===================================================================
--- trac/scripts/admin.py	(revision 4233)
+++ trac/scripts/admin.py	(working copy)
@@ -27,6 +27,9 @@
 import urllib
 
 import trac
+
+import trac.l10n
+
 from trac import perm, util, db_default
 from trac.config import default_dir
 from trac.env import Environment
@@ -730,7 +733,7 @@
 
     def _do_wiki_import(self, filename, title, cursor=None):
         if not os.path.isfile(filename):
-            raise Exception, '%s is not a file' % filename
+            raise Exception, _('%s is not a file') % filename
 
         f = open(filename,'r')
         data = util.to_utf8(f.read())
@@ -766,7 +769,7 @@
             print text
         else:
             if os.path.isfile(filename):
-                raise Exception("File '%s' exists" % filename)
+                raise Exception(_("File '%s' exists") % filename)
             f = open(filename,'w')
             f.write(text)
             f.close()
Index: trac/perm.py
===================================================================
--- trac/perm.py	(revision 4233)
+++ trac/perm.py	(working copy)
@@ -18,6 +18,8 @@
 
 """Management of permissions."""
 
+import trac.l10n
+
 from trac.core import *
 
 
@@ -86,7 +88,7 @@
         """Grant the user with the given name permission to perform to specified
         action."""
         if action.isupper() and action not in self.get_actions():
-            raise TracError, '%s is not a valid action.' % action
+            raise TracError, _('%s is not a valid action.') % action
 
         self.store.grant_permission(username, action)
 
@@ -94,7 +96,7 @@
         """Revokes the permission of the specified user to perform an action."""
         # TODO: Validate that this permission does in fact exist
         if action.isupper() and action not in self.get_actions():
-            raise TracError, '%s is not a valid action.' % action
+            raise TracError, _('%s is not a valid action.') % action
 
         self.store.revoke_permission(username, action)
 
@@ -169,7 +171,7 @@
         for store in self.stores:
             if store.__class__.__name__ == selected_store:
                 return store
-        raise TracError, 'Invalid permission store "%s"' % selected_store
+        raise TracError, _('Invalid permission store "%s"') % selected_store
     store = property(fget=lambda self: self._get_store())
 
 
Index: trac/Search.py
===================================================================
--- trac/Search.py	(revision 4233)
+++ trac/Search.py	(working copy)
@@ -18,6 +18,8 @@
 import re
 import time
 
+import trac.l10n
+
 from trac.core import *
 from trac.perm import IPermissionRequestor
 from trac.util import TracError, escape, format_datetime
@@ -107,7 +109,7 @@
     def get_navigation_items(self, req):
         if not req.perm.has_permission('SEARCH_VIEW'):
             return
-        yield 'mainnav', 'search', '<a href="%s" accesskey="4">Search</a>' \
+        yield 'mainnav', 'search', _('<a href="%s" accesskey="4">Search</a>') \
               % (self.env.href.search())
 
     # IPermissionRequestor methods
@@ -149,9 +151,9 @@
                 query = query[1:]
             # Refuse queries that obviously would result in a huge result set
             if len(query) < 3 and len(query.split()) == 1:
-                raise TracError('Search query too short. '
-                                'Query must be at least 3 characters long.',
-                                'Search Error')
+                raise TracError(_('Search query too short. '
+                                'Query must be at least 3 characters long.'),
+                                _('Search Error'))
             results = []
             for source in self.search_sources:
                 results += list(source.get_search_results(req, query, filters))
@@ -161,7 +163,7 @@
             n_pages = n / page_size + 1
             results = results[(page-1) * page_size: page * page_size]
 
-            req.hdf['title'] = 'Search Results'
+            req.hdf['title'] = _('Search Results')
             req.hdf['search.q'] = req.args.get('q').replace('"', "&#34;")
             req.hdf['search.page'] = page
             req.hdf['search.n_hits'] = n
@@ -171,12 +173,12 @@
                 next_href = self.env.href.search(zip(filters,
                                                      ['on'] * len(filters)),
                                                  q=query, page=page + 1)
-                add_link(req, 'next', next_href, 'Next Page')
+                add_link(req, 'next', next_href, _('Next Page'))
             if page > 1:
                 prev_href = self.env.href.search(zip(filters,
                                                      ['on'] * len(filters)),
                                                  q=query, page=page - 1)
-                add_link(req, 'prev', prev_href, 'Previous Page')
+                add_link(req, 'prev', prev_href, _('Previous Page'))
             req.hdf['search.page_href'] = escape(
                 self.env.href.search(zip(filters, ['on'] * len(filters)),
                                      q=query))
Index: trac/Timeline.py
===================================================================
--- trac/Timeline.py	(revision 4233)
+++ trac/Timeline.py	(working copy)
@@ -20,6 +20,8 @@
 import re
 import time
 
+import trac.l10n
+
 from trac.core import *
 from trac.perm import IPermissionRequestor
 from trac.util import enum, escape, format_date, format_time, http_date
@@ -70,7 +72,7 @@
     def get_navigation_items(self, req):
         if not req.perm.has_permission('TIMELINE_VIEW'):
             return
-        yield 'mainnav', 'timeline', '<a href="%s" accesskey="2">Timeline</a>' \
+        yield 'mainnav', 'timeline', _('<a href="%s" accesskey="2">Timeline</a>') \
                                      % self.env.href.timeline()
 
     # IPermissionRequestor methods
@@ -140,7 +142,7 @@
         if maxrows and len(events) > maxrows:
             del events[maxrows:]
 
-        req.hdf['title'] = 'Timeline'
+        req.hdf['title'] = _('Timeline')
 
         # Get the email addresses of all known users
         email_map = {}
Index: trac/Notify.py
===================================================================
--- trac/Notify.py	(revision 4233)
+++ trac/Notify.py	(working copy)
@@ -14,6 +14,8 @@
 #
 # Author: Daniel Lundin <daniel@edgewall.com>
 
+import trac.l10n
+
 from trac.__init__ import __version__
 from trac.core import TracError
 from trac.util import CRLF, TRUE, FALSE, enum, wrap
@@ -96,11 +98,11 @@
         self.replyto_email = self.config.get('notification', 'smtp_replyto')
         self.from_email = self.from_email or self.replyto_email
         if not self.from_email and not self.replyto_email:
-            raise TracError('Unable to send email due to identity crisis. <br />'
+            raise TracError(_('Unable to send email due to identity crisis. <br />'
                             'Both <b>notification.from</b> and'
                             ' <b>notification.reply_to</b> are unspecified'
-                            ' in configuration.',
-                            'SMTP Notification Error')
+                            ' in configuration.'),
+                            _('SMTP Notification Error'))
 
         # Authentication info (optional)
         self.user_name = self.config.get('notification', 'smtp_user')
@@ -127,7 +129,7 @@
         msg = MIMEMultipart()
         msg.attach(MIMEText(body, 'plain', 'utf-8'))
         msg.epilogue = ''
-        msg['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
+        msg['X-Mailer'] = _('Trac %s, by Edgewall Software') % __version__
         msg['X-Trac-Version'] =  __version__
         projname = self.config.get('project','name')
         msg['X-Trac-Project'] =  projname
@@ -192,8 +194,8 @@
                     old_descr = wrap(old, self.COLS, '> ', '> ', CRLF)
                     old_descr = old_descr.replace(2*CRLF, CRLF + '>' + CRLF)
                     cdescr = CRLF
-                    cdescr += 'Old description:' + 2*CRLF + old_descr + 2*CRLF
-                    cdescr += 'New description:' + 2*CRLF + new_descr + CRLF
+                    cdescr += _('Old description:') + 2*CRLF + old_descr + 2*CRLF
+                    cdescr += _('New description:') + 2*CRLF + new_descr + CRLF
                     self.hdf['email.changes_descr'] = cdescr
                 else:
                     newv = new
Index: trac/About.py
===================================================================
--- trac/About.py	(revision 4233)
+++ trac/About.py	(working copy)
@@ -20,6 +20,8 @@
 from __future__ import generators
 import re
 
+import trac.l10n
+
 from trac.core import *
 from trac.perm import IPermissionRequestor
 from trac.web import IRequestHandler
@@ -31,7 +33,7 @@
 
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler)
 
-    about_cs = """
+    about_cs = _("""
 <?cs include "header.cs"?>
 <div id="ctxtnav" class="nav">
  <h2>About Navigation</h2>
@@ -131,7 +133,7 @@
  <?cs /if ?>
 </div>
 <?cs include "footer.cs"?>
-""" # about_cs
+""") # about_cs
 
     # INavigationContributor methods
 
@@ -139,7 +141,7 @@
         return 'about'
 
     def get_navigation_items(self, req):
-        yield 'metanav', 'about', '<a href="%s" accesskey="9">About Trac</a>' \
+        yield 'metanav', 'about', _('<a href="%s" accesskey="9">About Trac</a>') \
               % self.env.href.about()
 
     # IPermissionRequestor methods
@@ -158,7 +160,7 @@
 
     def process_request(self, req):
         page = req.args.get('page', 'default')
-        req.hdf['title'] = 'About Trac'
+        req.hdf['title'] = _('About Trac')
         if req.perm.has_permission('CONFIG_VIEW'):
             req.hdf['about.config_href'] = self.env.href.about('config')
             req.hdf['about.plugins_href'] = self.env.href.about('plugins')
Index: trac/wiki/web_ui.py
===================================================================
--- trac/wiki/web_ui.py	(revision 4233)
+++ trac/wiki/web_ui.py	(working copy)
@@ -20,6 +20,8 @@
 import re
 import StringIO
 
+import trac.l10n
+
 from trac.attachment import attachment_to_hdf, Attachment
 from trac.core import *
 from trac.perm import IPermissionRequestor
@@ -47,9 +49,9 @@
     def get_navigation_items(self, req):
         if not req.perm.has_permission('WIKI_VIEW'):
             return
-        yield 'metanav', 'help', '<a href="%s" accesskey="6">Help/Guide</a>' \
+        yield 'metanav', 'help', _('<a href="%s" accesskey="6">Help/Guide</a>') \
               % escape(self.env.href.wiki('TracGuide'))
-        yield 'mainnav', 'wiki', '<a href="%s" accesskey="1">Wiki</a>' \
+        yield 'mainnav', 'wiki', _('<a href="%s" accesskey="1">Wiki</a>') \
               % escape(self.env.href.wiki())
 
     # IPermissionRequestor methods
@@ -122,7 +124,7 @@
 
     def get_timeline_filters(self, req):
         if req.perm.has_permission('WIKI_VIEW'):
-            yield ('wiki', 'Wiki changes')
+            yield ('wiki', _('Wiki changes'))
 
     def get_timeline_events(self, req, start, stop, filters):
         if 'wiki' in filters:
@@ -133,7 +135,7 @@
                            "FROM wiki WHERE time>=%s AND time<=%s",
                            (start, stop))
             for t,name,comment,author in cursor:
-                title = '<em>%s</em> edited by %s' % (
+                title = _('<em>%s</em> edited by %s') % (
                         escape(name), escape(author))
                 if format == 'rss':
                     href = self.env.abs_href.wiki(name)
@@ -211,7 +213,7 @@
         req.perm.assert_permission('WIKI_VIEW')
 
         if not page.exists:
-            raise TracError, "Version %s of page %s does not exist" \
+            raise TracError, _("Version %s of page %s does not exist") \
                              % (req.args.get('version'), page.name)
 
         add_stylesheet(req, 'common/css/diff.css')
@@ -293,7 +295,7 @@
         else:
             editrows = req.session.get('wiki_editrows', '20')
 
-        req.hdf['title'] = escape(page.name) + ' (edit)'
+        req.hdf['title'] = escape(page.name) + _(' (edit)')
         info = {
             'page_source': escape(page.text),
             'version': page.version,
@@ -321,9 +323,9 @@
         req.perm.assert_permission('WIKI_VIEW')
 
         if not page.exists:
-            raise TracError, "Page %s does not exist" % page.name
+            raise TracError, _("Page %s does not exist") % page.name
 
-        req.hdf['title'] = escape(page.name) + ' (history)'
+        req.hdf['title'] = escape(page.name) + _(' (history)')
 
         history = []
         for version, t, author, comment, ipnr in page.get_history():
@@ -355,7 +357,7 @@
             req.hdf['html.norobots'] = 1
 
         txt_href = self.env.href.wiki(page.name, version=version, format='txt')
-        add_link(req, 'alternate', txt_href, 'Plain Text', 'text/plain')
+        add_link(req, 'alternate', txt_href, _('Plain Text'), 'text/plain')
 
         req.hdf['wiki'] = {'page_name': page.name, 'exists': page.exists,
                            'version': page.version, 'readonly': page.readonly}
@@ -365,8 +367,8 @@
             req.hdf['wiki.history_href'] = escape(history_href)
         else:
             if not req.perm.has_permission('WIKI_CREATE'):
-                raise TracError('Page %s not found' % page.name)
-            req.hdf['wiki.page_html'] = '<p>Describe "%s" here</p>' % page.name
+                raise TracError(_('Page %s not found') % page.name)
+            req.hdf['wiki.page_html'] = _('<p>Describe "%s" here</p>') % page.name
 
         # Show attachments
         attachments = []
Index: trac/wiki/tests/formatter.py
===================================================================
--- trac/wiki/tests/formatter.py	(revision 4233)
+++ trac/wiki/tests/formatter.py	(working copy)
@@ -4,6 +4,8 @@
 import StringIO
 import unittest
 
+import trac.l10n
+
 from trac.core import *
 from trac.wiki.formatter import Formatter, OneLinerFormatter
 from trac.wiki.api import IWikiMacroProvider
@@ -83,8 +85,8 @@
         try:
             self.assertEquals(self.correct, v)
         except AssertionError, e:
-            raise AssertionError('%s\n\n%s:%s: for the input '
-                                 '(formatter flavor was "%s")' \
+            raise AssertionError(_('%s\n\n%s:%s: for the input '
+                                 '(formatter flavor was "%s")') \
                                  % (str(e), self.file, self.line,
                                     formatter.flavor))
         
Index: trac/wiki/model.py
===================================================================
--- trac/wiki/model.py	(revision 4233)
+++ trac/wiki/model.py	(working copy)
@@ -19,6 +19,8 @@
 from __future__ import generators
 import time
 
+import trac.l10n
+
 from trac.core import *
 from trac.wiki.api import WikiSystem
 
@@ -122,7 +124,7 @@
             cursor.execute("UPDATE wiki SET readonly=%s WHERE name=%s",
                            (self.readonly, self.name))
         else:
-            raise TracError('Page not modified')
+            raise TracError(_('Page not modified'))
 
         if handle_ta:
             db.commit()
Index: trac/wiki/macros.py
===================================================================
--- trac/wiki/macros.py	(revision 4233)
+++ trac/wiki/macros.py	(working copy)
@@ -25,6 +25,8 @@
 except ImportError:
     from StringIO import StringIO
 
+import trac.l10n
+
 from trac.config import default_dir
 from trac.core import *
 from trac.util import escape, format_date
@@ -258,7 +260,7 @@
         # we expect the 1st argument to be a filename (filespec)
         args = content.split(',')
         if len(args) == 0:
-            raise Exception("No argument.")
+            raise Exception(_("No argument."))
         filespec = args[0]
         size_re = re.compile('^[0-9]+%?$')
         align_re = re.compile('^(?:left|right|top|bottom)$')
@@ -300,7 +302,7 @@
             if parts[0] in ['wiki', 'ticket']:
                 module, id, file = parts
             else:
-                raise Exception("%s module can't have attachments" % parts[0])
+                raise Exception(_("%s module can't have attachments") % parts[0])
         elif len(parts) == 2:
             from trac.versioncontrol.web_ui import BrowserModule
             try:
@@ -336,9 +338,9 @@
             if len(path_info) > 2:
                 id = path_info[2]
             if module not in ['wiki', 'ticket']:
-                raise Exception('Cannot reference local attachment from here')
+                raise Exception(_('Cannot reference local attachment from here'))
         else:
-            raise Exception('No filespec given')
+            raise Exception(_('No filespec given'))
         if not url: # this is an attachment
             from trac.attachment import Attachment
             attachment = Attachment(self.env, module, id, file)
@@ -445,4 +447,4 @@
             macro_file = os.path.join(path, name + '.py')
             if os.path.isfile(macro_file):
                 return imp.load_source(name, macro_file)
-        raise TracError, 'Macro %s not found' % name
+        raise TracError, _('Macro %s not found') % name
Index: trac/util.py
===================================================================
--- trac/util.py	(revision 4233)
+++ trac/util.py	(working copy)
@@ -23,6 +23,8 @@
 import re
 import md5
 
+import trac.l10n
+
 TRUE =  ['yes', '1', 1, 'true',  'on',  'aye']
 FALSE = ['no',  '0', 0, 'false', 'off', 'nay']
 
@@ -121,7 +123,7 @@
 
     jump = 512
     if size < jump:
-        return '%d bytes' % size
+        return _('%d bytes') % size
 
     units = ['kB', 'MB', 'GB', 'TB']
     i = 0
@@ -138,20 +140,20 @@
     if not time2: time2 = time.time()
     if time1 > time2:
         time2, time1 = time1, time2
-    units = ((3600 * 24 * 365, 'year',   'years'),
-             (3600 * 24 * 30,  'month',  'months'),
-             (3600 * 24 * 7,   'week',   'weeks'),
-             (3600 * 24,       'day',    'days'),
-             (3600,            'hour',   'hours'),
-             (60,              'minute', 'minutes'))
+    units = ((3600 * 24 * 365, _('year'),   _('years')),
+             (3600 * 24 * 30,  _('month'),  _('months')),
+             (3600 * 24 * 7,   _('week'),   _('weeks')),
+             (3600 * 24,       _('day'),    _('days')),
+             (3600,            _('hour'),   _('hours')),
+             (60,              _('minute'), _('minutes')))
     age_s = int(time2 - time1)
     if age_s < 60:
-        return '%i second%s' % (age_s, age_s != 1 and 's' or '')
+        return _('%i second%s') % (age_s, age_s != 1 and 's' or '')
     for u, unit, unit_plural in units:
         r = float(age_s) / float(u)
         if r >= 0.9:
             r = int(round(r))
-            return '%d %s' % (r, r == 1 and unit or unit_plural)
+            return '%d %s' % (r, r == 1 and _(unit) or _(unit_plural))
     return ''
 
 def create_unique_file(path):
@@ -168,7 +170,7 @@
             idx += 1
             # A sanity check
             if idx > 100:
-                raise Exception('Failed to create unique name: ' + path)
+                raise Exception(_('Failed to create unique name: ') + path)
             path = '%s.%d%s' % (parts[0], idx, parts[1])
 
 def get_reporter_id(req):
@@ -226,9 +228,9 @@
         t = time.time()
     if not isinstance(t, (list, tuple, time.struct_time)):
         t = time.gmtime(int(t))
-    weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
-              'Oct', 'Nov', 'Dec']
+    weekdays = [_('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat'), _('Sun')]
+    months = [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'),
+              _('Oct'), _('Nov'), _('Dec')]
     return '%s, %d %s %04d %02d:%02d:%02d GMT' % (
            weekdays[t.tm_wday], t.tm_mday, months[t.tm_mon - 1], t.tm_year,
            t.tm_hour, t.tm_min, t.tm_sec)
@@ -245,7 +247,7 @@
         except ValueError:
             continue
     if seconds == None:
-        raise ValueError, '%s is not a known date format.' % text
+        raise ValueError, _('%s is not a known date format.') % text
     return seconds
 
 
Index: trac/db.py
===================================================================
--- trac/db.py	(revision 4233)
+++ trac/db.py	(working copy)
@@ -26,6 +26,7 @@
     threading._get_ident = lambda: 0
 import weakref
 
+import trac.l10n
 from trac.core import TracError
 
 __all__ = ['get_cnx_pool', 'init_db']
@@ -161,8 +162,8 @@
                     if timeout:
                         self._available.wait(timeout)
                         if (time.time() - start) >= timeout:
-                            raise TimeoutError, 'Unable to get database ' \
-                                                'connection within %d seconds' \
+                            raise TimeoutError, _('Unable to get database ' \
+                                                'connection within %d seconds') \
                                                 % timeout
                     else:
                         self._available.wait()
@@ -257,15 +258,15 @@
         self.cnx = None
         if path != ':memory:':
             if not os.access(path, os.F_OK):
-                raise TracError, 'Database "%s" not found.' % path
+                raise TracError, _('Database "%s" not found.') % path
 
             dbdir = os.path.dirname(path)
             if not os.access(path, os.R_OK + os.W_OK) or \
                    not os.access(dbdir, os.R_OK + os.W_OK):
                 from getpass import getuser
-                raise TracError, 'The user %s requires read _and_ write ' \
+                raise TracError, _('The user %s requires read _and_ write ' \
                                  'permission to the database file %s and the ' \
-                                 'directory it is located in.' \
+                                 'directory it is located in.') \
                                  % (getuser(), path)
 
         if have_pysqlite == 2:
@@ -316,7 +317,7 @@
         if path != ':memory:':
             # make the directory to hold the database
             if os.path.exists(path):
-                raise TracError, 'Database already exists at %s' % path
+                raise TracError, _('Database already exists at %s') % path
             os.makedirs(os.path.split(path)[0])
         cnx = sqlite.connect(path, timeout=int(params.get('timeout', 10000)))
         cursor = cnx.cursor()
@@ -446,7 +447,7 @@
 def _get_cnx_class(env_path, db_str):
     scheme, args = _parse_db_str(db_str)
     if not scheme in _cnx_map:
-        raise TracError, 'Unsupported database type "%s"' % scheme
+        raise TracError, _('Unsupported database type "%s"') % scheme
 
     if scheme == 'sqlite':
         # Special case for SQLite to support a path relative to the
@@ -465,8 +466,8 @@
             host = None
             path = rest
         else:
-            raise TracError, 'Database connection string %s must start with ' \
-                             'scheme:/' % db_str
+            raise TracError, _('Database connection string %s must start with ' \
+                             'scheme:/') % db_str
     else:
         if rest.startswith('/') and not rest.startswith('//'):
             host = None
Index: trac/web/standalone.py
===================================================================
--- trac/web/standalone.py	(revision 4233)
+++ trac/web/standalone.py	(working copy)
@@ -19,6 +19,8 @@
 # Todo:
 # - External auth using mod_proxy / squid.
 
+import trac.l10n
+
 from trac import util, __version__
 from trac.env import open_environment
 from trac.web.api import Request
@@ -291,8 +293,8 @@
         if path_info == '/login':
             auth = self.server.auths.get(project_name)
             if not auth:
-                raise util.TracError('Authentication not enabled. '
-                                     'Please use the tracd --auth option.\n')
+                raise util.TracError(_('Authentication not enabled. '
+                                     'Please use the tracd --auth option.\n'))
             req.remote_user = auth.do_auth(self)
             if not req.remote_user:
                 return
Index: trac/web/clearsilver.py
===================================================================
--- trac/web/clearsilver.py	(revision 4233)
+++ trac/web/clearsilver.py	(working copy)
@@ -14,6 +14,7 @@
 #
 # Author: Christopher Lenz <cmlenz@gmx.de>
 
+import trac.l10n
 from trac.util import enum, TracError
 
 
@@ -128,7 +129,7 @@
             import neo_util
             self.hdf = neo_util.HDF()
         except ImportError, e:
-            raise TracError, "ClearSilver not installed (%s)" % e
+            raise TracError, _("ClearSilver not installed (%s)") % e
         
         self['hdf.loadpaths'] = loadpaths
 
Index: trac/web/chrome.py
===================================================================
--- trac/web/chrome.py	(revision 4233)
+++ trac/web/chrome.py	(working copy)
@@ -18,6 +18,8 @@
 import os.path
 import re
 
+import trac.l10n
+
 from trac import mimeview, util
 from trac.core import *
 from trac.env import IEnvironmentSetupParticipant
@@ -173,7 +175,7 @@
 
         # FIXME: Should return a 404 error
         self.log.warning('File %s not found in any of %s', filename, dirs)
-        raise TracError, 'File not found'
+        raise TracError, _('File not found')
 
     # ITemplateProvider methods
 
Index: trac/web/modpython_frontend.py
===================================================================
--- trac/web/modpython_frontend.py	(revision 4233)
+++ trac/web/modpython_frontend.py	(working copy)
@@ -28,12 +28,13 @@
 
 from mod_python import apache, util
 
+import trac.l10n
+
 from trac.util import http_date
 from trac.web.api import Request, RequestDone
 from trac.web.main import dispatch_request, get_environment, \
                           send_pretty_error, send_project_index
 
-
 class ModPythonRequest(Request):
 
     idx_location = None
@@ -62,7 +63,7 @@
             root_uri = options['TracUriRoot'].rstrip('/')
             if self.req.uri[:len(root_uri)] != root_uri:
                 raise ValueError, \
-                     'TracRootUri set to %s but request URL starts with %s' \
+                     _('TracRootUri set to %s but request URL starts with %s') \
                      % (root_uri, self.req.uri[:len(root_uri)])
             self.path_info = self.req.uri[len(root_uri):]
         else:
@@ -179,10 +180,12 @@
             result[dest] = orig[src]
     return result
 
+import os
 def handler(req):
     options = req.get_options()
     if options.has_key('TracLocale'):
         locale.setlocale(locale.LC_ALL, options['TracLocale'])
+        trac.l10n.updateLocale()
     else:
         locale.setlocale(locale.LC_ALL, '')
 
Index: trac/web/auth.py
===================================================================
--- trac/web/auth.py	(revision 4233)
+++ trac/web/auth.py	(working copy)
@@ -18,6 +18,8 @@
 import re
 import time
 
+import trac.l10n
+
 from trac.core import *
 from trac.web.api import IAuthenticator, IRequestHandler
 from trac.web.chrome import INavigationContributor
@@ -64,11 +66,11 @@
 
     def get_navigation_items(self, req):
         if req.authname and req.authname != 'anonymous':
-            yield 'metanav', 'login', 'logged in as %s' % escape(req.authname)
-            yield 'metanav', 'logout', '<a href="%s">Logout</a>' \
+            yield 'metanav', 'login', _('logged in as %s') % escape(req.authname)
+            yield 'metanav', 'logout', _('<a href="%s">Logout</a>') \
                   % escape(self.env.href.logout())
         else:
-            yield 'metanav', 'login', '<a href="%s">Login</a>' \
+            yield 'metanav', 'login', _('<a href="%s">Login</a>') \
                   % escape(self.env.href.login())
 
     # IRequestHandler methods
Index: trac/web/session.py
===================================================================
--- trac/web/session.py	(revision 4233)
+++ trac/web/session.py	(working copy)
@@ -16,6 +16,8 @@
 # Author: Daniel Lundin <daniel@edgewall.com>
 #         Christopher Lenz <cmlenz@gmx.de>
 
+import trac.l10n
+
 from trac.util import hex_entropy, TracError
 
 import time
@@ -86,9 +88,9 @@
         cursor.execute("SELECT sid FROM session WHERE sid=%s "
                        "AND authenticated=0", (new_sid,))
         if cursor.fetchone():
-            raise TracError("Session '%s' already exists.<br />"
-                            "Please choose a different session id." % new_sid,
-                            "Error renaming session")
+            raise TracError(_("Session '%s' already exists.<br />"
+                            "Please choose a different session id.") % new_sid,
+                            _("Error renaming session"))
         self.env.log.debug('Changing session ID %s to %s' % (self.sid, new_sid))
         cursor.execute("UPDATE session SET sid=%s WHERE sid=%s "
                        "AND authenticated=0", (new_sid, self.sid))
Index: trac/web/main.py
===================================================================
--- trac/web/main.py	(revision 4233)
+++ trac/web/main.py	(working copy)
@@ -18,6 +18,8 @@
 
 import os
 
+import trac.l10n
+
 from trac.core import *
 from trac.env import open_environment
 from trac.perm import PermissionCache, PermissionError
@@ -101,7 +103,7 @@
 
         if not chosen_handler:
             # FIXME: Should return '404 Not Found' to the client
-            raise TracError, 'No handler matched request to %s' % req.path_info
+            raise TracError, _('No handler matched request to %s') % req.path_info
 
         try:
             resp = chosen_handler.process_request(req)
@@ -175,9 +177,9 @@
         'name_encoded': escape(env.config.get('project', 'name')),
         'descr': env.config.get('project', 'descr'),
         'footer': env.config.get('project', 'footer',
-                 'Visit the Trac open source project at<br />'
-                 '<a href="http://trac.edgewall.com/">'
-                 'http://trac.edgewall.com/</a>'),
+                 _('Visit the Trac open source project at<br />'
+                   '<a href="http://trac.edgewall.com/">'
+                   'http://trac.edgewall.com/</a>')),
         'url': env.config.get('project', 'url')
     }
 
@@ -218,8 +220,8 @@
         populate_hdf(req.hdf, env, req)
 
         if isinstance(e, TracError):
-            req.hdf['title'] = e.title or 'Error'
-            req.hdf['error.title'] = e.title or 'Error'
+            req.hdf['title'] = e.title or _('Error')
+            req.hdf['error.title'] = e.title or _('Error')
             req.hdf['error.type'] = 'TracError'
             req.hdf['error.message'] = e.message
             if e.show_traceback:
@@ -227,14 +229,14 @@
             req.display('error.cs', response=500)
 
         elif isinstance(e, PermissionError):
-            req.hdf['title'] = 'Permission Denied'
+            req.hdf['title'] = _('Permission Denied')
             req.hdf['error.type'] = 'permission'
             req.hdf['error.action'] = e.action
             req.hdf['error.message'] = e
             req.display('error.cs', response=403)
 
         else:
-            req.hdf['title'] = 'Oops'
+            req.hdf['title'] = _('Oops')
             req.hdf['error.type'] = 'internal'
             req.hdf['error.message'] = escape(str(e))
             req.hdf['error.traceback'] = escape(tb.getvalue())
@@ -250,7 +252,7 @@
             req.send_response(500)
             req.send_header('Content-Type', 'text/plain')
             req.end_headers()
-            req.write('Oops...\n\nTrac detected an internal error:\n\n')
+            req.write(_('Oops...\n\nTrac detected an internal error:\n\n'))
             req.write(str(e))
             req.write('\n')
             req.write(tb.getvalue())
@@ -274,7 +276,7 @@
                 req.hdf[key] = val
     else:
         req.hdf = HDFWrapper()
-        template = req.hdf.parse('''<html>
+        template = req.hdf.parse(_('''<html>
 <head><title>Available Projects</title></head>
 <body><h1>Available Projects</h1><ul><?cs
  each:project = projects ?><li><?cs
@@ -287,7 +289,7 @@
   /if ?>
   </li><?cs
  /each ?></ul></body>
-</html>''')
+</html>'''))
 
     if not env_paths and 'TRAC_ENV_PARENT_DIR' in options:
         dir = options['TRAC_ENV_PARENT_DIR']
@@ -330,9 +332,9 @@
             return None
     else:
         raise TracError, \
-              'The environment options "TRAC_ENV" or "TRAC_ENV_PARENT_DIR" ' \
+              _('The environment options "TRAC_ENV" or "TRAC_ENV_PARENT_DIR" ' \
               'or the mod_python options "TracEnv" or "TracEnvParentDir" ' \
               'are missing.  Trac requires one of these options to locate ' \
-              'the Trac environment(s).'
+              'the Trac environment(s).')
 
     return _open_environment(env_path, threaded)

