No lazy_* needed here; Add ReallyLazyProxy.__repr__.
[mediagoblin.git] / mediagoblin / tools / translate.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import gettext
18 import pkg_resources
19
20
21 from babel import localedata
22 from babel.support import LazyProxy
23
24 from mediagoblin import mg_globals
25
26 ###################
27 # Translation tools
28 ###################
29
30 AVAILABLE_LOCALES = None
31 TRANSLATIONS_PATH = pkg_resources.resource_filename(
32 'mediagoblin', 'i18n')
33
34
35 def set_available_locales():
36 """Set available locales for which we have translations"""
37 global AVAILABLE_LOCALES
38 locales=['en', 'en_US'] # these are available without translations
39 for locale in localedata.list():
40 if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]):
41 locales.append(locale)
42 AVAILABLE_LOCALES = locales
43
44
45 class ReallyLazyProxy(LazyProxy):
46 """
47 Like LazyProxy, except that it doesn't cache the value ;)
48 """
49 @property
50 def value(self):
51 return self._func(*self._args, **self._kwargs)
52
53 def __repr__(self):
54 return "<%s for %s(%r, %r)>" % (
55 self.__class__.__name__,
56 self._func,
57 self._args,
58 self._kwargs)
59
60
61 def locale_to_lower_upper(locale):
62 """
63 Take a locale, regardless of style, and format it like "en_US"
64 """
65 if '-' in locale:
66 lang, country = locale.split('-', 1)
67 return '%s_%s' % (lang.lower(), country.upper())
68 elif '_' in locale:
69 lang, country = locale.split('_', 1)
70 return '%s_%s' % (lang.lower(), country.upper())
71 else:
72 return locale.lower()
73
74
75 def locale_to_lower_lower(locale):
76 """
77 Take a locale, regardless of style, and format it like "en_us"
78 """
79 if '_' in locale:
80 lang, country = locale.split('_', 1)
81 return '%s-%s' % (lang.lower(), country.lower())
82 else:
83 return locale.lower()
84
85
86 def get_locale_from_request(request):
87 """
88 Return most appropriate language based on prefs/request request
89 """
90 request_args = (request.args, request.form)[request.method=='POST']
91
92 if 'lang' in request_args:
93 # User explicitely demanded a language, normalize lower_uppercase
94 target_lang = locale_to_lower_upper(request_args['lang'])
95
96 elif 'target_lang' in request.session:
97 # TODO: Uh, ohh, this is never ever set anywhere?
98 target_lang = request.session['target_lang']
99 else:
100 # Pull the most acceptable language based on browser preferences
101 # This returns one of AVAILABLE_LOCALES which is aready case-normalized.
102 # Note: in our tests request.accept_languages is None, so we need
103 # to explicitely fallback to en here.
104 target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \
105 or "en_US"
106
107 return target_lang
108
109 SETUP_GETTEXTS = {}
110
111 def get_gettext_translation(locale):
112 """
113 Return the gettext instance based on this locale
114 """
115 # Later on when we have plugins we may want to enable the
116 # multi-translations system they have so we can handle plugin
117 # translations too
118
119 # TODO: fallback nicely on translations from pt_PT to pt if not
120 # available, etc.
121 if locale in SETUP_GETTEXTS:
122 this_gettext = SETUP_GETTEXTS[locale]
123 else:
124 this_gettext = gettext.translation(
125 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
126 if localedata.exists(locale):
127 SETUP_GETTEXTS[locale] = this_gettext
128 return this_gettext
129
130
131 def set_thread_locale(locale):
132 """Set the current translation for this thread"""
133 mg_globals.thread_scope.translations = get_gettext_translation(locale)
134
135
136 def pass_to_ugettext(*args, **kwargs):
137 """
138 Pass a translation on to the appropriate ugettext method.
139
140 The reason we can't have a global ugettext method is because
141 mg_globals gets swapped out by the application per-request.
142 """
143 return mg_globals.thread_scope.translations.ugettext(
144 *args, **kwargs)
145
146 def pass_to_ungettext(*args, **kwargs):
147 """
148 Pass a translation on to the appropriate ungettext method.
149
150 The reason we can't have a global ugettext method is because
151 mg_globals gets swapped out by the application per-request.
152 """
153 return mg_globals.thread_scope.translations.ungettext(
154 *args, **kwargs)
155
156
157 def lazy_pass_to_ugettext(*args, **kwargs):
158 """
159 Lazily pass to ugettext.
160
161 This is useful if you have to define a translation on a module
162 level but you need it to not translate until the time that it's
163 used as a string. For example, in:
164 def func(self, message=_('Hello boys and girls'))
165
166 you would want to use the lazy version for _.
167 """
168 return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs)
169
170
171 def pass_to_ngettext(*args, **kwargs):
172 """
173 Pass a translation on to the appropriate ngettext method.
174
175 The reason we can't have a global ngettext method is because
176 mg_globals gets swapped out by the application per-request.
177 """
178 return mg_globals.thread_scope.translations.ngettext(
179 *args, **kwargs)
180
181
182 def lazy_pass_to_ngettext(*args, **kwargs):
183 """
184 Lazily pass to ngettext.
185
186 This is useful if you have to define a translation on a module
187 level but you need it to not translate until the time that it's
188 used as a string.
189 """
190 return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs)
191
192 def lazy_pass_to_ungettext(*args, **kwargs):
193 """
194 Lazily pass to ungettext.
195
196 This is useful if you have to define a translation on a module
197 level but you need it to not translate until the time that it's
198 used as a string.
199 """
200 return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs)
201
202
203 def fake_ugettext_passthrough(string):
204 """
205 Fake a ugettext call for extraction's sake ;)
206
207 In wtforms there's a separate way to define a method to translate
208 things... so we just need to mark up the text so that it can be
209 extracted, not so that it's actually run through gettext.
210 """
211 return string