All Projects → boardpack → pydantic-i18n

boardpack / pydantic-i18n

Licence: MIT license
pydantic-i18n is an extension to support an i18n for the pydantic error messages.

Programming Languages

python
139335 projects - #7 most used programming language
shell
77523 projects
Dockerfile
14818 projects

Projects that are alternatives of or similar to pydantic-i18n

mobx-react-intl
A connector between mobx-react and react-intl
Stars: ✭ 32 (+0%)
Mutual labels:  i18n, translation, internationalization
django-i18nfield
Store internationalized strings in Django models with full forms support
Stars: ✭ 32 (+0%)
Mutual labels:  i18n, translation, internationalization
Dom I18n
Provides a very basic HTML multilingual support using JavaScript
Stars: ✭ 125 (+290.63%)
Mutual labels:  i18n, translation, internationalization
Eslint Plugin I18n Json
Fully extendable eslint plugin for JSON i18n translation files.
Stars: ✭ 101 (+215.63%)
Mutual labels:  i18n, translation, internationalization
i18n
internationalize projects to Arabic
Stars: ✭ 67 (+109.38%)
Mutual labels:  i18n, translation, internationalization
Pseudo Localization
Dynamic pseudo-localization in the browser and nodejs
Stars: ✭ 109 (+240.63%)
Mutual labels:  i18n, translation, internationalization
fluent-vue
Internationalization plugin for Vue.js
Stars: ✭ 137 (+328.13%)
Mutual labels:  i18n, translation, internationalization
Django Rosetta
Rosetta is a Django application that eases the translation process of your Django projects
Stars: ✭ 806 (+2418.75%)
Mutual labels:  i18n, translation, internationalization
React Translated
A dead simple way to add complex translations (i18n) in a React (DOM/Native) project 🌎🌍🌏
Stars: ✭ 176 (+450%)
Mutual labels:  i18n, translation, internationalization
Node Gettext
A JavaScript implementation of gettext, a localization framework.
Stars: ✭ 175 (+446.88%)
Mutual labels:  i18n, translation, internationalization
Transloco
🚀 😍 The internationalization (i18n) library for Angular
Stars: ✭ 1,185 (+3603.13%)
Mutual labels:  i18n, translation, internationalization
i18n.cr
Internationalization API ( i18n ) for Crystal!
Stars: ✭ 36 (+12.5%)
Mutual labels:  i18n, translation, internationalization
React Intl Hooks
React hooks for internationalization without the hassle ⚛️🌍
Stars: ✭ 64 (+100%)
Mutual labels:  i18n, translation, internationalization
Phabricator zh hans
Phabricator zh-Hans Translation & Tools.
Stars: ✭ 113 (+253.13%)
Mutual labels:  i18n, translation, internationalization
Translatedjs
Internationalization and localization for JavaScript and Node.js
Stars: ✭ 17 (-46.87%)
Mutual labels:  i18n, translation, internationalization
Jquery I18next
i18next plugin for jquery usage
Stars: ✭ 143 (+346.88%)
Mutual labels:  i18n, translation, internationalization
React I18next
Internationalization for react done right. Using the i18next i18n ecosystem.
Stars: ✭ 6,942 (+21593.75%)
Mutual labels:  i18n, translation, internationalization
Frenchkiss.js
The blazing fast lightweight internationalization (i18n) module for javascript
Stars: ✭ 776 (+2325%)
Mutual labels:  i18n, translation, internationalization
Formatjs
The monorepo home to all of the FormatJS related libraries, most notably react-intl.
Stars: ✭ 12,869 (+40115.63%)
Mutual labels:  i18n, translation, internationalization
I18next Express Middleware
[deprecated] can be replaced with i18next-http-middleware
Stars: ✭ 195 (+509.38%)
Mutual labels:  i18n, translation, internationalization

pydantic-i18n

pydantic-i18n is an extension to support an i18n for the pydantic error messages.

Test Coverage Package version Code style: black Imports: isort


Documentation: https://pydantic-i18n.boardpack.org

Source Code: https://github.com/boardpack/pydantic-i18n


Requirements

Python 3.6+

pydantic-i18n has the next dependencies:

Installation

$ 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:

from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n


translations = {
    "en_US": {
        "field required": "field required",
    },
    "de_DE": {
        "field required": "Feld erforderlich",
    },
}

tr = PydanticI18n(translations)


class User(BaseModel):
    name: str


try:
    User()
except ValidationError as e:
    translated_errors = tr.translate(e.errors(), locale="de_DE")

print(translated_errors)
# [
#     {
#         'loc': ('name',),
#         'msg': 'Feld erforderlich',
#         'type': 'value_error.missing'
#     }
# ]

