Codebase list flask-babel / ef06e3d
Imported Upstream version 0.9 SVN-Git Migration 8 years ago
26 changed file(s) with 588 addition(s) and 1567 deletion(s). Raw diff Collapse all Expand all
00 Metadata-Version: 1.0
11 Name: Flask-Babel
2 Version: 0.8
2 Version: 0.9
33 Summary: Adds i18n/l10n support to Flask applications
44 Home-page: http://github.com/mitsuhiko/flask-babel
55 Author: Armin Ronacher
2929 Classifier: License :: OSI Approved :: BSD License
3030 Classifier: Operating System :: OS Independent
3131 Classifier: Programming Language :: Python
32 Classifier: Programming Language :: Python :: 3
3233 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
3334 Classifier: Topic :: Software Development :: Libraries :: Python Modules
66 Flask_Babel.egg-info/PKG-INFO
77 Flask_Babel.egg-info/SOURCES.txt
88 Flask_Babel.egg-info/dependency_links.txt
9 Flask_Babel.egg-info/namespace_packages.txt
109 Flask_Babel.egg-info/not-zip-safe
1110 Flask_Babel.egg-info/requires.txt
1211 Flask_Babel.egg-info/top_level.txt
1514 docs/index.rst
1615 docs/make.bat
1716 docs/_static/flask-babel.png
18 docs/_themes/.gitignore
19 docs/_themes/LICENSE
20 docs/_themes/README
21 docs/_themes/flask_theme_support.py
22 docs/_themes/flask/layout.html
23 docs/_themes/flask/relations.html
24 docs/_themes/flask/theme.conf
25 docs/_themes/flask/static/flasky.css_t
26 docs/_themes/flask/static/small_flask.css
27 docs/_themes/flask_small/layout.html
28 docs/_themes/flask_small/theme.conf
29 docs/_themes/flask_small/static/flasky.css_t
30 flaskext/__init__.py
31 flaskext/babel.py
17 flask_babel/__init__.py
18 flask_babel/_compat.py
3219 tests/babel.cfg
3320 tests/tests.py
3421 tests/translations/messages.pot
+0
-1
Flask_Babel.egg-info/namespace_packages.txt less more
0 flaskext
00 Flask
1 Babel
2 pytz
1 Babel>=1.0
32 speaklater>=1.2
43 Jinja2>=2.5
0 flaskext
0 flask_babel
22 all: clean-pyc test
33
44 test:
5 cd tests; python tests.py
5 @cd tests; python tests.py
6
7 tox-test:
8 @tox
69
710 clean-pyc:
811 find . -name '*.pyc' -exec rm -f {} +
912 find . -name '*.pyo' -exec rm -f {} +
1013 find . -name '*~' -exec rm -f {} +
1114
15 clean: clean-pyc
16
1217 upload-docs:
1318 $(MAKE) -C docs html
1419 python setup.py upload_sphinx
20
21 .PHONY: upload-docs clean-pyc clean tox-test test all
00 Metadata-Version: 1.0
11 Name: Flask-Babel
2 Version: 0.8
2 Version: 0.9
33 Summary: Adds i18n/l10n support to Flask applications
44 Home-page: http://github.com/mitsuhiko/flask-babel
55 Author: Armin Ronacher
2929 Classifier: License :: OSI Approved :: BSD License
3030 Classifier: Operating System :: OS Independent
3131 Classifier: Programming Language :: Python
32 Classifier: Programming Language :: Python :: 3
3233 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
3334 Classifier: Topic :: Software Development :: Libraries :: Python Modules
+0
-3
docs/_themes/.gitignore less more
0 *.pyc
1 *.pyo
2 .DS_Store
+0
-37
docs/_themes/LICENSE less more
0 Copyright (c) 2010 by Armin Ronacher.
1
2 Some rights reserved.
3
4 Redistribution and use in source and binary forms of the theme, with or
5 without modification, are permitted provided that the following conditions
6 are met:
7
8 * Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10
11 * Redistributions in binary form must reproduce the above
12 copyright notice, this list of conditions and the following
13 disclaimer in the documentation and/or other materials provided
14 with the distribution.
15
16 * The names of the contributors may not be used to endorse or
17 promote products derived from this software without specific
18 prior written permission.
19
20 We kindly ask you to only use these themes in an unmodified manner just
21 for Flask and Flask-related products, not for unrelated projects. If you
22 like the visual style and want to use it for your own projects, please
23 consider making some larger changes to the themes (such as changing
24 font faces, sizes, colors or margins).
25
26 THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
30 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
36 POSSIBILITY OF SUCH DAMAGE.
+0
-31
docs/_themes/README less more
0 Flask Sphinx Styles
1 ===================
2
3 This repository contains sphinx styles for Flask and Flask related
4 projects. To use this style in your Sphinx documentation, follow
5 this guide:
6
7 1. put this folder as _themes into your docs folder. Alternatively
8 you can also use git submodules to check out the contents there.
9 2. add this to your conf.py:
10
11 sys.path.append(os.path.abspath('_themes'))
12 html_theme_path = ['_themes']
13 html_theme = 'flask'
14
15 The following themes exist:
16
17 - 'flask' - the standard flask documentation theme for large
18 projects
19 - 'flask_small' - small one-page theme. Intended to be used by
20 very small addon libraries for flask.
21
22 The following options exist for the flask_small theme:
23
24 [options]
25 index_logo = '' filename of a picture in _static
26 to be used as replacement for the
27 h1 in the index.rst file.
28 index_logo_height = 120px height of the index logo
29 github_fork = '' repository name on github for the
30 "fork me" badge
+0
-25
docs/_themes/flask/layout.html less more
0 {%- extends "basic/layout.html" %}
1 {%- block extrahead %}
2 {{ super() }}
3 {% if theme_touch_icon %}
4 <link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
5 {% endif %}
6 <link media="only screen and (max-device-width: 480px)" href="{{
7 pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
8 {% endblock %}
9 {%- block relbar2 %}{% endblock %}
10 {% block header %}
11 {{ super() }}
12 {% if pagename == 'index' %}
13 <div class=indexwrapper>
14 {% endif %}
15 {% endblock %}
16 {%- block footer %}
17 <div class="footer">
18 &copy; Copyright {{ copyright }}.
19 Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
20 </div>
21 {% if pagename == 'index' %}
22 </div>
23 {% endif %}
24 {%- endblock %}
+0
-19
docs/_themes/flask/relations.html less more
0 <h3>Related Topics</h3>
1 <ul>
2 <li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
3 {%- for parent in parents %}
4 <li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
5 {%- endfor %}
6 {%- if prev %}
7 <li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
8 }}">{{ prev.title }}</a></li>
9 {%- endif %}
10 {%- if next %}
11 <li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
12 }}">{{ next.title }}</a></li>
13 {%- endif %}
14 {%- for parent in parents %}
15 </ul></li>
16 {%- endfor %}
17 </ul></li>
18 </ul>
+0
-395
docs/_themes/flask/static/flasky.css_t less more
0 /*
1 * flasky.css_t
2 * ~~~~~~~~~~~~
3 *
4 * :copyright: Copyright 2010 by Armin Ronacher.
5 * :license: Flask Design License, see LICENSE for details.
6 */
7
8 {% set page_width = '940px' %}
9 {% set sidebar_width = '220px' %}
10
11 @import url("basic.css");
12
13 /* -- page layout ----------------------------------------------------------- */
14
15 body {
16 font-family: 'Georgia', serif;
17 font-size: 17px;
18 background-color: white;
19 color: #000;
20 margin: 0;
21 padding: 0;
22 }
23
24 div.document {
25 width: {{ page_width }};
26 margin: 30px auto 0 auto;
27 }
28
29 div.documentwrapper {
30 float: left;
31 width: 100%;
32 }
33
34 div.bodywrapper {
35 margin: 0 0 0 {{ sidebar_width }};
36 }
37
38 div.sphinxsidebar {
39 width: {{ sidebar_width }};
40 }
41
42 hr {
43 border: 1px solid #B1B4B6;
44 }
45
46 div.body {
47 background-color: #ffffff;
48 color: #3E4349;
49 padding: 0 30px 0 30px;
50 }
51
52 img.floatingflask {
53 padding: 0 0 10px 10px;
54 float: right;
55 }
56
57 div.footer {
58 width: {{ page_width }};
59 margin: 20px auto 30px auto;
60 font-size: 14px;
61 color: #888;
62 text-align: right;
63 }
64
65 div.footer a {
66 color: #888;
67 }
68
69 div.related {
70 display: none;
71 }
72
73 div.sphinxsidebar a {
74 color: #444;
75 text-decoration: none;
76 border-bottom: 1px dotted #999;
77 }
78
79 div.sphinxsidebar a:hover {
80 border-bottom: 1px solid #999;
81 }
82
83 div.sphinxsidebar {
84 font-size: 14px;
85 line-height: 1.5;
86 }
87
88 div.sphinxsidebarwrapper {
89 padding: 18px 10px;
90 }
91
92 div.sphinxsidebarwrapper p.logo {
93 padding: 0 0 20px 0;
94 margin: 0;
95 text-align: center;
96 }
97
98 div.sphinxsidebar h3,
99 div.sphinxsidebar h4 {
100 font-family: 'Garamond', 'Georgia', serif;
101 color: #444;
102 font-size: 24px;
103 font-weight: normal;
104 margin: 0 0 5px 0;
105 padding: 0;
106 }
107
108 div.sphinxsidebar h4 {
109 font-size: 20px;
110 }
111
112 div.sphinxsidebar h3 a {
113 color: #444;
114 }
115
116 div.sphinxsidebar p.logo a,
117 div.sphinxsidebar h3 a,
118 div.sphinxsidebar p.logo a:hover,
119 div.sphinxsidebar h3 a:hover {
120 border: none;
121 }
122
123 div.sphinxsidebar p {
124 color: #555;
125 margin: 10px 0;
126 }
127
128 div.sphinxsidebar ul {
129 margin: 10px 0;
130 padding: 0;
131 color: #000;
132 }
133
134 div.sphinxsidebar input {
135 border: 1px solid #ccc;
136 font-family: 'Georgia', serif;
137 font-size: 1em;
138 }
139
140 /* -- body styles ----------------------------------------------------------- */
141
142 a {
143 color: #004B6B;
144 text-decoration: underline;
145 }
146
147 a:hover {
148 color: #6D4100;
149 text-decoration: underline;
150 }
151
152 div.body h1,
153 div.body h2,
154 div.body h3,
155 div.body h4,
156 div.body h5,
157 div.body h6 {
158 font-family: 'Garamond', 'Georgia', serif;
159 font-weight: normal;
160 margin: 30px 0px 10px 0px;
161 padding: 0;
162 }
163
164 {% if theme_index_logo %}
165 div.indexwrapper h1 {
166 text-indent: -999999px;
167 background: url({{ theme_index_logo }}) no-repeat center center;
168 height: {{ theme_index_logo_height }};
169 }
170 {% endif %}
171
172 div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
173 div.body h2 { font-size: 180%; }
174 div.body h3 { font-size: 150%; }
175 div.body h4 { font-size: 130%; }
176 div.body h5 { font-size: 100%; }
177 div.body h6 { font-size: 100%; }
178
179 a.headerlink {
180 color: #ddd;
181 padding: 0 4px;
182 text-decoration: none;
183 }
184
185 a.headerlink:hover {
186 color: #444;
187 background: #eaeaea;
188 }
189
190 div.body p, div.body dd, div.body li {
191 line-height: 1.4em;
192 }
193
194 div.admonition {
195 background: #fafafa;
196 margin: 20px -30px;
197 padding: 10px 30px;
198 border-top: 1px solid #ccc;
199 border-bottom: 1px solid #ccc;
200 }
201
202 div.admonition tt.xref, div.admonition a tt {
203 border-bottom: 1px solid #fafafa;
204 }
205
206 dd div.admonition {
207 margin-left: -60px;
208 padding-left: 60px;
209 }
210
211 div.admonition p.admonition-title {
212 font-family: 'Garamond', 'Georgia', serif;
213 font-weight: normal;
214 font-size: 24px;
215 margin: 0 0 10px 0;
216 padding: 0;
217 line-height: 1;
218 }
219
220 div.admonition p.last {
221 margin-bottom: 0;
222 }
223
224 div.highlight {
225 background-color: white;
226 }
227
228 dt:target, .highlight {
229 background: #FAF3E8;
230 }
231
232 div.note {
233 background-color: #eee;
234 border: 1px solid #ccc;
235 }
236
237 div.seealso {
238 background-color: #ffc;
239 border: 1px solid #ff6;
240 }
241
242 div.topic {
243 background-color: #eee;
244 }
245
246 p.admonition-title {
247 display: inline;
248 }
249
250 p.admonition-title:after {
251 content: ":";
252 }
253
254 pre, tt {
255 font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
256 font-size: 0.9em;
257 }
258
259 img.screenshot {
260 }
261
262 tt.descname, tt.descclassname {
263 font-size: 0.95em;
264 }
265
266 tt.descname {
267 padding-right: 0.08em;
268 }
269
270 img.screenshot {
271 -moz-box-shadow: 2px 2px 4px #eee;
272 -webkit-box-shadow: 2px 2px 4px #eee;
273 box-shadow: 2px 2px 4px #eee;
274 }
275
276 table.docutils {
277 border: 1px solid #888;
278 -moz-box-shadow: 2px 2px 4px #eee;
279 -webkit-box-shadow: 2px 2px 4px #eee;
280 box-shadow: 2px 2px 4px #eee;
281 }
282
283 table.docutils td, table.docutils th {
284 border: 1px solid #888;
285 padding: 0.25em 0.7em;
286 }
287
288 table.field-list, table.footnote {
289 border: none;
290 -moz-box-shadow: none;
291 -webkit-box-shadow: none;
292 box-shadow: none;
293 }
294
295 table.footnote {
296 margin: 15px 0;
297 width: 100%;
298 border: 1px solid #eee;
299 background: #fdfdfd;
300 font-size: 0.9em;
301 }
302
303 table.footnote + table.footnote {
304 margin-top: -15px;
305 border-top: none;
306 }
307
308 table.field-list th {
309 padding: 0 0.8em 0 0;
310 }
311
312 table.field-list td {
313 padding: 0;
314 }
315
316 table.footnote td.label {
317 width: 0px;
318 padding: 0.3em 0 0.3em 0.5em;
319 }
320
321 table.footnote td {
322 padding: 0.3em 0.5em;
323 }
324
325 dl {
326 margin: 0;
327 padding: 0;
328 }
329
330 dl dd {
331 margin-left: 30px;
332 }
333
334 blockquote {
335 margin: 0 0 0 30px;
336 padding: 0;
337 }
338
339 ul, ol {
340 margin: 10px 0 10px 30px;
341 padding: 0;
342 }
343
344 pre {
345 background: #eee;
346 padding: 7px 30px;
347 margin: 15px -30px;
348 line-height: 1.3em;
349 }
350
351 dl pre, blockquote pre, li pre {
352 margin-left: -60px;
353 padding-left: 60px;
354 }
355
356 dl dl pre {
357 margin-left: -90px;
358 padding-left: 90px;
359 }
360
361 tt {
362 background-color: #ecf0f3;
363 color: #222;
364 /* padding: 1px 2px; */
365 }
366
367 tt.xref, a tt {
368 background-color: #FBFBFB;
369 border-bottom: 1px solid white;
370 }
371
372 a.reference {
373 text-decoration: none;
374 border-bottom: 1px dotted #004B6B;
375 }
376
377 a.reference:hover {
378 border-bottom: 1px solid #6D4100;
379 }
380
381 a.footnote-reference {
382 text-decoration: none;
383 font-size: 0.7em;
384 vertical-align: top;
385 border-bottom: 1px dotted #004B6B;
386 }
387
388 a.footnote-reference:hover {
389 border-bottom: 1px solid #6D4100;
390 }
391
392 a:hover tt {
393 background: #EEE;
394 }
+0
-70
docs/_themes/flask/static/small_flask.css less more
0 /*
1 * small_flask.css_t
2 * ~~~~~~~~~~~~~~~~~
3 *
4 * :copyright: Copyright 2010 by Armin Ronacher.
5 * :license: Flask Design License, see LICENSE for details.
6 */
7
8 body {
9 margin: 0;
10 padding: 20px 30px;
11 }
12
13 div.documentwrapper {
14 float: none;
15 background: white;
16 }
17
18 div.sphinxsidebar {
19 display: block;
20 float: none;
21 width: 102.5%;
22 margin: 50px -30px -20px -30px;
23 padding: 10px 20px;
24 background: #333;
25 color: white;
26 }
27
28 div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
29 div.sphinxsidebar h3 a {
30 color: white;
31 }
32
33 div.sphinxsidebar a {
34 color: #aaa;
35 }
36
37 div.sphinxsidebar p.logo {
38 display: none;
39 }
40
41 div.document {
42 width: 100%;
43 margin: 0;
44 }
45
46 div.related {
47 display: block;
48 margin: 0;
49 padding: 10px 0 20px 0;
50 }
51
52 div.related ul,
53 div.related ul li {
54 margin: 0;
55 padding: 0;
56 }
57
58 div.footer {
59 display: none;
60 }
61
62 div.bodywrapper {
63 margin: 0;
64 }
65
66 div.body {
67 min-height: 0;
68 padding: 0;
69 }
+0
-9
docs/_themes/flask/theme.conf less more
0 [theme]
1 inherit = basic
2 stylesheet = flasky.css
3 pygments_style = flask_theme_support.FlaskyStyle
4
5 [options]
6 index_logo = ''
7 index_logo_height = 120px
8 touch_icon =
+0
-22
docs/_themes/flask_small/layout.html less more
0 {% extends "basic/layout.html" %}
1 {% block header %}
2 {{ super() }}
3 {% if pagename == 'index' %}
4 <div class=indexwrapper>
5 {% endif %}
6 {% endblock %}
7 {% block footer %}
8 {% if pagename == 'index' %}
9 </div>
10 {% endif %}
11 {% endblock %}
12 {# do not display relbars #}
13 {% block relbar1 %}{% endblock %}
14 {% block relbar2 %}
15 {% if theme_github_fork %}
16 <a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
17 src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
18 {% endif %}
19 {% endblock %}
20 {% block sidebar1 %}{% endblock %}
21 {% block sidebar2 %}{% endblock %}
+0
-287
docs/_themes/flask_small/static/flasky.css_t less more
0 /*
1 * flasky.css_t
2 * ~~~~~~~~~~~~
3 *
4 * Sphinx stylesheet -- flasky theme based on nature theme.
5 *
6 * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
7 * :license: BSD, see LICENSE for details.
8 *
9 */
10
11 @import url("basic.css");
12
13 /* -- page layout ----------------------------------------------------------- */
14
15 body {
16 font-family: 'Georgia', serif;
17 font-size: 17px;
18 color: #000;
19 background: white;
20 margin: 0;
21 padding: 0;
22 }
23
24 div.documentwrapper {
25 float: left;
26 width: 100%;
27 }
28
29 div.bodywrapper {
30 margin: 40px auto 0 auto;
31 width: 700px;
32 }
33
34 hr {
35 border: 1px solid #B1B4B6;
36 }
37
38 div.body {
39 background-color: #ffffff;
40 color: #3E4349;
41 padding: 0 30px 30px 30px;
42 }
43
44 img.floatingflask {
45 padding: 0 0 10px 10px;
46 float: right;
47 }
48
49 div.footer {
50 text-align: right;
51 color: #888;
52 padding: 10px;
53 font-size: 14px;
54 width: 650px;
55 margin: 0 auto 40px auto;
56 }
57
58 div.footer a {
59 color: #888;
60 text-decoration: underline;
61 }
62
63 div.related {
64 line-height: 32px;
65 color: #888;
66 }
67
68 div.related ul {
69 padding: 0 0 0 10px;
70 }
71
72 div.related a {
73 color: #444;
74 }
75
76 /* -- body styles ----------------------------------------------------------- */
77
78 a {
79 color: #004B6B;
80 text-decoration: underline;
81 }
82
83 a:hover {
84 color: #6D4100;
85 text-decoration: underline;
86 }
87
88 div.body {
89 padding-bottom: 40px; /* saved for footer */
90 }
91
92 div.body h1,
93 div.body h2,
94 div.body h3,
95 div.body h4,
96 div.body h5,
97 div.body h6 {
98 font-family: 'Garamond', 'Georgia', serif;
99 font-weight: normal;
100 margin: 30px 0px 10px 0px;
101 padding: 0;
102 }
103
104 {% if theme_index_logo %}
105 div.indexwrapper h1 {
106 text-indent: -999999px;
107 background: url({{ theme_index_logo }}) no-repeat center center;
108 height: {{ theme_index_logo_height }};
109 }
110 {% endif %}
111
112 div.body h2 { font-size: 180%; }
113 div.body h3 { font-size: 150%; }
114 div.body h4 { font-size: 130%; }
115 div.body h5 { font-size: 100%; }
116 div.body h6 { font-size: 100%; }
117
118 a.headerlink {
119 color: white;
120 padding: 0 4px;
121 text-decoration: none;
122 }
123
124 a.headerlink:hover {
125 color: #444;
126 background: #eaeaea;
127 }
128
129 div.body p, div.body dd, div.body li {
130 line-height: 1.4em;
131 }
132
133 div.admonition {
134 background: #fafafa;
135 margin: 20px -30px;
136 padding: 10px 30px;
137 border-top: 1px solid #ccc;
138 border-bottom: 1px solid #ccc;
139 }
140
141 div.admonition p.admonition-title {
142 font-family: 'Garamond', 'Georgia', serif;
143 font-weight: normal;
144 font-size: 24px;
145 margin: 0 0 10px 0;
146 padding: 0;
147 line-height: 1;
148 }
149
150 div.admonition p.last {
151 margin-bottom: 0;
152 }
153
154 div.highlight{
155 background-color: white;
156 }
157
158 dt:target, .highlight {
159 background: #FAF3E8;
160 }
161
162 div.note {
163 background-color: #eee;
164 border: 1px solid #ccc;
165 }
166
167 div.seealso {
168 background-color: #ffc;
169 border: 1px solid #ff6;
170 }
171
172 div.topic {
173 background-color: #eee;
174 }
175
176 div.warning {
177 background-color: #ffe4e4;
178 border: 1px solid #f66;
179 }
180
181 p.admonition-title {
182 display: inline;
183 }
184
185 p.admonition-title:after {
186 content: ":";
187 }
188
189 pre, tt {
190 font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
191 font-size: 0.85em;
192 }
193
194 img.screenshot {
195 }
196
197 tt.descname, tt.descclassname {
198 font-size: 0.95em;
199 }
200
201 tt.descname {
202 padding-right: 0.08em;
203 }
204
205 img.screenshot {
206 -moz-box-shadow: 2px 2px 4px #eee;
207 -webkit-box-shadow: 2px 2px 4px #eee;
208 box-shadow: 2px 2px 4px #eee;
209 }
210
211 table.docutils {
212 border: 1px solid #888;
213 -moz-box-shadow: 2px 2px 4px #eee;
214 -webkit-box-shadow: 2px 2px 4px #eee;
215 box-shadow: 2px 2px 4px #eee;
216 }
217
218 table.docutils td, table.docutils th {
219 border: 1px solid #888;
220 padding: 0.25em 0.7em;
221 }
222
223 table.field-list, table.footnote {
224 border: none;
225 -moz-box-shadow: none;
226 -webkit-box-shadow: none;
227 box-shadow: none;
228 }
229
230 table.footnote {
231 margin: 15px 0;
232 width: 100%;
233 border: 1px solid #eee;
234 }
235
236 table.field-list th {
237 padding: 0 0.8em 0 0;
238 }
239
240 table.field-list td {
241 padding: 0;
242 }
243
244 table.footnote td {
245 padding: 0.5em;
246 }
247
248 dl {
249 margin: 0;
250 padding: 0;
251 }
252
253 dl dd {
254 margin-left: 30px;
255 }
256
257 pre {
258 padding: 0;
259 margin: 15px -30px;
260 padding: 8px;
261 line-height: 1.3em;
262 padding: 7px 30px;
263 background: #eee;
264 border-radius: 2px;
265 -moz-border-radius: 2px;
266 -webkit-border-radius: 2px;
267 }
268
269 dl pre {
270 margin-left: -60px;
271 padding-left: 60px;
272 }
273
274 tt {
275 background-color: #ecf0f3;
276 color: #222;
277 /* padding: 1px 2px; */
278 }
279
280 tt.xref, a tt {
281 background-color: #FBFBFB;
282 }
283
284 a:hover tt {
285 background: #EEE;
286 }
+0
-10
docs/_themes/flask_small/theme.conf less more
0 [theme]
1 inherit = basic
2 stylesheet = flasky.css
3 nosidebar = true
4 pygments_style = flask_theme_support.FlaskyStyle
5
6 [options]
7 index_logo = ''
8 index_logo_height = 120px
9 github_fork = ''
+0
-86
docs/_themes/flask_theme_support.py less more
0 # flasky extensions. flasky pygments style based on tango style
1 from pygments.style import Style
2 from pygments.token import Keyword, Name, Comment, String, Error, \
3 Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
4
5
6 class FlaskyStyle(Style):
7 background_color = "#f8f8f8"
8 default_style = ""
9
10 styles = {
11 # No corresponding class for the following:
12 #Text: "", # class: ''
13 Whitespace: "underline #f8f8f8", # class: 'w'
14 Error: "#a40000 border:#ef2929", # class: 'err'
15 Other: "#000000", # class 'x'
16
17 Comment: "italic #8f5902", # class: 'c'
18 Comment.Preproc: "noitalic", # class: 'cp'
19
20 Keyword: "bold #004461", # class: 'k'
21 Keyword.Constant: "bold #004461", # class: 'kc'
22 Keyword.Declaration: "bold #004461", # class: 'kd'
23 Keyword.Namespace: "bold #004461", # class: 'kn'
24 Keyword.Pseudo: "bold #004461", # class: 'kp'
25 Keyword.Reserved: "bold #004461", # class: 'kr'
26 Keyword.Type: "bold #004461", # class: 'kt'
27
28 Operator: "#582800", # class: 'o'
29 Operator.Word: "bold #004461", # class: 'ow' - like keywords
30
31 Punctuation: "bold #000000", # class: 'p'
32
33 # because special names such as Name.Class, Name.Function, etc.
34 # are not recognized as such later in the parsing, we choose them
35 # to look the same as ordinary variables.
36 Name: "#000000", # class: 'n'
37 Name.Attribute: "#c4a000", # class: 'na' - to be revised
38 Name.Builtin: "#004461", # class: 'nb'
39 Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
40 Name.Class: "#000000", # class: 'nc' - to be revised
41 Name.Constant: "#000000", # class: 'no' - to be revised
42 Name.Decorator: "#888", # class: 'nd' - to be revised
43 Name.Entity: "#ce5c00", # class: 'ni'
44 Name.Exception: "bold #cc0000", # class: 'ne'
45 Name.Function: "#000000", # class: 'nf'
46 Name.Property: "#000000", # class: 'py'
47 Name.Label: "#f57900", # class: 'nl'
48 Name.Namespace: "#000000", # class: 'nn' - to be revised
49 Name.Other: "#000000", # class: 'nx'
50 Name.Tag: "bold #004461", # class: 'nt' - like a keyword
51 Name.Variable: "#000000", # class: 'nv' - to be revised
52 Name.Variable.Class: "#000000", # class: 'vc' - to be revised
53 Name.Variable.Global: "#000000", # class: 'vg' - to be revised
54 Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
55
56 Number: "#990000", # class: 'm'
57
58 Literal: "#000000", # class: 'l'
59 Literal.Date: "#000000", # class: 'ld'
60
61 String: "#4e9a06", # class: 's'
62 String.Backtick: "#4e9a06", # class: 'sb'
63 String.Char: "#4e9a06", # class: 'sc'
64 String.Doc: "italic #8f5902", # class: 'sd' - like a comment
65 String.Double: "#4e9a06", # class: 's2'
66 String.Escape: "#4e9a06", # class: 'se'
67 String.Heredoc: "#4e9a06", # class: 'sh'
68 String.Interpol: "#4e9a06", # class: 'si'
69 String.Other: "#4e9a06", # class: 'sx'
70 String.Regex: "#4e9a06", # class: 'sr'
71 String.Single: "#4e9a06", # class: 's1'
72 String.Symbol: "#4e9a06", # class: 'ss'
73
74 Generic: "#000000", # class: 'g'
75 Generic.Deleted: "#a40000", # class: 'gd'
76 Generic.Emph: "italic #000000", # class: 'ge'
77 Generic.Error: "#ef2929", # class: 'gr'
78 Generic.Heading: "bold #000080", # class: 'gh'
79 Generic.Inserted: "#00A000", # class: 'gi'
80 Generic.Output: "#888", # class: 'go'
81 Generic.Prompt: "#745334", # class: 'gp'
82 Generic.Strong: "bold #000000", # class: 'gs'
83 Generic.Subheading: "bold #800080", # class: 'gu'
84 Generic.Traceback: "bold #a40000", # class: 'gt'
85 }
00 Flask-Babel
11 ===========
22
3 .. module:: flaskext.babel
3 .. module:: flask.ext.babel
44
55 Flask-Babel is an extension to `Flask`_ that adds i18n and l10n support to
66 any Flask application with the help of `babel`_, `pytz`_ and
3030 object after configuring the application::
3131
3232 from flask import Flask
33 from flaskext.babel import Babel
33 from flask.ext.babel import Babel
3434
3535 app = Flask(__name__)
3636 app.config.from_pyfile('mysettings.cfg')
105105
106106 Here some examples:
107107
108 >>> from flaskext.babel import format_datetime
108 >>> from flask.ext.babel import format_datetime
109109 >>> from datetime import datetime
110110 >>> format_datetime(datetime(1987, 3, 5, 17, 12))
111111 u'Mar 5, 1987 5:12:00 PM'
121121 And again with a different language:
122122
123123 >>> app.config['BABEL_DEFAULT_LOCALE'] = 'de'
124 >>> from flaskext.babel import refresh; refresh()
124 >>> from flask.ext.babel import refresh; refresh()
125125 >>> format_datetime(datetime(1987, 3, 5, 17, 12), 'EEEE, d. MMMM yyyy H:mm')
126126 u'Donnerstag, 5. M\xe4rz 1987 17:12'
127127
141141 :func:`ngettext`. The first to translate singular strings and the second
142142 to translate strings that might become plural. Here some examples::
143143
144 from flaskext.babel import gettext, ngettext
144 from flask.ext.babel import gettext, ngettext
145145
146146 gettext(u'A simple string')
147147 gettext(u'Value: %(value)s', value=42)
152152 strings. Lazy strings will not be evaluated until they are actually used.
153153 To use such a lazy string, use the :func:`lazy_gettext` function::
154154
155 from flaskext.babel import lazy_gettext
155 from flask.ext.babel import lazy_gettext
156156
157157 class MyForm(formlibrary.FormBase):
158158 success_message = lazy_gettext(u'The form was successfully saved.')
0 # -*- coding: utf-8 -*-
1 """
2 flaskext.babel
3 ~~~~~~~~~~~~~~
4
5 Implements i18n/l10n support for Flask applications based on Babel.
6
7 :copyright: (c) 2013 by Armin Ronacher, Daniel Neuhäuser.
8 :license: BSD, see LICENSE for more details.
9 """
10 from __future__ import absolute_import
11 import os
12
13 # this is a workaround for a snow leopard bug that babel does not
14 # work around :)
15 if os.environ.get('LC_CTYPE', '').lower() == 'utf-8':
16 os.environ['LC_CTYPE'] = 'en_US.utf-8'
17
18 from datetime import datetime
19 from flask import _request_ctx_stack
20 from babel import dates, numbers, support, Locale
21 from werkzeug import ImmutableDict
22 try:
23 from pytz.gae import pytz
24 except ImportError:
25 from pytz import timezone, UTC
26 else:
27 timezone = pytz.timezone
28 UTC = pytz.UTC
29
30 from flask_babel._compat import string_types
31
32
33 class Babel(object):
34 """Central controller class that can be used to configure how
35 Flask-Babel behaves. Each application that wants to use Flask-Babel
36 has to create, or run :meth:`init_app` on, an instance of this class
37 after the configuration was initialized.
38 """
39
40 default_date_formats = ImmutableDict({
41 'time': 'medium',
42 'date': 'medium',
43 'datetime': 'medium',
44 'time.short': None,
45 'time.medium': None,
46 'time.full': None,
47 'time.long': None,
48 'date.short': None,
49 'date.medium': None,
50 'date.full': None,
51 'date.long': None,
52 'datetime.short': None,
53 'datetime.medium': None,
54 'datetime.full': None,
55 'datetime.long': None,
56 })
57
58 def __init__(self, app=None, default_locale='en', default_timezone='UTC',
59 date_formats=None, configure_jinja=True):
60 self._default_locale = default_locale
61 self._default_timezone = default_timezone
62 self._date_formats = date_formats
63 self._configure_jinja = configure_jinja
64 self.app = app
65
66 if app is not None:
67 self.init_app(app)
68
69 def init_app(self, app):
70 """Set up this instance for use with *app*, if no app was passed to
71 the constructor.
72 """
73 self.app = app
74 app.babel_instance = self
75 if not hasattr(app, 'extensions'):
76 app.extensions = {}
77 app.extensions['babel'] = self
78
79 app.config.setdefault('BABEL_DEFAULT_LOCALE', self._default_locale)
80 app.config.setdefault('BABEL_DEFAULT_TIMEZONE', self._default_timezone)
81 if self._date_formats is None:
82 self._date_formats = self.default_date_formats.copy()
83
84 #: a mapping of Babel datetime format strings that can be modified
85 #: to change the defaults. If you invoke :func:`format_datetime`
86 #: and do not provide any format string Flask-Babel will do the
87 #: following things:
88 #:
89 #: 1. look up ``date_formats['datetime']``. By default ``'medium'``
90 #: is returned to enforce medium length datetime formats.
91 #: 2. ``date_formats['datetime.medium'] (if ``'medium'`` was
92 #: returned in step one) is looked up. If the return value
93 #: is anything but `None` this is used as new format string.
94 #: otherwise the default for that language is used.
95 self.date_formats = self._date_formats
96
97 self.locale_selector_func = None
98 self.timezone_selector_func = None
99
100 if self._configure_jinja:
101 app.jinja_env.filters.update(
102 datetimeformat=format_datetime,
103 dateformat=format_date,
104 timeformat=format_time,
105 timedeltaformat=format_timedelta,
106
107 numberformat=format_number,
108 decimalformat=format_decimal,
109 currencyformat=format_currency,
110 percentformat=format_percent,
111 scientificformat=format_scientific,
112 )
113 app.jinja_env.add_extension('jinja2.ext.i18n')
114 app.jinja_env.install_gettext_callables(
115 lambda x: get_translations().ugettext(x),
116 lambda s, p, n: get_translations().ungettext(s, p, n),
117 newstyle=True
118 )
119
120 def localeselector(self, f):
121 """Registers a callback function for locale selection. The default
122 behaves as if a function was registered that returns `None` all the
123 time. If `None` is returned, the locale falls back to the one from
124 the configuration.
125
126 This has to return the locale as string (eg: ``'de_AT'``, ''`en_US`'')
127 """
128 assert self.locale_selector_func is None, \
129 'a localeselector function is already registered'
130 self.locale_selector_func = f
131 return f
132
133 def timezoneselector(self, f):
134 """Registers a callback function for timezone selection. The default
135 behaves as if a function was registered that returns `None` all the
136 time. If `None` is returned, the timezone falls back to the one from
137 the configuration.
138
139 This has to return the timezone as string (eg: ``'Europe/Vienna'``)
140 """
141 assert self.timezone_selector_func is None, \
142 'a timezoneselector function is already registered'
143 self.timezone_selector_func = f
144 return f
145
146
147 def list_translations(self):
148 """Returns a list of all the locales translations exist for. The
149 list returned will be filled with actual locale objects and not just
150 strings.
151
152 .. versionadded:: 0.6
153 """
154 dirname = os.path.join(self.app.root_path, 'translations')
155 if not os.path.isdir(dirname):
156 return []
157 result = []
158 for folder in os.listdir(dirname):
159 locale_dir = os.path.join(dirname, folder, 'LC_MESSAGES')
160 if not os.path.isdir(locale_dir):
161 continue
162 if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)):
163 result.append(Locale.parse(folder))
164 if not result:
165 result.append(Locale.parse(self._default_locale))
166 return result
167
168 @property
169 def default_locale(self):
170 """The default locale from the configuration as instance of a
171 `babel.Locale` object.
172 """
173 return Locale.parse(self.app.config['BABEL_DEFAULT_LOCALE'])
174
175 @property
176 def default_timezone(self):
177 """The default timezone from the configuration as instance of a
178 `pytz.timezone` object.
179 """
180 return timezone(self.app.config['BABEL_DEFAULT_TIMEZONE'])
181
182
183 def get_translations():
184 """Returns the correct gettext translations that should be used for
185 this request. This will never fail and return a dummy translation
186 object if used outside of the request or if a translation cannot be
187 found.
188 """
189 ctx = _request_ctx_stack.top
190 if ctx is None:
191 return None
192 translations = getattr(ctx, 'babel_translations', None)
193 if translations is None:
194 dirname = os.path.join(ctx.app.root_path, 'translations')
195 translations = support.Translations.load(dirname, [get_locale()])
196 ctx.babel_translations = translations
197 return translations
198
199
200 def get_locale():
201 """Returns the locale that should be used for this request as
202 `babel.Locale` object. This returns `None` if used outside of
203 a request.
204 """
205 ctx = _request_ctx_stack.top
206 if ctx is None:
207 return None
208 locale = getattr(ctx, 'babel_locale', None)
209 if locale is None:
210 babel = ctx.app.extensions['babel']
211 if babel.locale_selector_func is None:
212 locale = babel.default_locale
213 else:
214 rv = babel.locale_selector_func()
215 if rv is None:
216 locale = babel.default_locale
217 else:
218 locale = Locale.parse(rv)
219 ctx.babel_locale = locale
220 return locale
221
222
223 def get_timezone():
224 """Returns the timezone that should be used for this request as
225 `pytz.timezone` object. This returns `None` if used outside of
226 a request.
227 """
228 ctx = _request_ctx_stack.top
229 tzinfo = getattr(ctx, 'babel_tzinfo', None)
230 if tzinfo is None:
231 babel = ctx.app.extensions['babel']
232 if babel.timezone_selector_func is None:
233 tzinfo = babel.default_timezone
234 else:
235 rv = babel.timezone_selector_func()
236 if rv is None:
237 tzinfo = babel.default_timezone
238 else:
239 if isinstance(rv, string_types):
240 tzinfo = timezone(rv)
241 else:
242 tzinfo = rv
243 ctx.babel_tzinfo = tzinfo
244 return tzinfo
245
246
247 def refresh():
248 """Refreshes the cached timezones and locale information. This can
249 be used to switch a translation between a request and if you want
250 the changes to take place immediately, not just with the next request::
251
252 user.timezone = request.form['timezone']
253 user.locale = request.form['locale']
254 refresh()
255 flash(gettext('Language was changed'))
256
257 Without that refresh, the :func:`~flask.flash` function would probably
258 return English text and a now German page.
259 """
260 ctx = _request_ctx_stack.top
261 for key in 'babel_locale', 'babel_tzinfo', 'babel_translations':
262 if hasattr(ctx, key):
263 delattr(ctx, key)
264
265
266 def _get_format(key, format):
267 """A small helper for the datetime formatting functions. Looks up
268 format defaults for different kinds.
269 """
270 babel = _request_ctx_stack.top.app.extensions['babel']
271 if format is None:
272 format = babel.date_formats[key]
273 if format in ('short', 'medium', 'full', 'long'):
274 rv = babel.date_formats['%s.%s' % (key, format)]
275 if rv is not None:
276 format = rv
277 return format
278
279
280 def to_user_timezone(datetime):
281 """Convert a datetime object to the user's timezone. This automatically
282 happens on all date formatting unless rebasing is disabled. If you need
283 to convert a :class:`datetime.datetime` object at any time to the user's
284 timezone (as returned by :func:`get_timezone` this function can be used).
285 """
286 if datetime.tzinfo is None:
287 datetime = datetime.replace(tzinfo=UTC)
288 tzinfo = get_timezone()
289 return tzinfo.normalize(datetime.astimezone(tzinfo))
290
291
292 def to_utc(datetime):
293 """Convert a datetime object to UTC and drop tzinfo. This is the
294 opposite operation to :func:`to_user_timezone`.
295 """
296 if datetime.tzinfo is None:
297 datetime = get_timezone().localize(datetime)
298 return datetime.astimezone(UTC).replace(tzinfo=None)
299
300
301 def format_datetime(datetime=None, format=None, rebase=True):
302 """Return a date formatted according to the given pattern. If no
303 :class:`~datetime.datetime` object is passed, the current time is
304 assumed. By default rebasing happens which causes the object to
305 be converted to the users's timezone (as returned by
306 :func:`to_user_timezone`). This function formats both date and
307 time.
308
309 The format parameter can either be ``'short'``, ``'medium'``,
310 ``'long'`` or ``'full'`` (in which cause the language's default for
311 that setting is used, or the default from the :attr:`Babel.date_formats`
312 mapping is used) or a format string as documented by Babel.
313
314 This function is also available in the template context as filter
315 named `datetimeformat`.
316 """
317 format = _get_format('datetime', format)
318 return _date_format(dates.format_datetime, datetime, format, rebase)
319
320
321 def format_date(date=None, format=None, rebase=True):
322 """Return a date formatted according to the given pattern. If no
323 :class:`~datetime.datetime` or :class:`~datetime.date` object is passed,
324 the current time is assumed. By default rebasing happens which causes
325 the object to be converted to the users's timezone (as returned by
326 :func:`to_user_timezone`). This function only formats the date part
327 of a :class:`~datetime.datetime` object.
328
329 The format parameter can either be ``'short'``, ``'medium'``,
330 ``'long'`` or ``'full'`` (in which cause the language's default for
331 that setting is used, or the default from the :attr:`Babel.date_formats`
332 mapping is used) or a format string as documented by Babel.
333
334 This function is also available in the template context as filter
335 named `dateformat`.
336 """
337 if rebase and isinstance(date, datetime):
338 date = to_user_timezone(date)
339 format = _get_format('date', format)
340 return _date_format(dates.format_date, date, format, rebase)
341
342
343 def format_time(time=None, format=None, rebase=True):
344 """Return a time formatted according to the given pattern. If no
345 :class:`~datetime.datetime` object is passed, the current time is
346 assumed. By default rebasing happens which causes the object to
347 be converted to the users's timezone (as returned by
348 :func:`to_user_timezone`). This function formats both date and
349 time.
350
351 The format parameter can either be ``'short'``, ``'medium'``,
352 ``'long'`` or ``'full'`` (in which cause the language's default for
353 that setting is used, or the default from the :attr:`Babel.date_formats`
354 mapping is used) or a format string as documented by Babel.
355
356 This function is also available in the template context as filter
357 named `timeformat`.
358 """
359 format = _get_format('time', format)
360 return _date_format(dates.format_time, time, format, rebase)
361
362
363 def format_timedelta(datetime_or_timedelta, granularity='second'):
364 """Format the elapsed time from the given date to now or the given
365 timedelta. This currently requires an unreleased development
366 version of Babel.
367
368 This function is also available in the template context as filter
369 named `timedeltaformat`.
370 """
371 if isinstance(datetime_or_timedelta, datetime):
372 datetime_or_timedelta = datetime.utcnow() - datetime_or_timedelta
373 return dates.format_timedelta(datetime_or_timedelta, granularity,
374 locale=get_locale())
375
376
377 def _date_format(formatter, obj, format, rebase, **extra):
378 """Internal helper that formats the date."""
379 locale = get_locale()
380 extra = {}
381 if formatter is not dates.format_date and rebase:
382 extra['tzinfo'] = get_timezone()
383 return formatter(obj, format, locale=locale, **extra)
384
385
386 def format_number(number):
387 """Return the given number formatted for the locale in request
388
389 :param number: the number to format
390 :return: the formatted number
391 :rtype: unicode
392 """
393 locale = get_locale()
394 return numbers.format_number(number, locale=locale)
395
396
397 def format_decimal(number, format=None):
398 """Return the given decimal number formatted for the locale in request
399
400 :param number: the number to format
401 :param format: the format to use
402 :return: the formatted number
403 :rtype: unicode
404 """
405 locale = get_locale()
406 return numbers.format_decimal(number, format=format, locale=locale)
407
408
409 def format_currency(number, currency, format=None):
410 """Return the given number formatted for the locale in request
411
412 :param number: the number to format
413 :param currency: the currency code
414 :param format: the format to use
415 :return: the formatted number
416 :rtype: unicode
417 """
418 locale = get_locale()
419 return numbers.format_currency(
420 number, currency, format=format, locale=locale
421 )
422
423
424 def format_percent(number, format=None):
425 """Return formatted percent value for the locale in request
426
427 :param number: the number to format
428 :param format: the format to use
429 :return: the formatted percent number
430 :rtype: unicode
431 """
432 locale = get_locale()
433 return numbers.format_percent(number, format=format, locale=locale)
434
435
436 def format_scientific(number, format=None):
437 """Return value formatted in scientific notation for the locale in request
438
439 :param number: the number to format
440 :param format: the format to use
441 :return: the formatted percent number
442 :rtype: unicode
443 """
444 locale = get_locale()
445 return numbers.format_scientific(number, format=format, locale=locale)
446
447
448 def gettext(string, **variables):
449 """Translates a string with the current locale and passes in the
450 given keyword arguments as mapping to a string formatting string.
451
452 ::
453
454 gettext(u'Hello World!')
455 gettext(u'Hello %(name)s!', name='World')
456 """
457 t = get_translations()
458 if t is None:
459 return string % variables
460 return t.ugettext(string) % variables
461 _ = gettext
462
463
464 def ngettext(singular, plural, num, **variables):
465 """Translates a string with the current locale and passes in the
466 given keyword arguments as mapping to a string formatting string.
467 The `num` parameter is used to dispatch between singular and various
468 plural forms of the message. It is available in the format string
469 as ``%(num)d`` or ``%(num)s``. The source language should be
470 English or a similar language which only has one plural form.
471
472 ::
473
474 ngettext(u'%(num)d Apple', u'%(num)d Apples', num=len(apples))
475 """
476 variables.setdefault('num', num)
477 t = get_translations()
478 if t is None:
479 return (singular if num == 1 else plural) % variables
480 return t.ungettext(singular, plural, num) % variables
481
482
483 def pgettext(context, string, **variables):
484 """Like :func:`gettext` but with a context.
485
486 .. versionadded:: 0.7
487 """
488 t = get_translations()
489 if t is None:
490 return string % variables
491 return t.upgettext(context, string) % variables
492
493
494 def npgettext(context, singular, plural, num, **variables):
495 """Like :func:`ngettext` but with a context.
496
497 .. versionadded:: 0.7
498 """
499 variables.setdefault('num', num)
500 t = get_translations()
501 if t is None:
502 return (singular if num == 1 else plural) % variables
503 return t.unpgettext(context, singular, plural, num) % variables
504
505
506 def lazy_gettext(string, **variables):
507 """Like :func:`gettext` but the string returned is lazy which means
508 it will be translated when it is used as an actual string.
509
510 Example::
511
512 hello = lazy_gettext(u'Hello World')
513
514 @app.route('/')
515 def index():
516 return unicode(hello)
517 """
518 from speaklater import make_lazy_string
519 return make_lazy_string(gettext, string, **variables)
520
521
522 def lazy_pgettext(context, string, **variables):
523 """Like :func:`pgettext` but the string returned is lazy which means
524 it will be translated when it is used as an actual string.
525
526 .. versionadded:: 0.7
527 """
528 from speaklater import make_lazy_string
529 return make_lazy_string(pgettext, context, string, **variables)
0 # -*- coding: utf-8 -*-
1 """
2 flask.ext.babel._compat
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 :copyright: (c) 2013 by Armin Ronacher, Daniel Neuhäuser.
6 :license: BSD, see LICENSE for more details.
7 """
8 import sys
9
10
11 PY2 = sys.version_info[0] == 2
12
13
14 if PY2:
15 text_type = unicode
16 string_types = (str, unicode)
17 else:
18 text_type = str
19 string_types = (str, )
+0
-1
flaskext/__init__.py less more
0 __import__('pkg_resources').declare_namespace(__name__)
+0
-528
flaskext/babel.py less more
0 # -*- coding: utf-8 -*-
1 """
2 flaskext.babel
3 ~~~~~~~~~~~~~~
4
5 Implements i18n/l10n support for Flask applications based on Babel.
6
7 :copyright: (c) 2010 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from __future__ import absolute_import
11 import os
12
13 # this is a workaround for a snow leopard bug that babel does not
14 # work around :)
15 if os.environ.get('LC_CTYPE', '').lower() == 'utf-8':
16 os.environ['LC_CTYPE'] = 'en_US.utf-8'
17
18 from datetime import datetime
19 from flask import _request_ctx_stack
20 from babel import dates, numbers, support, Locale
21 from werkzeug import ImmutableDict
22 try:
23 from pytz.gae import pytz
24 except ImportError:
25 from pytz import timezone, UTC
26 else:
27 timezone = pytz.timezone
28 UTC = pytz.UTC
29
30
31 class Babel(object):
32 """Central controller class that can be used to configure how
33 Flask-Babel behaves. Each application that wants to use Flask-Babel
34 has to create, or run :meth:`init_app` on, an instance of this class
35 after the configuration was initialized.
36 """
37
38 default_date_formats = ImmutableDict({
39 'time': 'medium',
40 'date': 'medium',
41 'datetime': 'medium',
42 'time.short': None,
43 'time.medium': None,
44 'time.full': None,
45 'time.long': None,
46 'date.short': None,
47 'date.medium': None,
48 'date.full': None,
49 'date.long': None,
50 'datetime.short': None,
51 'datetime.medium': None,
52 'datetime.full': None,
53 'datetime.long': None,
54 })
55
56 def __init__(self, app=None, default_locale='en', default_timezone='UTC',
57 date_formats=None, configure_jinja=True):
58 self._default_locale = default_locale
59 self._default_timezone = default_timezone
60 self._date_formats = date_formats
61 self._configure_jinja = configure_jinja
62 self.app = app
63
64 if app is not None:
65 self.init_app(app)
66
67 def init_app(self, app):
68 """Set up this instance for use with *app*, if no app was passed to
69 the constructor.
70 """
71 self.app = app
72 app.babel_instance = self
73 if not hasattr(app, 'extensions'):
74 app.extensions = {}
75 app.extensions['babel'] = self
76
77 app.config.setdefault('BABEL_DEFAULT_LOCALE', self._default_locale)
78 app.config.setdefault('BABEL_DEFAULT_TIMEZONE', self._default_timezone)
79 if self._date_formats is None:
80 self._date_formats = self.default_date_formats.copy()
81
82 #: a mapping of Babel datetime format strings that can be modified
83 #: to change the defaults. If you invoke :func:`format_datetime`
84 #: and do not provide any format string Flask-Babel will do the
85 #: following things:
86 #:
87 #: 1. look up ``date_formats['datetime']``. By default ``'medium'``
88 #: is returned to enforce medium length datetime formats.
89 #: 2. ``date_formats['datetime.medium'] (if ``'medium'`` was
90 #: returned in step one) is looked up. If the return value
91 #: is anything but `None` this is used as new format string.
92 #: otherwise the default for that language is used.
93 self.date_formats = self._date_formats
94
95 self.locale_selector_func = None
96 self.timezone_selector_func = None
97
98 if self._configure_jinja:
99 app.jinja_env.filters.update(
100 datetimeformat=format_datetime,
101 dateformat=format_date,
102 timeformat=format_time,
103 timedeltaformat=format_timedelta,
104
105 numberformat=format_number,
106 decimalformat=format_decimal,
107 currencyformat=format_currency,
108 percentformat=format_percent,
109 scientificformat=format_scientific,
110 )
111 app.jinja_env.add_extension('jinja2.ext.i18n')
112 app.jinja_env.install_gettext_callables(
113 lambda x: get_translations().ugettext(x),
114 lambda s, p, n: get_translations().ungettext(s, p, n),
115 newstyle=True
116 )
117
118 def localeselector(self, f):
119 """Registers a callback function for locale selection. The default
120 behaves as if a function was registered that returns `None` all the
121 time. If `None` is returned, the locale falls back to the one from
122 the configuration.
123
124 This has to return the locale as string (eg: ``'de_AT'``, ''`en_US`'')
125 """
126 assert self.locale_selector_func is None, \
127 'a localeselector function is already registered'
128 self.locale_selector_func = f
129 return f
130
131 def timezoneselector(self, f):
132 """Registers a callback function for timezone selection. The default
133 behaves as if a function was registered that returns `None` all the
134 time. If `None` is returned, the timezone falls back to the one from
135 the configuration.
136
137 This has to return the timezone as string (eg: ``'Europe/Vienna'``)
138 """
139 assert self.timezone_selector_func is None, \
140 'a timezoneselector function is already registered'
141 self.timezone_selector_func = f
142 return f
143
144
145 def list_translations(self):
146 """Returns a list of all the locales translations exist for. The
147 list returned will be filled with actual locale objects and not just
148 strings.
149
150 .. versionadded:: 0.6
151 """
152 dirname = os.path.join(self.app.root_path, 'translations')
153 if not os.path.isdir(dirname):
154 return []
155 result = []
156 for folder in os.listdir(dirname):
157 locale_dir = os.path.join(dirname, folder, 'LC_MESSAGES')
158 if not os.path.isdir(locale_dir):
159 continue
160 if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)):
161 result.append(Locale.parse(folder))
162 if not result:
163 result.append(Locale.parse(self._default_locale))
164 return result
165
166 @property
167 def default_locale(self):
168 """The default locale from the configuration as instance of a
169 `babel.Locale` object.
170 """
171 return Locale.parse(self.app.config['BABEL_DEFAULT_LOCALE'])
172
173 @property
174 def default_timezone(self):
175 """The default timezone from the configuration as instance of a
176 `pytz.timezone` object.
177 """
178 return timezone(self.app.config['BABEL_DEFAULT_TIMEZONE'])
179
180
181 def get_translations():
182 """Returns the correct gettext translations that should be used for
183 this request. This will never fail and return a dummy translation
184 object if used outside of the request or if a translation cannot be
185 found.
186 """
187 ctx = _request_ctx_stack.top
188 if ctx is None:
189 return None
190 translations = getattr(ctx, 'babel_translations', None)
191 if translations is None:
192 dirname = os.path.join(ctx.app.root_path, 'translations')
193 translations = support.Translations.load(dirname, [get_locale()])
194 ctx.babel_translations = translations
195 return translations
196
197
198 def get_locale():
199 """Returns the locale that should be used for this request as
200 `babel.Locale` object. This returns `None` if used outside of
201 a request.
202 """
203 ctx = _request_ctx_stack.top
204 if ctx is None:
205 return None
206 locale = getattr(ctx, 'babel_locale', None)
207 if locale is None:
208 babel = ctx.app.extensions['babel']
209 if babel.locale_selector_func is None:
210 locale = babel.default_locale
211 else:
212 rv = babel.locale_selector_func()
213 if rv is None:
214 locale = babel.default_locale
215 else:
216 locale = Locale.parse(rv)
217 ctx.babel_locale = locale
218 return locale
219
220
221 def get_timezone():
222 """Returns the timezone that should be used for this request as
223 `pytz.timezone` object. This returns `None` if used outside of
224 a request.
225 """
226 ctx = _request_ctx_stack.top
227 tzinfo = getattr(ctx, 'babel_tzinfo', None)
228 if tzinfo is None:
229 babel = ctx.app.extensions['babel']
230 if babel.timezone_selector_func is None:
231 tzinfo = babel.default_timezone
232 else:
233 rv = babel.timezone_selector_func()
234 if rv is None:
235 tzinfo = babel.default_timezone
236 else:
237 if isinstance(rv, basestring):
238 tzinfo = timezone(rv)
239 else:
240 tzinfo = rv
241 ctx.babel_tzinfo = tzinfo
242 return tzinfo
243
244
245 def refresh():
246 """Refreshes the cached timezones and locale information. This can
247 be used to switch a translation between a request and if you want
248 the changes to take place immediately, not just with the next request::
249
250 user.timezone = request.form['timezone']
251 user.locale = request.form['locale']
252 refresh()
253 flash(gettext('Language was changed'))
254
255 Without that refresh, the :func:`~flask.flash` function would probably
256 return English text and a now German page.
257 """
258 ctx = _request_ctx_stack.top
259 for key in 'babel_locale', 'babel_tzinfo', 'babel_translations':
260 if hasattr(ctx, key):
261 delattr(ctx, key)
262
263
264 def _get_format(key, format):
265 """A small helper for the datetime formatting functions. Looks up
266 format defaults for different kinds.
267 """
268 babel = _request_ctx_stack.top.app.extensions['babel']
269 if format is None:
270 format = babel.date_formats[key]
271 if format in ('short', 'medium', 'full', 'long'):
272 rv = babel.date_formats['%s.%s' % (key, format)]
273 if rv is not None:
274 format = rv
275 return format
276
277
278 def to_user_timezone(datetime):
279 """Convert a datetime object to the user's timezone. This automatically
280 happens on all date formatting unless rebasing is disabled. If you need
281 to convert a :class:`datetime.datetime` object at any time to the user's
282 timezone (as returned by :func:`get_timezone` this function can be used).
283 """
284 if datetime.tzinfo is None:
285 datetime = datetime.replace(tzinfo=UTC)
286 tzinfo = get_timezone()
287 return tzinfo.normalize(datetime.astimezone(tzinfo))
288
289
290 def to_utc(datetime):
291 """Convert a datetime object to UTC and drop tzinfo. This is the
292 opposite operation to :func:`to_user_timezone`.
293 """
294 if datetime.tzinfo is None:
295 datetime = get_timezone().localize(datetime)
296 return datetime.astimezone(UTC).replace(tzinfo=None)
297
298
299 def format_datetime(datetime=None, format=None, rebase=True):
300 """Return a date formatted according to the given pattern. If no
301 :class:`~datetime.datetime` object is passed, the current time is
302 assumed. By default rebasing happens which causes the object to
303 be converted to the users's timezone (as returned by
304 :func:`to_user_timezone`). This function formats both date and
305 time.
306
307 The format parameter can either be ``'short'``, ``'medium'``,
308 ``'long'`` or ``'full'`` (in which cause the language's default for
309 that setting is used, or the default from the :attr:`Babel.date_formats`
310 mapping is used) or a format string as documented by Babel.
311
312 This function is also available in the template context as filter
313 named `datetimeformat`.
314 """
315 format = _get_format('datetime', format)
316 return _date_format(dates.format_datetime, datetime, format, rebase)
317
318
319 def format_date(date=None, format=None, rebase=True):
320 """Return a date formatted according to the given pattern. If no
321 :class:`~datetime.datetime` or :class:`~datetime.date` object is passed,
322 the current time is assumed. By default rebasing happens which causes
323 the object to be converted to the users's timezone (as returned by
324 :func:`to_user_timezone`). This function only formats the date part
325 of a :class:`~datetime.datetime` object.
326
327 The format parameter can either be ``'short'``, ``'medium'``,
328 ``'long'`` or ``'full'`` (in which cause the language's default for
329 that setting is used, or the default from the :attr:`Babel.date_formats`
330 mapping is used) or a format string as documented by Babel.
331
332 This function is also available in the template context as filter
333 named `dateformat`.
334 """
335 if rebase and isinstance(date, datetime):
336 date = to_user_timezone(date)
337 format = _get_format('date', format)
338 return _date_format(dates.format_date, date, format, rebase)
339
340
341 def format_time(time=None, format=None, rebase=True):
342 """Return a time formatted according to the given pattern. If no
343 :class:`~datetime.datetime` object is passed, the current time is
344 assumed. By default rebasing happens which causes the object to
345 be converted to the users's timezone (as returned by
346 :func:`to_user_timezone`). This function formats both date and
347 time.
348
349 The format parameter can either be ``'short'``, ``'medium'``,
350 ``'long'`` or ``'full'`` (in which cause the language's default for
351 that setting is used, or the default from the :attr:`Babel.date_formats`
352 mapping is used) or a format string as documented by Babel.
353
354 This function is also available in the template context as filter
355 named `timeformat`.
356 """
357 format = _get_format('time', format)
358 return _date_format(dates.format_time, time, format, rebase)
359
360
361 def format_timedelta(datetime_or_timedelta, granularity='second'):
362 """Format the elapsed time from the given date to now or the given
363 timedelta. This currently requires an unreleased development
364 version of Babel.
365
366 This function is also available in the template context as filter
367 named `timedeltaformat`.
368 """
369 if isinstance(datetime_or_timedelta, datetime):
370 datetime_or_timedelta = datetime.utcnow() - datetime_or_timedelta
371 return dates.format_timedelta(datetime_or_timedelta, granularity,
372 locale=get_locale())
373
374
375 def _date_format(formatter, obj, format, rebase, **extra):
376 """Internal helper that formats the date."""
377 locale = get_locale()
378 extra = {}
379 if formatter is not dates.format_date and rebase:
380 extra['tzinfo'] = get_timezone()
381 return formatter(obj, format, locale=locale, **extra)
382
383
384 def format_number(number):
385 """Return the given number formatted for the locale in request
386
387 :param number: the number to format
388 :return: the formatted number
389 :rtype: unicode
390 """
391 locale = get_locale()
392 return numbers.format_number(number, locale=locale)
393
394
395 def format_decimal(number, format=None):
396 """Return the given decimal number formatted for the locale in request
397
398 :param number: the number to format
399 :param format: the format to use
400 :return: the formatted number
401 :rtype: unicode
402 """
403 locale = get_locale()
404 return numbers.format_decimal(number, format=format, locale=locale)
405
406
407 def format_currency(number, currency, format=None):
408 """Return the given number formatted for the locale in request
409
410 :param number: the number to format
411 :param currency: the currency code
412 :param format: the format to use
413 :return: the formatted number
414 :rtype: unicode
415 """
416 locale = get_locale()
417 return numbers.format_currency(
418 number, currency, format=format, locale=locale
419 )
420
421
422 def format_percent(number, format=None):
423 """Return formatted percent value for the locale in request
424
425 :param number: the number to format
426 :param format: the format to use
427 :return: the formatted percent number
428 :rtype: unicode
429 """
430 locale = get_locale()
431 return numbers.format_percent(number, format=format, locale=locale)
432
433
434 def format_scientific(number, format=None):
435 """Return value formatted in scientific notation for the locale in request
436
437 :param number: the number to format
438 :param format: the format to use
439 :return: the formatted percent number
440 :rtype: unicode
441 """
442 locale = get_locale()
443 return numbers.format_scientific(number, format=format, locale=locale)
444
445
446 def gettext(string, **variables):
447 """Translates a string with the current locale and passes in the
448 given keyword arguments as mapping to a string formatting string.
449
450 ::
451
452 gettext(u'Hello World!')
453 gettext(u'Hello %(name)s!', name='World')
454 """
455 t = get_translations()
456 if t is None:
457 return string % variables
458 return t.ugettext(string) % variables
459 _ = gettext
460
461
462 def ngettext(singular, plural, num, **variables):
463 """Translates a string with the current locale and passes in the
464 given keyword arguments as mapping to a string formatting string.
465 The `num` parameter is used to dispatch between singular and various
466 plural forms of the message. It is available in the format string
467 as ``%(num)d`` or ``%(num)s``. The source language should be
468 English or a similar language which only has one plural form.
469
470 ::
471
472 ngettext(u'%(num)d Apple', u'%(num)d Apples', num=len(apples))
473 """
474 variables.setdefault('num', num)
475 t = get_translations()
476 if t is None:
477 return (singular if num == 1 else plural) % variables
478 return t.ungettext(singular, plural, num) % variables
479
480
481 def pgettext(context, string, **variables):
482 """Like :func:`gettext` but with a context.
483
484 .. versionadded:: 0.7
485 """
486 t = get_translations()
487 if t is None:
488 return string % variables
489 return t.upgettext(context, string) % variables
490
491
492 def npgettext(context, singular, plural, num, **variables):
493 """Like :func:`ngettext` but with a context.
494
495 .. versionadded:: 0.7
496 """
497 variables.setdefault('num', num)
498 t = get_translations()
499 if t is None:
500 return (singular if num == 1 else plural) % variables
501 return t.unpgettext(context, singular, plural, num) % variables
502
503
504 def lazy_gettext(string, **variables):
505 """Like :func:`gettext` but the string returned is lazy which means
506 it will be translated when it is used as an actual string.
507
508 Example::
509
510 hello = lazy_gettext(u'Hello World')
511
512 @app.route('/')
513 def index():
514 return unicode(hello)
515 """
516 from speaklater import make_lazy_string
517 return make_lazy_string(gettext, string, **variables)
518
519
520 def lazy_pgettext(context, string, **variables):
521 """Like :func:`pgettext` but the string returned is lazy which means
522 it will be translated when it is used as an actual string.
523
524 .. versionadded:: 0.7
525 """
526 from speaklater import make_lazy_string
527 return make_lazy_string(pgettext, context, string, **variables)
1919
2020 setup(
2121 name='Flask-Babel',
22 version='0.8',
22 version='0.9',
2323 url='http://github.com/mitsuhiko/flask-babel',
2424 license='BSD',
2525 author='Armin Ronacher',
2626 author_email='armin.ronacher@active-4.com',
2727 description='Adds i18n/l10n support to Flask applications',
2828 long_description=__doc__,
29 packages=['flaskext'],
30 namespace_packages=['flaskext'],
29 packages=['flask_babel'],
3130 zip_safe=False,
3231 platforms='any',
3332 install_requires=[
3433 'Flask',
35 'Babel',
36 'pytz',
34 'Babel>=1.0',
3735 'speaklater>=1.2',
3836 'Jinja2>=2.5'
3937 ],
4442 'License :: OSI Approved :: BSD License',
4543 'Operating System :: OS Independent',
4644 'Programming Language :: Python',
45 'Programming Language :: Python :: 3',
4746 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
4847 'Topic :: Software Development :: Libraries :: Python Modules'
4948 ]
88 from decimal import Decimal
99 import flask
1010 from datetime import datetime
11 from flaskext import babel
12 from flaskext.babel import gettext, ngettext, lazy_gettext
11 import flask_babel as babel
12 from flask_babel import gettext, ngettext, lazy_gettext
13 from flask_babel._compat import text_type
1314
1415
1516 class DateFormattingTestCase(unittest.TestCase):
2021 d = datetime(2010, 4, 12, 13, 46)
2122
2223 with app.test_request_context():
23 assert babel.format_datetime(d) == 'Apr 12, 2010 1:46:00 PM'
24 assert babel.format_datetime(d) == 'Apr 12, 2010, 1:46:00 PM'
2425 assert babel.format_date(d) == 'Apr 12, 2010'
2526 assert babel.format_time(d) == '1:46:00 PM'
2627
2728 with app.test_request_context():
2829 app.config['BABEL_DEFAULT_TIMEZONE'] = 'Europe/Vienna'
29 assert babel.format_datetime(d) == 'Apr 12, 2010 3:46:00 PM'
30 assert babel.format_datetime(d) == 'Apr 12, 2010, 3:46:00 PM'
3031 assert babel.format_date(d) == 'Apr 12, 2010'
3132 assert babel.format_time(d) == '3:46:00 PM'
3233
4243 d = datetime(2010, 4, 12, 13, 46)
4344
4445 with app.test_request_context():
45 assert babel.format_datetime(d) == 'Apr 12, 2010 1:46:00 PM'
46 assert babel.format_datetime(d) == 'Apr 12, 2010, 1:46:00 PM'
4647 assert babel.format_date(d) == 'Apr 12, 2010'
4748 assert babel.format_time(d) == '1:46:00 PM'
4849
4950 with app.test_request_context():
5051 app.config['BABEL_DEFAULT_TIMEZONE'] = 'Europe/Vienna'
51 assert babel.format_datetime(d) == 'Apr 12, 2010 3:46:00 PM'
52 assert babel.format_datetime(d) == 'Apr 12, 2010, 3:46:00 PM'
5253 assert babel.format_date(d) == 'Apr 12, 2010'
5354 assert babel.format_time(d) == '3:46:00 PM'
5455
8788 return the_timezone
8889
8990 with app.test_request_context():
90 assert babel.format_datetime(d) == 'Apr 12, 2010 1:46:00 PM'
91 assert babel.format_datetime(d) == 'Apr 12, 2010, 1:46:00 PM'
9192
9293 the_locale = 'de_DE'
9394 the_timezone = 'Europe/Vienna'
100101 b = babel.Babel(app)
101102 d = datetime(2010, 4, 12, 13, 46)
102103 with app.test_request_context():
103 assert babel.format_datetime(d) == 'Apr 12, 2010 1:46:00 PM'
104 assert babel.format_datetime(d) == 'Apr 12, 2010, 1:46:00 PM'
104105 app.config['BABEL_DEFAULT_TIMEZONE'] = 'Europe/Vienna'
105106 babel.refresh()
106 assert babel.format_datetime(d) == 'Apr 12, 2010 3:46:00 PM'
107 assert babel.format_datetime(d) == 'Apr 12, 2010, 3:46:00 PM'
107108
108109
109110 class NumberFormattingTestCase(unittest.TestCase):
155156 b = babel.Babel(app, default_locale='de_DE')
156157 yes = lazy_gettext(u'Yes')
157158 with app.test_request_context():
158 assert unicode(yes) == 'Ja'
159 assert text_type(yes) == 'Ja'
159160 app.config['BABEL_DEFAULT_LOCALE'] = 'en_US'
160161 with app.test_request_context():
161 assert unicode(yes) == 'Yes'
162 assert text_type(yes) == 'Yes'
162163
163164 def test_list_translations(self):
164165 app = flask.Flask(__name__)