__init__.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. """
  2. flaskext.babel
  3. ~~~~~~~~~~~~~~
  4. Implements i18n/l10n support for Flask applications based on Babel.
  5. :copyright: (c) 2013 by Armin Ronacher, Daniel Neuhäuser.
  6. :license: BSD, see LICENSE for more details.
  7. """
  8. from __future__ import absolute_import
  9. import os
  10. from datetime import datetime
  11. from contextlib import contextmanager
  12. from flask import current_app, request
  13. from flask.ctx import has_request_context
  14. from flask.helpers import locked_cached_property
  15. from babel import dates, numbers, support, Locale
  16. from pytz import timezone, UTC
  17. from werkzeug.datastructures import ImmutableDict
  18. from flask_babel.speaklater import LazyString
  19. class Babel(object):
  20. """Central controller class that can be used to configure how
  21. Flask-Babel behaves. Each application that wants to use Flask-Babel
  22. has to create, or run :meth:`init_app` on, an instance of this class
  23. after the configuration was initialized.
  24. """
  25. default_date_formats = ImmutableDict({
  26. 'time': 'medium',
  27. 'date': 'medium',
  28. 'datetime': 'medium',
  29. 'time.short': None,
  30. 'time.medium': None,
  31. 'time.full': None,
  32. 'time.long': None,
  33. 'date.short': None,
  34. 'date.medium': None,
  35. 'date.full': None,
  36. 'date.long': None,
  37. 'datetime.short': None,
  38. 'datetime.medium': None,
  39. 'datetime.full': None,
  40. 'datetime.long': None,
  41. })
  42. def __init__(self, app=None, default_locale='en', default_timezone='UTC',
  43. default_domain='messages', date_formats=None,
  44. configure_jinja=True):
  45. self._default_locale = default_locale
  46. self._default_timezone = default_timezone
  47. self._default_domain = default_domain
  48. self._date_formats = date_formats
  49. self._configure_jinja = configure_jinja
  50. self.app = app
  51. self.locale_selector_func = None
  52. self.timezone_selector_func = None
  53. if app is not None:
  54. self.init_app(app)
  55. def init_app(self, app):
  56. """Set up this instance for use with *app*, if no app was passed to
  57. the constructor.
  58. """
  59. self.app = app
  60. app.babel_instance = self
  61. if not hasattr(app, 'extensions'):
  62. app.extensions = {}
  63. app.extensions['babel'] = self
  64. app.config.setdefault('BABEL_DEFAULT_LOCALE', self._default_locale)
  65. app.config.setdefault('BABEL_DEFAULT_TIMEZONE', self._default_timezone)
  66. app.config.setdefault('BABEL_DOMAIN', self._default_domain)
  67. if self._date_formats is None:
  68. self._date_formats = self.default_date_formats.copy()
  69. #: a mapping of Babel datetime format strings that can be modified
  70. #: to change the defaults. If you invoke :func:`format_datetime`
  71. #: and do not provide any format string Flask-Babel will do the
  72. #: following things:
  73. #:
  74. #: 1. look up ``date_formats['datetime']``. By default ``'medium'``
  75. #: is returned to enforce medium length datetime formats.
  76. #: 2. ``date_formats['datetime.medium'] (if ``'medium'`` was
  77. #: returned in step one) is looked up. If the return value
  78. #: is anything but `None` this is used as new format string.
  79. #: otherwise the default for that language is used.
  80. self.date_formats = self._date_formats
  81. if self._configure_jinja:
  82. app.jinja_env.filters.update(
  83. datetimeformat=format_datetime,
  84. dateformat=format_date,
  85. timeformat=format_time,
  86. timedeltaformat=format_timedelta,
  87. numberformat=format_number,
  88. decimalformat=format_decimal,
  89. currencyformat=format_currency,
  90. percentformat=format_percent,
  91. scientificformat=format_scientific,
  92. )
  93. app.jinja_env.add_extension('jinja2.ext.i18n')
  94. app.jinja_env.install_gettext_callables(
  95. lambda x: get_translations().ugettext(x),
  96. lambda s, p, n: get_translations().ungettext(s, p, n),
  97. newstyle=True
  98. )
  99. def localeselector(self, f):
  100. """Registers a callback function for locale selection. The default
  101. behaves as if a function was registered that returns `None` all the
  102. time. If `None` is returned, the locale falls back to the one from
  103. the configuration.
  104. This has to return the locale as string (eg: ``'de_AT'``, ``'en_US'``)
  105. """
  106. self.locale_selector_func = f
  107. return f
  108. def timezoneselector(self, f):
  109. """Registers a callback function for timezone selection. The default
  110. behaves as if a function was registered that returns `None` all the
  111. time. If `None` is returned, the timezone falls back to the one from
  112. the configuration.
  113. This has to return the timezone as string (eg: ``'Europe/Vienna'``)
  114. """
  115. self.timezone_selector_func = f
  116. return f
  117. def list_translations(self):
  118. """Returns a list of all the locales translations exist for. The
  119. list returned will be filled with actual locale objects and not just
  120. strings.
  121. .. versionadded:: 0.6
  122. """
  123. result = []
  124. for dirname in self.translation_directories:
  125. if not os.path.isdir(dirname):
  126. continue
  127. for folder in os.listdir(dirname):
  128. locale_dir = os.path.join(dirname, folder, 'LC_MESSAGES')
  129. if not os.path.isdir(locale_dir):
  130. continue
  131. if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)):
  132. result.append(Locale.parse(folder))
  133. # If not other translations are found, add the default locale.
  134. if not result:
  135. result.append(Locale.parse(self._default_locale))
  136. return result
  137. @property
  138. def default_locale(self):
  139. """The default locale from the configuration as instance of a
  140. `babel.Locale` object.
  141. """
  142. return Locale.parse(self.app.config['BABEL_DEFAULT_LOCALE'])
  143. @property
  144. def default_timezone(self):
  145. """The default timezone from the configuration as instance of a
  146. `pytz.timezone` object.
  147. """
  148. return timezone(self.app.config['BABEL_DEFAULT_TIMEZONE'])
  149. @property
  150. def domain(self):
  151. """The message domain for the translations as a string.
  152. """
  153. return self.app.config['BABEL_DOMAIN']
  154. @locked_cached_property
  155. def domain_instance(self):
  156. """The message domain for the translations.
  157. """
  158. return Domain(domain=self.app.config['BABEL_DOMAIN'])
  159. @property
  160. def translation_directories(self):
  161. directories = self.app.config.get(
  162. 'BABEL_TRANSLATION_DIRECTORIES',
  163. 'translations'
  164. ).split(';')
  165. for path in directories:
  166. if os.path.isabs(path):
  167. yield path
  168. else:
  169. yield os.path.join(self.app.root_path, path)
  170. def get_translations():
  171. """Returns the correct gettext translations that should be used for
  172. this request. This will never fail and return a dummy translation
  173. object if used outside of the request or if a translation cannot be
  174. found.
  175. """
  176. return get_domain().get_translations()
  177. def get_locale():
  178. """Returns the locale that should be used for this request as
  179. `babel.Locale` object. This returns `None` if used outside of
  180. a request.
  181. """
  182. ctx = _get_current_context()
  183. if ctx is None:
  184. return None
  185. locale = getattr(ctx, 'babel_locale', None)
  186. if locale is None:
  187. babel = current_app.extensions['babel']
  188. if babel.locale_selector_func is None:
  189. locale = babel.default_locale
  190. else:
  191. rv = babel.locale_selector_func()
  192. if rv is None:
  193. locale = babel.default_locale
  194. else:
  195. locale = Locale.parse(rv)
  196. ctx.babel_locale = locale
  197. return locale
  198. def get_timezone():
  199. """Returns the timezone that should be used for this request as
  200. `pytz.timezone` object. This returns `None` if used outside of
  201. a request.
  202. """
  203. ctx = _get_current_context()
  204. tzinfo = getattr(ctx, 'babel_tzinfo', None)
  205. if tzinfo is None:
  206. babel = current_app.extensions['babel']
  207. if babel.timezone_selector_func is None:
  208. tzinfo = babel.default_timezone
  209. else:
  210. rv = babel.timezone_selector_func()
  211. if rv is None:
  212. tzinfo = babel.default_timezone
  213. else:
  214. tzinfo = timezone(rv) if isinstance(rv, str) else rv
  215. ctx.babel_tzinfo = tzinfo
  216. return tzinfo
  217. def refresh():
  218. """Refreshes the cached timezones and locale information. This can
  219. be used to switch a translation between a request and if you want
  220. the changes to take place immediately, not just with the next request::
  221. user.timezone = request.form['timezone']
  222. user.locale = request.form['locale']
  223. refresh()
  224. flash(gettext('Language was changed'))
  225. Without that refresh, the :func:`~flask.flash` function would probably
  226. return English text and a now German page.
  227. """
  228. ctx = _get_current_context()
  229. for key in 'babel_locale', 'babel_tzinfo', 'babel_translations':
  230. if hasattr(ctx, key):
  231. delattr(ctx, key)
  232. if hasattr(ctx, 'forced_babel_locale'):
  233. ctx.babel_locale = ctx.forced_babel_locale
  234. @contextmanager
  235. def force_locale(locale):
  236. """Temporarily overrides the currently selected locale.
  237. Sometimes it is useful to switch the current locale to different one, do
  238. some tasks and then revert back to the original one. For example, if the
  239. user uses German on the web site, but you want to send them an email in
  240. English, you can use this function as a context manager::
  241. with force_locale('en_US'):
  242. send_email(gettext('Hello!'), ...)
  243. :param locale: The locale to temporary switch to (ex: 'en_US').
  244. """
  245. ctx = _get_current_context()
  246. if ctx is None:
  247. yield
  248. return
  249. orig_attrs = {}
  250. for key in ('babel_translations', 'babel_locale'):
  251. orig_attrs[key] = getattr(ctx, key, None)
  252. try:
  253. ctx.babel_locale = Locale.parse(locale)
  254. ctx.forced_babel_locale = ctx.babel_locale
  255. ctx.babel_translations = None
  256. yield
  257. finally:
  258. if hasattr(ctx, 'forced_babel_locale'):
  259. del ctx.forced_babel_locale
  260. for key, value in orig_attrs.items():
  261. setattr(ctx, key, value)
  262. def _get_format(key, format):
  263. """A small helper for the datetime formatting functions. Looks up
  264. format defaults for different kinds.
  265. """
  266. babel = current_app.extensions['babel']
  267. if format is None:
  268. format = babel.date_formats[key]
  269. if format in ('short', 'medium', 'full', 'long'):
  270. rv = babel.date_formats['%s.%s' % (key, format)]
  271. if rv is not None:
  272. format = rv
  273. return format
  274. def to_user_timezone(datetime):
  275. """Convert a datetime object to the user's timezone. This automatically
  276. happens on all date formatting unless rebasing is disabled. If you need
  277. to convert a :class:`datetime.datetime` object at any time to the user's
  278. timezone (as returned by :func:`get_timezone` this function can be used).
  279. """
  280. if datetime.tzinfo is None:
  281. datetime = datetime.replace(tzinfo=UTC)
  282. tzinfo = get_timezone()
  283. return tzinfo.normalize(datetime.astimezone(tzinfo))
  284. def to_utc(datetime):
  285. """Convert a datetime object to UTC and drop tzinfo. This is the
  286. opposite operation to :func:`to_user_timezone`.
  287. """
  288. if datetime.tzinfo is None:
  289. datetime = get_timezone().localize(datetime)
  290. return datetime.astimezone(UTC).replace(tzinfo=None)
  291. def format_datetime(datetime=None, format=None, rebase=True):
  292. """Return a date formatted according to the given pattern. If no
  293. :class:`~datetime.datetime` object is passed, the current time is
  294. assumed. By default rebasing happens which causes the object to
  295. be converted to the users's timezone (as returned by
  296. :func:`to_user_timezone`). This function formats both date and
  297. time.
  298. The format parameter can either be ``'short'``, ``'medium'``,
  299. ``'long'`` or ``'full'`` (in which cause the language's default for
  300. that setting is used, or the default from the :attr:`Babel.date_formats`
  301. mapping is used) or a format string as documented by Babel.
  302. This function is also available in the template context as filter
  303. named `datetimeformat`.
  304. """
  305. format = _get_format('datetime', format)
  306. return _date_format(dates.format_datetime, datetime, format, rebase)
  307. def format_date(date=None, format=None, rebase=True):
  308. """Return a date formatted according to the given pattern. If no
  309. :class:`~datetime.datetime` or :class:`~datetime.date` object is passed,
  310. the current time is assumed. By default rebasing happens which causes
  311. the object to be converted to the users's timezone (as returned by
  312. :func:`to_user_timezone`). This function only formats the date part
  313. of a :class:`~datetime.datetime` object.
  314. The format parameter can either be ``'short'``, ``'medium'``,
  315. ``'long'`` or ``'full'`` (in which cause the language's default for
  316. that setting is used, or the default from the :attr:`Babel.date_formats`
  317. mapping is used) or a format string as documented by Babel.
  318. This function is also available in the template context as filter
  319. named `dateformat`.
  320. """
  321. if rebase and isinstance(date, datetime):
  322. date = to_user_timezone(date)
  323. format = _get_format('date', format)
  324. return _date_format(dates.format_date, date, format, rebase)
  325. def format_time(time=None, format=None, rebase=True):
  326. """Return a time formatted according to the given pattern. If no
  327. :class:`~datetime.datetime` object is passed, the current time is
  328. assumed. By default rebasing happens which causes the object to
  329. be converted to the users's timezone (as returned by
  330. :func:`to_user_timezone`). This function formats both date and
  331. time.
  332. The format parameter can either be ``'short'``, ``'medium'``,
  333. ``'long'`` or ``'full'`` (in which cause the language's default for
  334. that setting is used, or the default from the :attr:`Babel.date_formats`
  335. mapping is used) or a format string as documented by Babel.
  336. This function is also available in the template context as filter
  337. named `timeformat`.
  338. """
  339. format = _get_format('time', format)
  340. return _date_format(dates.format_time, time, format, rebase)
  341. def format_timedelta(datetime_or_timedelta, granularity='second',
  342. add_direction=False, threshold=0.85):
  343. """Format the elapsed time from the given date to now or the given
  344. timedelta.
  345. This function is also available in the template context as filter
  346. named `timedeltaformat`.
  347. """
  348. if isinstance(datetime_or_timedelta, datetime):
  349. datetime_or_timedelta = datetime.utcnow() - datetime_or_timedelta
  350. return dates.format_timedelta(
  351. datetime_or_timedelta,
  352. granularity,
  353. threshold=threshold,
  354. add_direction=add_direction,
  355. locale=get_locale()
  356. )
  357. def _date_format(formatter, obj, format, rebase, **extra):
  358. """Internal helper that formats the date."""
  359. locale = get_locale()
  360. extra = {}
  361. if formatter is not dates.format_date and rebase:
  362. extra['tzinfo'] = get_timezone()
  363. return formatter(obj, format, locale=locale, **extra)
  364. def format_number(number):
  365. """Return the given number formatted for the locale in request
  366. :param number: the number to format
  367. :return: the formatted number
  368. :rtype: unicode
  369. """
  370. locale = get_locale()
  371. return numbers.format_decimal(number, locale=locale)
  372. def format_decimal(number, format=None):
  373. """Return the given decimal number formatted for the locale in request
  374. :param number: the number to format
  375. :param format: the format to use
  376. :return: the formatted number
  377. :rtype: unicode
  378. """
  379. locale = get_locale()
  380. return numbers.format_decimal(number, format=format, locale=locale)
  381. def format_currency(number, currency, format=None, currency_digits=True,
  382. format_type='standard'):
  383. """Return the given number formatted for the locale in request
  384. :param number: the number to format
  385. :param currency: the currency code
  386. :param format: the format to use
  387. :param currency_digits: use the currency’s number of decimal digits
  388. [default: True]
  389. :param format_type: the currency format type to use
  390. [default: standard]
  391. :return: the formatted number
  392. :rtype: unicode
  393. """
  394. locale = get_locale()
  395. return numbers.format_currency(
  396. number,
  397. currency,
  398. format=format,
  399. locale=locale,
  400. currency_digits=currency_digits,
  401. format_type=format_type
  402. )
  403. def format_percent(number, format=None):
  404. """Return formatted percent value for the locale in request
  405. :param number: the number to format
  406. :param format: the format to use
  407. :return: the formatted percent number
  408. :rtype: unicode
  409. """
  410. locale = get_locale()
  411. return numbers.format_percent(number, format=format, locale=locale)
  412. def format_scientific(number, format=None):
  413. """Return value formatted in scientific notation for the locale in request
  414. :param number: the number to format
  415. :param format: the format to use
  416. :return: the formatted percent number
  417. :rtype: unicode
  418. """
  419. locale = get_locale()
  420. return numbers.format_scientific(number, format=format, locale=locale)
  421. class Domain(object):
  422. """Localization domain. By default will use look for tranlations in Flask
  423. application directory and "messages" domain - all message catalogs should
  424. be called ``messages.mo``.
  425. """
  426. def __init__(self, translation_directories=None, domain='messages'):
  427. if isinstance(translation_directories, str):
  428. translation_directories = [translation_directories]
  429. self._translation_directories = translation_directories
  430. self.domain = domain
  431. self.cache = {}
  432. def __repr__(self):
  433. return '<Domain({!r}, {!r})>'.format(self._translation_directories, self.domain)
  434. @property
  435. def translation_directories(self):
  436. if self._translation_directories is not None:
  437. return self._translation_directories
  438. babel = current_app.extensions['babel']
  439. return babel.translation_directories
  440. def as_default(self):
  441. """Set this domain as default for the current request"""
  442. ctx = _get_current_context()
  443. if ctx is None:
  444. raise RuntimeError("No request context")
  445. ctx.babel_domain = self
  446. def get_translations_cache(self, ctx):
  447. """Returns dictionary-like object for translation caching"""
  448. return self.cache
  449. def get_translations(self):
  450. ctx = _get_current_context()
  451. if ctx is None:
  452. return support.NullTranslations()
  453. cache = self.get_translations_cache(ctx)
  454. locale = get_locale()
  455. try:
  456. return cache[str(locale), self.domain]
  457. except KeyError:
  458. translations = support.Translations()
  459. for dirname in self.translation_directories:
  460. catalog = support.Translations.load(
  461. dirname,
  462. [locale],
  463. self.domain
  464. )
  465. translations.merge(catalog)
  466. # FIXME: Workaround for merge() being really, really stupid. It
  467. # does not copy _info, plural(), or any other instance variables
  468. # populated by GNUTranslations. We probably want to stop using
  469. # `support.Translations.merge` entirely.
  470. if hasattr(catalog, 'plural'):
  471. translations.plural = catalog.plural
  472. cache[str(locale), self.domain] = translations
  473. return translations
  474. def gettext(self, string, **variables):
  475. """Translates a string with the current locale and passes in the
  476. given keyword arguments as mapping to a string formatting string.
  477. ::
  478. gettext(u'Hello World!')
  479. gettext(u'Hello %(name)s!', name='World')
  480. """
  481. t = self.get_translations()
  482. s = t.ugettext(string)
  483. return s if not variables else s % variables
  484. def ngettext(self, singular, plural, num, **variables):
  485. """Translates a string with the current locale and passes in the
  486. given keyword arguments as mapping to a string formatting string.
  487. The `num` parameter is used to dispatch between singular and various
  488. plural forms of the message. It is available in the format string
  489. as ``%(num)d`` or ``%(num)s``. The source language should be
  490. English or a similar language which only has one plural form.
  491. ::
  492. ngettext(u'%(num)d Apple', u'%(num)d Apples', num=len(apples))
  493. """
  494. variables.setdefault('num', num)
  495. t = self.get_translations()
  496. s = t.ungettext(singular, plural, num)
  497. return s if not variables else s % variables
  498. def pgettext(self, context, string, **variables):
  499. """Like :func:`gettext` but with a context.
  500. .. versionadded:: 0.7
  501. """
  502. t = self.get_translations()
  503. s = t.upgettext(context, string)
  504. return s if not variables else s % variables
  505. def npgettext(self, context, singular, plural, num, **variables):
  506. """Like :func:`ngettext` but with a context.
  507. .. versionadded:: 0.7
  508. """
  509. variables.setdefault('num', num)
  510. t = self.get_translations()
  511. s = t.unpgettext(context, singular, plural, num)
  512. return s if not variables else s % variables
  513. def lazy_gettext(self, string, **variables):
  514. """Like :func:`gettext` but the string returned is lazy which means
  515. it will be translated when it is used as an actual string.
  516. Example::
  517. hello = lazy_gettext(u'Hello World')
  518. @app.route('/')
  519. def index():
  520. return unicode(hello)
  521. """
  522. return LazyString(self.gettext, string, **variables)
  523. def lazy_ngettext(self, singular, plural, num, **variables):
  524. """Like :func:`ngettext` but the string returned is lazy which means
  525. it will be translated when it is used as an actual string.
  526. Example::
  527. apples = lazy_ngettext(u'%(num)d Apple', u'%(num)d Apples', num=len(apples))
  528. @app.route('/')
  529. def index():
  530. return unicode(apples)
  531. """
  532. return LazyString(self.ngettext, singular, plural, num, **variables)
  533. def lazy_pgettext(self, context, string, **variables):
  534. """Like :func:`pgettext` but the string returned is lazy which means
  535. it will be translated when it is used as an actual string.
  536. .. versionadded:: 0.7
  537. """
  538. return LazyString(self.pgettext, context, string, **variables)
  539. def _get_current_context():
  540. if has_request_context():
  541. return request
  542. if current_app:
  543. return current_app
  544. def get_domain():
  545. ctx = _get_current_context()
  546. if ctx is None:
  547. # this will use NullTranslations
  548. return Domain()
  549. try:
  550. return ctx.babel_domain
  551. except AttributeError:
  552. pass
  553. babel = current_app.extensions['babel']
  554. ctx.babel_domain = babel.domain_instance
  555. return ctx.babel_domain
  556. # Create shortcuts for the default Flask domain
  557. def gettext(*args, **kwargs):
  558. return get_domain().gettext(*args, **kwargs)
  559. _ = gettext
  560. def ngettext(*args, **kwargs):
  561. return get_domain().ngettext(*args, **kwargs)
  562. def pgettext(*args, **kwargs):
  563. return get_domain().pgettext(*args, **kwargs)
  564. def npgettext(*args, **kwargs):
  565. return get_domain().npgettext(*args, **kwargs)
  566. def lazy_gettext(*args, **kwargs):
  567. return LazyString(gettext, *args, **kwargs)
  568. def lazy_pgettext(*args, **kwargs):
  569. return LazyString(pgettext, *args, **kwargs)
  570. def lazy_ngettext(*args, **kwargs):
  571. return LazyString(ngettext, *args, **kwargs)