(This script is complete, it should run "as is")

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:

from fastapi import Request
from fastapi.exceptions import RequestValidationError
from starlette.responses import JSONResponse
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY

from 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 locale


async 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:

from fastapi import Depends, FastAPI, Request
from fastapi.exceptions import RequestValidationError

from 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.

10: Override default request validation error handler with validation_exception_handler.

Run it

Run the server with:

$ uvicorn main:app --reload

INFO:     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:

$ 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:

{
  "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.

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:

from pydantic_i18n import PydanticI18n

print(PydanticI18n.get_pydantic_messages())
# {
#     "field required": "field required",
#     "extra fields not permitted": "extra fields not permitted",
#     "none is not an allowed value": "none is not an allowed value",
#     "value is not none": "value is not none",
#     "value could not be parsed to a boolean": "value could not be parsed to a boolean",
#     "byte type expected": "byte type expected",
#     .....
# }

(This script is complete, it should run "as is")

You can also choose JSON string or Babel format with output parameter values "json" and "babel":

from pydantic_i18n import PydanticI18n

print(PydanticI18n.get_pydantic_messages(output="json"))
# {
#     "field required": "field required",
#     "extra fields not permitted": "extra fields not permitted",
#     "none is not an allowed value": "none is not an allowed value",
#     .....
# }

print(PydanticI18n.get_pydantic_messages(output="babel"))
# msgid "field required"
# msgstr "field required"
#
# msgid "extra fields not permitted"
# msgstr "extra fields not permitted"
#
# msgid "none is not an allowed value"
# msgstr "none is not an allowed value"
# ....

(This script is complete, it should run "as is")

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.

from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n


translations = {
    "en_US": {
        "field required": "field required",
    },
    "de_DE": {
        "field required": "Feld erforderlich",
    },
}

tr = PydanticI18n(translations)


class User(BaseModel):
    name: str


try:
    User()
except ValidationError as e:
    translated_errors = tr.translate(e.errors(), locale="de_DE")

print(translated_errors)
# [
#     {
#         'loc': ('name',),
#         'msg': 'Feld erforderlich',
#         'type': 'value_error.missing'
#     }
# ]

(This script is complete, it should run "as is")

JsonLoader

JsonLoader needs to get the path to some directory with the next structure:


|-- translations
    |-- en_US.json
    |-- de_DE.json
    |-- ...

where e.g. en_US.json looks like:

{
    "field required": "field required"
}

and de_DE.json:

{
    "field required": "Feld erforderlich"
}

Then we can use JsonLoader to load our translations:

from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, JsonLoader

loader = JsonLoader("./translations")
tr = PydanticI18n(loader)


class User(BaseModel):
    name: str


try:
    User()
except ValidationError as e:
    translated_errors = tr.translate(e.errors(), locale="de_DE")

print(translated_errors)
# [
#     {
#         'loc': ('name',),
#         'msg': 'Feld erforderlich',
#         'type': 'value_error.missing'
#     }
# ]

(This script is complete, it should run "as is")

BabelLoader

BabelLoader works in the similar way as JsonLoader. It also needs a translations directory with the next structure:


|-- 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{:target="_blank"} and e.g. from this article{:target="_blank"}.

Here is an example of the BabelLoader usage:

from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, BabelLoader

loader = BabelLoader("./translations")
tr = PydanticI18n(loader)


class User(BaseModel):
    name: str


try:
    User()
except ValidationError as e:
    translated_errors = tr.translate(e.errors(), locale="de")

print(translated_errors)
# [
#     {
#         'loc': ('name',),
#         'msg': 'Feld erforderlich',
#         'type': 'value_error.missing'
#     }
# ]

(This script is complete, it should run "as is")

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:

|-- translations
    |-- en_US.csv
    |-- de_DE.csv
    |-- ...

en_US.csv content:

field required,field required

de_DE.csv content:

field required,Feld erforderlich
import os
from typing import List, Dict

from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, BaseLoader


class 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: str


if __name__ == '__main__':
    loader = CsvLoader("./translations")
    tr = PydanticI18n(loader)

    try:
        User()
    except ValidationError as e:
        translated_errors = tr.translate(e.errors(), locale="de")

    print(translated_errors)
    # [
    #     {
    #         'loc': ('name',),
    #         'msg': 'Feld erforderlich',
    #         'type': 'value_error.missing'
    #     }
    # ]

(This script is complete, it should run "as is")

Acknowledgments

Thanks to Samuel Colvin and his pydantic library.

Also, thanks to Sebastián Ramírez and his 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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].