Commit | Line | Data |
---|---|---|
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 | ||
17 | import gettext | |
18 | import pkg_resources | |
6ef75af5 | 19 | |
cf3b5926 | 20 | import six |
6ef75af5 SS |
21 | |
22 | from babel import localedata | |
ae3bc7fa AW |
23 | from babel.support import LazyProxy |
24 | ||
25 | from mediagoblin import mg_globals | |
26 | ||
27 | ################### | |
28 | # Translation tools | |
29 | ################### | |
30 | ||
826919c9 | 31 | AVAILABLE_LOCALES = None |
ae3bc7fa AW |
32 | TRANSLATIONS_PATH = pkg_resources.resource_filename( |
33 | 'mediagoblin', 'i18n') | |
34 | ||
4a4e4e4a | 35 | # Known RTL languages |
b02e37c2 | 36 | KNOWN_RTL = set(["ar", "fa", "he", "iw", "ur", "yi", "ji"]) |
4a4e4e4a JMM |
37 | |
38 | def is_rtl(lang): | |
39 | """Returns true when the local language is right to left""" | |
40 | return lang in KNOWN_RTL | |
ae3bc7fa | 41 | |
826919c9 SS |
42 | def 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 |
52 | class 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 |
68 | def 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 | ||
82 | def 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 | ||
93 | def 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 | |
116 | SETUP_GETTEXTS = {} | |
117 | ||
6ef75af5 | 118 | def 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 |
138 | def 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 |
143 | def 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 |
154 | def 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 |
166 | def 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 | ||
180 | def 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 | ||
191 | def 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 |
201 | def 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 | |
212 | def 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 |