Skip to content

Instantly share code, notes, and snippets.

@zacharyvoase
Created February 6, 2017 23:54
Show Gist options
  • Save zacharyvoase/2e78f69d6de53fb1424becd429360374 to your computer and use it in GitHub Desktop.
Save zacharyvoase/2e78f69d6de53fb1424becd429360374 to your computer and use it in GitHub Desktop.
Translatable: A monad for composable translations.
"""
A monad for composable translations.
Wrap simple values:
>>> t = Translatable.value(123)
>>> t # doctest: +ELLIPSIS
Translatable(translation_keys=(), function=<function <lambda> at ...>)
>>> t.translate({})
123
Translate single keys:
>>> en_keys = {'HELLO': "Hello", 'NAME': "customer", 'WELCOME': "Welcome to the site!"}
>>> es_keys = {'HELLO': "Hola", 'NAME': "cliente", 'WELCOME': "Bienvenidos al sitio web!"}
>>> t = Translatable.key('HELLO')
>>> t.translate(en_keys)
'Hello'
>>> t.translate(es_keys)
'Hola'
Decorate function definitions to get a translatable:
>>> @trans('HELLO', 'NAME')
... def greeting(hello, name):
... return '{}, {}!'.format(hello, name)
>>> greeting.translate(en_keys)
'Hello, customer!'
>>> greeting.translate(es_keys)
'Hola, cliente!'
Use `Translatable.join()` to join the results of multiple Translatables,
producing a tuple with each result:
>>> welcome = Translatable.key('WELCOME')
>>> joined = Translatable.join(greeting, welcome)
>>> joined.translate(en_keys)
('Hello, customer!', 'Welcome to the site!')
>>> joined.translate(es_keys)
('Hola, cliente!', 'Bienvenidos al sitio web!')
Use `.map()` to compose the result of a Translatable with a mapping function:
>>> json_t = joined.map(lambda (greeting, welcome): {'title': greeting, 'description': welcome})
>>> json_t.translate(en_keys)
{'description': 'Welcome to the site!', 'title': 'Hello, customer!'}
>>> json_t.translate(es_keys)
{'description': 'Bienvenidos al sitio web!', 'title': 'Hola, cliente!'}
In case a translation key is not found, a `TranslationNotFound` will be
raised:
>>> Translatable.key('GOODBYE').translate(en_keys) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TranslationNotFound: 'GOODBYE'
"""
import collections
class TranslationNotFound(KeyError):
"""Indicates that a translation could not be found at runtime."""
pass
_translatable = collections.namedtuple('Translatable', ['translation_keys', 'function'])
class Translatable(_translatable):
"""
A monad for composable and introspectable translations.
"""
def translate(self, strings):
"""Given a dict-like store of translation strings, translate this object."""
binding = []
for tkey in self.translation_keys:
if tkey not in strings:
raise TranslationNotFound(tkey)
binding.append(strings[tkey])
return self.function(*binding)
def translate_flatten(self, strings):
"""Recursively translate the result of this translatable."""
result = self.translate(strings)
while isinstance(result, Translatable):
result = result.translate(strings)
return result
@classmethod
def key(cls, tkey):
"""Just get the string translation of a given key."""
return cls((tkey,), lambda x: x)
@classmethod
def value(cls, value):
"""Wrap a plain value in a Translatable."""
return Translatable((), lambda: value)
def map(self, mapper):
"""Run the result of this translatable through a mapping function."""
function = self.function
return Translatable(self.translation_keys, lambda *strings: mapper(function(*strings)))
@classmethod
def join(cls, *translatables):
"""Join multiple translatables into one, returning a tuple of results."""
tkeys = sum((t.translation_keys for t in translatables), ())
functions = [(len(t.translation_keys), t.function) for t in translatables]
def joined(*strings):
results = ()
for (string_count, function) in functions:
results += (function(*strings[:string_count]),)
strings = strings[string_count:]
return results
return cls(tkeys, joined)
def trans(*translation_keys):
"""A decorator to produce Translatables using function definitions."""
def wrapper(func):
return Translatable(translation_keys, func)
return wrapper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment