Created
February 6, 2017 23:54
-
-
Save zacharyvoase/2e78f69d6de53fb1424becd429360374 to your computer and use it in GitHub Desktop.
Translatable: A monad for composable translations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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