Index: quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py
===================================================================
--- quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py	(revision 872)
+++ quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py	(revision 873)
@@ -18,5 +18,4 @@
 from Products.CMFCore.ActionInformation import Action, ActionCategory
 from Products.CMFCore.Expression import Expression
-from Products.CMFPlone import PloneMessageFactory as _
 from Products.CMFPlone import utils
 from Products.CMFPlone.browser.navigation import get_view_url
@@ -26,4 +25,5 @@
 
 from quintagroup.plonetabs.config import *
+from quintagroup.plonetabs import messageFactory as _
 from interfaces import IPloneTabsControlPanel
 
@@ -147,5 +147,6 @@
             action = self.addAction(cat_name, data)
             IStatusMessage(self.request).addStatusMessage(
-                _(u"'%s' action successfully added." % action.id), type="info")
+                _(u"'${id}' action successfully added.",
+                  mapping={'id':action.id}), type="info")
             self.redirect(search="category=%s" % cat_name)
             return False
@@ -172,5 +173,6 @@
             action = self.updateAction(id, cat_name, data)
             IStatusMessage(self.request).addStatusMessage(
-                _(u"'%s' action saved." % action.id), type="info")
+                _(u"'${id}' action saved.", mapping={'id': action.id}),
+                type="info")
             self.redirect(search="category=%s" % cat_name)
             return False
@@ -192,10 +194,11 @@
             self.deleteAction(id, cat_name)
             IStatusMessage(self.request).addStatusMessage(
-                _(u"'%s' action deleted." % id), type="info")
+                _(u"'${id}' action deleted.", mapping={'id': id}), type="info")
             self.redirect(search="category=%s" % cat_name)
             return False
         else:
             IStatusMessage(self.request).addStatusMessage(
-                _(u"No '%s' action in '%s' category." % (id, cat_name)),
+                _(u"No '${id}' action in '${cat_name}' category.",
+                  mapping={'id': id, 'cat_name': cat_name}),
                 type="error")
             return True
@@ -211,11 +214,11 @@
             self.moveAction(id, cat_name, steps=1)
             IStatusMessage(self.request).addStatusMessage(
-                _(u"'%s' action moved up." % id), type="info")
+                _(u"'${id}' action moved up.", mapping={'id': id}), type="info")
             self.redirect(search="category=%s" % cat_name)
             return False
         else:
             IStatusMessage(self.request).addStatusMessage(
-                _(u"No '%s' action in '%s' category." % (id, cat_name)),
-                type="error")
+                _(u"No '${id}' action in '${cat_name}' category.",
+                  mapping={'id': id, 'cat_name': cat_name}), type="error")
             return True
     
@@ -230,10 +233,12 @@
             self.moveAction(id, cat_name, steps=-1)
             IStatusMessage(self.request).addStatusMessage(
-                _(u"'%s' action moved down." % id), type="info")
+                _(u"'${id}' action moved down.", mapping={'id': id}),
+                type="info")
             self.redirect(search="category=%s" % cat_name)
             return False
         else:
             IStatusMessage(self.request).addStatusMessage(
-                _(u"No '%s' action in '%s' category." % (id, cat_name)),
+                _(u"No '${id}' action in '${cat_name}' category.",
+                  mapping={'id': id, 'cat_name': cat_name}),
                 type="error")
             return True
@@ -257,8 +262,17 @@
     ###################################
     
+    def _charset(self):
+        pp = getToolByName(self.context, 'portal_properties', None)
+        if pp is not None:
+            site_properties = getattr(pp, 'site_properties', None)
+            if site_properties is not None:
+                return site_properties.getProperty('default_charset', 'utf-8')
+        return 'utf-8'
+    
     def getPageTitle(self, category="portal_tabs"):
         """See interface"""
         portal_props = getToolByName(self.context, "portal_properties")
-        default_title = "Plone '%s' Configuration" % category
+        default_title = _(u"Plone '${cat_name}' Configuration",
+                          mapping={'cat_name': category})
         
         if not hasattr(portal_props, PROPERTY_SHEET):
@@ -275,5 +289,13 @@
             dict[cat] = title
         
-        return dict.get(category, None) or default_title
+        title = dict.get(category, None)
+        if title is None:
+            return default_title
+        
+        charset = self._charset()
+        if not isinstance(title, unicode):
+            title = title.decode(charset)
+        
+        return _(title)
     
     def hasActions(self, category="portal_tabs"):
