Пример #1
0
class Localizer(LanguageManager, Folder):
    """
    The Localizer meta type lets you customize the language negotiation
    policy.
    """

    meta_type = 'Localizer'
    implements(ILocalizer)

    id = 'Localizer'

    _properties = (
        {
            'id': 'title',
            'type': 'string'
        },
        {
            'id': 'accept_methods',
            'type': 'tokens'
        },
        {
            'id': 'user_defined_languages',
            'type': 'lines'
        },
    )

    accept_methods = ('accept_path', 'accept_cookie', 'accept_url')

    security = ClassSecurityInfo()

    manage_options = \
        (Folder.manage_options[0],) \
        + LanguageManager.manage_options \
        + Folder.manage_options[1:]

    user_defined_languages = ()

    def __init__(self, title, languages):
        self.title = title

        self._languages = languages
        self._default_language = languages[0]

    #######################################################################
    # API / Private
    #######################################################################
    def _getCopy(self, container):
        return Localizer.inheritedAttribute('_getCopy')(self, container)

    def _needs_upgrade(self):
        return not self.hooked()

    def _upgrade(self):
        # Upgrade to 0.9
        if not self.hooked():
            self.manage_hook(1)

    #######################################################################
    # API / Public
    #######################################################################

    # Get some data
    security.declarePublic('get_supported_languages')

    def get_supported_languages(self):
        """
        Get the supported languages, that is the languages that the
        are being working so the site is or will provide to the public.
        """
        return self._languages

    security.declarePublic('get_selected_language')

    def get_selected_language(self):
        """ """
        return lang_negotiator(self._languages) \
               or self._default_language

    # Hooking the traversal machinery
    # Fix this! a new permission needed?


##    security.declareProtected('View management screens', 'manage_hookForm')
##    manage_hookForm = LocalDTMLFile('ui/Localizer_hook', globals())
##    security.declareProtected('Manage properties', 'manage_hook')
    security.declarePrivate('manage_hook')

    def manage_hook(self, hook=0):
        """ """
        if hook != self.hooked():
            if hook:
                hook = NameCaller(self.id)
                registerBeforeTraverse(aq_parent(self), hook, self.meta_type)
            else:
                unregisterBeforeTraverse(aq_parent(self), self.meta_type)

    security.declarePublic('hooked')

    def hooked(self):
        """ """
        if queryBeforeTraverse(aq_parent(self), self.meta_type):
            return 1
        return 0

    # New code to control the language policy
    def accept_cookie(self, accept_language):
        """Add the language from a cookie."""
        lang = self.REQUEST.cookies.get('LOCALIZER_LANGUAGE', None)
        if lang is not None:
            accept_language.set(lang, 2.0)

    def accept_path(self, accept_language):
        """Add the language from the path."""
        stack = self.REQUEST['TraversalRequestNameStack']
        if stack and (stack[-1] in self._languages):
            lang = stack.pop()
            accept_language.set(lang, 3.0)

    def accept_url(self, accept_language):
        """Add the language from the URL."""
        lang = self.REQUEST.form.get('LOCALIZER_LANGUAGE')
        if lang is not None:
            accept_language.set(lang, 2.0)

    def __call__(self, container, REQUEST):
        """Hooks the traversal path."""
        try:
            accept_language = REQUEST['AcceptLanguage']
        except KeyError:
            return

        for id in self.accept_methods:
            try:
                method = getattr(self, id)
                method(accept_language)
            except:
                LOG(self.meta_type,
                    PROBLEM,
                    'method "%s" raised an exception.' % id,
                    error=True)

    # Changing the language, useful snippets
    security.declarePublic('get_languages_map')

    def get_languages_map(self):
        """
        Return a list of dictionaries, each dictionary has the language
        id, its title and a boolean value to indicate wether it's the
        user preferred language, for example:

          [{'id': 'en', 'title': 'English', 'selected': 1}]

        Used in changeLanguageForm.
        """
        # For now only LPM instances are considered to be containers of
        # multilingual data.
        try:
            ob = self.getLocalPropertyManager()
        except AttributeError:
            ob = self

        ob_language = ob.get_selected_language()
        ob_languages = ob.get_available_languages()

        langs = []
        for x in ob_languages:
            langs.append({
                'id': x,
                'title': self.get_language_name(x),
                'selected': x == ob_language
            })

        return langs

    security.declarePublic('changeLanguage')
    changeLanguageForm = LocalDTMLFile('ui/changeLanguageForm', globals())

    def changeLanguage(self, lang, goto=None, expires=None):
        """Change the user language to `lang`.

        This method will set a cookie and redirect to `goto` URL.
        """
        request = self.REQUEST
        response = request.RESPONSE

        # Changes the cookie (it could be something different)
        parent = aq_parent(self)
        path = parent.absolute_url()[len(request['SERVER_URL']):] or '/'
        if expires is None:
            response.setCookie('LOCALIZER_LANGUAGE', lang, path=path)
        else:
            response.setCookie('LOCALIZER_LANGUAGE',
                               lang,
                               path=path,
                               expires=unquote(expires))
        # Comes back
        if goto is None:
            goto = request['HTTP_REFERER']

        response.redirect(goto)

    security.declarePublic('translationContext')

    @contextmanager
    def translationContext(self, lang):
        """Context manager to temporarily change the current language.
      """
        class ForcedLanguage:
            __allow_access_to_unprotected_subobjects__ = 1

            def __init__(self, lang):
                self.lang = lang

            def select_language(self, available_languages):
                return self.lang

            def set(self, lang, priority):
                if lang != self.lang:
                    LOG('Localizer',
                        PROBLEM,
                        'Cannot change language inside a translationContext',
                        error=1)

        MARKER = []
        from patches import get_request  # late import, as this is patched by
        # unit tests
        request = get_request()  # Localizer always use this request internally
        old_accept_language = request.get('AcceptLanguage', MARKER)
        request.set('AcceptLanguage', ForcedLanguage(lang))
        try:
            assert self.get_selected_language() == lang
            yield
        finally:
            request.other.pop('AcceptLanguage')
            if old_accept_language is not MARKER:
                request.set('AcceptLanguage', old_accept_language)

    security.declarePublic('translate')

    def translate(self, domain, msgid, lang=None, *args, **kw):
        """
        backward compatibility shim over zope.i18n.translate. Please avoid.
        """
        # parameter reordering/mangling necessary
        assert not args
        if lang is not None:
            kw['target_language'] = lang
        return translate(to_unicode(msgid), domain=domain, **kw)
