Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented request scoped translations #29

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ ipython_config.py
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
.vscode
.idea
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

Expand Down
62 changes: 27 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# FastAPI BABEL
### Get [pybabbel](https://github.com/python-babel/babel) tools directly within your FastAPI project without hassle.

FastAPI Babel is will be integrated within FastAPI framework and gives you support of i18n, l10n, date and time locales and all other pybabel functionalities.
FastAPI Babel is integrated within FastAPI framework and gives you support of i18n, l10n, date and time locales and all other pybabel functionalities.

## Features:
- **I18n** (Internationalization)
Expand Down Expand Up @@ -40,8 +40,7 @@ and
2. make `babel.py` file:

```python
from fastapi_babel import Babel
from fastapi_babel import BabelConfigs
from fastapi_babel import Babel, BabelConfigs

configs = BabelConfigs(
ROOT_DIR=__file__,
Expand All @@ -64,9 +63,7 @@ if __name__ == "__main__":
4. Create main.py file:

```python
from fastapi_babel import Babel
from fastapi_babel import BabelConfigs
from fastapi_babel import _
from fastapi_babel import Babel, BabelConfigs, _

configs = BabelConfigs(
ROOT_DIR=__file__,
Expand All @@ -88,11 +85,11 @@ if __name__ == "__main__":
main()
```

5. Extract the massage <a name="step5"></a>
5. Extract the message <a name="step5"></a>

`pybabel extract -F babel.cfg -o messages.pot .`

6. Initialize pybabble
6. Initialize pybabel

`pybabel init -i messages.pot -d lang -l fa`

Expand All @@ -106,8 +103,6 @@ if __name__ == "__main__":

`python3 main.py`

10. Enjoy

- ### FastAPI Babel Commands
Install click at first:
`pip install click`
Expand All @@ -124,7 +119,7 @@ babel.run_cli()
For more information just take a look at help flag of `main.py`
`python main.py --help`

#### Why FastAPI Babel CLI is recommanded ?
#### Why FastAPI Babel CLI is recommended ?
FastAPI Babel CLI will eliminate the need of concering the directories and paths, so you can concentrate on the project and spend less time on going forward and backward. You only need to specify **domain name**, **babel.cfg** and** localization directory **.

**NOTICE:** Do **not** use `FastAPI Babbel` beside fastapi runner files (`main.py` or `run.py`), as uvicorn cli will not work.
Expand All @@ -137,32 +132,31 @@ FastAPI Babel CLI will eliminate the need of concering the directories and paths
- create file `babel.py` and write the code below.

```python
from fastapi_babel import Babel
from fastapi_babel import BabelConfigs
from fastapi_babel import Babel, BabelConfigs, BabelMiddleware

configs = BabelConfigs(
ROOT_DIR=__file__,
BABEL_DEFAULT_LOCALE="en",
BABEL_TRANSLATION_DIRECTORY="lang",
)
babel = Babel(configs=configs)
app.add_middleware(BabelMiddleware, babel_configs=configs)

if __name__ == "__main__":
babel.run_cli()
Babel(configs).run_cli()
```
1. Extract messages with following command

`python3 babel.py extract -d/--dir {watch_dir}`

**Notice: ** watch_dir is your project root directory, or where you want to extract the messages into that directory.
**Notice: ** watch_dir is your project root directory, where the messages will be extracted.

2. add your own langauge locale directory, for instance `fa`.
2. Add your own language locale directory, for instance `fa`.

`python3 babel.py init -l fa`

3. go to ./lang/Fa/.po and add your translations.
3. Go to ./lang/Fa/.po and add your translations.

4. compile all locale directorties.
4. compile all locale directories.
`python3 babel.py compile`

```python
Expand All @@ -171,7 +165,11 @@ from fastapi_babel import _
from .babel import babel

app = FastAPI()
babel.init_app(app)
app.add_middleware(BabelMiddleware, babel_configs=BabelConfigs(
ROOT_DIR=__file__,
BABEL_DEFAULT_LOCALE="en",
BABEL_TRANSLATION_DIRECTORY="lang",
))

@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
Expand Down Expand Up @@ -201,26 +199,20 @@ extensions=jinja2.ext.autoescape,jinja2.ext.with_
*main.py*

```python
from fastapi import FastAPI, Request

from fastapi_babel import Babel
from fastapi_babel import BabelConfigs
from fastapi_babel import _

from fastapi_babel import Babel, BabelConfigs, BabelMiddleware, _
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

templates = Jinja2Templates(directory="templates")
configs = BabelConfigs(
ROOT_DIR=__file__,
BABEL_DEFAULT_LOCALE="en",
BABEL_TRANSLATION_DIRECTORY="lang",
)
from fastapi import FastAPI, Request

app = FastAPI()
babel = Babel(app, configs=configs)
babel.install_jinja(templates)
babel_configs = BabelConfigs(
ROOT_DIR=__file__,
BABEL_DEFAULT_LOCALE="en",
BABEL_TRANSLATION_DIRECTORY="lang",
)
templates = Jinja2Templates(directory="templates")
app.add_middleware(BabelMiddleware, babel_configs=babel_configs, jinja2_templates=templates)
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/items/{id}", response_class=HTMLResponse)
Expand Down
17 changes: 8 additions & 9 deletions examples/with_jinja/full.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@
from fastapi_babel import _ # noqa
from fastapi_babel import Babel, BabelConfigs

templates = Jinja2Templates(directory="templates")
configs = BabelConfigs(
ROOT_DIR=__file__,
BABEL_DEFAULT_LOCALE="en",
BABEL_TRANSLATION_DIRECTORY="lang",
)

app = FastAPI()
babel = Babel(app, configs=configs)
babel.install_jinja(templates)
babel_configs = BabelConfigs(
ROOT_DIR=__file__,
BABEL_DEFAULT_LOCALE="en",
BABEL_TRANSLATION_DIRECTORY="lang",
)
templates = Jinja2Templates(directory="templates")
app.add_middleware(BabelMiddleware, babel_configs=babel_configs, jinja2_templates=templates)
app.mount("/static", StaticFiles(directory="static"), name="static")


Expand All @@ -25,4 +24,4 @@ async def read_item(request: Request, id: str):


if __name__ == "__main__":
babel.run_cli()
Babel(configs=babel_configs).run_cli()
2 changes: 1 addition & 1 deletion examples/wtforms/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
)

if __name__ == "__main__":
babel_cli = BabelCli(babel_instance=babel)
babel_cli = BabelCli(babel)
babel_cli.run()
1 change: 1 addition & 0 deletions fastapi_babel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .core import Babel, BabelCli, _
from .middleware import BabelMiddleware
from .properties import RootConfigs as BabelConfigs

__version__ = "0.0.8"
Expand Down
73 changes: 40 additions & 33 deletions fastapi_babel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,40 @@
from subprocess import run
from typing import Callable, Optional

from fastapi import FastAPI
from fastapi import FastAPI, Request, Depends
from fastapi.templating import Jinja2Templates

from .helpers import check_click_import, check_jinja_import
from .middleware import InternationalizationMiddleware as Middleware
from .properties import RootConfigs
from .exceptions import BabelProxyError
from contextvars import ContextVar


class Babel:

instance: Optional[Babel] = None
instance: Optional[Babel] = None # Singleton used by Babel CLI

def __init__(self, app: Optional[FastAPI] = None, *, configs: RootConfigs) -> None:
def __init__(self, configs: RootConfigs) -> None:
"""
`Babel` is manager for babel localization
and i18n tools like gettext, translation, ...

Args:
configs (RootConfigs): Babel configs for using.
"""
Babel.instance = self
self.config: RootConfigs = configs
self.__locale: str = self.config.BABEL_DEFAULT_LOCALE
self.__d_locale: str = self.config.BABEL_DEFAULT_LOCALE
self.__default_locale: str = self.config.BABEL_DEFAULT_LOCALE
self.__domain: str = self.config.BABEL_DOMAIN.split(".")[0]
if isinstance(app, FastAPI):
self.init_app(app)

@property
def domain(self) -> str:
return self.__domain

@property
def default_locale(self) -> str:
return self.__default_locale

@property
def locale(self) -> str:
return self.__locale
Expand All @@ -47,7 +48,7 @@ def locale(self, value: str) -> None:

@property
def gettext(self) -> Callable[[str], str]:
if self.__d_locale != self.locale:
if self.default_locale != self.locale:
gt = translation(
self.domain,
self.config.BABEL_TRANSLATION_DIRECTORY,
Expand All @@ -57,14 +58,6 @@ def gettext(self) -> Callable[[str], str]:
return gt.gettext
return gettext

def init_app(self, app: FastAPI) -> None:
"""`Babel.init_app` is a helper function for using babel in FastAPI application.

Args:
app (FastAPI): FastAPI application object.
"""
app.add_middleware(Middleware, babel=self)

def install_jinja(self, templates: Jinja2Templates) -> None:
"""
`Babel.install_jinja` install gettext to jinja2 environment
Expand Down Expand Up @@ -99,7 +92,7 @@ def __repr__(self) -> str:
return _(self.message)


def make_gettext(message: str) -> str:
def make_gettext(request: Request = Depends()) -> Callable[[str], str]:
"""translate the message and retrieve message from .PO and .MO depends on
`Babel.locale` locale.

Expand All @@ -109,26 +102,40 @@ def make_gettext(message: str) -> str:
Returns:
str: transalted message.
"""
if Babel.instance is None:
raise BabelProxyError()
return Babel.instance.gettext(message)

def translate(message: str) -> str:
# Get Babel instance from request or fallback to the CLI instance (when defined)
babel = getattr(request.state, 'babel', Babel.instance)
if babel is None:
raise BabelProxyError("Babel instance is not available in the current request context.")

return babel.gettext(message)

return translate



_context_var: ContextVar[Callable[[str], str]] = ContextVar("gettext")


def _(message: str) -> str:
gettext = _context_var.get()
return gettext(message)

_: Callable[[str], str] = make_gettext
lazy_gettext = __LazyText


class BabelCli:
__module_name__ = "pybabel"

def __init__(self, babel_instance: Babel) -> None:
def __init__(self, babel: Babel) -> None:
"""Babel cli manager to facilitate using pybabel commands by specified congigs
fron `BabelConfigs`.

Args:
babel_instance (Babel): `Babel` instance
babel (Babel): `Babel` instance
"""
self.babel: Babel = babel_instance
Babel.instance: Babel = babel

def extract(self, watch_dir: str) -> None:
"""extract all messages that annotated using gettext/_
Expand All @@ -145,9 +152,9 @@ def extract(self, watch_dir: str) -> None:
BabelCli.__module_name__,
"extract",
"-F",
self.babel.config.BABEL_CONFIG_FILE,
Babel.instance.config.BABEL_CONFIG_FILE,
"-o",
self.babel.config.BABEL_MESSAGE_POT_FILE,
Babel.instance.config.BABEL_MESSAGE_POT_FILE,
watch_dir,
]
)
Expand All @@ -166,11 +173,11 @@ def init(self, lang: Optional[str] = None) -> None:
BabelCli.__module_name__,
"init",
"-i",
self.babel.config.BABEL_MESSAGE_POT_FILE,
Babel.instance.config.BABEL_MESSAGE_POT_FILE,
"-d",
self.babel.config.BABEL_TRANSLATION_DIRECTORY,
Babel.instance.config.BABEL_TRANSLATION_DIRECTORY,
"-l",
lang or self.babel.config.BABEL_DEFAULT_LOCALE,
lang or Babel.instance.config.BABEL_DEFAULT_LOCALE,
]
)

Expand All @@ -186,9 +193,9 @@ def update(self, watch_dir: Optional[str] = None) -> None:
BabelCli.__module_name__,
"update",
"-i",
self.babel.config.BABEL_MESSAGE_POT_FILE,
Babel.instance.config.BABEL_MESSAGE_POT_FILE,
"-d",
watch_dir or self.babel.config.BABEL_TRANSLATION_DIRECTORY,
watch_dir or Babel.instance.config.BABEL_TRANSLATION_DIRECTORY,
]
)

Expand All @@ -202,7 +209,7 @@ def compile(self):
BabelCli.__module_name__,
"compile",
"-d",
self.babel.config.BABEL_TRANSLATION_DIRECTORY,
Babel.instance.config.BABEL_TRANSLATION_DIRECTORY,
]
)

Expand Down
Loading