1"""Basic translation context class."""
2
3import sys
4import textwrap
5
6from flufl.i18n._expand import expand
7from flufl.i18n._substitute import Template, attrdict
8from public import public
9
10
11@public
12class Translator:
13    """A translation context."""
14
15    def __init__(self, catalog, dedent=True, depth=2):
16        """Create a translation context.
17
18        :param catalog: The translation catalog.
19        :type catalog: `gettext.NullTranslations` or subclass
20        :param dedent: Whether the input string should be dedented.
21        :type dedent: bool
22        :param depth: Number of stack frames to call sys._getframe() with.
23        :type depth: int
24        """
25        self._catalog = catalog
26        self.dedent = dedent
27        self.depth = depth
28
29    def translate(self, original, extras=None):
30        """Translate the string.
31
32        :param original: The original string to translate.
33        :type original: string
34        :param extras: Extra substitution mapping, elements of which override
35            the locals and globals.
36        :return: The translated string.
37        :rtype: string
38        """
39        if original == '':
40            return ''
41        assert original, 'Cannot translate: {}'.format(original)
42        # Because the original string is what the text extractors put into the
43        # catalog, we must first look up the original unadulterated string in
44        # the catalog.  Use the global translation context for this.
45        #
46        # Translations must be unicode safe internally.  The translation
47        # service is one boundary to the outside world, so to honor this
48        # constraint, make sure that all strings to come out of this are
49        # unicodes, even if the translated string or dictionary values are
50        # 8-bit strings.
51        tns = self._catalog.gettext(original)
52        # Do PEP 292 style $-string interpolation into the resulting string.
53        #
54        # This lets you write something like:
55        #
56        #     now = time.ctime(time.time())
57        #     print _('The current time is: $now')
58        #
59        # and have it Just Work.  Key precedence is:
60        #
61        #     extras > locals > globals
62        #
63        # Get the frame of the caller.
64        frame = sys._getframe(self.depth)
65        # Create the raw dictionary of substitutions.
66        raw_dict = frame.f_globals.copy()
67        raw_dict.update(frame.f_locals)
68        if extras is not None:
69            raw_dict.update(extras)
70        # Python 2 requires ** dictionaries to have str, not unicode keys.
71        # For our purposes, keys should always be ascii.  Values though should
72        # be unicode.
73        translated_string = expand(tns, attrdict(raw_dict), Template)
74        # Dedent the string if so desired.
75        if self.dedent:
76            translated_string = textwrap.dedent(translated_string)
77        return translated_string
78