Imported Upstream version 0.9
SVN-Git Migration
8 years ago
0 | 0 | Metadata-Version: 1.0 |
1 | 1 | Name: Flask-Babel |
2 | Version: 0.8 | |
2 | Version: 0.9 | |
3 | 3 | Summary: Adds i18n/l10n support to Flask applications |
4 | 4 | Home-page: http://github.com/mitsuhiko/flask-babel |
5 | 5 | Author: Armin Ronacher |
29 | 29 | Classifier: License :: OSI Approved :: BSD License |
30 | 30 | Classifier: Operating System :: OS Independent |
31 | 31 | Classifier: Programming Language :: Python |
32 | Classifier: Programming Language :: Python :: 3 | |
32 | 33 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
33 | 34 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
6 | 6 | Flask_Babel.egg-info/PKG-INFO |
7 | 7 | Flask_Babel.egg-info/SOURCES.txt |
8 | 8 | Flask_Babel.egg-info/dependency_links.txt |
9 | Flask_Babel.egg-info/namespace_packages.txt | |
10 | 9 | Flask_Babel.egg-info/not-zip-safe |
11 | 10 | Flask_Babel.egg-info/requires.txt |
12 | 11 | Flask_Babel.egg-info/top_level.txt |
15 | 14 | docs/index.rst |
16 | 15 | docs/make.bat |
17 | 16 | 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 | |
32 | 19 | tests/babel.cfg |
33 | 20 | tests/tests.py |
34 | 21 | tests/translations/messages.pot |
2 | 2 | all: clean-pyc test |
3 | 3 | |
4 | 4 | test: |
5 | cd tests; python tests.py | |
5 | @cd tests; python tests.py | |
6 | ||
7 | tox-test: | |
8 | @tox | |
6 | 9 | |
7 | 10 | clean-pyc: |
8 | 11 | find . -name '*.pyc' -exec rm -f {} + |
9 | 12 | find . -name '*.pyo' -exec rm -f {} + |
10 | 13 | find . -name '*~' -exec rm -f {} + |
11 | 14 | |
15 | clean: clean-pyc | |
16 | ||
12 | 17 | upload-docs: |
13 | 18 | $(MAKE) -C docs html |
14 | 19 | python setup.py upload_sphinx |
20 | ||
21 | .PHONY: upload-docs clean-pyc clean tox-test test all |
0 | 0 | Metadata-Version: 1.0 |
1 | 1 | Name: Flask-Babel |
2 | Version: 0.8 | |
2 | Version: 0.9 | |
3 | 3 | Summary: Adds i18n/l10n support to Flask applications |
4 | 4 | Home-page: http://github.com/mitsuhiko/flask-babel |
5 | 5 | Author: Armin Ronacher |
29 | 29 | Classifier: License :: OSI Approved :: BSD License |
30 | 30 | Classifier: Operating System :: OS Independent |
31 | 31 | Classifier: Programming Language :: Python |
32 | Classifier: Programming Language :: Python :: 3 | |
32 | 33 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
33 | 34 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
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 | 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 | {%- 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 | © 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 | <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 | /* | |
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 | /* | |
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 | [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 | {% 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 | /* | |
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 | [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 | # 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 | } |
0 | 0 | Flask-Babel |
1 | 1 | =========== |
2 | 2 | |
3 | .. module:: flaskext.babel | |
3 | .. module:: flask.ext.babel | |
4 | 4 | |
5 | 5 | Flask-Babel is an extension to `Flask`_ that adds i18n and l10n support to |
6 | 6 | any Flask application with the help of `babel`_, `pytz`_ and |
30 | 30 | object after configuring the application:: |
31 | 31 | |
32 | 32 | from flask import Flask |
33 | from flaskext.babel import Babel | |
33 | from flask.ext.babel import Babel | |
34 | 34 | |
35 | 35 | app = Flask(__name__) |
36 | 36 | app.config.from_pyfile('mysettings.cfg') |
105 | 105 | |
106 | 106 | Here some examples: |
107 | 107 | |
108 | >>> from flaskext.babel import format_datetime | |
108 | >>> from flask.ext.babel import format_datetime | |
109 | 109 | >>> from datetime import datetime |
110 | 110 | >>> format_datetime(datetime(1987, 3, 5, 17, 12)) |
111 | 111 | u'Mar 5, 1987 5:12:00 PM' |
121 | 121 | And again with a different language: |
122 | 122 | |
123 | 123 | >>> app.config['BABEL_DEFAULT_LOCALE'] = 'de' |
124 | >>> from flaskext.babel import refresh; refresh() | |
124 | >>> from flask.ext.babel import refresh; refresh() | |
125 | 125 | >>> format_datetime(datetime(1987, 3, 5, 17, 12), 'EEEE, d. MMMM yyyy H:mm') |
126 | 126 | u'Donnerstag, 5. M\xe4rz 1987 17:12' |
127 | 127 | |
141 | 141 | :func:`ngettext`. The first to translate singular strings and the second |
142 | 142 | to translate strings that might become plural. Here some examples:: |
143 | 143 | |
144 | from flaskext.babel import gettext, ngettext | |
144 | from flask.ext.babel import gettext, ngettext | |
145 | 145 | |
146 | 146 | gettext(u'A simple string') |
147 | 147 | gettext(u'Value: %(value)s', value=42) |
152 | 152 | strings. Lazy strings will not be evaluated until they are actually used. |
153 | 153 | To use such a lazy string, use the :func:`lazy_gettext` function:: |
154 | 154 | |
155 | from flaskext.babel import lazy_gettext | |
155 | from flask.ext.babel import lazy_gettext | |
156 | 156 | |
157 | 157 | class MyForm(formlibrary.FormBase): |
158 | 158 | 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 | # -*- 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) |
19 | 19 | |
20 | 20 | setup( |
21 | 21 | name='Flask-Babel', |
22 | version='0.8', | |
22 | version='0.9', | |
23 | 23 | url='http://github.com/mitsuhiko/flask-babel', |
24 | 24 | license='BSD', |
25 | 25 | author='Armin Ronacher', |
26 | 26 | author_email='armin.ronacher@active-4.com', |
27 | 27 | description='Adds i18n/l10n support to Flask applications', |
28 | 28 | long_description=__doc__, |
29 | packages=['flaskext'], | |
30 | namespace_packages=['flaskext'], | |
29 | packages=['flask_babel'], | |
31 | 30 | zip_safe=False, |
32 | 31 | platforms='any', |
33 | 32 | install_requires=[ |
34 | 33 | 'Flask', |
35 | 'Babel', | |
36 | 'pytz', | |
34 | 'Babel>=1.0', | |
37 | 35 | 'speaklater>=1.2', |
38 | 36 | 'Jinja2>=2.5' |
39 | 37 | ], |
44 | 42 | 'License :: OSI Approved :: BSD License', |
45 | 43 | 'Operating System :: OS Independent', |
46 | 44 | 'Programming Language :: Python', |
45 | 'Programming Language :: Python :: 3', | |
47 | 46 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', |
48 | 47 | 'Topic :: Software Development :: Libraries :: Python Modules' |
49 | 48 | ] |
8 | 8 | from decimal import Decimal |
9 | 9 | import flask |
10 | 10 | 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 | |
13 | 14 | |
14 | 15 | |
15 | 16 | class DateFormattingTestCase(unittest.TestCase): |
20 | 21 | d = datetime(2010, 4, 12, 13, 46) |
21 | 22 | |
22 | 23 | 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' | |
24 | 25 | assert babel.format_date(d) == 'Apr 12, 2010' |
25 | 26 | assert babel.format_time(d) == '1:46:00 PM' |
26 | 27 | |
27 | 28 | with app.test_request_context(): |
28 | 29 | 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' | |
30 | 31 | assert babel.format_date(d) == 'Apr 12, 2010' |
31 | 32 | assert babel.format_time(d) == '3:46:00 PM' |
32 | 33 | |
42 | 43 | d = datetime(2010, 4, 12, 13, 46) |
43 | 44 | |
44 | 45 | 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' | |
46 | 47 | assert babel.format_date(d) == 'Apr 12, 2010' |
47 | 48 | assert babel.format_time(d) == '1:46:00 PM' |
48 | 49 | |
49 | 50 | with app.test_request_context(): |
50 | 51 | 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' | |
52 | 53 | assert babel.format_date(d) == 'Apr 12, 2010' |
53 | 54 | assert babel.format_time(d) == '3:46:00 PM' |
54 | 55 | |
87 | 88 | return the_timezone |
88 | 89 | |
89 | 90 | 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' | |
91 | 92 | |
92 | 93 | the_locale = 'de_DE' |
93 | 94 | the_timezone = 'Europe/Vienna' |
100 | 101 | b = babel.Babel(app) |
101 | 102 | d = datetime(2010, 4, 12, 13, 46) |
102 | 103 | 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' | |
104 | 105 | app.config['BABEL_DEFAULT_TIMEZONE'] = 'Europe/Vienna' |
105 | 106 | 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' | |
107 | 108 | |
108 | 109 | |
109 | 110 | class NumberFormattingTestCase(unittest.TestCase): |
155 | 156 | b = babel.Babel(app, default_locale='de_DE') |
156 | 157 | yes = lazy_gettext(u'Yes') |
157 | 158 | with app.test_request_context(): |
158 | assert unicode(yes) == 'Ja' | |
159 | assert text_type(yes) == 'Ja' | |
159 | 160 | app.config['BABEL_DEFAULT_LOCALE'] = 'en_US' |
160 | 161 | with app.test_request_context(): |
161 | assert unicode(yes) == 'Yes' | |
162 | assert text_type(yes) == 'Yes' | |
162 | 163 | |
163 | 164 | def test_list_translations(self): |
164 | 165 | app = flask.Flask(__name__) |