Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/boardpack/pydantic-i18n
pydantic-i18n is an extension to support an i18n for the pydantic error messages.
https://github.com/boardpack/pydantic-i18n
fastapi i18n internationalization pydantic python translation
Last synced: 1 day ago
JSON representation
pydantic-i18n is an extension to support an i18n for the pydantic error messages.
- Host: GitHub
- URL: https://github.com/boardpack/pydantic-i18n
- Owner: boardpack
- License: mit
- Created: 2021-08-02T22:58:53.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2024-11-25T23:07:04.000Z (2 months ago)
- Last Synced: 2024-11-26T09:13:12.436Z (2 months ago)
- Topics: fastapi, i18n, internationalization, pydantic, python, translation
- Language: Python
- Homepage: https://pydantic-i18n.boardpack.org
- Size: 351 KB
- Stars: 83
- Watchers: 0
- Forks: 11
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-pydantic - pydantic-i18n - An extension to support an i18n for the pydantic error messages. (Utilities)
- awesome-pydantic - pydantic-i18n - An extension to support an i18n for the pydantic error messages. (Utilities)
README
pydantic-i18n is an extension to support an i18n for the pydantic error messages.
---
**Documentation**: https://pydantic-i18n.boardpack.org
**Source Code**: https://github.com/boardpack/pydantic-i18n
---
## Requirements
Python 3.8+
pydantic-i18n has the next dependencies:
## Installation
```console
$ pip install pydantic-i18n---> 100%
```## First steps
To start to work with pydantic-i18n, you can just create a dictionary (or
create any needed translations storage and then convert it into dictionary)
and pass to the main `PydanticI18n` class.To translate messages, you need to pass result of `exception.errors()` call to
the `translate` method:```Python hl_lines="14 24"
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18ntranslations = {
"en_US": {
"Field required": "field required",
},
"de_DE": {
"Field required": "Feld erforderlich",
},
}tr = PydanticI18n(translations)
class User(BaseModel):
name: strtry:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/dict-loader/tutorial001.py))_In the next chapters, you will see current available loaders and how to
implement your own loader.## Usage with FastAPI
Here is a simple example usage with FastAPI.
### Create it
Let's create a `tr.py` file:
```Python linenums="1" hl_lines="13-22 25-26 32 35"
from fastapi import Request
from fastapi.exceptions import RequestValidationError
from starlette.responses import JSONResponse
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITYfrom pydantic_i18n import PydanticI18n
__all__ = ["get_locale", "validation_exception_handler"]
DEFAULT_LOCALE = "en_US"
translations = {
"en_US": {
"Field required": "field required",
},
"de_DE": {
"Field required": "Feld erforderlich",
},
}tr = PydanticI18n(translations)
def get_locale(locale: str = DEFAULT_LOCALE) -> str:
return localeasync def validation_exception_handler(
request: Request, exc: RequestValidationError
) -> JSONResponse:
current_locale = request.query_params.get("locale", DEFAULT_LOCALE)
return JSONResponse(
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": tr.translate(exc.errors(), current_locale)},
)
````11-20`: As you see, we selected the simplest variant to store translations,
you can use any that you need.`23-24`: To not include `locale` query parameter into every handler, we
created a simple function `get_locale`, which we will include as a global
dependency with `Depends`.`29-36`: An example of overridden function to return translated messages of the
validation exception.Now we are ready to create a FastAPI application:
```Python linenums="1" hl_lines="8 10"
from fastapi import Depends, FastAPI, Request
from fastapi.exceptions import RequestValidationErrorfrom pydantic import BaseModel
import tr
app = FastAPI(dependencies=[Depends(tr.get_locale)])
app.add_exception_handler(RequestValidationError, tr.validation_exception_handler)
class User(BaseModel):
name: str@app.post("/user", response_model=User)
def create_user(request: Request, user: User):
pass
````8`: Add `get_locale` function as a global dependency.
!!! note
If you need to use i18n only for specific part of your
application, you can add this `get_locale` function to the specific
`APIRouter`. More information about `APIRouter` you can find
[here](https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter).`10`: Override default request validation error handler with
`validation_exception_handler`.### Run it
Run the server with:
```console
$ uvicorn main:app --reloadINFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
```About the command
uvicorn main:app --reload
...The command `uvicorn main:app` refers to:
* `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--reload`: make the server restart after code changes. Only do this for development.### Send it
Open your browser at http://127.0.0.1:8000/docs#/default/create_user_user_post.
Send POST-request with empty body and `de_DE` locale query param via swagger UI
or `curl`:```bash
$ curl -X 'POST' \
'http://127.0.0.1:8000/user?locale=de_DE' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
}'
```### Check it
As a result, you will get the next response body:
```json hl_lines="8"
{
"detail": [
{
"loc": [
"body",
"name"
],
"msg": "Feld erforderlich",
"type": "value_error.missing"
}
]
}
```If you don't mention the `locale` param, English locale will be used by
default.## Use placeholder in error strings
You can use placeholders in error strings, but you **must mark** every placeholder with `{}`.
```Python
from decimal import Decimalfrom pydantic import BaseModel, ValidationError, Field
from pydantic_i18n import PydanticI18ntranslations = {
"en_US": {
"Decimal input should have no more than {} in total":
"Decimal input should have no more than {} in total",
},
"es_AR": {
"Decimal input should have no more than {} in total":
"La entrada decimal no debe tener más de {} en total",
},
}tr = PydanticI18n(translations)
class CoolSchema(BaseModel):
my_field: Decimal = Field(max_digits=3)try:
CoolSchema(my_field=1111)
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="es_AR")print(translated_errors)
# [
# {
# 'type': 'decimal_max_digits',
# 'loc': ('my_field',),
# 'msg': 'La entrada decimal no debe tener más de 3 digits en total',
# 'input': 1111,
# 'ctx': {
# 'max_digits': 3
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/decimal_max_digits'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/placeholder/tutorial001.py))_## Get current error strings from Pydantic
pydantic-i18n doesn't provide prepared translations of all current error
messages from pydantic, but you can use a special class method
`PydanticI18n.get_pydantic_messages` to load original messages in English. By
default, it returns a `dict` object:```Python
from pydantic_i18n import PydanticI18nprint(PydanticI18n.get_pydantic_messages())
# {
# "Object has no attribute '{}'": "Object has no attribute '{}'",
# "Invalid JSON: {}": "Invalid JSON: {}",
# "JSON input should be string, bytes or bytearray": "JSON input should be string, bytes or bytearray",
# "Recursion error - cyclic reference detected": "Recursion error - cyclic reference detected",
# "Field required": "Field required",
# "Field is frozen": "Field is frozen",
# .....
# }
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/pydantic-messages/tutorial001.py))_You can also choose JSON string or Babel format with `output` parameter values
`"json"` and `"babel"`:```Python
from pydantic_i18n import PydanticI18nprint(PydanticI18n.get_pydantic_messages(output="json"))
# {
# "Field required": "Field required",
# "Field is frozen": "Field is frozen",
# "Error extracting attribute: {}": "Error extracting attribute: {}",
# .....
# }print(PydanticI18n.get_pydantic_messages(output="babel"))
# msgid "Field required"
# msgstr "Field required"
#
# msgid "Field is frozen"
# msgstr "Field is frozen"
#
# msgid "Error extracting attribute: {}"
# msgstr "Error extracting attribute: {}"
# ....```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/pydantic-messages/tutorial002.py))_## Loaders
pydantic-i18n provides a list of loaders to use translations.
### DictLoader
DictLoader is the simplest loader and default in PydanticI18n. So you can
just pass your translations dictionary without any other preparation steps.```Python
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18ntranslations = {
"en_US": {
"Field required": "field required",
},
"de_DE": {
"Field required": "Feld erforderlich",
},
}tr = PydanticI18n(translations)
class User(BaseModel):
name: strtry:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/dict-loader/tutorial001.py))_### JsonLoader
JsonLoader needs to get the path to some directory with the next structure:
```text
|-- translations
|-- en_US.json
|-- de_DE.json
|-- ...
```where e.g. `en_US.json` looks like:
```json
{
"Field required": "Field required"
}
```and `de_DE.json`:
```json
{
"Field required": "Feld erforderlich"
}
```Then we can use `JsonLoader` to load our translations:
```Python
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, JsonLoaderloader = JsonLoader("./translations")
tr = PydanticI18n(loader)class User(BaseModel):
name: strtry:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',
# ),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/json-loader/tutorial001.py))_### BabelLoader
BabelLoader works in the similar way as JsonLoader. It also needs a
translations directory with the next structure:```text
|-- translations
|-- en_US
|-- LC_MESSAGES
|-- messages.mo
|-- messages.po
|-- de_DE
|-- LC_MESSAGES
|-- messages.mo
|-- messages.po
|-- ...
```Information about translations preparation you can find on the
[Babel docs pages](http://babel.pocoo.org/en/latest/cmdline.html){:target="_blank"} and e.g.
from [this article](https://phrase.com/blog/posts/i18n-advantages-babel-python/#Message_Extraction){:target="_blank"}.Here is an example of the `BabelLoader` usage:
```Python
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, BabelLoaderloader = BabelLoader("./translations")
tr = PydanticI18n(loader)class User(BaseModel):
name: strtry:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/babel-loader/tutorial001.py))_### Write your own loader
If current loaders aren't suitable for you, it's possible to write your own
loader and use it with pydantic-i18n. To do it, you need to import
`BaseLoader` and implement the next items:- property `locales` to get a list of locales;
- method `get_translations` to get content for the specific locale.In some cases you will also need to change implementation of the `gettext`
method.Here is an example of the loader to get translations from CSV files:
```text
|-- translations
|-- en_US.csv
|-- de_DE.csv
|-- ...
````en_US.csv` content:
```csv
Field required,Field required
````de_DE.csv` content:
```csv
Field required,Feld erforderlich
``````Python
import os
from typing import List, Dictfrom pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, BaseLoaderclass CsvLoader(BaseLoader):
def __init__(self, directory: str):
self.directory = directory@property
def locales(self) -> List[str]:
return [
filename[:-4]
for filename in os.listdir(self.directory)
if filename.endswith(".csv")
]def get_translations(self, locale: str) -> Dict[str, str]:
with open(os.path.join(self.directory, f"{locale}.csv")) as fp:
data = dict(line.strip().split(",") for line in fp)return data
class User(BaseModel):
name: strif __name__ == '__main__':
loader = CsvLoader("./translations")
tr = PydanticI18n(loader)try:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/own-loader/tutorial001.py))_## Acknowledgments
Thanks to [Samuel Colvin](https://github.com/samuelcolvin) and his
[pydantic](https://github.com/samuelcolvin/pydantic) library.Also, thanks to [Sebastián Ramírez](https://github.com/tiangolo) and his
[FastAPI](https://github.com/tiangolo/fastapi) project, some scripts and
documentation structure and parts were used from there.## License
This project is licensed under the terms of the MIT license.