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