Пример #2
0
class LanguageManager(Tabs):
    """ """

    security = ClassSecurityInfo()

    # TODO For backwards compatibility with Python 2.1 the variable
    # _languages is a tuple.  Change it to a frozenset.
    _languages = ()
    _default_language = None

    ########################################################################
    # API
    ########################################################################
    def get_languages(self):
        """Returns all the object languages.
        """
        return self._languages

    def set_languages(self, languages):
        """Sets the object languages.
        """
        self._languages = tuple(languages)

    def add_language(self, language):
        """Adds a new language.
        """
        if language not in self._languages:
            # Sort the language list, else selected languages
            # can be nearly random.
            new_language_list = tuple(self._languages) + (language, )
            new_language_list = tuple(sorted(new_language_list))
            self._languages = new_language_list

    def del_language(self, language):
        """Removes a language.
        """
        if language in self._languages:
            languages = [x for x in self._languages if x != language]
            self._languages = tuple(languages)

    def get_languages_mapping(self):
        """Returns a list of dictionary, one for each objects language. The
        dictionary contains the language code, its name and a boolean value
        that tells wether the language is the default one or not.
        """
        return [{
            'code': x,
            'name': self.get_language_name(x),
            'default': x == self._default_language
        } for x in self._languages]

    def get_available_languages(self, **kw):
        """Returns the langauges available. For example, a language could be
        considered as available only if there is some data associated to it.

        This method is used by the language negotiation code (see
        'get_selected_language'), sometimes you will want to redefine it in
        your classes.
        """
        return self._languages

    def get_default_language(self):
        """Returns the default language.

        This method is used by the language negotiation code (see
        'get_selected_language'), sometimes you will want to redefine it in
        your classes.

        For example, maybe you will want to define it to return always a
        default language, even when internally it is None.
        """
        return self._default_language

    ########################################################################
    # Web API
    ########################################################################

    # Security settings
    security.declarePublic('get_languages')
    security.declareProtected('Manage languages', 'set_languages')
    security.declareProtected('Manage languages', 'add_language')
    security.declareProtected('Manage languages', 'del_language')
    security.declarePublic('get_languages_mapping')

    security.declarePublic('get_language_name')

    def get_language_name(self, id=None):
        """
        Returns the name of the given language code.

        XXX Kept here for backwards compatibility only
        """
        if id is None:
            id = self.get_default_language()
        language_name = get_language_name(id)
        if language_name == '???':
            return self.get_user_defined_language_name(id) or language_name
        else:
            return language_name

    security.declarePublic('get_available_languages')
    security.declarePublic('get_default_language')

    # XXX Kept here temporarily, further refactoring needed
    security.declarePublic('get_selected_language')

    def get_selected_language(self, **kw):
        """
        Returns the selected language. Here the language negotiation takes
        place.

        Accepts keyword arguments which will be passed to
        'get_available_languages'.
        """
        available_languages = apply(self.get_available_languages, (), kw)

        return lang_negotiator(available_languages) \
               or self.get_default_language()

    ########################################################################
    # ZMI
    ########################################################################
    manage_options = ({
        'action': 'manage_languages',
        'label': u'Languages',
        'help': ('Localizer', 'LM_languages.stx')
    }, )

    def filtered_manage_options(self, REQUEST=None):
        options = Tabs.filtered_manage_options(self, REQUEST=REQUEST)

        # Insert the upgrade form if needed
        if self._needs_upgrade():
            options.insert(
                0, {
                    'action': 'manage_upgradeForm',
                    'label': u'Upgrade',
                    'help': ('Localizer', 'LM_upgrade.stx')
                })

        # Translate the labels
        r = []
        for option in options:
            option = option.copy()
            option['label'] = _(option['label'])
            r.append(option)

        # Ok
        return r

    security.declareProtected('View management screens', 'manage_languages')
    manage_languages = LocalDTMLFile('ui/LM_languages', globals())

    security.declarePublic('get_all_languages')

    def get_all_languages(self):
        """
        Returns all ISO languages, used by 'manage_languages'.
        """
        return get_languages() + self.get_user_defined_languages()

    security.declareProtected('Manage languages', 'manage_addLanguage')

    def manage_addLanguage(self, language, REQUEST=None, RESPONSE=None):
        """ """
        self.add_language(language)

        if RESPONSE is not None:
            RESPONSE.redirect("%s/manage_languages" % REQUEST['URL1'])

    security.declareProtected('Manage languages', 'manage_delLanguages')

    def manage_delLanguages(self, languages, REQUEST, RESPONSE):
        """ """
        for language in languages:
            self.del_language(language)

        RESPONSE.redirect("%s/manage_languages" % REQUEST['URL1'])

    security.declareProtected('Manage languages', 'manage_changeDefaultLang')

    def manage_changeDefaultLang(self, language, REQUEST=None, RESPONSE=None):
        """ """
        self._default_language = language

        if REQUEST is not None:
            RESPONSE.redirect("%s/manage_languages" % REQUEST['URL1'])

    # Unicode support, custom ZMI
    manage_page_header = LocalDTMLFile('ui/manage_page_header', globals())

    ########################################################################
    # Upgrade
    def _needs_upgrade(self):
        return False

    def _upgrade(self):
        pass

    security.declarePublic('need_upgrade')

    def need_upgrade(self):
        """ """
        return self._needs_upgrade()

    security.declareProtected('Manage Access Rules', 'manage_upgradeForm',
                              'manage_upgrade')
    manage_upgradeForm = LocalDTMLFile('ui/LM_upgrade', globals())

    def manage_upgrade(self, REQUEST, RESPONSE):
        """ """
        self._upgrade()
        RESPONSE.redirect('manage_main')

    # Add a feature which allows users to be able to add a new language.
    security.declarePublic('get_user_defined_language_name')

    def get_user_defined_language_name(self, id=None):
        """
        Returns the name of the given user defined language code.
        """
        for language_dict in self.get_user_defined_languages():
            if language_dict['code'] == id:
                return language_dict['name']

    security.declarePublic('get_user_defined_languages')

    def get_user_defined_languages(self):
        user_define_language_dict_list = []
        localizer = getattr(self, 'Localizer', None)
        if localizer is not None:
            for value in getattr(self, 'user_defined_languages', ()):
                splitted_value = value.split(' ', 1)
                if len(splitted_value) == 2:
                    user_define_language_dict_list.append({
                        'name':
                        splitted_value[0].strip(),
                        'code':
                        splitted_value[1].strip(),
                    })
        return user_define_language_dict_list

    def _add_user_defined_language(self, language_name, language_code):
        self.user_defined_languages = (getattr(self, 'user_defined_languages',
                                               ()) +
                                       ('%s %s' %
                                        (language_name, language_code), ))
        self._p_changed = True

    def _del_user_defined_language(self, language_code):
        user_defined_languages = []
        for language_dict in self.get_user_defined_languages():
            if language_dict['code'] != language_code:
                user_defined_languages.append(
                    '%s %s' % (language_dict['name'], language_dict['code']))
        self.user_defined_languages = tuple(user_defined_languages)
        self._p_changed = True
Пример #3
0
from OFS.Folder import Folder
from zLOG import LOG, ERROR, INFO, PROBLEM
from zope.interface import implements
from zope.i18n import translate
from ZPublisher.BeforeTraverse import registerBeforeTraverse, \
     unregisterBeforeTraverse, queryBeforeTraverse, NameCaller

# Import Localizer modules
from interfaces import ILocalizer
from LocalFiles import LocalDTMLFile
from MessageCatalog import MessageCatalog, to_unicode
from utils import lang_negotiator
from LanguageManager import LanguageManager

# Constructors
manage_addLocalizerForm = LocalDTMLFile('ui/Localizer_add', globals())


def manage_addLocalizer(self, title, languages, REQUEST=None, RESPONSE=None):
    """
    Add a new Localizer instance.
    """
    self._setObject('Localizer', Localizer(title, languages))

    if REQUEST is not None:
        RESPONSE.redirect('manage_main')


