# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['starlette_babel', 'starlette_babel.contrib']

package_data = \
{'': ['*']}

install_requires = \
['Babel>=2.10.3,<3.0.0', 'starlette>=0.21.0,<0.22.0']

setup_kwargs = {
    'name': 'starlette-babel',
    'version': '0.2.2',
    'description': 'Locale support for Starlette',
    'long_description': '# Starlette Babel\n\nProvides translations, formatters, and timezone support for Starlette application by integrating Babel library.\n\n![PyPI](https://img.shields.io/pypi/v/starlette_babel)\n![GitHub Workflow Status](https://img.shields.io/github/workflow/status/alex-oleshkevich/starlette_babel/Lint)\n![GitHub](https://img.shields.io/github/license/alex-oleshkevich/starlette_babel)\n![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/pypi/starlette_babel)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/starlette_babel)\n![GitHub Release Date](https://img.shields.io/github/release-date/alex-oleshkevich/starlette_babel)\n\n## Installation\n\nInstall `starlette_babel` using PIP or poetry:\n\n```bash\npip install starlette_babel\n# or\npoetry add starlette_babel\n```\n\n## Features\n\n- Locale middleware\n- Multi-domain translations\n- Locale selectors\n- Timezone middleware\n- Timezone selectors\n- Locale-aware formatters\n- Jinja2 integration\n\n## Quick start\n\nSee example application in [examples/](examples/) directory of this repository.\n\n## Setting up translator and locale features\n\n### Configure Starlette application\n\nTo start using locale aware formatters, text translation and other components you have to set up a translator\nand add middleware to your Starlette application.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nfrom starlette_babel import get_translator, LocaleMiddleware\n\nsupported_locales = [\'be\', \'en\', \'pl\']\nshared_translator = get_translator()  # process global instance\nshared_translator.load_from_directories([\'/path/to/locales/\'])  # one or multiple locale directories\n\napp = Starlette(\n    middleware=[\n        Middleware(LocaleMiddleware, locales=supported_locales, default_locale=\'en\'),\n    ]\n)\n```\n\n### Getting locale information\n\n#### From request object\n\nThe `LocaleMiddleware` adds two state options to the request: `locale` and `language`.\n\n```python\nfrom babel import Locale\n\n\ndef index_view(request):\n    current_locale: Locale = request.state.locale\n    current_language: str = request.state.language\n```\n\n#### Using `get_locale` helper\n\nAlternatively, use `get_locale` to get the locale information\n\n```python\nfrom babel import Locale\nfrom starlette_babel import get_locale\n\nlocale: Locale = get_locale()\n```\n\n### Locale selectors\n\n`LocaleMiddleware` uses locale selectors to detect the locale from the request object.\nThe selector is a callable that accepts `HTTPConnection` object and returns either a locale code as a string or\nNone. The first locale selector that returns non-None value wins.\nIf all selectors fail then the middleware sets locale from `default_locale` option.\nThe detected locale should be in the list defined by the `locales` option otherwise it won\'t be accepted.\n\nThe default selector order is:\n\n1. from `locale` query parameter\n2. from `language` cookie\n3. from `get_preferred_language` user method (will use `request.user`, if available)\n4. from `accept-language` header\n5. fallback to configured default locale\n\n#### Customizing locale selectors or changing their order\n\nIf you want to customize the way the middleware detects the locale, pass `selectors` option to the middleware:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nfrom starlette_babel import LocaleFromHeader, LocaleFromQuery, LocaleMiddleware\n\napp = Starlette(\n    middleware=[\n        Middleware(LocaleMiddleware, selectors=[\n            LocaleFromQuery(), LocaleFromHeader(),\n        ])\n    ]\n)\n```\n\nIn this example we use only two selectors. They will be called in the order they are defined.\n\n#### Custom locale selectors\n\nYou can define your own selector by writing a function or a callable object:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.requests import HTTPConnection\n\nfrom starlette_babel import LocaleMiddleware\n\n\ndef my_locale_selector(conn: HTTPConnection) -> str | None:\n    return \'be_BY\'\n\n\napp = Starlette(\n    middleware=[\n        Middleware(LocaleMiddleware, selectors=[\n            my_locale_selector,\n        ])\n    ]\n)\n```\n\n### Mark translatable strings\n\nAt this point your application is translatable and each request contain locale information that you can use.\nLet\'s define some translatable strings\n\n> Please note, that we did not write any translations and the example below won\'t actually translate anything.\n> This is an example of how to mark strings for translation. We will cover message extraction a bit later.\n\n```python\nfrom starlette.responses import PlainTextResponse\n\nfrom starlette_babel import gettext_lazy as _\n\nwelcome_message = _(\'Welcome\')\n\n\ndef index_view(request):\n    return PlainTextResponse(welcome_message)\n```\n\n### Extract translatable strings from the source code\n\nStrings marked as translatable won\'t translate themselves. They should be extracted into `.po` files and compiled into\nmachine-readable `.mo` files. This topic is out of the scope if this documentation and is well-documented by the\n[official Babel documentation](https://babel.pocoo.org/en/latest/).\n\nA brief hint on what to do is:\n\n1. configure `pybabel` tool via `pybabel.ini`\n2. create directories for each supported locale\n3. extract strings from the source code using `pybabel extract` command\n4. update locale specific message catalogues (`messages.po`) using `pybabel update` command\n5. compile these catalogues into machine-readable format (`messages.mo`) using `pybabel compile` command.\n\nThese commands are documented\nat [https://babel.pocoo.org/en/latest/cmdline.html](https://babel.pocoo.org/en/latest/cmdline.html)\n\n#### Locales directory structure\n\nThe locales directory is a directory where we keep our translation files. Usually, this directory called `locales`.\nThe structure is like this: `locales/_code_/LC_MESSAGES/messages.po` where `_code_` is a locale code.\n\nExample:\n\n```shell\nyour_app_package_name/\n    locales/\n      en/\n        LC_MESSAGES/\n          messages.po\n      de/\n        LC_MESSAGES/\n          messages.po\n      messages.pot\n```\n\nIf the directory format does not match the expectation, translator would not be able to load messages and will fail\nsilently. You can use the [examples/locales](examples/locales) for a reference.\n\n### Enable Jinja2 plugin\n\nIf you use Jinja2 templates you can integrate translator and formatters provided by this library with Jinja.\n\n```python\nimport jinja2\n\nfrom starlette_babel.contrib.jinja import configure_jinja_env\n\njinja_env = jinja2.Environment()\nconfigure_jinja_env(jinja_env)\n```\n\nThe `configure_jinja_env` makes the following utilities available in the templates:\n\n#### Global functions\n\n- `_` - alias for `gettext`\n- `_p` - alias for `ngettext`\n\n```html\n\n<time>{{ _(\'Welcome\') }}</time>\n```\n\n#### Filters\n\n- datetime\n- date\n- time\n- timedelta\n- number\n- currency\n- percent\n- scientific\n\nAll these filters are locale-aware and will format passed data using locale defined format.\n\n```html\n\n<time>your local time is {{ now|datetime }}</time>\n```\n\n### Manually setting the locale\n\nYou can set the current locale manually\n\n```python\nfrom starlette_babel import set_locale\n\nset_locale(\'pl\')\n```\n\n### Temporary setting the locale\n\nYou can switch locale temporary for a code block using `switch_locale` context manager. When the manager exits the\nprevious locale gets restored. This utility is very useful in unit tests.\n\n```python\nfrom starlette_babel import switch_locale, set_locale\n\nset_locale(\'pl\')\n# all speak Polish here\n\nwith switch_locale(\'be\'):\n    # all speak Belarussian here\n    ...\n\n# all speak Polish here again\n```\n\n### Manually translating strings\n\nYou can translate messages using `translator.gettext` and `translator.ngettext` directly in the code of the view\nfunction:\n\n```python\nfrom starlette_babel import Translator\n\ntranslator = Translator([\'/path/to/locales\'])\n\n\ndef index_view(request):\n    translated = translator.gettext(\'Hello\', locale=\'en\')\n```\n\n### Translation domains\n\n> This is advanced topic. Most apps don\'t need this but library developers may need it.\n\nA translation domain is like a namespace. Same message can have different translations depending on the context (aka\ndomain). This library natively supports domains. We infer domain name from the .po file name, dropping the extension.\nFor the file like `locales/en/LC_MESSAGES/errors.po` the domain is `errors`.\nThe default translation domain is `messages`.\n\n```python\nfrom starlette_babel import Translator\n\ntranslator = Translator([\'/path/to/locales\'])\nhello_message = translator.gettext(\'Hello\', locale=\'en\')  # uses default `messages` domain\nshopping_hello_message = translator.gettext(\'Hello\', locale=\'en\', domain=\'shopping\')  # uses `shopping` domain\n```\n\n#### Directory structure\n\nThe structure is exactly the same as stated above.\n\n```shell\nyour_app_package_name/\n    locales/\n      en/\n        LC_MESSAGES/\n          messages.po\n          shopping.po # <-- new file. defines "shopping" domain\n```\n\n## Formatters\n\nThe library integrates formatting utilities from the Babel package.\nOur version automatically applies current locale/timezone without defining them manually.\n\nHere is the list of adapted formatters:\n\n- format_datetime\n- format_date\n- format_time\n- format_timedelta\n- format_interval\n- format_number\n- format_currency\n- format_percent\n- format_scientific\n\nConsult [Babel documentation](https://babel.pocoo.org/en/latest/index.html) for more information.\n\n### Usage\n\n```python\nimport datetime\n\nfrom starlette_babel import format_datetime, set_locale, set_timezone\n\nset_locale(\'be\')\nset_timezone(\'Europe/Minsk\')\nnow = datetime.datetime.now()\nlocal_time = format_datetime(now)  # <-- this\n```\n\n### Jinja integration\n\nThere formatters are automatically exposed to templates after applying `configure_jinja_env` on Jinja environment.\n\n## Timezone support\n\nTo enable timezone support add `TimezoneMiddleware`. The middleware behaves much like `LocaleMiddleware` and shares same\nconcepts.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nfrom starlette_babel import TimezoneMiddleware\n\napp = Starlette(\n    middleware=[\n        Middleware(TimezoneMiddleware, fallback=\'Europe/London\')\n    ]\n)\n```\n\nBy default, the middleware will try these selectors:\n\n1. from `tz` query parameter\n2. from `timezone` cookie\n2. from `get_timezone` user method\n\n### Retrieving timezone information\n\n#### Reading timezone from request object\n\n```python\nfrom pytz import BaseTzInfo\n\n\ndef index_view(request):\n    timezone: BaseTzInfo = request.state.timezone\n```\n\n#### Using `get_timezone` helper\n\nUse `get_timezone` helper to get the timezone information set by the middleware.\nIf middleware not used it will return UTC zone info.\n\n```python\nfrom pytz import BaseTzInfo\n\nfrom starlette_babel import get_timezone\n\ntz: BaseTzInfo = get_timezone()\n```\n\n### Customizing selectors or changing their order\n\nYou can change selectors set or the order they are defined by configuring `selectors` option of the middleware:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nfrom starlette_babel import TimezoneFromCookie, TimezoneFromQuery, TimezoneMiddleware\n\napp = Starlette(\n    middleware=[\n        Middleware(TimezoneMiddleware, fallback=\'Europe/London\', selectors=[\n            TimezoneFromQuery(), TimezoneFromCookie(),\n        ])\n    ]\n)\n```\n\n### Custom timezone selectors\n\nA selector is a callable that accepts `HTTPConnection` and returns timezone code as a string:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nfrom starlette_babel import TimezoneMiddleware\n\n\ndef my_timezone_selector(conn):\n    return \'Europe/Minsk\'\n\n\napp = Starlette(\n    middleware=[\n        Middleware(TimezoneMiddleware, fallback=\'Europe/London\', selectors=[\n            my_timezone_selector,\n        ])\n    ]\n)\n```\n\n### Setting timezone manually\n\nUse `set_timezone` to set the timezone.\n\n```python\nfrom starlette_babel import set_timezone\n\nset_timezone(\'Europe/Minsk\')\n```\n\n### Temporary switch timezone\n\nUse `set_timezone` to set the timezone.\n\n```python\nfrom starlette_babel import switch_timezone\n\nset_timezone(\'Europe/Minsk\')\n# time in +03\n\nwith switch_timezone(\'Europe/Warsaw\'):\n    # time in +02\n    ...\n\n# time in +03 again\n```\n\n### Convert datetime into user local time\n\nYou can apply currently active timezone to any datetime instance using `to_user_timezone` helper.\n\n```python\nimport datetime\nfrom starlette_babel import to_user_timezone, set_timezone\n\nset_timezone(\'Europe/Minsk\')\nnow = datetime.datetime.utcnow()  # time in UTC\nuser_now = to_user_timezone(now)  # time in Europe/Minsk\n```\n\n### Convert user local time to UTC\n\nYou can also convert datetime instance back to UTC using `to_utc` helper.\n\n```python\nimport datetime\n\nfrom starlette_babel import set_timezone, to_user_timezone, to_utc\n\nset_timezone(\'Europe/Minsk\')\nnow = datetime.datetime.now()  # time in UTC\nuser_now = to_user_timezone(now)  # time in Europe/Minsk\nutc_now = to_utc(user_now)  # time in UTC again\n```\n\n### Getting current time in user timezone\n\nTo get current user time use `now` helper.\n\n```python\nfrom starlette_babel import set_timezone, now\n\nset_timezone(\'Europe/Minsk\')\nuser_now = now()  # time in Europe/Minsk\n```\n',
    'author': 'Alex Oleshkevich',
    'author_email': 'alex.oleshkevich@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/alex-oleshkevich/starlette_babel',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.10.0,<4.0.0',
}


setup(**setup_kwargs)
