expand_json utility... yet another convenience function :)
[mediagoblin.git] / mediagoblin / tools / translate.py
CommitLineData
ae3bc7fa 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
ae3bc7fa
AW
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
17import gettext
18import pkg_resources
6ef75af5
SS
19
20
21from babel import localedata
ae3bc7fa
AW
22from babel.support import LazyProxy
23
24from mediagoblin import mg_globals
25
26###################
27# Translation tools
28###################
29
826919c9 30AVAILABLE_LOCALES = None
ae3bc7fa
AW
31TRANSLATIONS_PATH = pkg_resources.resource_filename(
32 'mediagoblin', 'i18n')
33
4a4e4e4a
JMM
34# Known RTL languages
35KNOWN_RTL = set(["ar", "fa", "zh","he","iw","ja","ur","yi","ji"])
36
37def is_rtl(lang):
38 """Returns true when the local language is right to left"""
39 return lang in KNOWN_RTL
ae3bc7fa 40
826919c9
SS
41def set_available_locales():
42 """Set available locales for which we have translations"""
43 global AVAILABLE_LOCALES
44 locales=['en', 'en_US'] # these are available without translations
023cda09 45 for locale in localedata.locale_identifiers():
6ef75af5
SS
46 if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]):
47 locales.append(locale)
826919c9 48 AVAILABLE_LOCALES = locales
6ef75af5
SS
49
50
c47be4b8
CAW
51class ReallyLazyProxy(LazyProxy):
52 """
53 Like LazyProxy, except that it doesn't cache the value ;)
54 """
55 @property
56 def value(self):
57 return self._func(*self._args, **self._kwargs)
58
5ae0cbaa
E
59 def __repr__(self):
60 return "<%s for %s(%r, %r)>" % (
61 self.__class__.__name__,
62 self._func,
63 self._args,
64 self._kwargs)
65
c47be4b8 66
ae3bc7fa
AW
67def locale_to_lower_upper(locale):
68 """
7b9f9d1e 69 Take a locale, regardless of style, and format it like "en_US"
ae3bc7fa
AW
70 """
71 if '-' in locale:
72 lang, country = locale.split('-', 1)
73 return '%s_%s' % (lang.lower(), country.upper())
74 elif '_' in locale:
75 lang, country = locale.split('_', 1)
76 return '%s_%s' % (lang.lower(), country.upper())
77 else:
78 return locale.lower()
79
80
81def locale_to_lower_lower(locale):
82 """
9d3e56d5 83 Take a locale, regardless of style, and format it like "en_us"
ae3bc7fa
AW
84 """
85 if '_' in locale:
86 lang, country = locale.split('_', 1)
87 return '%s-%s' % (lang.lower(), country.lower())
88 else:
89 return locale.lower()
90
91
92def get_locale_from_request(request):
93 """
c39b9afc 94 Return most appropriate language based on prefs/request request
ae3bc7fa 95 """
c39b9afc 96 request_args = (request.args, request.form)[request.method=='POST']
ae3bc7fa 97
04453ccf 98 if 'lang' in request_args:
c39b9afc
SS
99 # User explicitely demanded a language, normalize lower_uppercase
100 target_lang = locale_to_lower_upper(request_args['lang'])
ae3bc7fa 101
7b9f9d1e
SS
102 elif 'target_lang' in request.session:
103 # TODO: Uh, ohh, this is never ever set anywhere?
ae3bc7fa 104 target_lang = request.session['target_lang']
ae3bc7fa 105 else:
c39b9afc
SS
106 # Pull the most acceptable language based on browser preferences
107 # This returns one of AVAILABLE_LOCALES which is aready case-normalized.
108 # Note: in our tests request.accept_languages is None, so we need
109 # to explicitely fallback to en here.
110 target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \
111 or "en_US"
112
113 return target_lang
ae3bc7fa
AW
114
115SETUP_GETTEXTS = {}
116
6ef75af5 117def get_gettext_translation(locale):
ae3bc7fa 118 """
6ef75af5 119 Return the gettext instance based on this locale
ae3bc7fa
AW
120 """
121 # Later on when we have plugins we may want to enable the
122 # multi-translations system they have so we can handle plugin
123 # translations too
124
125 # TODO: fallback nicely on translations from pt_PT to pt if not
126 # available, etc.
c80982c7 127 if locale in SETUP_GETTEXTS:
ae3bc7fa
AW
128 this_gettext = SETUP_GETTEXTS[locale]
129 else:
130 this_gettext = gettext.translation(
131 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
6ef75af5 132 if localedata.exists(locale):
ae3bc7fa 133 SETUP_GETTEXTS[locale] = this_gettext
6ef75af5 134 return this_gettext
ae3bc7fa
AW
135
136
50cb5122
E
137def set_thread_locale(locale):
138 """Set the current translation for this thread"""
139 mg_globals.thread_scope.translations = get_gettext_translation(locale)
140
141
ae3bc7fa
AW
142def pass_to_ugettext(*args, **kwargs):
143 """
144 Pass a translation on to the appropriate ugettext method.
145
146 The reason we can't have a global ugettext method is because
147 mg_globals gets swapped out by the application per-request.
148 """
c80982c7 149 return mg_globals.thread_scope.translations.ugettext(
ae3bc7fa
AW
150 *args, **kwargs)
151
f1c3807d
J
152def pass_to_ungettext(*args, **kwargs):
153 """
154 Pass a translation on to the appropriate ungettext method.
155
156 The reason we can't have a global ugettext method is because
157 mg_globals gets swapped out by the application per-request.
158 """
159 return mg_globals.thread_scope.translations.ungettext(
160 *args, **kwargs)
161
c47be4b8 162
ae3bc7fa
AW
163def lazy_pass_to_ugettext(*args, **kwargs):
164 """
165 Lazily pass to ugettext.
166
167 This is useful if you have to define a translation on a module
168 level but you need it to not translate until the time that it's
c843de8a
SS
169 used as a string. For example, in:
170 def func(self, message=_('Hello boys and girls'))
171
172 you would want to use the lazy version for _.
ae3bc7fa 173 """
c47be4b8 174 return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs)
ae3bc7fa
AW
175
176
177def pass_to_ngettext(*args, **kwargs):
178 """
179 Pass a translation on to the appropriate ngettext method.
180
181 The reason we can't have a global ngettext method is because
182 mg_globals gets swapped out by the application per-request.
183 """
c80982c7 184 return mg_globals.thread_scope.translations.ngettext(
ae3bc7fa
AW
185 *args, **kwargs)
186
187
188def lazy_pass_to_ngettext(*args, **kwargs):
189 """
190 Lazily pass to ngettext.
191
192 This is useful if you have to define a translation on a module
193 level but you need it to not translate until the time that it's
194 used as a string.
195 """
c47be4b8 196 return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs)
ae3bc7fa 197
f1c3807d
J
198def lazy_pass_to_ungettext(*args, **kwargs):
199 """
200 Lazily pass to ungettext.
201
202 This is useful if you have to define a translation on a module
203 level but you need it to not translate until the time that it's
204 used as a string.
205 """
c47be4b8 206 return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs)
f1c3807d 207
ae3bc7fa
AW
208
209def fake_ugettext_passthrough(string):
210 """
211 Fake a ugettext call for extraction's sake ;)
212
213 In wtforms there's a separate way to define a method to translate
214 things... so we just need to mark up the text so that it can be
215 extracted, not so that it's actually run through gettext.
216 """
217 return string