diff --git a/talm_importer/importer.py b/talm_importer/importer.py
index fc4e149..f8565d1 100644
--- a/talm_importer/importer.py
+++ b/talm_importer/importer.py
@@ -5,6 +5,7 @@
 # Licensed under the same license as Trac - http://trac.edgewall.org/wiki/TracLicense
 #
 
+import encodings
 import os
 import re
 import shutil
@@ -15,10 +16,12 @@ import unicodedata
 from trac.core import *
 from trac.attachment import AttachmentModule
 from trac.core import Component
+from trac.config import Option
 from trac.perm import IPermissionRequestor
 from trac.ticket import TicketSystem
 from trac.ticket import model
 from trac.util import get_reporter_id
+from trac.util.compat import set, sorted
 from trac.util.html import html
 from trac.web import IRequestHandler
 from trac.web.chrome import INavigationContributor, ITemplateProvider
@@ -29,6 +32,8 @@ from talm_importer.readers import get_reader
 
 
 class ImportModule(Component):
+    csv_default_encoding = Option('importer', 'csv_default_encoding', 'utf-8',
+        doc="Default encoding of CSV file")
 
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler, ITemplateProvider)
 
@@ -61,26 +66,32 @@ class ImportModule(Component):
             req.redirect(req.href('importer'))
                 
         if action == 'upload' and req.method == 'POST':
-            req.session['uploadedfile'] = None
+            req.session['importer.uploadedfile'] = None
             uploadedfilename, uploadedfile = self._save_uploaded_file(req)
-            req.session['sheet'] = req.args['sheet']
-            req.session['uploadedfile'] = uploadedfile
-            req.session['uploadedfilename'] = uploadedfilename
-            req.session['tickettime'] = str(int(time.time()))
-            return self._do_preview(uploadedfile, int(req.session['sheet']), req)
+            req.session['importer.sheet'] = req.args['sheet']
+            req.session['importer.encoding'] = req.args['encoding']
+            req.session['importer.uploadedfile'] = uploadedfile
+            req.session['importer.uploadedfilename'] = uploadedfilename
+            req.session['importer.tickettime'] = str(int(time.time()))
+            return self._do_preview(uploadedfile, int(req.session['importer.sheet']), req,
+                                    encoding=req.session['importer.encoding'])
         elif action == 'import' and req.method == 'POST':
-            tickettime = int(req.session['tickettime'])
+            tickettime = int(req.session['importer.tickettime'])
             if tickettime == 0:
                 raise TracError('No time set since preview, unable to import: please upload the file again')
 
-            return self._do_import(req.session['uploadedfile'], int(req.session['sheet']), req, req.session['uploadedfilename'], tickettime)
+            return self._do_import(req.session['importer.uploadedfile'], int(req.session['importer.sheet']),
+                                   req, req.session['importer.uploadedfilename'], tickettime,
+                                   encoding=req.session['importer.encoding'])
             
         else:
-            req.session['uploadedfile'] = None
-            req.session['uploadedfilename'] = None
+            req.session['importer.uploadedfile'] = None
+            req.session['importer.uploadedfilename'] = None
 
             data = { 'reconciliate_by_owner_also': self._reconciliate_by_owner_also(),
-                     'fields': ['ticket or id'] + [field['name'] for field in TicketSystem(self.env).get_ticket_fields()] }
+                     'fields': ['ticket or id'] + [field['name'] for field in TicketSystem(self.env).get_ticket_fields()],
+                     'csv_default_encoding': self.csv_default_encoding,
+                     'encodings': self._get_encodings() }
 
             return 'importer.html', data, None
 
@@ -104,15 +115,15 @@ class ImportModule(Component):
     def _datetime_format(self):
         return str(self.config.get('importer', 'datetime_format', '%x')) # default is Locale's appropriate date representation
 
-    def _do_preview(self, uploadedfile, sheet, req):
-        filereader = get_reader(uploadedfile, sheet, self._datetime_format())
+    def _do_preview(self, uploadedfile, sheet, req, encoding=None):
+        filereader = get_reader(uploadedfile, sheet, self._datetime_format(), encoding=encoding)
         try:
             return self._process(filereader, get_reporter_id(req), PreviewProcessor(self.env, req))
         finally:
             filereader.close()
 
