├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README ├── setup.py └── speaklater.py /.gitignore: -------------------------------------------------------------------------------- 1 | \.DS_Store 2 | *.pyc 3 | *.pyo 4 | *.egg-info 5 | dist 6 | build 7 | MANIFEST 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A module that provides lazy strings for translations. Basically you get an 2 | object that appears to be a string but changes the value every time the value 3 | is evaluated based on a callable you provide. 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | try: 3 | from setuptools import setup 4 | except ImportError: 5 | from distutils.core import setup 6 | 7 | def get_docs(): 8 | result = [] 9 | in_docs = False 10 | f = open(os.path.join(os.path.dirname(__file__), 'speaklater.py')) 11 | try: 12 | for line in f: 13 | if in_docs: 14 | if line.lstrip().startswith(':copyright:'): 15 | break 16 | result.append(line[4:].rstrip()) 17 | elif line.strip() == 'r"""': 18 | in_docs = True 19 | finally: 20 | f.close() 21 | return '\n'.join(result) 22 | 23 | setup( 24 | name='speaklater', 25 | author='Armin Ronacher', 26 | author_email='armin.ronacher@active-4.com', 27 | version='1.3', 28 | url='http://github.com/mitsuhiko/speaklater', 29 | py_modules=['speaklater'], 30 | description='implements a lazy string for python useful for use with gettext', 31 | long_description=get_docs(), 32 | zip_safe=False, 33 | classifiers=[ 34 | 'Development Status :: 5 - Production/Stable', 35 | 'License :: OSI Approved :: BSD License', 36 | 'Topic :: Software Development :: Internationalization', 37 | 'Programming Language :: Python' 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /speaklater.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | r""" 3 | speaklater 4 | ~~~~~~~~~~ 5 | 6 | A module that provides lazy strings for translations. Basically you 7 | get an object that appears to be a string but changes the value every 8 | time the value is evaluated based on a callable you provide. 9 | 10 | For example you can have a global `lazy_gettext` function that returns 11 | a lazy string with the value of the current set language. 12 | 13 | Example: 14 | 15 | >>> from speaklater import make_lazy_string 16 | >>> sval = u'Hello World' 17 | >>> string = make_lazy_string(lambda: sval) 18 | 19 | This lazy string will evaluate to the value of the `sval` variable. 20 | 21 | >>> string 22 | lu'Hello World' 23 | >>> unicode(string) 24 | u'Hello World' 25 | >>> string.upper() 26 | u'HELLO WORLD' 27 | 28 | If you change the value, the lazy string will change as well: 29 | 30 | >>> sval = u'Hallo Welt' 31 | >>> string.upper() 32 | u'HALLO WELT' 33 | 34 | This is especially handy when combined with a thread local and gettext 35 | translations or dicts of translatable strings: 36 | 37 | >>> from speaklater import make_lazy_gettext 38 | >>> from threading import local 39 | >>> l = local() 40 | >>> l.translations = {u'Yes': 'Ja'} 41 | >>> lazy_gettext = make_lazy_gettext(lambda: l.translations.get) 42 | >>> yes = lazy_gettext(u'Yes') 43 | >>> print yes 44 | Ja 45 | >>> l.translations[u'Yes'] = u'Si' 46 | >>> print yes 47 | Si 48 | 49 | Lazy strings are no real strings so if you pass this sort of string to 50 | a function that performs an instance check, it will fail. In that case 51 | you have to explicitly convert it with `unicode` and/or `string` depending 52 | on what string type the lazy string encapsulates. 53 | 54 | To check if a string is lazy, you can use the `is_lazy_string` function: 55 | 56 | >>> from speaklater import is_lazy_string 57 | >>> is_lazy_string(u'yes') 58 | False 59 | >>> is_lazy_string(yes) 60 | True 61 | 62 | New in version 1.2: It's now also possible to pass keyword arguments to 63 | the callback used with `make_lazy_string`. 64 | 65 | :copyright: (c) 2010 by Armin Ronacher. 66 | :license: BSD, see LICENSE for more details. 67 | """ 68 | 69 | 70 | def is_lazy_string(obj): 71 | """Checks if the given object is a lazy string.""" 72 | return isinstance(obj, _LazyString) 73 | 74 | 75 | def make_lazy_string(__func, *args, **kwargs): 76 | """Creates a lazy string by invoking func with args.""" 77 | return _LazyString(__func, args, kwargs) 78 | 79 | 80 | def make_lazy_gettext(lookup_func): 81 | """Creates a lazy gettext function dispatches to a gettext 82 | function as returned by `lookup_func`. 83 | 84 | Example: 85 | 86 | >>> translations = {u'Yes': u'Ja'} 87 | >>> lazy_gettext = make_lazy_gettext(lambda: translations.get) 88 | >>> x = lazy_gettext(u'Yes') 89 | >>> x 90 | lu'Ja' 91 | >>> translations[u'Yes'] = u'Si' 92 | >>> x 93 | lu'Si' 94 | """ 95 | def lazy_gettext(string): 96 | if is_lazy_string(string): 97 | return string 98 | return make_lazy_string(lookup_func(), string) 99 | return lazy_gettext 100 | 101 | 102 | class _LazyString(object): 103 | """Class for strings created by a function call. 104 | 105 | The proxy implementation attempts to be as complete as possible, so that 106 | the lazy objects should mostly work as expected, for example for sorting. 107 | """ 108 | __slots__ = ('_func', '_args', '_kwargs') 109 | 110 | def __init__(self, func, args, kwargs): 111 | self._func = func 112 | self._args = args 113 | self._kwargs = kwargs 114 | 115 | value = property(lambda x: x._func(*x._args, **x._kwargs)) 116 | 117 | def __contains__(self, key): 118 | return key in self.value 119 | 120 | def __nonzero__(self): 121 | return bool(self.value) 122 | 123 | def __dir__(self): 124 | return dir(unicode) 125 | 126 | def __iter__(self): 127 | return iter(self.value) 128 | 129 | def __len__(self): 130 | return len(self.value) 131 | 132 | def __str__(self): 133 | return str(self.value) 134 | 135 | def __unicode__(self): 136 | return unicode(self.value) 137 | 138 | def __add__(self, other): 139 | return self.value + other 140 | 141 | def __radd__(self, other): 142 | return other + self.value 143 | 144 | def __mod__(self, other): 145 | return self.value % other 146 | 147 | def __rmod__(self, other): 148 | return other % self.value 149 | 150 | def __mul__(self, other): 151 | return self.value * other 152 | 153 | def __rmul__(self, other): 154 | return other * self.value 155 | 156 | def __lt__(self, other): 157 | return self.value < other 158 | 159 | def __le__(self, other): 160 | return self.value <= other 161 | 162 | def __eq__(self, other): 163 | return self.value == other 164 | 165 | def __ne__(self, other): 166 | return self.value != other 167 | 168 | def __gt__(self, other): 169 | return self.value > other 170 | 171 | def __ge__(self, other): 172 | return self.value >= other 173 | 174 | def __getattr__(self, name): 175 | if name == '__members__': 176 | return self.__dir__() 177 | return getattr(self.value, name) 178 | 179 | def __getstate__(self): 180 | return self._func, self._args, self._kwargs 181 | 182 | def __setstate__(self, tup): 183 | self._func, self._args, self._kwargs = tup 184 | 185 | def __getitem__(self, key): 186 | return self.value[key] 187 | 188 | def __copy__(self): 189 | return self 190 | 191 | def __repr__(self): 192 | try: 193 | return 'l' + repr(self.value) 194 | except Exception: 195 | return '<%s broken>' % self.__class__.__name__ 196 | 197 | 198 | if __name__ == '__main__': 199 | import doctest 200 | doctest.testmod() 201 | --------------------------------------------------------------------------------