1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
##############################################################################
#
# 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 and ignores all
:term:`translation domain` information. To use it explicitly::
from repoze.bfg.configuration import Configurator
from repoze.bfg.i18n import InterpolationOnlyTranslator
config = Configurator(translator_factory=InterpolationOnlyTranslator)
An instance of this class is returned by
:func:`repoze.bfg.i18n.get_translator` if no explicit translator
factory is registered.
"""
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'(?<!\$)(\$(?:(%(n)s)|{(%(n)s)}))'
% ({'n': NAME_RE}))
def interpolate(text, mapping=None):
""" Interpolate a string with one or more *replacement markers*
(``${foo}`` or ``${bar}``). Note that if a :term:`translation
string` is passed to this function, it will be implicitly
converted back to the Unicode object."""
def replace(match):
whole, param1, param2 = match.groups()
return unicode(mapping.get(param1 or param2, whole))
if not text or not mapping:
return text
return _interp_regex.sub(replace, text)
|