-    def _do_import(self, uploadedfile, sheet, req, uploadedfilename, tickettime):
-        filereader = get_reader(uploadedfile, sheet, self._datetime_format())
+    def _do_import(self, uploadedfile, sheet, req, uploadedfilename, tickettime, encoding=None):
+        filereader = get_reader(uploadedfile, sheet, self._datetime_format(), encoding=encoding)
         try:
             try:
                 return self._process(filereader, get_reporter_id(req), ImportProcessor(self.env, req, uploadedfilename, tickettime))
@@ -463,6 +474,13 @@ class ImportModule(Component):
         if not row:
             raise TracError('Ticket %s found in file, but not present in Trac: cannot import.' % str(ticket_id))
         return row[0]
+
+    def _get_encodings(self):
+        encs = set(encodings.aliases.aliases.values())
+        for enc in ('base64_codec', 'bz2_codec', 'hex_codec', 'quopri_codec',
+                    'rot_13', 'uu_codec', 'zlib_codec'):
+            encs.discard(enc)
+        return sorted(list(enc.replace('_', '-') for enc in encs))
         
 if __name__ == '__main__': 
     import doctest
diff --git a/talm_importer/processors.py b/talm_importer/processors.py
index 23ac4a8..389c347 100644
--- a/talm_importer/processors.py
+++ b/talm_importer/processors.py
@@ -10,6 +10,7 @@ from trac.ticket import Ticket, model
 from trac.util import get_reporter_id
 from trac.util.datefmt import format_datetime
 from trac.util.html import Markup
+from trac.util.text import to_unicode
 from trac.wiki import wiki_to_html
 
 
@@ -176,7 +177,7 @@ class PreviewProcessor(object):
     def start(self, importedfields, reconciliate_by_owner_also, has_comments):
         self.data['title'] = 'Preview Import'
 
-        self.message = ''
+        self.message = u''
 
         if 'ticket' in [f.lower() for f in importedfields]:
             self.message += ' * A \'\'\'ticket\'\'\' column was found: Existing tickets will be updated with the values from the file. Values that are changing appear in italics in the preview below.\n' 
@@ -206,19 +207,21 @@ class PreviewProcessor(object):
         self.message += ' * Some Trac fields are not present in the import. They will default to:\n\n'
         self.message += "   ||'''field'''||'''Default value'''||\n"
         if missingemptyfields != []:
-            self.message += '   ||' + ', '.join([x.capitalize() for x in missingemptyfields]) + '||' + "''(Empty value)''" + '||\n'
+            self.message += u"   ||%s||''(Empty value)''||\n" \
+                            % u', '.join([to_unicode(x.capitalize()) for x in missingemptyfields])
             
         if missingdefaultedfields != []:
             for f in missingdefaultedfields:
-                self.message += '   ||' + f.capitalize() + '||' + str(computedfields[f]['value']) + '||\n'
+                self.message += u'   ||%s||%s||\n' % (to_unicode(f.capitalize()), computedfields[f]['value'])
 
         self.message += '(You can change some of these default values in the Trac Admin module, if you are administrator; or you can add the corresponding column to your spreadsheet and re-upload it).\n'
 
     def process_notimported_fields(self, notimportedfields):
-        self.message += ' * Some fields will not be imported because they don\'t exist in Trac: ' + ', '.join([x and x or "''(empty name)''" for x in notimportedfields])  + '.\n'
+        self.message += u' * Some fields will not be imported because they don\'t exist in Trac: %s.\n' \
+                        % u', '.join([x and to_unicode(x) or u"''(empty name)''" for x in notimportedfields])
 
     def process_comment_field(self, comment):
-        self.message += ' * The field "%s" will be used as comment when modifying tickets, and appended to the description for new tickets.\n' % comment
+        self.message += u' * The field "%s" will be used as comment when modifying tickets, and appended to the description for new tickets.\n' % comment
 
     def start_process_row(self, row_idx, ticket_id):
         from ticket import PatchedTicket
@@ -263,22 +266,23 @@ class PreviewProcessor(object):
     def process_new_lookups(self, newvalues):
         if 'status' in newvalues:
             if len(newvalues['status']) > 1:
-                msg = ' * Some values for the "Status" field do not exist: %s. They will be imported, but will result in invalid status.\n\n'
+                msg = u' * Some values for the "Status" field do not exist: %s. They will be imported, but will result in invalid status.\n\n'
             else:
-                msg = ' * A value for the "Status" field does not exist: %s. It will be imported, but will result in an invalid status.\n\n'
+                msg = u' * A value for the "Status" field does not exist: %s. It will be imported, but will result in an invalid status.\n\n'
                 
