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 SS |
19 | |
20 | ||
21 | from babel import localedata | |
ae3bc7fa AW |
22 | from babel.support import LazyProxy |
23 | ||
24 | from mediagoblin import mg_globals | |
25 | ||
26 | ################### | |
27 | # Translation tools | |
28 | ################### | |
29 | ||
826919c9 | 30 | AVAILABLE_LOCALES = None |
ae3bc7fa AW |
31 | TRANSLATIONS_PATH = pkg_resources.resource_filename( |
32 | 'mediagoblin', 'i18n') | |
33 | ||
4a4e4e4a JMM |
34 | # Known RTL languages |
35 | KNOWN_RTL = set(["ar", "fa", "zh","he","iw","ja","ur","yi","ji"]) | |
36 | ||
37 | def is_rtl(lang): | |
38 | """Returns true when the local language is right to left""" | |
39 | return lang in KNOWN_RTL | |
ae3bc7fa | 40 | |
826919c9 SS |
41 | def 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 |
51 | class 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 |
67 | def 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 | ||
81 | def 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 | ||
92 | def 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 | |
115 | SETUP_GETTEXTS = {} | |
116 | ||
6ef75af5 | 117 | def 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 |
137 | def 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 |
142 | def 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 |
152 | def 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 |
163 | def 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 | ||
177 | def 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 | ||
188 | def 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 |
198 | def 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 | |
209 | def 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 |