Codebase list flask-babel / e8f85f3
Import Upstream version 0.10.0 Håvard Flaget Aasen 4 years ago
15 changed file(s) with 331 addition(s) and 85 deletion(s). Raw diff Collapse all Expand all
0 Metadata-Version: 1.0
0 Metadata-Version: 1.1
11 Name: Flask-Babel
2 Version: 0.9
2 Version: 0.10.0
33 Summary: Adds i18n/l10n support to Flask applications
4 Home-page: http://github.com/mitsuhiko/flask-babel
4 Home-page: http://github.com/python-babel/flask-babel
55 Author: Armin Ronacher
66 Author-email: armin.ronacher@active-4.com
77 License: BSD
00 LICENSE
11 MANIFEST.in
22 Makefile
3 README
43 setup.cfg
54 setup.py
65 Flask_Babel.egg-info/PKG-INFO
1817 flask_babel/_compat.py
1918 tests/babel.cfg
2019 tests/tests.py
20 tests/renamed_translations/messages.pot
21 tests/renamed_translations/de/LC_MESSAGES/messages.mo
22 tests/renamed_translations/de/LC_MESSAGES/messages.po
2123 tests/translations/messages.pot
2224 tests/translations/de/LC_MESSAGES/messages.mo
2325 tests/translations/de/LC_MESSAGES/messages.po
00 Flask
1 Babel>=1.0
1 Babel>=2.3
22 speaklater>=1.2
3 Jinja2>=2.5
3 Jinja2>=2.5
1616
1717 upload-docs:
1818 $(MAKE) -C docs html
19 python setup.py upload_sphinx
19 python setup.py upload_docs
2020
2121 .PHONY: upload-docs clean-pyc clean tox-test test all
0 Metadata-Version: 1.0
0 Metadata-Version: 1.1
11 Name: Flask-Babel
2 Version: 0.9
2 Version: 0.10.0
33 Summary: Adds i18n/l10n support to Flask applications
4 Home-page: http://github.com/mitsuhiko/flask-babel
4 Home-page: http://github.com/python-babel/flask-babel
55 Author: Armin Ronacher
66 Author-email: armin.ronacher@active-4.com
77 License: BSD
+0
-5
README less more
0 Flask Babel
1
2 Implements i18n and l10n support for Flask. This is based on the Python
3 babel module as well as pytz both of which are installed automatically
4 for you if you install this library.
4848 # built documents.
4949 #
5050 # The short X.Y version.
51 version = '1.0'
51 version = '0.10.0'
5252 # The full version, including alpha/beta/rc tags.
53 release = '1.0'
53 release = '0.10.0'
5454
5555 # The language for content autogenerated by Sphinx. Refer to documentation
5656 # for a list of supported languages.
133133 The other big part next to date formatting are translations. For that,
134134 Flask uses :mod:`gettext` together with Babel. The idea of gettext is
135135 that you can mark certain strings as translatable and a tool will pick all
136 those app, collect them in a separate file for you to translate. At
136 those up, collect them in a separate file for you to translate. At
137137 runtime the original strings (which should be English) will be replaced by
138138 the language you selected.
139139
289289
290290 .. autofunction:: refresh
291291
292 .. autofunction:: force_locale
293
292294
293295 .. _Flask: http://flask.pocoo.org/
294296 .. _babel: http://babel.edgewall.org/
1616 os.environ['LC_CTYPE'] = 'en_US.utf-8'
1717
1818 from datetime import datetime
19 from flask import _request_ctx_stack
19 from contextlib import contextmanager
20 from flask import current_app, request
21 from flask.ctx import has_request_context
2022 from babel import dates, numbers, support, Locale
2123 from werkzeug import ImmutableDict
2224 try:
2729 timezone = pytz.timezone
2830 UTC = pytz.UTC
2931
30 from flask_babel._compat import string_types
32 from flask_babel._compat import string_types, text_type
3133
3234
3335 class Babel(object):
6264 self._date_formats = date_formats
6365 self._configure_jinja = configure_jinja
6466 self.app = app
67 self.locale_selector_func = None
68 self.timezone_selector_func = None
6569
6670 if app is not None:
6771 self.init_app(app)
9498 #: otherwise the default for that language is used.
9599 self.date_formats = self._date_formats
96100
97 self.locale_selector_func = None
98 self.timezone_selector_func = None
99
100101 if self._configure_jinja:
101102 app.jinja_env.filters.update(
102103 datetimeformat=format_datetime,
143144 self.timezone_selector_func = f
144145 return f
145146
146
147147 def list_translations(self):
148148 """Returns a list of all the locales translations exist for. The
149149 list returned will be filled with actual locale objects and not just
151151
152152 .. versionadded:: 0.6
153153 """
154 dirname = os.path.join(self.app.root_path, 'translations')
155 if not os.path.isdir(dirname):
156 return []
157154 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):
155
156 for dirname in self.translation_directories:
157 if not os.path.isdir(dirname):
161158 continue
162 if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)):
163 result.append(Locale.parse(folder))
159
160 for folder in os.listdir(dirname):
161 locale_dir = os.path.join(dirname, folder, 'LC_MESSAGES')
162 if not os.path.isdir(locale_dir):
163 continue
164
165 if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)):
166 result.append(Locale.parse(folder))
167
168 # If not other translations are found, add the default locale.
164169 if not result:
165170 result.append(Locale.parse(self._default_locale))
171
166172 return result
167173
168174 @property
179185 """
180186 return timezone(self.app.config['BABEL_DEFAULT_TIMEZONE'])
181187
188 @property
189 def translation_directories(self):
190 directories = self.app.config.get(
191 'BABEL_TRANSLATION_DIRECTORIES',
192 'translations'
193 ).split(';')
194
195 for path in directories:
196 if os.path.isabs(path):
197 yield path
198 else:
199 yield os.path.join(self.app.root_path, path)
200
182201
183202 def get_translations():
184203 """Returns the correct gettext translations that should be used for
186205 object if used outside of the request or if a translation cannot be
187206 found.
188207 """
189 ctx = _request_ctx_stack.top
190 if ctx is None:
191 return None
208 ctx = _get_current_context()
209
192210 translations = getattr(ctx, 'babel_translations', None)
193211 if translations is None:
194 dirname = os.path.join(ctx.app.root_path, 'translations')
195 translations = support.Translations.load(dirname, [get_locale()])
212 translations = support.Translations()
213
214 babel = current_app.extensions['babel']
215 for dirname in babel.translation_directories:
216 translations.merge(
217 support.Translations.load(
218 dirname,
219 [get_locale()]
220 )
221 )
222
196223 ctx.babel_translations = translations
224
197225 return translations
198226
199227
202230 `babel.Locale` object. This returns `None` if used outside of
203231 a request.
204232 """
205 ctx = _request_ctx_stack.top
233 ctx = _get_current_context()
206234 if ctx is None:
207235 return None
208236 locale = getattr(ctx, 'babel_locale', None)
209237 if locale is None:
210 babel = ctx.app.extensions['babel']
238 babel = current_app.extensions['babel']
211239 if babel.locale_selector_func is None:
212240 locale = babel.default_locale
213241 else:
225253 `pytz.timezone` object. This returns `None` if used outside of
226254 a request.
227255 """
228 ctx = _request_ctx_stack.top
256 ctx = _get_current_context()
229257 tzinfo = getattr(ctx, 'babel_tzinfo', None)
230258 if tzinfo is None:
231 babel = ctx.app.extensions['babel']
259 babel = current_app.extensions['babel']
232260 if babel.timezone_selector_func is None:
233261 tzinfo = babel.default_timezone
234262 else:
257285 Without that refresh, the :func:`~flask.flash` function would probably
258286 return English text and a now German page.
259287 """
260 ctx = _request_ctx_stack.top
288 ctx = _get_current_context()
261289 for key in 'babel_locale', 'babel_tzinfo', 'babel_translations':
262290 if hasattr(ctx, key):
263291 delattr(ctx, key)
264292
265293
294 @contextmanager
295 def force_locale(locale):
296 """Temporarily overrides the currently selected locale.
297
298 Sometimes it is useful to switch the current locale to different one, do
299 some tasks and then revert back to the original one. For example, if the
300 user uses German on the web site, but you want to send them an email in
301 English, you can use this function as a context manager::
302
303 with force_locale('en_US'):
304 send_email(gettext('Hello!'), ...)
305
306 :param locale: The locale to temporary switch to (ex: 'en_US').
307 """
308 ctx = _get_current_context()
309 if ctx is None:
310 yield
311 return
312
313 babel = current_app.extensions['babel']
314
315 orig_locale_selector_func = babel.locale_selector_func
316 orig_attrs = {}
317 for key in ('babel_translations', 'babel_locale'):
318 orig_attrs[key] = getattr(ctx, key, None)
319
320 try:
321 babel.locale_selector_func = lambda: locale
322 for key in orig_attrs:
323 setattr(ctx, key, None)
324 yield
325 finally:
326 babel.locale_selector_func = orig_locale_selector_func
327 for key, value in orig_attrs.items():
328 setattr(ctx, key, value)
329
330
266331 def _get_format(key, format):
267332 """A small helper for the datetime formatting functions. Looks up
268333 format defaults for different kinds.
269334 """
270 babel = _request_ctx_stack.top.app.extensions['babel']
335 babel = current_app.extensions['babel']
271336 if format is None:
272337 format = babel.date_formats[key]
273338 if format in ('short', 'medium', 'full', 'long'):
360425 return _date_format(dates.format_time, time, format, rebase)
361426
362427
363 def format_timedelta(datetime_or_timedelta, granularity='second'):
428 def format_timedelta(datetime_or_timedelta, granularity='second',
429 add_direction=False):
364430 """Format the elapsed time from the given date to now or the given
365431 timedelta. This currently requires an unreleased development
366432 version of Babel.
370436 """
371437 if isinstance(datetime_or_timedelta, datetime):
372438 datetime_or_timedelta = datetime.utcnow() - datetime_or_timedelta
373 return dates.format_timedelta(datetime_or_timedelta, granularity,
374 locale=get_locale())
439 return dates.format_timedelta(
440 datetime_or_timedelta,
441 granularity,
442 add_direction=add_direction,
443 locale=get_locale()
444 )
375445
376446
377447 def _date_format(formatter, obj, format, rebase, **extra):
385455
386456 def format_number(number):
387457 """Return the given number formatted for the locale in request
388
458
389459 :param number: the number to format
390460 :return: the formatted number
391461 :rtype: unicode
406476 return numbers.format_decimal(number, format=format, locale=locale)
407477
408478
409 def format_currency(number, currency, format=None):
479 def format_currency(number, currency, format=None, currency_digits=True,
480 format_type='standard'):
410481 """Return the given number formatted for the locale in request
411482
412483 :param number: the number to format
413484 :param currency: the currency code
414485 :param format: the format to use
486 :param currency_digits: use the currency’s number of decimal digits
487 [default: True]
488 :param format_type: the currency format type to use
489 [default: standard]
415490 :return: the formatted number
416491 :rtype: unicode
417492 """
418493 locale = get_locale()
419494 return numbers.format_currency(
420 number, currency, format=format, locale=locale
495 number,
496 currency,
497 format=format,
498 locale=locale,
499 currency_digits=currency_digits,
500 format_type=format_type
421501 )
422502
423503
456536 """
457537 t = get_translations()
458538 if t is None:
459 return string % variables
460 return t.ugettext(string) % variables
539 return string if not variables else string % variables
540 s = t.ugettext(string)
541 return s if not variables else s % variables
461542 _ = gettext
462543
463544
476557 variables.setdefault('num', num)
477558 t = get_translations()
478559 if t is None:
479 return (singular if num == 1 else plural) % variables
480 return t.ungettext(singular, plural, num) % variables
560 s = singular if num == 1 else plural
561 return s if not variables else s % variables
562
563 s = t.ungettext(singular, plural, num)
564 return s if not variables else s % variables
481565
482566
483567 def pgettext(context, string, **variables):
487571 """
488572 t = get_translations()
489573 if t is None:
490 return string % variables
491 return t.upgettext(context, string) % variables
574 return string if not variables else string % variables
575 s = t.upgettext(context, string)
576 return s if not variables else s % variables
492577
493578
494579 def npgettext(context, singular, plural, num, **variables):
499584 variables.setdefault('num', num)
500585 t = get_translations()
501586 if t is None:
502 return (singular if num == 1 else plural) % variables
503 return t.unpgettext(context, singular, plural, num) % variables
587 s = singular if num == 1 else plural
588 return s if not variables else s % variables
589 s = t.unpgettext(context, singular, plural, num)
590 return s if not variables else s % variables
591
592
593 def make_json_lazy_string(func, *args, **kwargs):
594 """Like :method:`speaklater.make_lazy_string` but returns a subclass
595 that provides an :method:`__html__` method. That method is used by
596 :class:`flask.json.JSONEncoder` to serialize objects of unrecognized
597 types.
598 """
599 from speaklater import _LazyString
600
601 class JsonLazyString(_LazyString):
602 def __html__(self):
603 return text_type(self)
604
605 return JsonLazyString(func, args, kwargs)
504606
505607
506608 def lazy_gettext(string, **variables):
515617 def index():
516618 return unicode(hello)
517619 """
518 from speaklater import make_lazy_string
519 return make_lazy_string(gettext, string, **variables)
620 return make_json_lazy_string(gettext, string, **variables)
520621
521622
522623 def lazy_pgettext(context, string, **variables):
525626
526627 .. versionadded:: 0.7
527628 """
528 from speaklater import make_lazy_string
529 return make_lazy_string(pgettext, context, string, **variables)
629 return make_json_lazy_string(pgettext, context, string, **variables)
630
631
632 def _get_current_context():
633 if has_request_context():
634 return request
635
636 if current_app:
637 return current_app
0 [build_sphinx]
1 source-dir = docs/
2 build-dir = docs/_build
3 all_files = 1
4
5 [upload_sphinx]
0 [upload_docs]
61 upload-dir = docs/_build/html
72
83 [egg_info]
1919
2020 setup(
2121 name='Flask-Babel',
22 version='0.9',
23 url='http://github.com/mitsuhiko/flask-babel',
22 version='0.10.0',
23 url='http://github.com/python-babel/flask-babel',
2424 license='BSD',
2525 author='Armin Ronacher',
2626 author_email='armin.ronacher@active-4.com',
3131 platforms='any',
3232 install_requires=[
3333 'Flask',
34 'Babel>=1.0',
34 'Babel>=2.3',
3535 'speaklater>=1.2',
3636 'Jinja2>=2.5'
3737 ],
0 # German translations for PROJECT.
1 # Copyright (C) 2010 ORGANIZATION
2 # This file is distributed under the same license as the PROJECT project.
3 # FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: PROJECT VERSION\n"
8 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
9 "POT-Creation-Date: 2010-05-29 17:00+0200\n"
10 "PO-Revision-Date: 2010-05-30 12:56+0200\n"
11 "Last-Translator: Armin Ronacher <armin.ronacher@active-4.com>\n"
12 "Language-Team: de <LL@li.org>\n"
13 "Plural-Forms: nplurals=2; plural=(n != 1)\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=utf-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17 "Generated-By: Babel 0.9.5\n"
18
19 #: tests.py:94
20 #, python-format
21 msgid "Hello %(name)s!"
22 msgstr "Hallo %(name)s!"
23
24 #: tests.py:95 tests.py:96
25 #, python-format
26 msgid "%(num)s Apple"
27 msgid_plural "%(num)s Apples"
28 msgstr[0] "%(num)s Apfel"
29 msgstr[1] "%(num)s Äpfel"
30
31 #: tests.py:119
32 msgid "Yes"
33 msgstr "Ja"
34
0 # Translations template for PROJECT.
1 # Copyright (C) 2010 ORGANIZATION
2 # This file is distributed under the same license as the PROJECT project.
3 # FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
4 #
5 #, fuzzy
6 msgid ""
7 msgstr ""
8 "Project-Id-Version: PROJECT VERSION\n"
9 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10 "POT-Creation-Date: 2010-05-30 12:56+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=utf-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17 "Generated-By: Babel 0.9.5\n"
18
19 #: tests.py:94
20 #, python-format
21 msgid "Hello %(name)s!"
22 msgstr ""
23
24 #: tests.py:95 tests.py:96
25 #, python-format
26 msgid "%(num)s Apple"
27 msgid_plural "%(num)s Apples"
28 msgstr[0] ""
29 msgstr[1] ""
30
31 #: tests.py:119
32 msgid "Yes"
33 msgstr ""
34
99 import flask
1010 from datetime import datetime
1111 import flask_babel as babel
12 from flask_babel import gettext, ngettext, lazy_gettext
12 from flask_babel import gettext, ngettext, lazy_gettext, get_translations
13 from babel.support import NullTranslations
1314 from flask_babel._compat import text_type
1415
1516
17 class IntegrationTestCase(unittest.TestCase):
18 def test_no_request_context(self):
19 b = babel.Babel()
20 app = flask.Flask(__name__)
21 b.init_app(app)
22
23 with app.app_context():
24 assert isinstance(get_translations(), NullTranslations)
25
26 def test_multiple_directories(self):
27 """
28 Ensure we can load translations from multiple directories.
29 """
30 b = babel.Babel()
31 app = flask.Flask(__name__)
32
33 app.config.update({
34 'BABEL_TRANSLATION_DIRECTORIES': ';'.join((
35 'translations',
36 'renamed_translations'
37 )),
38 'BABEL_DEFAULT_LOCALE': 'de_DE'
39 })
40
41 b.init_app(app)
42
43 with app.test_request_context():
44 translations = b.list_translations()
45
46 assert(len(translations) == 2)
47 assert(str(translations[0]) == 'de')
48 assert(str(translations[1]) == 'de')
49
50 assert gettext(
51 u'Hello %(name)s!',
52 name='Peter'
53 ) == 'Hallo Peter!'
54
55
1656 class DateFormattingTestCase(unittest.TestCase):
1757
1858 def test_basics(self):
1959 app = flask.Flask(__name__)
20 b = babel.Babel(app)
60 babel.Babel(app)
2161 d = datetime(2010, 4, 12, 13, 46)
2262
2363 with app.test_request_context():
3474 with app.test_request_context():
3575 app.config['BABEL_DEFAULT_LOCALE'] = 'de_DE'
3676 assert babel.format_datetime(d, 'long') == \
37 '12. April 2010 15:46:00 MESZ'
77 '12. April 2010 um 15:46:00 MESZ'
3878
3979 def test_init_app(self):
4080 b = babel.Babel()
5696 with app.test_request_context():
5797 app.config['BABEL_DEFAULT_LOCALE'] = 'de_DE'
5898 assert babel.format_datetime(d, 'long') == \
59 '12. April 2010 15:46:00 MESZ'
99 '12. April 2010 um 15:46:00 MESZ'
60100
61101 def test_custom_formats(self):
62102 app = flask.Flask(__name__)
83123 @b.localeselector
84124 def select_locale():
85125 return the_locale
126
86127 @b.timezoneselector
87128 def select_timezone():
88129 return the_timezone
94135 the_timezone = 'Europe/Vienna'
95136
96137 with app.test_request_context():
97 assert babel.format_datetime(d) == '12.04.2010 15:46:00'
138 assert babel.format_datetime(d) == '12.04.2010, 15:46:00'
98139
99140 def test_refreshing(self):
100141 app = flask.Flask(__name__)
101 b = babel.Babel(app)
142 babel.Babel(app)
102143 d = datetime(2010, 4, 12, 13, 46)
103144 with app.test_request_context():
104145 assert babel.format_datetime(d) == 'Apr 12, 2010, 1:46:00 PM'
106147 babel.refresh()
107148 assert babel.format_datetime(d) == 'Apr 12, 2010, 3:46:00 PM'
108149
150 def test_force_locale(self):
151 app = flask.Flask(__name__)
152 b = babel.Babel(app)
153
154 @b.localeselector
155 def select_locale():
156 return 'de_DE'
157
158 with app.test_request_context():
159 assert str(babel.get_locale()) == 'de_DE'
160 with babel.force_locale('en_US'):
161 assert str(babel.get_locale()) == 'en_US'
162 assert str(babel.get_locale()) == 'de_DE'
163
109164
110165 class NumberFormattingTestCase(unittest.TestCase):
111166
112167 def test_basics(self):
113168 app = flask.Flask(__name__)
114 b = babel.Babel(app)
169 babel.Babel(app)
115170 n = 1099
116171
117172 with app.test_request_context():
126181
127182 def test_basics(self):
128183 app = flask.Flask(__name__)
129 b = babel.Babel(app, default_locale='de_DE')
184 babel.Babel(app, default_locale='de_DE')
130185
131186 with app.test_request_context():
132187 assert gettext(u'Hello %(name)s!', name='Peter') == 'Hallo Peter!'
133 assert ngettext(u'%(num)s Apple', u'%(num)s Apples', 3) == u'3 Äpfel'
134 assert ngettext(u'%(num)s Apple', u'%(num)s Apples', 1) == u'1 Apfel'
188 assert ngettext(u'%(num)s Apple', u'%(num)s Apples', 3) == \
189 u'3 Äpfel'
190 assert ngettext(u'%(num)s Apple', u'%(num)s Apples', 1) == \
191 u'1 Apfel'
135192
136193 def test_template_basics(self):
137194 app = flask.Flask(__name__)
138 b = babel.Babel(app, default_locale='de_DE')
195 babel.Babel(app, default_locale='de_DE')
139196
140197 t = lambda x: flask.render_template_string('{{ %s }}' % x)
141198
142199 with app.test_request_context():
143 assert t("gettext('Hello %(name)s!', name='Peter')") == 'Hallo Peter!'
144 assert t("ngettext('%(num)s Apple', '%(num)s Apples', 3)") == u'3 Äpfel'
145 assert t("ngettext('%(num)s Apple', '%(num)s Apples', 1)") == u'1 Apfel'
200 assert t("gettext('Hello %(name)s!', name='Peter')") == \
201 u'Hallo Peter!'
202 assert t("ngettext('%(num)s Apple', '%(num)s Apples', 3)") == \
203 u'3 Äpfel'
204 assert t("ngettext('%(num)s Apple', '%(num)s Apples', 1)") == \
205 u'1 Apfel'
146206 assert flask.render_template_string('''
147207 {% trans %}Hello {{ name }}!{% endtrans %}
148208 ''', name='Peter').strip() == 'Hallo Peter!'
153213
154214 def test_lazy_gettext(self):
155215 app = flask.Flask(__name__)
156 b = babel.Babel(app, default_locale='de_DE')
216 babel.Babel(app, default_locale='de_DE')
157217 yes = lazy_gettext(u'Yes')
158218 with app.test_request_context():
159219 assert text_type(yes) == 'Ja'
220 assert yes.__html__() == 'Ja'
160221 app.config['BABEL_DEFAULT_LOCALE'] = 'en_US'
161222 with app.test_request_context():
162223 assert text_type(yes) == 'Yes'
224 assert yes.__html__() == 'Yes'
163225
164226 def test_list_translations(self):
165227 app = flask.Flask(__name__)
168230 assert len(translations) == 1
169231 assert str(translations[0]) == 'de'
170232
233 def test_no_formatting(self):
234 """
235 Ensure we don't format strings unless a variable is passed.
236 """
237 app = flask.Flask(__name__)
238 babel.Babel(app)
239
240 with app.test_request_context():
241 assert gettext(u'Test %s') == u'Test %s'
242 assert gettext(u'Test %(name)s', name=u'test') == u'Test test'
243 assert gettext(u'Test %s') % 'test' == u'Test test'
244
171245
172246 if __name__ == '__main__':
173247 unittest.main()