-            self.message += (msg % ','.join(newvalues['status']))
+            self.message += (msg % u','.join(newvalues['status']))
             del newvalues['status']
             
         if newvalues:
             self.message += ' * Some lookup values are not found and will be added to the possible list of values:\n\n'
             self.message += "   ||'''field'''||'''New values'''||\n"
             for field, values in newvalues.iteritems():                
-                self.message += "   ||" + field.capitalize() + "||" + ', '.join(values) + "||\n"
+                self.message += u"   ||%s||%s||\n" % (to_unicode(field.capitalize()), u', '.join(values))
             
 
     def process_new_users(self, newusers):
-        self.message += ' * Some user names do not exist in the system: ' + ', '.join(newusers) + '. Make sure that they are valid users.\n'
+        self.message += u' * Some user names do not exist in the system: %s. Make sure that they are valid users.\n' % (u', '.join(newusers))
+
             
     def end_process(self, numrows):
         self.message = 'Scroll to see a preview of the tickets as they will be imported. If the data is correct, select the \'\'\'Execute Import\'\'\' button.\n' + ' * ' + str(numrows) + ' tickets will be imported (' + str(self.added) + ' added, ' + str(self.modifiedcount) + ' modified, ' + str(self.notmodifiedcount) + ' unchanged).\n' + self.message
diff --git a/talm_importer/readers.py b/talm_importer/readers.py
index 8693f1c..9c38f72 100644
--- a/talm_importer/readers.py
+++ b/talm_importer/readers.py
@@ -4,64 +4,90 @@
 # Licensed under the same license as Trac - http://trac.edgewall.org/wiki/TracLicense
 #
 
+import codecs
 import csv
 import datetime
+try:
+    import xlrd
+except ImportError:
+    xlrd = None
 
 from trac.core import TracError
 
 
-def get_reader(filename, sheet_index, datetime_format):
+def get_reader(filename, sheet_index, datetime_format, encoding='utf-8'):
     # NOTE THAT the sheet index is 1-based !
     # KISS - keep it simple: if it can be opened as XLS, do, otherwise try as CSV.
-    try:
-        return XLSReader(filename, sheet_index, datetime_format)
-    except ImportError:
-        try:
-            return CSVReader(filename)
-        except:
-            raise TracError('XLS reading is not configured, and this file is not a valid CSV file: unable to read file.')
-    except IndexError:
-            raise TracError('The sheet index (' + str(sheet_index) + ') does not seem to correspond to an existing sheet in the spreadsheet')
-    except Exception, e:
+    if xlrd:
         try:
-            return CSVReader(filename)
-        except:
-            raise TracError('Unable to read this file, does not seem to be a valid Excel or CSV file.')
+            return XLSReader(filename, sheet_index, datetime_format)
+        except IndexError:
+            raise TracError('The sheet index (%s) does not seem to correspond to an existing sheet in the spreadsheet'
+                            % sheet_index)
+        except Exception:
+            pass
+
+    try:
+        return CSVReader(filename, encoding)
+    except UnicodeDecodeError:
+        raise TracError('Unable to read the CSV file with "%s"' % encoding)
+    except:
+        if xlrd:
+            message = 'Unable to read this file, does not seem to be a valid Excel or CSV file.'
+        else:
+            message = 'XLS reading is not configured, and this file is not a valid CSV file: unable to read file.'
+        raise TracError(message)
+
+class UTF8Reader(object):
+    def __init__(self, file, encoding):
+        self.reader = codecs.getreader(encoding)(file, 'replace')
 
+    def __iter__(self):
+        return self
+
+    def next(self):
+        return self.reader.next().encode("utf-8")
+
+class CSVDictReader(csv.DictReader):
+    def __init__(self, reader, fieldnames):
+        csv.DictReader.__init__(self, reader, fieldnames)
 
-class CP1252DictReader(csv.DictReader):
     def next(self):
         d = csv.DictReader.next(self)
-        return dict([(key, value.decode('cp1252', 'replace')) for key, value in d.iteritems()])
+        def to_unicode(arg):
+            return arg and arg.decode('utf-8')
+        return dict((to_unicode(key), to_unicode(value)) for key, value in d.iteritems())
 
 class CSVReader(object):
-    def __init__(self, filename):
-        self.file =  open(filename, "rb")
-        reader = csv.reader(self.file)
-        self.csvfields = reader.next()
-            
-    def get_sheet_count():
+    def __init__(self, filename, encoding):
+        self.file = open(filename, 'rb')
+        self.file_reader = UTF8Reader(self.file, encoding)
+        self.csv_reader = csv.reader(self.file_reader)
+        self.csvfields = self.csv_reader.next()
+        if self.csvfields and self.csvfields[0].startswith('\xEF\xBB\xBF'):
+            # Skip BOM
+            self.csvfields[0] = self.csvfields[0][3:]
+        
+    def get_sheet_count(self):
         return 1
         
     def readers(self):
