# HG changeset patch # User Sylvain Thénault # Date 1253173939 -7200 # Node ID 293068aeee410937fb8f985e3a2747674f21ac4b # Parent 4fdb165ae3de77a42accec6073cfdbaf80736aa5# Parent 5247789df5413ad10d8ff9d62f87f1571f9c1a51 merge diff -r 4fdb165ae3de -r 293068aeee41 common/i18n.py --- 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') diff -r 4fdb165ae3de -r 293068aeee41 cwconfig.py --- 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) diff -r 4fdb165ae3de -r 293068aeee41 dbapi.py --- 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): diff -r 4fdb165ae3de -r 293068aeee41 devtools/devctl.py --- 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 diff -r 4fdb165ae3de -r 293068aeee41 gettext.py --- 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. diff -r 4fdb165ae3de -r 293068aeee41 schema.py --- 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=''): diff -r 4fdb165ae3de -r 293068aeee41 server/session.py --- 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): diff -r 4fdb165ae3de -r 293068aeee41 web/formfields.py --- 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(): diff -r 4fdb165ae3de -r 293068aeee41 web/request.py --- 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) diff -r 4fdb165ae3de -r 293068aeee41 web/views/formrenderers.py --- 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'
' % 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'+ %s.' - % (rschema, entity.eid, js, __('add a %s' % targettype))) + % (rschema, entity.eid, js, __(ctx, 'add a %s' % targettype))) w(u'
') w(u'
 
') w(u'')