class Localizer(LanguageManager, Folder):
    """
    The Localizer meta type lets you customize the language negotiation
Пример #4
0
    return url + '?' + '&'.join(params)


# Empty header information for PO files (UTF-8 is the default encoding)
empty_po_header = {
    'last_translator_name': '',
    'last_translator_email': '',
    'language_team': '',
    'charset': 'UTF-8'
}

###########################################################################
# The Message Catalog class, and ZMI constructor
###########################################################################
manage_addMessageCatalogForm = LocalDTMLFile('ui/MC_add', globals())


def manage_addMessageCatalog(self,
                             id,
                             title,
                             languages,
                             sourcelang=None,
                             REQUEST=None):
    """ """
    if sourcelang is None:
        sourcelang = languages[0]

    self._setObject(id, MessageCatalog(id, title, sourcelang, languages))

    if REQUEST is not None:
Пример #5
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)

    security = ClassSecurityInfo()

    POLICY_ADD_FALSE = 0
    POLICY_ADD_TRUE = 1
    POLICY_ADD_LOG = 2

    def __init__(self, id, title, sourcelang, languages):
        self.id = id

        self.title = title
        self.policy = self.POLICY_ADD_TRUE

        # Language Manager data
        self._languages = tuple(languages)
        self._default_language = sourcelang

        # Here the message translations are stored
        self._messages = PersistentMapping()

        # Data for the PO files headers
        self._po_headers = PersistentMapping()
        for lang in self._languages:
            self._po_headers[lang] = empty_po_header

    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)

    def translate(self,
                  msgid,
                  mapping=None,
                  context=None,
                  target_language=None,
                  default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        # BBB support str in mapping by converting to unicode for
        # backward compatibility.
        if mapping:
            mapping = dict([to_unicode(k), to_unicode(v)]
                           for k, v in mapping.iteritems())
        return interpolate(msgstr, mapping)

    #######################################################################
    # Private API
    #######################################################################
    def get_message_key(self, message):
        if message in self._messages:
            return message
        # A message may be stored as unicode or byte string
        encoding = HTTPRequest.default_encoding
        if isinstance(message, unicode):
            message = message.encode(encoding)
        else:
            message = unicode(message, encoding)
        if message in self._messages:
            return message

    def get_translations(self, message):
        message = self.get_message_key(message)
        return self._messages[message]

    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')

    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic('message_exists')

    def message_exists(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        return bool(self.get_message_key(message))

    security.declareProtected('Manage messages', 'message_edit')

    def message_edit(self, message, language, translation, note):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        self._messages[message][language] = translation
        self._messages[message]['note'] = note

    security.declareProtected('Manage messages', 'message_del')

    def message_del(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        del self._messages[message]

    security.declarePublic('gettext')

    def gettext(self, message, lang=None, add=None, default=None):
        """Returns the message translation from the database if available.

        If add=1, add any unknown message to the database.
        If a default is provided, use it instead of the message id
        as a translation for unknown messages.
        """
        if not isinstance(message, basestring):
            raise TypeError('only strings can be translated, not: %r' %
                            (message, ))

        if default is None:
            default = message

        message = message.strip()

        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or to_unicode(message)

        # Add it if it's not in the dictionary
        if add is None:
            add = getattr(self, 'policy', self.POLICY_ADD_TRUE)
        if add != self.POLICY_ADD_FALSE and not self._messages.has_key(
                message) and message:
            if add == self.POLICY_ADD_LOG:
                LOG(
                    'New entry added to message catalog %s :' % self.id, INFO,
                    '%s\n%s' %
                    (message, ''.join(format_list(extract_stack()[:-1]))))
            self._messages[message] = PersistentMapping()

        # Get the string
        if self._messages.has_key(message):
            m = self._messages[message]

            if lang is None:
                # Builds the list of available languages
                # should the empty translations be filtered?
                available_languages = list(self._languages)

                # Imagine that the default language is 'en'. There is no
                # translation from 'en' to 'en' in the message catalog
                # The user has the preferences 'en' and 'nl' in that order
                # The next two lines make certain 'en' is shown, not 'nl'
                if not self._default_language in available_languages:
                    available_languages.append(self._default_language)

                # Get the language!
                lang = lang_negotiator(available_languages)

                # Is it None? use the default
                if lang is None:
                    lang = self._default_language

            if lang is not None:
                return m.get(lang) or default

        return default

    __call__ = gettext

    #######################################################################
    # Management screens
    #######################################################################
    manage_options = (
        {'label': u'Messages', 'action': 'manage_messages',
         'help': ('Localizer', 'MC_messages.stx')},
        {'label': u'Properties', 'action': 'manage_propertiesForm'},
        {'label': u'Import', 'action': 'manage_Import_form',
         'help': ('Localizer', 'MC_importExport.stx')},
        {'label': u'Export', 'action': 'manage_Export_form',
         'help': ('Localizer', 'MC_importExport.stx')}) \
        + LanguageManager.manage_options \
        + SimpleItem.manage_options

    #######################################################################
    # Management screens -- Messages
    #######################################################################
    security.declareProtected('Manage messages', 'manage_messages')
    manage_messages = LocalDTMLFile('ui/MC_messages', globals())

    security.declarePublic('get_namespace')

    def get_namespace(self, REQUEST):
        """For the management interface, allows to filter the messages to
        show.
        """
        # Check whether there are languages or not
        languages = self.get_languages_mapping()
        if not languages:
            return {}

        # Input
        batch_start = REQUEST.get('batch_start', 0)
        batch_size = REQUEST.get('batch_size', 15)
        empty = REQUEST.get('empty', 0)
        regex = REQUEST.get('regex', '')
        message = REQUEST.get('msg', None)

        # Build the namespace
        namespace = {}
        namespace['batch_size'] = batch_size
        namespace['empty'] = empty
        namespace['regex'] = regex

        # The language
        lang = REQUEST.get('lang', None) or languages[0]['code']
        namespace['language'] = lang

        # Filter the messages
        query = regex.strip()
        try:
            query = compile(query)
        except:
            query = compile('')

        messages = []
        for m, t in self._messages.items():
            if query.search(m) and (not empty or not t.get(lang, '').strip()):
                messages.append(m)
        messages.sort(filter_sort)
        # How many messages
        n = len(messages)
        namespace['n_messages'] = n

        # Calculate the start
        while batch_start >= n:
            batch_start = batch_start - batch_size
        if batch_start < 0:
            batch_start = 0
        namespace['batch_start'] = batch_start
        # Select the batch to show
        batch_end = batch_start + batch_size
        messages = messages[batch_start:batch_end]
        # Batch links
        namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size,
                                        batch_size, regex, lang, empty)
        namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size,
                                    batch_size, regex, lang, empty)

        # Get the message
        message_encoded = None
        translations = {}
        if message is None:
            if messages:
                message = messages[0]
                translations = self.get_translations(message)
                message = to_unicode(message)
                message_encoded = message_encode(message)
        else:
            message_encoded = message
            message = message_decode(message_encoded)
            translations = self.get_translations(message)
            message = to_unicode(message)
        namespace['message'] = message
        namespace['message_encoded'] = message_encoded
        namespace['translations'] = translations
        namespace['translation'] = translations.get(lang, '')
        namespace['note'] = translations.get('note', '')

        # Calculate the current message
        namespace['messages'] = []
        for x in messages:
            x = to_unicode(x)
            x_encoded = message_encode(x)
            url = get_url(REQUEST.URL,
                          batch_start,
                          batch_size,
                          regex,
                          lang,
                          empty,
                          msg=x_encoded)
            namespace['messages'].append({
                'message': x,
                'message_encoded': x_encoded,
                'current': x == message,
                'url': url
            })

        # The languages
        for language in languages:
            code = language['code']
            language['name'] = _(language['name'], language=code)
            language['url'] = get_url(REQUEST.URL,
                                      batch_start,
                                      batch_size,
                                      regex,
                                      code,
                                      empty,
                                      msg=message_encoded)
        namespace['languages'] = languages

        return namespace

    security.declareProtected('Manage messages', 'manage_editMessage')

    def manage_editMessage(self, message, language, translation, note, REQUEST,
                           RESPONSE):
        """Modifies a message.
        """
        message_encoded = message
        message = message_decode(message_encoded)
        message_key = self.get_message_key(message)
        self.message_edit(message_key, language, translation, note)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'],
                      REQUEST['batch_size'],
                      REQUEST['regex'],
                      REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      msg=message_encoded,
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)

    security.declareProtected('Manage messages', 'manage_delMessage')

    def manage_delMessage(self, message, REQUEST, RESPONSE):
        """ """
        message = message_decode(message)
        message_key = self.get_message_key(message)
        self.message_del(message_key)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'],
                      REQUEST['batch_size'],
                      REQUEST['regex'],
                      REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)

    #######################################################################
    # Management screens -- Properties
    # Management screens -- Import/Export
    # FTP access
    #######################################################################
    security.declareProtected('View management screens',
                              'manage_propertiesForm')
    manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals())

    security.declareProtected('View management screens', 'manage_properties')

    def manage_properties(self, title, policy, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title
        self.policy = int(policy)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')

    # Properties management screen
    security.declareProtected('View management screens', 'get_po_header')

    def get_po_header(self, lang):
        """ """
        # For backwards compatibility
        if not hasattr(aq_base(self), '_po_headers'):
            self._po_headers = PersistentMapping()

        return self._po_headers.get(lang, empty_po_header)

    security.declareProtected('View management screens', 'update_po_header')

    def update_po_header(self,
                         lang,
                         last_translator_name=None,
                         last_translator_email=None,
                         language_team=None,
                         charset=None,
                         REQUEST=None,
                         RESPONSE=None):
        """ """
        header = self.get_po_header(lang)

        if last_translator_name is None:
            last_translator_name = header['last_translator_name']

        if last_translator_email is None:
            last_translator_email = header['last_translator_email']

        if language_team is None:
            language_team = header['language_team']

        if charset is None:
            charset = header['charset']

        header = {
            'last_translator_name': last_translator_name,
            'last_translator_email': last_translator_email,
            'language_team': language_team,
            'charset': charset
        }

        self._po_headers[lang] = header

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')

    security.declareProtected('View management screens', 'manage_Import_form')
    manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals())

    security.declarePublic('get_policies')

    def get_policies(self):
        """ """
        if not hasattr(self, 'policy'):
            self.policy = self.POLICY_ADD_TRUE
        policies = [
            [self.POLICY_ADD_FALSE, "Never add new entries automatically"],
            [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"],
            [
                self.POLICY_ADD_LOG,
                "Add new entries automatically if missing and log the backtrace"
            ],
        ]
        return policies

    security.declarePublic('get_charsets')

    def get_charsets(self):
        """ """
        return charsets[:]

    security.declarePublic('manage_export')

    def manage_export(self, x, REQUEST=None, RESPONSE=None):
        """Exports the content of the message catalog either to a template
        file (locale.pot) or to an language specific PO file (<x>.po).
        """
        # Get the PO header info
        header = self.get_po_header(x)
        last_translator_name = header['last_translator_name']
        last_translator_email = header['last_translator_email']
        language_team = header['language_team']
        charset = header['charset']

        # PO file header, empty message.
        po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time()))
        pot_creation_date = po_revision_date
        last_translator = '%s <%s>' % (last_translator_name,
                                       last_translator_email)

        if x == 'locale.pot':
            language_team = 'LANGUAGE <*****@*****.**>'
        else:
            language_team = '%s <%s>' % (x, language_team)

        r = [
            'msgid ""',
            'msgstr "Project-Id-Version: %s\\n"' % self.title,
            '"POT-Creation-Date: %s\\n"' % pot_creation_date,
            '"PO-Revision-Date: %s\\n"' % po_revision_date,
            '"Last-Translator: %s\\n"' % last_translator,
            '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"',
            '"Content-Type: text/plain; charset=%s\\n"' % charset,
            '"Content-Transfer-Encoding: 8bit\\n"', '', ''
        ]

        # Get the messages, and perhaps its translations.
        # Convert keys to unicode for proper sorting.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[to_unicode(k, encoding=charset)] = u""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                k = to_unicode(k, encoding=charset)
                d[k] = to_unicode(v.get(x, ""), encoding=charset)

        # Generate the file
        def backslashescape(x):
            x = to_str(x)
            quote_esc = compile(r'"')
            x = quote_esc.sub('\\"', x)

            trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')]
            for a, b in trans:
                x = x.replace(a, b)

            return x

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for k in dkeys:
            r.append('msgid "%s"' % backslashescape(k))
            v = d[k]
            r.append('msgstr "%s"' % backslashescape(v))
            r.append('')

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type', 'application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'inline;filename=%s' % filename)

        return '\n'.join(r)

    security.declareProtected('Manage messages', 'po_import')

    def po_import(self, lang, data):
        """ """
        messages = self._messages

        # Load the data
        po = polib.pofile(data)
        encoding = to_str(po.encoding)
        for entry in po:
            msgid = to_unicode(entry.msgid, encoding=encoding)
            if msgid:
                msgstr = to_unicode(entry.msgstr or '', encoding=encoding)
                translation_map = messages.get(msgid)
                if translation_map is None:
                    # convert old non-unicode translations if they exist:
                    translation_map = messages.pop(self.get_message_key(msgid),
                                                   None)
                    if translation_map is None:
                        translation_map = PersistentMapping()
                    messages[msgid] = translation_map
                translation_map[lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=encoding)

    security.declareProtected('Manage messages', 'manage_import')

    def manage_import(self, lang, file, REQUEST=None, RESPONSE=None):
        """ """
        # XXX For backwards compatibility only, use "po_import" instead.
        if isinstance(file, str):
            content = file
        else:
            content = file.read()

        self.po_import(lang, content)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_messages')

    def objectItems(self, spec=None):
        """ """
        for lang in self._languages:
            if not hasattr(aq_base(self), lang):
                self._setObject(lang, POFile(lang))

        r = MessageCatalog.inheritedAttribute('objectItems')(self, spec)
        return r

    #######################################################################
    # TMX support
    security.declareProtected('View management screens', 'manage_Export_form')
    manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals())

    #######################################################################
    # Backwards compatibility (XXX)
    #######################################################################

    security.declarePublic('hasmsg')
    hasmsg = message_exists
    security.declarePublic('hasLS')
    hasLS = message_exists  # CMFLocalizer uses it
Пример #6
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)

    security = ClassSecurityInfo()

    def __init__(self, id, title, sourcelang, languages):
        self.id = id

        self.title = title

        # Language Manager data
        self._languages = tuple(languages)
        self._default_language = sourcelang

        # Here the message translations are stored
        self._messages = PersistentMapping()

        # Data for the PO files headers
        self._po_headers = PersistentMapping()
        for lang in self._languages:
            self._po_headers[lang] = empty_po_header

    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)

    def translate(self,
                  msgid,
                  mapping=None,
                  context=None,
                  target_language=None,
                  default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        return interpolate(msgstr, mapping)

    #######################################################################
    # Private API
    #######################################################################
    def get_message_key(self, message):
        if message in self._messages:
            return message
        # A message may be stored as unicode or byte string
        encoding = HTTPRequest.default_encoding
        if isinstance(message, unicode):
            message = message.encode(encoding)
        else:
            message = unicode(message, encoding)
        if message in self._messages:
            return message

    def get_translations(self, message):
        message = self.get_message_key(message)
        return self._messages[message]

    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')

    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic('message_exists')

    def message_exists(self, message):
        """ """
        return self._messages.has_key(message)

    security.declareProtected('Manage messages', 'message_edit')

    def message_edit(self, message, language, translation, note):
        """ """
        self._messages[message][language] = translation
        self._messages[message]['note'] = note

    security.declareProtected('Manage messages', 'message_del')

    def message_del(self, message):
        """ """
        del self._messages[message]

    security.declarePublic('gettext')

    def gettext(self, message, lang=None, add=1, default=None):
        """Returns the message translation from the database if available.

        If add=1, add any unknown message to the database.
        If a default is provided, use it instead of the message id
        as a translation for unknown messages.
        """
        if not isinstance(message, (str, unicode)):
            raise TypeError, 'only strings can be translated.'

        message = message.strip()

        if default is None:
            default = message

        # Add it if it's not in the dictionary
        if add and not self._messages.has_key(message) and message:
            self._messages[message] = PersistentMapping()

        # Get the string
        if self._messages.has_key(message):
            m = self._messages[message]

            if lang is None:
                # Builds the list of available languages
                # should the empty translations be filtered?
                available_languages = list(self._languages)

                # Imagine that the default language is 'en'. There is no
                # translation from 'en' to 'en' in the message catalog
                # The user has the preferences 'en' and 'nl' in that order
                # The next two lines make certain 'en' is shown, not 'nl'
                if not self._default_language in available_languages:
                    available_languages.append(self._default_language)

                # Get the language!
                lang = lang_negotiator(available_languages)

                # Is it None? use the default
                if lang is None:
                    lang = self._default_language

            if lang is not None:
                return m.get(lang) or default

        return default

    __call__ = gettext

    #######################################################################
    # Management screens
    #######################################################################
    manage_options = (
        {'label': u'Messages', 'action': 'manage_messages',
         'help': ('Localizer', 'MC_messages.stx')},
        {'label': u'Properties', 'action': 'manage_propertiesForm'},
        {'label': u'Import', 'action': 'manage_Import_form',
         'help': ('Localizer', 'MC_importExport.stx')},
        {'label': u'Export', 'action': 'manage_Export_form',
         'help': ('Localizer', 'MC_importExport.stx')}) \
        + LanguageManager.manage_options \
        + SimpleItem.manage_options

    #######################################################################
    # Management screens -- Messages
    #######################################################################
    security.declareProtected('Manage messages', 'manage_messages')
    manage_messages = LocalDTMLFile('ui/MC_messages', globals())

    security.declarePublic('get_namespace')

    def get_namespace(self, REQUEST):
        """For the management interface, allows to filter the messages to
        show.
        """
        # Check whether there are languages or not
        languages = self.get_languages_mapping()
        if not languages:
            return {}

        # Input
        batch_start = REQUEST.get('batch_start', 0)
        batch_size = REQUEST.get('batch_size', 15)
        empty = REQUEST.get('empty', 0)
        regex = REQUEST.get('regex', '')
        message = REQUEST.get('msg', None)

        # Build the namespace
        namespace = {}
        namespace['batch_size'] = batch_size
        namespace['empty'] = empty
        namespace['regex'] = regex

        # The language
        lang = REQUEST.get('lang', None) or languages[0]['code']
        namespace['language'] = lang

        # Filter the messages
        query = regex.strip()
        try:
            query = compile(query)
        except:
            query = compile('')

        messages = []
        for m, t in self._messages.items():
            if query.search(m) and (not empty or not t.get(lang, '').strip()):
                messages.append(m)
        messages.sort(filter_sort)
        # How many messages
        n = len(messages)
        namespace['n_messages'] = n

        # Calculate the start
        while batch_start >= n:
            batch_start = batch_start - batch_size
        if batch_start < 0:
            batch_start = 0
        namespace['batch_start'] = batch_start
        # Select the batch to show
        batch_end = batch_start + batch_size
        messages = messages[batch_start:batch_end]
        # Batch links
        namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size,
                                        batch_size, regex, lang, empty)
        namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size,
                                    batch_size, regex, lang, empty)

        # Get the message
        message_encoded = None
        translations = {}
        if message is None:
            if messages:
                message = messages[0]
                translations = self.get_translations(message)
                message = to_unicode(message)
                message_encoded = message_encode(message)
        else:
            message_encoded = message
            message = message_decode(message_encoded)
            translations = self.get_translations(message)
            message = to_unicode(message)
        namespace['message'] = message
        namespace['message_encoded'] = message_encoded
        namespace['translations'] = translations
        namespace['translation'] = translations.get(lang, '')
        namespace['note'] = translations.get('note', '')

        # Calculate the current message
        namespace['messages'] = []
        for x in messages:
            x = to_unicode(x)
            x_encoded = message_encode(x)
            url = get_url(REQUEST.URL,
                          batch_start,
                          batch_size,
                          regex,
                          lang,
                          empty,
                          msg=x_encoded)
            namespace['messages'].append({
                'message': x,
                'message_encoded': x_encoded,
                'current': x == message,
                'url': url
            })

        # The languages
        for language in languages:
            code = language['code']
            language['name'] = _(language['name'], language=code)
            language['url'] = get_url(REQUEST.URL,
                                      batch_start,
                                      batch_size,
                                      regex,
                                      code,
                                      empty,
                                      msg=message_encoded)
        namespace['languages'] = languages

        return namespace

    security.declareProtected('Manage messages', 'manage_editMessage')

    def manage_editMessage(self, message, language, translation, note, REQUEST,
                           RESPONSE):
        """Modifies a message.
        """
        message_encoded = message
        message = message_decode(message_encoded)
        message_key = self.get_message_key(message)
        self.message_edit(message_key, language, translation, note)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'],
                      REQUEST['batch_size'],
                      REQUEST['regex'],
                      REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      msg=message_encoded,
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)

    security.declareProtected('Manage messages', 'manage_delMessage')

    def manage_delMessage(self, message, REQUEST, RESPONSE):
        """ """
        message = message_decode(message)
        message_key = self.get_message_key(message)
        self.message_del(message_key)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'],
                      REQUEST['batch_size'],
                      REQUEST['regex'],
                      REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)

    #######################################################################
    # Management screens -- Properties
    # Management screens -- Import/Export
    # FTP access
    #######################################################################
    security.declareProtected('View management screens',
                              'manage_propertiesForm')
    manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals())

    security.declareProtected('View management screens', 'manage_properties')

    def manage_properties(self, title, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')

    # Properties management screen
    security.declareProtected('View management screens', 'get_po_header')

    def get_po_header(self, lang):
        """ """
        # For backwards compatibility
        if not hasattr(aq_base(self), '_po_headers'):
            self._po_headers = PersistentMapping()

        return self._po_headers.get(lang, empty_po_header)

    security.declareProtected('View management screens', 'update_po_header')

    def update_po_header(self,
                         lang,
                         last_translator_name=None,
                         last_translator_email=None,
                         language_team=None,
                         charset=None,
                         REQUEST=None,
                         RESPONSE=None):
        """ """
        header = self.get_po_header(lang)

        if last_translator_name is None:
            last_translator_name = header['last_translator_name']

        if last_translator_email is None:
            last_translator_email = header['last_translator_email']

        if language_team is None:
            language_team = header['language_team']

        if charset is None:
            charset = header['charset']

        header = {
            'last_translator_name': last_translator_name,
            'last_translator_email': last_translator_email,
            'language_team': language_team,
            'charset': charset
        }

        self._po_headers[lang] = header

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')

    security.declareProtected('View management screens', 'manage_Import_form')
    manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals())

    security.declarePublic('get_charsets')

    def get_charsets(self):
        """ """
        return charsets[:]

    security.declarePublic('manage_export')

    def manage_export(self, x, REQUEST=None, RESPONSE=None):
        """Exports the content of the message catalog either to a template
        file (locale.pot) or to an language specific PO file (<x>.po).
        """
        # Get the PO header info
        header = self.get_po_header(x)
        last_translator_name = header['last_translator_name']
        last_translator_email = header['last_translator_email']
        language_team = header['language_team']
        charset = header['charset']

        # PO file header, empty message.
        po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time()))
        pot_creation_date = po_revision_date
        last_translator = '%s <%s>' % (last_translator_name,
                                       last_translator_email)

        if x == 'locale.pot':
            language_team = 'LANGUAGE <*****@*****.**>'
        else:
            language_team = '%s <%s>' % (x, language_team)

        r = [
            'msgid ""',
            'msgstr "Project-Id-Version: %s\\n"' % self.title,
            '"POT-Creation-Date: %s\\n"' % pot_creation_date,
            '"PO-Revision-Date: %s\\n"' % po_revision_date,
            '"Last-Translator: %s\\n"' % last_translator,
            '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"',
            '"Content-Type: text/plain; charset=%s\\n"' % charset,
            '"Content-Transfer-Encoding: 8bit\\n"', '', ''
        ]

        # Get the messages, and perhaps its translations.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[k] = ""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                try:
                    d[k] = v[x]
                except KeyError:
                    d[k] = ""

        # Generate the file
        def backslashescape(x):
            quote_esc = compile(r'"')
            x = quote_esc.sub('\\"', x)

            trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')]
            for a, b in trans:
                x = x.replace(a, b)

            return x

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for k in dkeys:
            r.append('msgid "%s"' % backslashescape(k))
            v = d[k]
            r.append('msgstr "%s"' % backslashescape(v))
            r.append('')

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type', 'application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'inline;filename=%s' % filename)

        r2 = []
        for x in r:
            if isinstance(x, unicode):
                r2.append(x.encode(charset))
            else:
                r2.append(x)

        return '\n'.join(r2)

    security.declareProtected('Manage messages', 'po_import')

    def po_import(self, lang, data):
        """ """
        messages = self._messages

        # Load the data
        po = itools.gettext.POFile(string=data)
        for msgid in po.get_msgids():
            # TODO Keep the context if any
            _context, msgid = msgid
            if msgid:
                msgstr = po.get_msgstr(msgid) or ''
                if not messages.has_key(msgid):
                    messages[msgid] = PersistentMapping()
                messages[msgid][lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=po.get_encoding())

    security.declareProtected('Manage messages', 'manage_import')

    def manage_import(self, lang, file, REQUEST=None, RESPONSE=None):
        """ """
        # XXX For backwards compatibility only, use "po_import" instead.
        if isinstance(file, str):
            content = file
        else:
            content = file.read()

        self.po_import(lang, content)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_messages')

    def objectItems(self, spec=None):
        """ """
        for lang in self._languages:
            if not hasattr(aq_base(self), lang):
                self._setObject(lang, POFile(lang))

        r = MessageCatalog.inheritedAttribute('objectItems')(self, spec)
        return r

    #######################################################################
    # TMX support
    security.declareProtected('View management screens', 'manage_Export_form')
    manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals())

    security.declareProtected('Manage messages', 'tmx_export')

    def tmx_export(self, REQUEST, RESPONSE=None):
        """Exports the content of the message catalog to a TMX file
        """
        src_lang = self._default_language

        # Get the header info
        header = self.get_po_header(src_lang)
        charset = header['charset']

        # Init the TMX handler
        tmx = TMXFile()
        tmx.header['creationtool'] = u'Localizer'
        tmx.header['creationtoolversion'] = u'1.x'
        tmx.header['datatype'] = u'plaintext'
        tmx.header['segtype'] = u'paragraph'
        tmx.header['adminlang'] = src_lang
        tmx.header['srclang'] = src_lang
        tmx.header['o-encoding'] = u'%s' % charset.lower()

        # handle messages
        for msgkey, transunit in self._messages.items():
            unit = TMXUnit({})
            for lang in transunit.keys():
                if lang != 'note':
                    sentence = Sentence({'lang': lang})
                    sentence.text = transunit[lang]
                    unit.msgstr[lang] = sentence

            if src_lang not in transunit.keys():
                sentence = Sentence({'lang': src_lang})
                sentence.text = msgkey
                unit.msgstr[src_lang] = sentence

            if transunit.has_key('note'):
                note = TMXNote(transunit.get('note'))
                unit.notes.append(note)
            tmx.messages[msgkey] = unit

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type', 'application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'attachment; filename="%s.tmx"' % self.id)

        return tmx.to_str()

    security.declareProtected('Manage messages', 'tmx_import')

    def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None):
        """Imports a TMX level 1 file.
        """
        try:
            data = file.read()
            tmx = TMXFile(string=data)
        except:
            return MessageDialog(
                title='Parse error',
                message=_('impossible to parse the file'),
                action='manage_Import_form',
            )

        num_notes = 0
        num_trans = 0

        if howmuch == 'clear':
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()
            self._default_language = tmx.get_srclang()

        for id, msg in tmx.messages.items():
            if not self._messages.has_key(id) and howmuch == 'existing':
                continue
            msg.msgstr.pop(self._default_language)
            if not self._messages.has_key(id):
                self._messages[id] = {}
            for lang in msg.msgstr.keys():
                # normalize the languageTag and extract the core
                (core, local) = LanguageTag.decode(lang)
                lang = LanguageTag.encode((core, local))
                if lang not in self._languages:
                    self._languages += (lang, )
                if msg.msgstr[lang].text:
                    self._messages[id][lang] = msg.msgstr[lang].text
                    if core != lang and core != self._default_language:
                        if core not in self._languages:
                            self._languages += (core, )
                        if not msg.msgstr.has_key(core):
                            self._messages[id][core] = msg.msgstr[lang].text
            if msg.notes:
                ns = [m.text for m in msg.notes]
                self._messages[id]['note'] = u' '.join(ns)
                num_notes += 1
            num_trans += 1

        if REQUEST is not None:
            message = _(u'Imported %d messages and %d notes')
            return MessageDialog(title=_(u'Messages imported'),
                                 message=message % (num_trans, num_notes),
                                 action='manage_messages')

    #######################################################################
    # Backwards compatibility (XXX)
    #######################################################################

    hasmsg = message_exists
    hasLS = message_exists  # CMFLocalizer uses it

    security.declareProtected('Manage messages', 'xliff_export')

    def xliff_export(self,
                     dst_lang,
                     export_all=1,
                     REQUEST=None,
                     RESPONSE=None):
        """Exports the content of the message catalog to an XLIFF file
        """
        from DateTime import DateTime

        src_lang = self._default_language
        export_all = int(export_all)

        # Init the XLIFF handler
        xliff = XLFFile()
        # Add the translation units
        original = '/%s' % self.absolute_url(1)
        for msgkey, transunit in self._messages.items():
            target = transunit.get(dst_lang, '')
            # If 'export_all' is true export all messages, otherwise export
            # only untranslated messages
            if export_all or not target:
                unit = xliff.add_unit(original, msgkey, None)
                unit.attributes['id'] = md5text(msgkey)
                if target:
                    unit.target = target
                # Add note
                note = transunit.get('note')
                if note:
                    unit.notes.append(XLFNote(note))

        # build the data-stucture for the File tag
        file = xliff.files[original]
        attributes = file.attributes
        attributes['original'] = original
        attributes['product-name'] = u'Localizer'
        attributes['product-version'] = u'1.1.x'
        attributes['data-type'] = u'plaintext'
        attributes['source-language'] = src_lang
        attributes['target-language'] = dst_lang
        attributes['date'] = DateTime().HTML4()

        # Serialize
        xliff = xliff.to_str()
        # Set response headers
        RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8')
        filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang)
        RESPONSE.setHeader('Content-Disposition',
                           'attachment; filename="%s"' % filename)
        # Ok
        return xliff

    security.declareProtected('Manage messages', 'xliff_import')

    def xliff_import(self, howmuch, file, REQUEST=None):
        """XLIFF is the XML Localization Interchange File Format designed by a
        group of software providers.  It is specified by www.oasis-open.org
        """
        try:
            data = file.read()
            xliff = XLFFile(string=data)
        except:
            return MessageDialog(
                title='Parse error',
                message=_('impossible to parse the file'),
                action='manage_Import_form',
            )

        num_notes = 0
        num_trans = 0
        (file_ids, sources, targets) = xliff.get_languages()

        if howmuch == 'clear':
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()
            self._default_language = sources[0]

        # update languages
        if len(sources) > 1 or sources[0] != self._default_language:
            return MessageDialog(
                title='Language error',
                message=_('incompatible language sources'),
                action='manage_Import_form',
            )
        for lang in targets:
            if lang != self._default_language and lang not in self._languages:
                self._languages += (lang, )

        # get messages
        for file in xliff.files:
            cur_target = file.attributes.get('target-language', '')
            for msg in file.body.keys():
                if not self._messages.has_key(msg) and howmuch == 'existing':
                    pass
                else:
                    if not self._messages.has_key(msg):
                        self._messages[msg] = {}

                    if cur_target and file.body[msg].target:
                        self._messages[msg][cur_target] = file.body[msg].target
                        num_trans += 1
                    if file.body[msg].notes:
                        ns = [n.text for n in file.body[msg].notes]
                        comment = ' '.join(ns)
                        self._messages[msg]['note'] = comment
                        num_notes += 1

        if REQUEST is not None:
            return MessageDialog(
                title = _(u'Messages imported'),
                message = (_(u'Imported %d messages and %d notes to %s') % \
                           (num_trans, num_notes, ' '.join(targets))),
                action = 'manage_messages')
Пример #7
0
class Localizer(LanguageManager, Folder):
    """
    The Localizer meta type lets you customize the language negotiation
    policy.
    """

    meta_type = 'Localizer'
    implements(ILocalizer)

    id = 'Localizer'

    _properties = ({
        'id': 'title',
        'type': 'string'
    }, {
        'id': 'accept_methods',
        'type': 'tokens'
    })

    accept_methods = ('accept_path', 'accept_cookie', 'accept_url')

    security = ClassSecurityInfo()

    manage_options = \
        (Folder.manage_options[0],) \
        + LanguageManager.manage_options \
        + Folder.manage_options[1:]

    def __init__(self, title, languages):
        self.title = title

        self._languages = languages
        self._default_language = languages[0]

    #######################################################################
    # API / Private
    #######################################################################
    def _getCopy(self, container):
        return Localizer.inheritedAttribute('_getCopy')(self, container)

    def _needs_upgrade(self):
        return not self.hooked()

    def _upgrade(self):
        # Upgrade to 0.9
        if not self.hooked():
            self.manage_hook(1)

    #######################################################################
    # API / Public
    #######################################################################

    # Get some data
    security.declarePublic('get_supported_languages')

    def get_supported_languages(self):
        """
        Get the supported languages, that is the languages that the
        are being working so the site is or will provide to the public.
        """
        return self._languages

    security.declarePublic('get_selected_language')

    def get_selected_language(self):
        """ """
        return lang_negotiator(self._languages) \
               or self._default_language

    # Hooking the traversal machinery
    # Fix this! a new permission needed?


##    security.declareProtected('View management screens', 'manage_hookForm')
##    manage_hookForm = LocalDTMLFile('ui/Localizer_hook', globals())
##    security.declareProtected('Manage properties', 'manage_hook')
    security.declarePrivate('manage_hook')

    def manage_hook(self, hook=0):
        """ """
        if hook != self.hooked():
            if hook:
                hook = NameCaller(self.id)
                registerBeforeTraverse(aq_parent(self), hook, self.meta_type)
            else:
                unregisterBeforeTraverse(aq_parent(self), self.meta_type)

    security.declarePublic('hooked')

    def hooked(self):
        """ """
        if queryBeforeTraverse(aq_parent(self), self.meta_type):
            return 1
        return 0

    # New code to control the language policy
    def accept_cookie(self, accept_language):
        """Add the language from a cookie."""
        lang = self.REQUEST.cookies.get('LOCALIZER_LANGUAGE', None)
        if lang is not None:
            accept_language.set(lang, 2.0)

    def accept_path(self, accept_language):
        """Add the language from the path."""
        stack = self.REQUEST['TraversalRequestNameStack']
        if stack and (stack[-1] in self._languages):
            lang = stack.pop()
            accept_language.set(lang, 3.0)

    def accept_url(self, accept_language):
        """Add the language from the URL."""
        lang = self.REQUEST.form.get('LOCALIZER_LANGUAGE')
        if lang is not None:
            accept_language.set(lang, 2.0)

    def __call__(self, container, REQUEST):
        """Hooks the traversal path."""
        try:
            accept_language = REQUEST['AcceptLanguage']
        except KeyError:
            return

        for id in self.accept_methods:
            try:
                method = getattr(self, id)
                method(accept_language)
            except:
                LOG(self.meta_type, PROBLEM,
                    'method "%s" raised an exception.' % id)

    # Changing the language, useful snippets
    security.declarePublic('get_languages_map')

    def get_languages_map(self):
        """
        Return a list of dictionaries, each dictionary has the language
        id, its title and a boolean value to indicate wether it's the
        user preferred language, for example:

          [{'id': 'en', 'title': 'English', 'selected': 1}]

        Used in changeLanguageForm.
        """
        # For now only LPM instances are considered to be containers of
        # multilingual data.
        try:
            ob = self.getLocalPropertyManager()
        except AttributeError:
            ob = self

        ob_language = ob.get_selected_language()
        ob_languages = ob.get_available_languages()

        langs = []
        for x in ob_languages:
            langs.append({
                'id': x,
                'title': get_language_name(x),
                'selected': x == ob_language
            })

        return langs

    security.declarePublic('changeLanguage')
    changeLanguageForm = LocalDTMLFile('ui/changeLanguageForm', globals())

    def changeLanguage(self, lang, goto=None, expires=None):
        """ """
        request = self.REQUEST
        response = request.RESPONSE

        # Changes the cookie (it could be something different)
        parent = aq_parent(self)
        path = parent.absolute_url()[len(request['SERVER_URL']):] or '/'
        if expires is None:
            response.setCookie('LOCALIZER_LANGUAGE', lang, path=path)
        else:
            response.setCookie('LOCALIZER_LANGUAGE',
                               lang,
                               path=path,
                               expires=unquote(expires))
        # Comes back
        if goto is None:
            goto = request['HTTP_REFERER']

        response.redirect(goto)
Пример #8
0
class LocalPropertyManager(LanguageManager, LocalAttributesBase):
    """
    Mixin class that allows to manage localized properties.
    Somewhat similar to OFS.PropertyManager.
    """

    security = ClassSecurityInfo()

    # Metadata for local properties
    # Example: ({'id': 'title', 'type': 'string'},)
    _local_properties_metadata = ()

    # Local properties are stored here
    # Example: {'title': {'en': ('Title', timestamp), 'es': ('Títul', timestamp)}}
    _local_properties = {}

    # Useful to find or index all LPM instances
    isLocalPropertyManager = 1

    def getLocalPropertyManager(self):
        """
        Returns the instance, useful to get the object through acquisition.
        """
        return self

    manage_options = (
        {'action': 'manage_localPropertiesForm',
         'label': u'Local properties',
         'help': ('Localizer', 'LPM_properties.stx')},
        {'action': 'manage_transPropertiesForm',
         'label': u'Translate properties',
         'help': ('Localizer', 'LPM_translate.stx')}) \
        + LanguageManager.manage_options

    security.declarePublic('hasLocalProperty')

    def hasLocalProperty(self, id):
        """Return true if object has a property 'id'"""
        for property in self._local_properties_metadata:
            if property['id'] == id:
                return 1
        return 0

    security.declareProtected('View management screens',
                              'manage_localPropertiesForm')
    manage_localPropertiesForm = LocalDTMLFile('ui/LPM_properties', globals())

    security.declareProtected('View management screens',
                              'manage_transPropertiesForm')
    manage_transPropertiesForm = LocalDTMLFile('ui/LPM_translations',
                                               globals())

    security.declareProtected('Manage properties', 'set_localpropvalue')

    def set_localpropvalue(self, id, lang, value):
        # Get previous value
        old_value, timestamp = self.get_localproperty(id, lang)
        if old_value is None:
            old_value = ''
        # Update value only if it is different
        if value != old_value:
            properties = self._local_properties.copy()
            if not properties.has_key(id):
                properties[id] = {}

            properties[id][lang] = (value, time())

            self._local_properties = properties

    def get_localproperty(self, name, language):
        if name not in self._local_properties:
            return None, None
        property = self._local_properties[name]
        if language not in property:
            return None, None
        value = property[language]
        if isinstance(value, tuple):
            return value
        return value, None

    security.declareProtected('Manage properties', 'set_localproperty')

    def set_localproperty(self, id, type, lang=None, value=None):
        """Adds a new local property"""
        if not self.hasLocalProperty(id):
            self._local_properties_metadata += ({'id': id, 'type': type}, )
            setattr(self, id, LocalAttribute(id))

        if lang is not None:
            self.set_localpropvalue(id, lang, value)

    security.declareProtected('Manage properties', 'del_localproperty')

    def del_localproperty(self, id):
        """Deletes a property"""
        # Update properties metadata
        p = [x for x in self._local_properties_metadata if x['id'] != id]
        self._local_properties_metadata = tuple(p)

        # delete attribute
        try:
            del self._local_properties[id]
        except KeyError:
            pass

        try:
            delattr(self, id)
        except KeyError:
            pass

    security.declareProtected('Manage properties', 'manage_addLocalProperty')

    def manage_addLocalProperty(self, id, type, REQUEST=None, RESPONSE=None):
        """Adds a new local property"""
        self.set_localproperty(id, type)

        if RESPONSE is not None:
            url = "%s/manage_localPropertiesForm?manage_tabs_message=Saved changes." % REQUEST[
                'URL1']
            RESPONSE.redirect(url)

    security.declareProtected('Manage properties', 'manage_editLocalProperty')

    def manage_editLocalProperty(self, REQUEST, RESPONSE=None):
        """Edit a property"""
        def_lang = self.get_default_language()

        form = REQUEST.form
        for prop in self.getLocalProperties():
            name = prop['id']
            if form.has_key(name):
                value = form[name].strip()
                self.set_localpropvalue(name, def_lang, value)

        if REQUEST is not None:
            url = "%s/%s?manage_tabs_message=Saved changes." \
                  % (REQUEST['URL1'], REQUEST['destination'])
            REQUEST.RESPONSE.redirect(url)

    security.declareProtected('Manage properties', 'manage_delLocalProperty')

    def manage_delLocalProperty(self, ids=[], REQUEST=None, RESPONSE=None):
        """Deletes a property"""
        for id in ids:
            self.del_localproperty(id)

        if RESPONSE is not None:
            url = "%s/manage_localPropertiesForm?manage_tabs_message=Saved changes." % REQUEST[
                'URL1']
            RESPONSE.redirect(url)

    security.declareProtected('Manage properties', 'manage_transLocalProperty')

    def manage_transLocalProperty(self,
                                  id,
                                  code,
                                  value,
                                  REQUEST,
                                  RESPONSE=None):
        """Translate a property."""
        self.set_localpropvalue(id, code, value.strip())

        if RESPONSE is not None:
            url = "%s/%s?lang=%s&prop=%s&manage_tabs_message=Saved changes." \
                  % (REQUEST['URL1'], REQUEST['destination'], code, id)
            RESPONSE.redirect(url)

    security.declareProtected('Manage properties', 'is_obsolete')

    def is_obsolete(self, prop, lang):
        default_language = self.get_default_language()

        value, t0 = self.get_localproperty(prop, default_language)
        value, t1 = self.get_localproperty(prop, lang)

        if t0 is None:
            return False
        if t1 is None:
            return True
        return t1 < t0

    security.declarePublic('getTargetLanguages')

    def get_targetLanguages(self):
        """Get all languages except the default one."""
        def_lang = self.get_default_language()
        all_langs = self.get_languages_mapping()
        for record in all_langs:
            if def_lang == record['code']:
                all_langs.remove(record)
        return all_langs

    security.declarePublic('getLocalProperties')

    def getLocalProperties(self):
        """Returns a copy of the properties metadata."""
        return tuple([x.copy() for x in self._local_properties_metadata])

    security.declarePublic('getLocalAttribute')

    def getLocalAttribute(self, id, lang=None):
        """Returns a local property"""
        # No language, look for the first non-empty available version
        if lang is None:
            lang = self.get_selected_language(property=id)

        value, timestamp = self.get_localproperty(id, lang)
        if value is None:
            return ''
        return value

    # Languages logic
    security.declarePublic('get_available_languages')

    def get_available_languages(self, **kw):
        """ """
        languages = self.get_languages()
        id = kw.get('property', None)
        if id is None:
            # Is this thing right??
            return languages
        else:
            if id in self._local_properties:
                property = self._local_properties[id]
                return [x for x in languages if property.get(x, None)]
            else:
                return []

    security.declarePublic('get_default_language')

    def get_default_language(self):
        """ """
        if self._default_language:
            return self._default_language

        languages = self.get_languages()
        if languages:
            return languages[0]

        return None

    # Upgrading..
    def _needs_upgrade(self):
        return hasattr(aq_base(self), 'original_language')

    def _upgrade(self):
        # In version 0.7 the language management logic moved to the
        # mixin class LanguageManager, as a consequence the attribute
        # "original_language" changes its name to "_default_language".
        if hasattr(aq_base(self), 'original_languge'):
            self._default_language = self.original_language
            del self.original_language

        # XXX With version 1.1.0b5 (as of patch 14) the '_local_properties'
        # data structure keeps a timestamp to mark obsolete translations.
        # The upgrade code below must be activated once the new upgrade
        # framework is deployed, something that should happen for the 1.2
        # release.


##        for k, v in self._local_properties.items():
##            for i, j in v.items():
##                if type(j) is not tuple:
##                    # XXX add the timestamp for every property
##                    self._local_properties[k][i] = (j, time())
##        self._p_changed = 1

# Define <id>_<lang> attributes, useful for example to catalog

    def __getattr__(self, name):
        try:
            index = name.rfind('_')
            id, lang = name[:index], name[index + 1:]
            property = self._local_properties[id]
        except:
            raise AttributeError, "%s instance has no attribute '%s'" \
                                  % (self.__class__.__name__, name)

        return self.getLocalAttribute(id, lang)
Пример #9
0
class LocalContent(CatalogAware, LocalPropertyManager, PropertyManager,
                   SimpleItem):
    """ """

    meta_type = 'LocalContent'

    security = ClassSecurityInfo()

    # Properties metadata
    _local_properties_metadata = ({'id': 'title', 'type': 'string'},
                                  {'id': 'body', 'type': 'text'})

    _properties = ()

    title = LocalAttribute('title')   # Override title from SimpleItem
    body = LocalAttribute('body')


    manage_options = \
        LocalPropertyManager.manage_options \
        + PropertyManager.manage_options[:1] \
        + ({'action': 'manage_import', 'label': u'Import',
            'help': ('Localizer', 'MC_importExport.stx')},
           {'action': 'manage_export', 'label': u'Export',
            'help': ('Localizer', 'MC_importExport.stx')}) \
        + PropertyManager.manage_options[1:] \
        + SimpleItem.manage_options


    def __init__(self, id, sourcelang, languages):
        self.id = id
        self._default_language = sourcelang
        self._languages = languages


    index_html = None     # Prevent accidental acquisition


    def __call__(self, client=None, REQUEST=None, RESPONSE=None, **kw):
        if REQUEST is None:
            REQUEST = self.REQUEST

        # Get the template to use
        template_id = 'default_template'
        if hasattr(aq_base(self), 'default_template'):
            template_id = self.default_template

        # Render the object
        template = getattr(aq_parent(self), template_id)
        template = template.__of__(self)
        return apply(template, ((client, self), REQUEST), kw)


    # Override some methods to be sure that LocalContent objects are
    # reindexed when changed.
    def set_localpropvalue(self, id, lang, value):
        LocalContent.inheritedAttribute('set_localpropvalue')(self, id, lang,
                                                              value)

        self.reindex_object()


    def del_localproperty(self, id):
        LocalContent.inheritedAttribute('del_localproperty')(self, id)

        self.reindex_object()

    security.declareProtected('View management screens', 'manage_import')
    manage_import = LocalDTMLFile('ui/LC_import_form', globals())

    #######################################################################
    # TMX support
    security.declareProtected('View management screens', 'manage_export')
    manage_export = LocalDTMLFile('ui/LC_export_form', globals())

    security.declareProtected('Manage messages', 'tmx_export')
    def tmx_export(self, REQUEST, RESPONSE):
        """Exports the content of the message catalog to a TMX file.
        """

        src_lang = self._default_language

        # Init the TMX handler
        tmx = TMXFile()
        tmx.header['creationtool'] = u'Localizer'
        tmx.header['creationtoolversion'] = u'1.x'
        tmx.header['datatype'] = u'plaintext'
        tmx.header['segtype'] = u'paragraph'
        tmx.header['adminlang'] = src_lang
        tmx.header['srclang'] = src_lang
        tmx.header['o-encoding'] = u'utf-8'

        # Add the translation units
        for key in self._local_properties.keys():
            unit = TMXUnit({})
            for lang in self._languages:
                sentence = Sentence({'lang': lang})
                trans, fuzzy = self.get_localproperty(key, lang)
                sentence.text = trans
                unit.msgstr[lang] = sentence
            tmx.messages[self.get_localproperty(key, src_lang)[0]] = unit

        # Serialize
        data = tmx.to_str()
        # Set response headers
        RESPONSE.setHeader('Content-type','application/data')
        RESPONSE.setHeader('Content-Disposition',
                           'attachment; filename="%s.tmx"' % self.id)
        # Ok
        return data



    security.declareProtected('Manage messages', 'tmx_import')
    def tmx_import(self, file, REQUEST=None, RESPONSE=None):
        """Imports a TMX level 1 file.
        """
        try:
            data = file.read()
            tmx = TMXFile(string=data)
        except:
            return MessageDialog(title = 'Parse error',
                               message = _('impossible to parse the file') ,
                               action = 'manage_import',)

        for id, msg in tmx.messages.items():
            for prop, d in self._local_properties.items():
                if d[self._default_language][0] == id:
                    msg.msgstr.pop(self._default_language)
                    for lang in msg.msgstr.keys():
                        # normalize the languageTag and extract the core
                        (core, local) = LanguageTag.decode(lang)
                        lang = LanguageTag.encode((core, local))
                        if lang not in self._languages:
                            self._languages += (lang,)
                        texte = msg.msgstr[lang].text
                        if texte:
                            self.set_localpropvalue(prop, lang, texte)
                            if core != lang and core != self._default_language:
                                if core not in self._languages:
                                    self._languages += (core,)
                                if not msg.msgstr.has_key(core):
                                    self.set_localpropvalue(prop, lang, texte)

        if REQUEST is not None:
            RESPONSE.redirect('manage_localPropertiesForm')



    security.declareProtected('Manage messages', 'xliff_export')
    def xliff_export(self, dst_lang, export_all=1, REQUEST=None,
                     RESPONSE=None):
        """ Exports the content of the message catalog to an XLIFF file
        """
        from DateTime import DateTime

        src_lang = self._default_language
        export_all = int(export_all)

        # Init the XLIFF handler
        xliff = XLFFile()
        # Add the translation units
        original = '/%s' % self.absolute_url(1)
        for prop in self._local_properties.keys():
            target, fuzzy = self.get_localproperty(prop, dst_lang)
            msgkey, fuzzy = self.get_localproperty(prop, src_lang)
            # If 'export_all' is true export all messages, otherwise export
            # only untranslated messages
            if export_all or not target:
                unit = xliff.add_unit(original, msgkey, None)
                unit.attributes['id'] = md5text(msgkey)
                if target:
                    unit.target = target

        # Set the file attributes
        file = xliff.files[original]
        attributes = file.attributes
        attributes['original'] = original
        attributes['product-name'] = u'Localizer'
        attributes['product-version'] = u'1.1.x'
        attributes['data-type'] = u'plaintext'
        attributes['source-language'] = src_lang
        attributes['target-language'] = dst_lang
        attributes['date'] = DateTime().HTML4()

        # Serialize
        xliff = xliff.to_str()
        # Set response headers
        RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8')
        filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang)
        RESPONSE.setHeader('Content-Disposition',
           'attachment; filename="%s"' % filename)
        # Ok
        return xliff


    security.declareProtected('Manage messages', 'xliff_import')
    def xliff_import(self, file, REQUEST=None):
        """ XLIFF is the XML Localization Interchange File Format
            designed by a group of software providers.
            It is specified by www.oasis-open.org
        """
        try:
            data = file.read()
            xliff = XLFFile(string=data)
        except:
            return MessageDialog(title = 'Parse error',
                                 message = _('impossible to parse the file') ,
                                 action = 'manage_import',)

        num_trans = 0
        (file_ids, sources, targets) = xliff.get_languages()

        # update languages
        if len(sources) > 1 or sources[0] != self._default_language:
            return MessageDialog(title = 'Language error',
                                 message = _('incompatible language sources') ,
                                 action = 'manage_import',)
        for lang in targets:
            if lang != self._default_language and lang not in self._languages:
                self._languages += (lang,)

        # get messages
        for file in xliff.files:
            cur_target = file.attributes.get('target-language', '')
            for msg in file.body.keys():
                for (prop, val) in self._local_properties.items():
                    if val[self._default_language][0] == msg:
                        if cur_target and file.body[msg].target:
                            texte = file.body[msg].target
                            self.set_localpropvalue(prop, cur_target, texte)
                            num_trans += 1

        if REQUEST is not None:
            return MessageDialog(
                title = _(u'Messages imported'),
                message = (_(u'Imported %d messages to %s') %
                           (num_trans, ' '.join(targets))),
                action = 'manage_localPropertiesForm')
Пример #10
0
# Import from Localizer
from LocalAttributes import LocalAttribute
from LocalFiles import LocalDTMLFile
from LocalPropertyManager import LocalPropertyManager
from utils import _


def md5text(str):
    """Create an MD5 sum (or hash) of a text. It is guaranteed to be 32 bytes
    long.
    """
    return md5(str.encode('utf-8')).hexdigest()


manage_addLocalContentForm = LocalDTMLFile('ui/LocalContent_add', globals())
def manage_addLocalContent(self, id, sourcelang, languages, REQUEST=None):
    """ """
    languages.append(sourcelang)   # Make sure source is one of the target langs
    self._setObject(id, LocalContent(id, sourcelang, tuple(languages)))

    if REQUEST is not None:
        return self.manage_main(self, REQUEST)


class LocalContent(CatalogAware, LocalPropertyManager, PropertyManager,
                   SimpleItem):
    """ """

    meta_type = 'LocalContent'