@@ -473,14 +495,14 @@
         if checked == '1':
             self.setSiteProperties(**{field: False})
-            prefix = 'Generated'
             if field == 'disable_nonfolderish_sections':
-                prefix += ' not folderish'
-            message = _(u"%s tabs switched on." % prefix)
+                message = _(u"Generated not folderish tabs switched on.")
+            else:
+                message = _(u"Generated tabs switched on.")
         else:
             self.setSiteProperties(**{field: True})
-            prefix = 'Generated'
             if field == 'disable_nonfolderish_sections':
-                prefix += ' not folderish'
-            message = _(u"%s tabs switched off." % prefix)
+                message = _(u"Generated not folderish tabs switched off.")
+            else:
+                message = _(u"Generated tabs switched off.")
         
         # update client
@@ -506,5 +528,6 @@
         if obj_id not in portal.objectIds():
             raise KSSExplicitError, \
-                  "Object with %s id doesn't exist in portal root" % obj_id
+                  _(u"Object with '${id}' id doesn't exist in portal root.",
+                    mapping={'id': obj_id})
         
         if checked == '1':
@@ -815,9 +838,9 @@
             chooser.checkName(data['id'], self.context)
         except Exception, e:
-            errors['id'] = "%s" % str(e)
+            errors['id'] = self._formatError(e, **{'id':data['id']})
         
         # validate action name
         if not data['title'].strip():
-            errors['title'] = 'Empty or invalid title specified'
+            errors['title'] = _(u"Empty or invalid title specified")
         
         # validate condition expression
@@ -826,5 +849,9 @@
                 Expression(data['available_expr'])
             except Exception, e:
-                errors["available_expr"] = "%s" % str(e)
+                mapping = {'expr': data['available_expr']}
+                idx = data['available_expr'].find(':')
+                if idx != -1:
+                    mapping['expr_type'] = data['available_expr'][:idx]
+                errors["available_expr"] = self._formatError(e, **mapping)
         
         # validate action expression
@@ -833,7 +860,24 @@
                 Expression(data['url_expr'])
             except Exception, e:
-                errors["url_expr"] = "%s" % str(e)
+                mapping = {'expr': data['url_expr']}
+                idx = data['url_expr'].find(':')
+                if idx != -1:
+                    mapping['expr_type'] = data['url_expr'][:idx]
+                errors["url_expr"] = self._formatError(e, **mapping)
         
         return errors
+    
+    def _formatError(self, message, **kw):
+        """Make error message a little bit prettier to ease translation"""
+        charset = self._charset()
+        message = str(message)
+        message = message.replace('"', "'")
+        mapping = {}
+        for key, value in kw.items():
+            message = message.replace("'%s'" % value, "'${%s}'" % key)
+            # trying to work around zope.i18n issue
+            mapping[key] = unicode(value, charset)
+        message = message.strip()
+        return _(unicode(message, charset), mapping=mapping)
     
     def processErrors(self, errors, prefix='', sufix=''):
@@ -921,5 +965,6 @@
         except Exception:
             raise KSSExplicitError, \
-                  "'%s' action category does not exist" % cat_name
+                  _(u"'${cat_name}' action category does not exist.",
+                    mapping={'cat_name': cat_name})
         
         # extract action id from given list item id on client
@@ -930,5 +975,6 @@
         except Exception:
             raise KSSExplicitError, \
-                  "No '%s' action in '%s' category." % (action_id, cat_name)
+                  _(u"No '${id}' action in '${cat_name}' category.",
+                    mapping={'id': action_id, 'cat_name': cat_name})
         
         return (action_id, category, action)
@@ -937,9 +983,10 @@
         """Display error messages on the client"""
         ksscore = self.getCommandSet('core')
+        ts = getToolByName(self.context, 'translation_service')
         for field in fields:
             self.kss_issueFieldError(ksscore, field,
-                                     errors.get(field, False), editform)
-    
-    def kss_issueFieldError(self, ksscore, name, error, editform):
+                                     errors.get(field, False), editform, ts)
+    
+    def kss_issueFieldError(self, ksscore, name, error, editform, ts):
         """Issue this error message for the field"""
         if editform:
@@ -957,5 +1004,6 @@
 
         if error:
-            ksscore.replaceInnerHTML(field_error_selector, _(error))
+            error = ts.translate(error, context=self.context)
+            ksscore.replaceInnerHTML(field_error_selector, error)
             ksscore.addClass(field_selector, 'error')
         else:
