merge 3.5
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 17 Sep 2009 09:52:19 +0200
branch3.5
changeset 3278 293068aeee41
parent 3277 4fdb165ae3de (current diff)
parent 3275 5247789df541 (diff)
child 3279 6a2cde3f886e
merge
--- a/common/i18n.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/common/i18n.py	Thu Sep 17 09:52:19 2009 +0200
@@ -25,10 +25,14 @@
     output.close()
 
 
-def add_msg(w, msgid):
+def add_msg(w, msgid, msgctx=None):
     """write an empty pot msgid definition"""
     if isinstance(msgid, unicode):
         msgid = msgid.encode('utf-8')
+    if msgctx:
+        if isinstance(msgctx, unicode):
+            msgctx = msgctx.encode('utf-8')
+        w('msgctxt "%s"\n' % msgctx)
     msgid = msgid.replace('"', r'\"').splitlines()
     if len(msgid) > 1:
         w('msgid ""\n')
--- a/cwconfig.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/cwconfig.py	Thu Sep 17 09:52:19 2009 +0200
@@ -812,7 +812,7 @@
             self.info("loading language %s", language)
             try:
                 tr = translation('cubicweb', path, languages=[language])
-                self.translations[language] = tr.ugettext
+                self.translations[language] = (tr.ugettext, tr.upgettext)
             except (ImportError, AttributeError, IOError):
                 self.exception('localisation support error for language %s',
                                language)
--- a/dbapi.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/dbapi.py	Thu Sep 17 09:52:19 2009 +0200
@@ -214,10 +214,13 @@
             self.lang = 'en'
         # use req.__ to translate a message without registering it to the catalog
         try:
-            self._ = self.__ = self.translations[self.lang]
+            gettext, pgettext = self.translations[self.lang]
+            self._ = self.__ = gettext
+            self.pgettext = pgettext
         except KeyError:
             # this occurs usually during test execution
             self._ = self.__ = unicode
+            self.pgettext = lambda x,y: y
         self.debug('request default language: %s', self.lang)
 
     def decorate_rset(self, rset):
--- a/devtools/devctl.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/devtools/devctl.py	Thu Sep 17 09:52:19 2009 +0200
@@ -121,6 +121,7 @@
         entities = [e for e in schema.entities() if not e in libschema]
     else:
         entities = schema.entities()
+    rinlined = uicfg.autoform_is_inlined
     done = set()
     for eschema in sorted(entities):
         etype = eschema.type
@@ -129,8 +130,14 @@
         if not eschema.is_final():
             add_msg(w, 'This %s' % etype)
             add_msg(w, 'New %s' % etype)
-            add_msg(w, 'add a %s' % etype)
-            add_msg(w, 'remove this %s' % etype)
+            for rschema, targetschemas, role in eschema.relation_definitions(True):
+                targetschemas = [tschema for tschema in targetschemas
+                                 if rinlined.etype_get(eschema, rschema, role, tschema)]
+                for tschema in targetschemas:
+                    add_msg(w, 'add a %s' % tschema,
+                            'inlined:%s.%s.%s' % (etype, rschema, role))
+                    add_msg(w, 'remove this %s' % tschema,
+                            'inlined:%s:%s:%s' % (etype, rschema, role))
         if eschema.description and not eschema.description in done:
             done.add(eschema.description)
             add_msg(w, eschema.description)
@@ -143,7 +150,8 @@
         relations = schema.relations()
     for rschema in sorted(set(relations)):
         rtype = rschema.type
-        add_msg(w, rtype)
+        for subjschema in rschema.subjects():
+            add_msg(w, rtype, subjschema.type)
         done.add(rtype)
         if not (schema.rschema(rtype).is_final() or rschema.symetric):
             add_msg(w, '%s_object' % rtype)
@@ -157,7 +165,7 @@
         if eschema.is_final():
             continue
         for role, rschemas in (('subject', eschema.subject_relations()),
-                            ('object', eschema.object_relations())):
+                               ('object', eschema.object_relations())):
             for rschema in rschemas:
                 if rschema.is_final():
                     continue
--- a/gettext.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/gettext.py	Thu Sep 17 09:52:19 2009 +0200
@@ -8,7 +8,6 @@
 languages.  L10N refers to the adaptation of your program, once
 internationalized, to the local language and cultural habits.
 
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 # This module represents the integration of work, contributions, feedback, and
@@ -47,7 +46,7 @@
 #   find this format documented anywhere.
 
 
-import copy, os, re, struct, sys
+import locale, copy, os, re, struct, sys
 from errno import ENOENT
 
 
@@ -78,7 +77,10 @@
     Python lambda function that implements an equivalent expression.
     """
     # Security check, allow only the "n" identifier
-    from StringIO import StringIO
+    try:
+        from cStringIO import StringIO
+    except ImportError:
+        from StringIO import StringIO
     import token, tokenize
     tokens = tokenize.generate_tokens(StringIO(plural).readline)
     try:
@@ -172,6 +174,7 @@
     def __init__(self, fp=None):
         self._info = {}
         self._charset = None
+        self._output_charset = None
         self._fallback = None
         if fp is not None:
             self._parse(fp)
@@ -190,6 +193,21 @@
             return self._fallback.gettext(message)
         return message
 
+    def pgettext(self, context, message):
+        if self._fallback:
+            return self._fallback.pgettext(context, message)
+        return message
+
+    def lgettext(self, message):
+        if self._fallback:
+            return self._fallback.lgettext(message)
+        return message
+
+    def lpgettext(self, context, message):
+        if self._fallback:
+            return self._fallback.lpgettext(context, message)
+        return message
+
     def ngettext(self, msgid1, msgid2, n):
         if self._fallback:
             return self._fallback.ngettext(msgid1, msgid2, n)
@@ -198,11 +216,40 @@
         else:
             return msgid2
 
+    def npgettext(self, context, msgid1, msgid2, n):
+        if self._fallback:
+            return self._fallback.npgettext(context, msgid1, msgid2, n)
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+
+    def lngettext(self, msgid1, msgid2, n):
+        if self._fallback:
+            return self._fallback.lngettext(msgid1, msgid2, n)
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+
+    def lnpgettext(self, context, msgid1, msgid2, n):
+        if self._fallback:
+            return self._fallback.lnpgettext(context, msgid1, msgid2, n)
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+
     def ugettext(self, message):
         if self._fallback:
             return self._fallback.ugettext(message)
         return unicode(message)
 
+    def upgettext(self, context, message):
+        if self._fallback:
+            return self._fallback.upgettext(context, message)
+        return unicode(message)
+
     def ungettext(self, msgid1, msgid2, n):
         if self._fallback:
             return self._fallback.ungettext(msgid1, msgid2, n)
@@ -211,15 +258,49 @@
         else:
             return unicode(msgid2)
 
+    def unpgettext(self, context, msgid1, msgid2, n):
+        if self._fallback:
+            return self._fallback.unpgettext(context, msgid1, msgid2, n)
+        if n == 1:
+            return unicode(msgid1)
+        else:
+            return unicode(msgid2)
+
     def info(self):
         return self._info
 
     def charset(self):
         return self._charset
 
-    def install(self, unicode=False):
+    def output_charset(self):
+        return self._output_charset
+
+    def set_output_charset(self, charset):
+        self._output_charset = charset
+
+    def install(self, unicode=False, names=None):
         import __builtin__
         __builtin__.__dict__['_'] = unicode and self.ugettext or self.gettext
+        if hasattr(names, "__contains__"):
+            if "gettext" in names:
+                __builtin__.__dict__['gettext'] = __builtin__.__dict__['_']
+            if "pgettext" in names:
+                __builtin__.__dict__['pgettext'] = (unicode and self.upgettext
+                                                    or self.pgettext)
+            if "ngettext" in names:
+                __builtin__.__dict__['ngettext'] = (unicode and self.ungettext
+                                                             or self.ngettext)
+            if "npgettext" in names:
+                __builtin__.__dict__['npgettext'] = \
+                    (unicode and self.unpgettext or self.npgettext)
+            if "lgettext" in names:
+                __builtin__.__dict__['lgettext'] = self.lgettext
+            if "lpgettext" in names:
+                __builtin__.__dict__['lpgettext'] = self.lpgettext
+            if "lngettext" in names:
+                __builtin__.__dict__['lngettext'] = self.lngettext
+            if "lnpgettext" in names:
+                __builtin__.__dict__['lnpgettext'] = self.lnpgettext
 
 
 class GNUTranslations(NullTranslations):
@@ -227,6 +308,10 @@
     LE_MAGIC = 0x950412deL
     BE_MAGIC = 0xde120495L
 
+    # The encoding of a msgctxt and a msgid in a .mo file is
+    # msgctxt + "\x04" + msgid (gettext version >= 0.15)
+    CONTEXT_ENCODING = "%s\x04%s"
+
     def _parse(self, fp):
         """Override this method to support alternative .mo formats."""
         unpack = struct.unpack
@@ -262,18 +347,19 @@
             # See if we're looking at GNU .mo conventions for metadata
             if mlen == 0:
                 # Catalog description
-                # don't handle multi-lines fields here, and skip
-                # lines which don't look like a header description
-                # (e.g. "header: value")
                 lastk = k = None
                 for item in tmsg.splitlines():
                     item = item.strip()
-                    if not item or not ':' in item:
+                    if not item:
                         continue
-                    k, v = item.split(':', 1)
-                    k = k.strip().lower()
-                    v = v.strip()
-                    self._info[k] = v
+                    if ':' in item:
+                        k, v = item.split(':', 1)
+                        k = k.strip().lower()
+                        v = v.strip()
+                        self._info[k] = v
+                        lastk = k
+                    elif lastk:
+                        self._info[lastk] += '\n' + item
                     if k == 'content-type':
                         self._charset = v.split('charset=')[1]
                     elif k == 'plural-forms':
@@ -289,7 +375,7 @@
             # cause no problems since us-ascii should always be a subset of
             # the charset encoding.  We may want to fall back to 8-bit msgids
             # if the Unicode conversion fails.
-            if msg.find('\x00') >= 0:
+            if '\x00' in msg:
                 # Plural forms
                 msgid1, msgid2 = msg.split('\x00')
                 tmsg = tmsg.split('\x00')
@@ -315,14 +401,56 @@
                 return self._fallback.gettext(message)
             return message
         # Encode the Unicode tmsg back to an 8-bit string, if possible
-        if self._charset:
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        elif self._charset:
+            return tmsg.encode(self._charset)
+        return tmsg
+
+    def pgettext(self, context, message):
+        ctxt_msg_id = self.CONTEXT_ENCODING % (context, message)
+        missing = object()
+        tmsg = self._catalog.get(ctxt_msg_id, missing)
+        if tmsg is missing:
+            if self._fallback:
+                return self._fallback.pgettext(context, message)
+            return message
+        # Encode the Unicode tmsg back to an 8-bit string, if possible
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        elif self._charset:
             return tmsg.encode(self._charset)
         return tmsg
+        
+    def lgettext(self, message):
+        missing = object()
+        tmsg = self._catalog.get(message, missing)
+        if tmsg is missing:
+            if self._fallback:
+                return self._fallback.lgettext(message)
+            return message
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        return tmsg.encode(locale.getpreferredencoding())
+
+    def lpgettext(self, context, message):
+        ctxt_msg_id = self.CONTEXT_ENCODING % (context, message)
+        missing = object()
+        tmsg = self._catalog.get(ctxt_msg_id, missing)
+        if tmsg is missing:
+            if self._fallback:
+                return self._fallback.lpgettext(context, message)
+            return message
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        return tmsg.encode(locale.getpreferredencoding())
 
     def ngettext(self, msgid1, msgid2, n):
         try:
             tmsg = self._catalog[(msgid1, self.plural(n))]
-            if self._charset:
+            if self._output_charset:
+                return tmsg.encode(self._output_charset)
+            elif self._charset:
                 return tmsg.encode(self._charset)
             return tmsg
         except KeyError:
@@ -333,6 +461,52 @@
             else:
                 return msgid2
 
+    def npgettext(self, context, msgid1, msgid2, n):
+        ctxt_msg_id = self.CONTEXT_ENCODING % (context, msgid1)
+        try:
+            tmsg = self._catalog[(ctxt_msg_id, self.plural(n))]
+            if self._output_charset:
+                return tmsg.encode(self._output_charset)
+            elif self._charset:
+                return tmsg.encode(self._charset)
+            return tmsg
+        except KeyError:
+            if self._fallback:
+                return self._fallback.npgettext(context, msgid1, msgid2, n)
+            if n == 1:
+                return msgid1
+            else:
+                return msgid2        
+
+    def lngettext(self, msgid1, msgid2, n):
+        try:
+            tmsg = self._catalog[(msgid1, self.plural(n))]
+            if self._output_charset:
+                return tmsg.encode(self._output_charset)
+            return tmsg.encode(locale.getpreferredencoding())
+        except KeyError:
+            if self._fallback:
+                return self._fallback.lngettext(msgid1, msgid2, n)
+            if n == 1:
+                return msgid1
+            else:
+                return msgid2
+
+    def lnpgettext(self, context, msgid1, msgid2, n):
+        ctxt_msg_id = self.CONTEXT_ENCODING % (context, msgid1)
+        try:
+            tmsg = self._catalog[(ctxt_msg_id, self.plural(n))]
+            if self._output_charset:
+                return tmsg.encode(self._output_charset)
+            return tmsg.encode(locale.getpreferredencoding())
+        except KeyError:
+            if self._fallback:
+                return self._fallback.lnpgettext(context, msgid1, msgid2, n)
+            if n == 1:
+                return msgid1
+            else:
+                return msgid2
+
     def ugettext(self, message):
         missing = object()
         tmsg = self._catalog.get(message, missing)
@@ -342,6 +516,16 @@
             return unicode(message)
         return tmsg
 
+    def upgettext(self, context, message):
+        ctxt_message_id = self.CONTEXT_ENCODING % (context, message)
+        missing = object()
+        tmsg = self._catalog.get(ctxt_message_id, missing)
+        if tmsg is missing:
+            if self._fallback:
+                return self._fallback.upgettext(context, message)
+            return unicode(message)
+        return tmsg
+
     def ungettext(self, msgid1, msgid2, n):
         try:
             tmsg = self._catalog[(msgid1, self.plural(n))]
@@ -354,6 +538,19 @@
                 tmsg = unicode(msgid2)
         return tmsg
 
+    def unpgettext(self, context, msgid1, msgid2, n):
+        ctxt_message_id = self.CONTEXT_ENCODING % (context, msgid1)
+        try:
+            tmsg = self._catalog[(ctxt_message_id, self.plural(n))]
+        except KeyError:
+            if self._fallback:
+                return self._fallback.unpgettext(context, msgid1, msgid2, n)
+            if n == 1:
+                tmsg = unicode(msgid1)
+            else:
+                tmsg = unicode(msgid2)
+        return tmsg
+
 
 # Locate a .mo file using the gettext strategy
 def find(domain, localedir=None, languages=None, all=0):
@@ -397,7 +594,7 @@
 _translations = {}
 
 def translation(domain, localedir=None, languages=None,
-                class_=None, fallback=False):
+                class_=None, fallback=False, codeset=None):
     if class_ is None:
         class_ = GNUTranslations
     mofiles = find(domain, localedir, languages, all=1)
@@ -414,9 +611,12 @@
         t = _translations.get(key)
         if t is None:
             t = _translations.setdefault(key, class_(open(mofile, 'rb')))
-        # Copy the translation object to allow setting fallbacks.
-        # All other instance data is shared with the cached object.
+        # Copy the translation object to allow setting fallbacks and
+        # output charset. All other instance data is shared with the
+        # cached object.
         t = copy.copy(t)
+        if codeset:
+            t.set_output_charset(codeset)
         if result is None:
             result = t
         else:
@@ -424,13 +624,16 @@
     return result
 
 
-def install(domain, localedir=None, unicode=False):
-    translation(domain, localedir, fallback=True).install(unicode)
+def install(domain, localedir=None, unicode=False, codeset=None, names=None):
+    t = translation(domain, localedir, fallback=True, codeset=codeset)
+    t.install(unicode, names)
 
 
 
 # a mapping b/w domains and locale directories
 _localedirs = {}
+# a mapping b/w domains and codesets
+_localecodesets = {}
 # current global domain, `messages' used for compatibility w/ GNU gettext
 _current_domain = 'messages'
 
@@ -443,22 +646,55 @@
 
 
 def bindtextdomain(domain, localedir=None):
+    global _localedirs
     if localedir is not None:
         _localedirs[domain] = localedir
     return _localedirs.get(domain, _default_localedir)
 
 
+def bind_textdomain_codeset(domain, codeset=None):
+    global _localecodesets
+    if codeset is not None:
+        _localecodesets[domain] = codeset
+    return _localecodesets.get(domain)
+
+
 def dgettext(domain, message):
     try:
-        t = translation(domain, _localedirs.get(domain, None))
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
     except IOError:
         return message
     return t.gettext(message)
 
+def dpgettext(domain, context, message):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except IOError:
+        return message
+    return t.pgettext(context, message)
+
+def ldgettext(domain, message):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except IOError:
+        return message
+    return t.lgettext(message)
+
+def ldpgettext(domain, context, message):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except IOError:
+        return message
+    return t.lpgettext(context, message)
 
 def dngettext(domain, msgid1, msgid2, n):
     try:
-        t = translation(domain, _localedirs.get(domain, None))
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
     except IOError:
         if n == 1:
             return msgid1
@@ -466,14 +702,62 @@
             return msgid2
     return t.ngettext(msgid1, msgid2, n)
 
+def dnpgettext(domain, context, msgid1, msgid2, n):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except IOError:
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+    return t.npgettext(context, msgid1, msgid2, n)
+
+def ldngettext(domain, msgid1, msgid2, n):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except IOError:
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+    return t.lngettext(msgid1, msgid2, n)
+
+def ldnpgettext(domain, context, msgid1, msgid2, n):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except IOError:
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+    return t.lnpgettext(context, msgid1, msgid2, n)
 
 def gettext(message):
     return dgettext(_current_domain, message)
 
+def pgettext(context, message):
+    return dpgettext(_current_domain, context, message)
+
+def lgettext(message):
+    return ldgettext(_current_domain, message)
+
+def lpgettext(context, message):
+    return ldpgettext(_current_domain, context, message)
 
 def ngettext(msgid1, msgid2, n):
     return dngettext(_current_domain, msgid1, msgid2, n)
 
+def npgettext(context, msgid1, msgid2, n):
+    return dnpgettext(_current_domain, context, msgid1, msgid2, n)
+
+def lngettext(msgid1, msgid2, n):
+    return ldngettext(_current_domain, msgid1, msgid2, n)
+
+def lnpgettext(context, msgid1, msgid2, n):
+    return ldnpgettext(_current_domain, context, msgid1, msgid2, n)
 
 # dcgettext() has been deemed unnecessary and is not implemented.
 
--- a/schema.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/schema.py	Thu Sep 17 09:52:19 2009 +0200
@@ -100,7 +100,7 @@
         etype = ETYPE_NAME_MAP[etype]
     return etype
 
-def display_name(req, key, form=''):
+def display_name(req, key, form='', context=None):
     """return a internationalized string for the key (schema entity or relation
     name) in a given form
     """
@@ -111,7 +111,11 @@
         key = key + '_' + form
     # ensure unicode
     # added .lower() in case no translation are available
-    return unicode(req._(key)).lower()
+    if context:
+        return req.pgettext(context, key).lower()
+    else:
+        return unicode(req._(key)).lower()
+
 __builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name)
 
 def ERSchema_display_name(self, req, form=''):
--- a/server/session.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/server/session.py	Thu Sep 17 09:52:19 2009 +0200
@@ -210,13 +210,18 @@
         vreg = self.vreg
         language = language or self.user.property_value('ui.language')
         try:
-            self._ = self.__ = vreg.config.translations[language]
+            gettext, pgettext = vreg.config.translations[language]
+            self._ = self.__ = gettext
+            self.pgettext = pgettext
         except KeyError:
             language = vreg.property_value('ui.language')
             try:
-                self._ = self.__ = vreg.config.translations[language]
+                gettext, pgettext = vreg.config.translations[language]
+                self._ = self.__ = gettext
+                self.pgettext = pgettext
             except KeyError:
                 self._ = self.__ = unicode
+                self.pgettext = lambda x,y: y
         self.lang = language
 
     def change_property(self, prop, value):
--- a/web/formfields.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/web/formfields.py	Thu Sep 17 09:52:19 2009 +0200
@@ -513,6 +513,7 @@
         help = rschema.rproperty(targetschema, eschema, 'description')
     kwargs['required'] = card in '1+'
     kwargs['name'] = rschema.type
+    kwargs['label'] = (eschema.type, rschema.type)
     kwargs.setdefault('help', help)
     if rschema.is_final():
         if skip_meta_attr and rschema in eschema.meta_attributes():
--- a/web/request.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/web/request.py	Thu Sep 17 09:52:19 2009 +0200
@@ -114,7 +114,8 @@
         self.set_default_language(vreg)
 
     def set_language(self, lang):
-        self._ = self.__ = self.translations[lang]
+        gettext, self.pgettext = self.translations[lang]
+        self._ = self.__ = gettext
         self.lang = lang
         self.cnx.set_session_props(lang=lang)
         self.debug('request language: %s', lang)
--- a/web/views/formrenderers.py	Thu Sep 17 09:51:39 2009 +0200
+++ b/web/views/formrenderers.py	Thu Sep 17 09:52:19 2009 +0200
@@ -87,7 +87,10 @@
     def render_label(self, form, field):
         if field.label is None:
             return u''
-        label = self.req._(field.label)
+        if isinstance(field.label, tuple): # i.e. needs contextual translation
+            label = self.req.pgettext(*field.label)
+        else:
+            label = self.req._(field.label)
         attrs = {'for': form.context[field]['id']}
         if field.required:
             attrs['class'] = 'required'
@@ -485,7 +488,7 @@
 
     def inline_relation_form(self, w, form, rschema, targettype, role):
         entity = form.edited_entity
-        __ = self.req.__
+        __ = self.req.pgettext
         w(u'<div id="inline%sslot">' % rschema)
         existant = entity.has_eid() and entity.related(rschema)
         if existant:
@@ -513,8 +516,9 @@
                 entity.eid, targettype, rschema, role)
             if card in '1?':
                 js = "toggleVisibility('%s'); %s" % (divid, js)
+            ctx = 'inlined:%s.%s.%s' % (entity.e_schema, rschema, role)
             w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
-              % (rschema, entity.eid, js, __('add a %s' % targettype)))
+              % (rschema, entity.eid, js, __(ctx, 'add a %s' % targettype)))
             w(u'</div>')
             w(u'<div class="trame_grise">&#160;</div>')
         w(u'</div>')