-        return self.csvfields, CP1252DictReader(self.file, self.csvfields)
+        return self.csvfields, CSVDictReader(self.file_reader, self.csvfields)
             
     def close(self):
         self.file.close()
 
 class XLSReader(object):
     def __init__(self, filename, sheet_index, datetime_format):
-        import xlrd
         self.book = xlrd.open_workbook(filename)
         self.sheetcount = self.book.nsheets
         self.sh = self.book.sheet_by_index(sheet_index - 1)
         self._datetime_format = datetime_format
 
-    def get_sheet_count():
+    def get_sheet_count(self):
         return self.sheetcount
         
     def readers(self):
-        import xlrd
         # TODO: do something with sh.name. Probably add it as a column. 
         # TODO: read the other sheets. What if they don't have the same columns ?
         header = []
diff --git a/talm_importer/templates/importer.html b/talm_importer/templates/importer.html
index 5b43225..f19a568 100644
--- a/talm_importer/templates/importer.html
+++ b/talm_importer/templates/importer.html
@@ -67,21 +67,25 @@ If the spreadsheet contains a Comment column, this column will be added as comme
      <label for="import-file">File to import:</label>
        <input type="file" name="import-file" />
    </div>
-   <br/>
    <div class="field">
-     <label for="sheet">For an Excel spreadsheet, index of the worksheet to import:</label>
-       <select name="sheet">
-       <option value="1">1</option>
-       <option value="2">2</option>
-       <option value="3">3</option>
-       <option value="4">4</option>
-       <option value="5">5</option>
-       <option value="6">6</option>
-       <option value="7">7</option>
-       <option value="8">8</option>
-       <option value="9">9</option>
+     <label for="importer-sheet">For an Excel spreadsheet, index of the worksheet to import:
+       <select id="importer-sheet" name="sheet">
+         <option py:for="i in xrange(1, 10)" value="$i">$i</option>
        </select>
-       <i>Note: worksheets containing non-data (e.g., charts) are skipped when counting the worksheets.</i>
+     </label>
+     <br />
+     <span class="hint">
+       <strong>Note:</strong> worksheets containing non-data (e.g., charts) are skipped when counting the worksheets.
+     </span>
+   </div>
+   <div class="field">
+     <label for="importer-encoding">For a CSV file, select charset encoding of the file:
+       <select id="importer-encoding" name="encoding">
+         <option py:for="encoding in encodings"
+                 selected="${encoding == csv_default_encoding and 'selected'}"
+           >$encoding</option>
+       </select>
+     </label>
    </div>
   <div class="buttons">
    <input type="hidden" name="action" value="upload" />
diff --git a/talm_importer/test.py b/talm_importer/test.py
index 9ada2e2..f60631d 100644
--- a/talm_importer/test.py
+++ b/talm_importer/test.py
@@ -44,7 +44,7 @@ class ImporterTestCase(unittest.TestCase):
            pass
         req.authname = 'testuser'
         #req.hdf = HDFWrapper([]) # replace by this if you want to generate HTML: req.hdf = HDFWrapper(loadpaths=chrome.get_all_templates_dirs())
-        template, data, content_type = ImportModule(env)._do_preview(filename, 1, req)
+        template, data, content_type = ImportModule(env)._do_preview(filename, 1, req, encoding='cp1252')
         #sys.stdout = tempstdout
         #req.display(template, content_type or 'text/html')
         #open('/tmp/out.html', 'w').write(req.hdf.render(template, None))
@@ -69,7 +69,8 @@ class ImporterTestCase(unittest.TestCase):
         #print enums_before
         # when testing, always use the same time so that the results are comparable
         #print "importing " + filename + " with tickettime " + str(ImporterTestCase.TICKET_TIME)
-        template, data, content_type = ImportModule(env)._do_import(filename, sheet, req, filename, ImporterTestCase.TICKET_TIME)
+        template, data, content_type = ImportModule(env)._do_import(filename, sheet, req, filename,
+                                                                    ImporterTestCase.TICKET_TIME, encoding='cp1252')
         #sys.stdout = tempstdout
         #req.display(template, content_type or 'text/html')
         #open('/tmp/out.html', 'w').write(req.hdf.render(template, None))
