Welcome, guest | Sign In | My Account | Store | Cart

Injecting _() in the __builtin__ module in order to inject global functions _() and N_() is common in applications which need i18n. This is a variation on the theme that does this in a multi-threaded environment, using threading.local from Python-2.4.

Python, 82 lines
 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
#!/usr/bin/env python
"""
Usage of threaded.local as gettext() to implement the __builtin__
I18n _() kludge in a multi-threaded environment.  This is a simple
proof-of-concept that demonstrates the kludge.
"""

import threading, time, __builtin__, random


# Create a translator function that can be used in __builtin__ to
# fetch translations from the gettext catalogs and will work in a
# multi-threaded environment. This gets setup once.
catalogs = threading.local()

def gettext_getfunc( lang ):
    """
    Returns a function used to translate to a specific catalog.
    """
    # Note: you would get the gettext catalog here and install it in the
    # closure.

    def tr( s ):
        # Note: we do not really translate here, we just prepend the
        # language, but you get the idea.
        return '[%s] %s' % (lang, s)

    return tr

def gettext_translate( s ):
    """
    Thread-safe version of _().
    We look up the thread-local translation function.
    """
    return catalogs.translate(s)

# Inject the _() function in the builtins.  You could also inject a N_()
# function for noop translation markers.
__builtin__._ = gettext_translate


def handle_request():
    """
    Treat a request, with i18n strings.
    """
    # Fetch and return a translated string.
    # This is the interesting bit, from a client's point-of-view.
    print _('bli'), _('bla'), _('blo')

    # Do something else.
    time.sleep(random.random())


# A thread class.  This would be provided by the web framework,
# normally.
class Thread(threading.Thread):
    def __init__( self, num ):
        threading.Thread.__init__(self)
        self.num = num

    def run( self ):
        while 1: # many requests
            # Setup a request.

            # Get the request's language (we just select a random
            # language here for demonstration purposes).
            reqlang = random.choice(['en', 'fr', 'es', 'de',
                                     'pt', 'si', 'dk', 'it'])

            # Setup current language for thread with a closure that
            # efficiently implement the desired catalog lookup.
            catalogs.translate = gettext_getfunc(reqlang)

            # Dispatch a request to be handled.
            handle_request()


# Create and launch test threads.
threads = []
for x in xrange(5):
    t = Thread(x)
    t.start()

I was toying with the idea of converting my web application framework to multi-threaded rather than prefork (mod_python/apache2 prefork), so I wondered how I would solve the _() builtin kludge problem. This is a proof-of-concept. Comments welcome.

P.S. I purposefully do not include actual gettext code here, because I want the test program to be easy to setup (i.e. no catalogs needed) and this same trick could work with other i18n catalog libraries.

1 comment

Michael Hines 9 years, 5 months ago  # | flag

This code is pure beauty. I had no idea about threading.local(), nor being able to implement my own interception of the builtin gettext "_" function. Solve all my multi-threaded internationalization problems.

Thanks for posting!