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