############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import re from zope.interface import implements from zope.interface import classProvides from zope.i18nmessageid import Message from repoze.bfg.interfaces import ITranslator from repoze.bfg.interfaces import ITranslatorFactory from repoze.bfg.interfaces import IChameleonTranslate from repoze.bfg.threadlocal import get_current_registry from repoze.bfg.threadlocal import get_current_request class TranslationString(Message): """ The constructor for a :term:`translation string`. This constructor accepts one required argument named ``text``. ``text`` must be the default text of the translation string, optionally including replacement markers such as ``${foo}``. Optional keyword arguments to the TranslationString constructor include ``msgid``, ``mapping`` and ``domain``. ``mapping``, if supplied, must be a dictionarylike object which represents the replacement values for any replacement markers found within the ``text`` value of this ``msgid`` represents an explicit :term:`message identifier` for this translation string. Usually, the ``text`` of a translation string serves as its message identifier. However, using this option you can pass an explicit message identifier, usually a simple string. This is useful when the ``text`` of a translation string is too complicated or too long to be used as a translation key. If ``msgid`` is ``None`` (the default), the ``msgid`` value used by this translation string will be assumed to be the value of ``text``. ``domain`` represents the :term:`translation domain`. By default, the translation domain is ``None``, indicating that this translation string is associated with no translation domain. After a translation string is constructed, its ``text`` value is available as the ``default`` attribute of the object, the ``msgid`` is available as the ``msgid`` attribute of the object, the ``domain`` is available as the ``domain`` attribute, and the ``mapping`` is available as the ``mapping`` attribute. """ def __new__(cls, text, mapping=None, msgid=None, domain=None): if msgid is None: msgid = text return Message.__new__(cls, msgid, domain=domain, default=text, mapping=mapping) class TranslationStringFactory(object): """ Create a factory which will generate translation strings without requiring that each call to the factory be passed a ``domain`` value. The ``domain`` value passed to this class' constructor will be used as the ``domain`` values of :class:`repoze.bfg.i18n.TranslationString` objects generated by the ``__call__`` of this class. The ``text``, ``mapping``, and ``msgid`` values provided to ``__call__`` have the meaning as described by the constructor of the :class:`repoze.bfg.i18n.TranslationString`""" def __init__(self, domain): self.domain = domain def __call__(self, text, mapping=None, msgid=None): return TranslationString(text, mapping=mapping, msgid=msgid, domain=self.domain) bfg_tstr = TranslationStringFactory('bfg') bfg_tstr.__doc__ = """\ A :class:`repoze.bfg.i18n.TranslationStringFactory` instance with a default ``domain`` value of ``bfg``. This object may be called with the values ``text``, ``mapping``, and ``msgid`` as per the documentation of the :class:`repoze.bfg.i18n.TranslationStringFactory` class.""" def get_translator(request, translator_factory=None): """ Return a :term:`translator` for the given request based on the :term:`translator factory` registered for the current application and the :term:`request` passed in as the request object. If no translator factory was sent to the :class:`repoze.bfg.configuration.Configurator` constructor at application startup, this function will return a very simple default 'interpolation only' translator. Note that the translation factory will only be constructed once per request instance. """ translator = getattr(request, '_bfg_translator', None) if translator is None: if translator_factory is None: try: reg = request.registry except AttributeError: reg = get_current_registry() translator_factory = reg.queryUtility( ITranslatorFactory, default=InterpolationOnlyTranslator) translator = translator_factory(request) try: request._bfg_translator = translator except AttributeError: # pragma: no cover pass # it's only a cache return translator class InterpolationOnlyTranslator(object): """ A class implementing the :term:`translator factory` interface as its constructor and the :term:`translator` interface as its ``__call__`` method. Useful as a minimal translator factory, this class only does basic interpolation of mapping values; it does not actually do any language translations. Use:: from repoze.bfg.configuration import Configurator from repoze.bfg.i18n import InterpolationOnlyTranslator config = Configurator(translator_factory=InterpolationOnlyTranslator) """ classProvides(ITranslatorFactory) implements(ITranslator) def __init__(self, request): self.request = request def __call__(self, message): mapping = getattr(message, 'mapping', None) return interpolate(message, mapping) class ChameleonTranslate(object): """ Registered as a Chameleon translate function 'under the hood' to allow our ITranslator and ITranslatorFactory to drive template translation.""" implements(IChameleonTranslate) def __init__(self, translator_factory): self.translator_factory = translator_factory def __call__(self, text, domain=None, mapping=None, context=None, target_language=None, default=None): if text is None: return None if default is None: default = text if mapping is None: mapping = {} if not hasattr(text, 'mapping'): text = TranslationString(default, mapping=mapping, msgid=text, domain=domain) translator = self.make_translator(target_language) return translator(text) def make_translator(self, target_language): translator = None request = get_current_request() if request is not None: translator = get_translator(request, self.translator_factory) if translator is None: translator = InterpolationOnlyTranslator(request) return translator NAME_RE = r"[a-zA-Z][-a-zA-Z0-9_]*" _interp_regex = re.compile(r'(?