Package: pydantic-i18n
pydantic-i18n is an extension to support an i18n for the pydantic error messages.Documentation:https://pydantic-i18n.boardpack.orgSource Code:https://github.com/boardpack/pydantic-i18nRequirementsPython 3.8+pydantic-i18n has the next dependencies:PydanticBabelInstallation$pipinstallpydantic-i18n---> 100%First stepsTo 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 mainPydanticI18nclass.To translate messages, you need to pass result ofexception.errors()call to
thetranslatemethod:frompydanticimportBaseModel,ValidationErrorfrompydantic_i18nimportPydanticI18ntranslations={"en_US":{"field required":"field required",},"de_DE":{"field required":"Feld erforderlich",},}tr=PydanticI18n(translations)classUser(BaseModel):name:strtry:User()exceptValidationErrorase: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 FastAPIHere is a simple example usage with FastAPI.Create itLet's create atr.pyfile:fromfastapiimportRequestfromfastapi.exceptionsimportRequestValidationErrorfromstarlette.responsesimportJSONResponsefromstarlette.statusimportHTTP_422_UNPROCESSABLE_ENTITYfrompydantic_i18nimportPydanticI18n__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)defget_locale(locale:str=DEFAULT_LOCALE)->str:returnlocaleasyncdefvalidation_exception_handler(request:Request,exc:RequestValidationError)->JSONResponse:current_locale=request.query_params.get("locale",DEFAULT_LOCALE)returnJSONResponse(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 includelocalequery parameter into every handler, we
created a simple functionget_locale, which we will include as a global
dependency withDepends.29-36: An example of overridden function to return translated messages of the
validation exception.Now we are ready to create a FastAPI application:fromfastapiimportDepends,FastAPI,Requestfromfastapi.exceptionsimportRequestValidationErrorfrompydanticimportBaseModelimporttrapp=FastAPI(dependencies=[Depends(tr.get_locale)])app.add_exception_handler(RequestValidationError,tr.validation_exception_handler)classUser(BaseModel):name:str@app.post("/user",response_model=User)defcreate_user(request:Request,user:User):pass8: Addget_localefunction as a global dependency.!!! note
If you need to use i18n only for specific part of your
application, you can add thisget_localefunction to the specificAPIRouter. More information aboutAPIRouteryou can findhere.10: Override default request validation error handler withvalidation_exception_handler.Run itRun the server with:$uvicornmain: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 commanduvicorn main:app --reload...The commanduvicorn main:apprefers to:main: the filemain.py(the Python "module").app: the object created inside ofmain.pywith the lineapp = FastAPI().--reload: make the server restart after code changes. Only do this for development.Send itOpen your browser athttp://127.0.0.1:8000/docs#/default/create_user_user_post.Send POST-request with empty body andde_DElocale query param via swagger UI
orcurl:$curl-X'POST'\'http://127.0.0.1:8000/user?locale=de_DE'\-H'accept: application/json'\-H'Content-Type: application/json'\-d'{}'Check itAs 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 thelocaleparam, English locale will be used by
default.Get current error strings from Pydanticpydantic-i18n doesn't provide prepared translations of all current error
messages from pydantic, but you can use a special class methodPydanticI18n.get_pydantic_messagesto load original messages in English. By
default, it returns adictobject:frompydantic_i18nimportPydanticI18nprint(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 withoutputparameter values"json"and"babel":frompydantic_i18nimportPydanticI18nprint(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")Loaderspydantic-i18n provides a list of loaders to use translations.DictLoaderDictLoader is the simplest loader and default in PydanticI18n. So you can
just pass your translations dictionary without any other preparation steps.frompydanticimportBaseModel,ValidationErrorfrompydantic_i18nimportPydanticI18ntranslations={"en_US":{"field required":"field required",},"de_DE":{"field required":"Feld erforderlich",},}tr=PydanticI18n(translations)classUser(BaseModel):name:strtry:User()exceptValidationErrorase: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")JsonLoaderJsonLoader needs to get the path to some directory with the next structure:|-- translations
    |-- en_US.json
    |-- de_DE.json
    |-- ...where e.g.en_US.jsonlooks like:{"field required":"field required"}andde_DE.json:{"field required":"Feld erforderlich"}Then we can useJsonLoaderto load our translations:frompydanticimportBaseModel,ValidationErrorfrompydantic_i18nimportPydanticI18n,JsonLoaderloader=JsonLoader("./translations")tr=PydanticI18n(loader)classUser(BaseModel):name:strtry:User()exceptValidationErrorase: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")BabelLoaderBabelLoader 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 theBabel docs pages{:target="_blank"} and e.g.
fromthis article{:target="_blank"}.Here is an example of theBabelLoaderusage:frompydanticimportBaseModel,ValidationErrorfrompydantic_i18nimportPydanticI18n,BabelLoaderloader=BabelLoader("./translations")tr=PydanticI18n(loader)classUser(BaseModel):name:strtry:User()exceptValidationErrorase: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 loaderIf 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 importBaseLoaderand implement the next items:propertylocalesto get a list of locales;methodget_translationsto get content for the specific locale.In some cases you will also need to change implementation of thegettextmethod.Here is an example of the loader to get translations from CSV files:|-- translations
    |-- en_US.csv
    |-- de_DE.csv
    |-- ...en_US.csvcontent:field required,field requiredde_DE.csvcontent:field required,Feld erforderlichimportosfromtypingimportList,DictfrompydanticimportBaseModel,ValidationErrorfrompydantic_i18nimportPydanticI18n,BaseLoaderclassCsvLoader(BaseLoader):def__init__(self,directory:str):self.directory=directory@propertydeflocales(self)->List[str]:return[filename[:-4]forfilenameinos.listdir(self.directory)iffilename.endswith(".csv")]defget_translations(self,locale:str)->Dict[str,str]:withopen(os.path.join(self.directory,f"{locale}.csv"))asfp:data=dict(line.strip().split(",")forlineinfp)returndataclassUser(BaseModel):name:strif__name__=='__main__':loader=CsvLoader("./translations")tr=PydanticI18n(loader)try:User()exceptValidationErrorase: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")AcknowledgmentsThanks toSamuel Colvinand hispydanticlibrary.Also, thanks toSebastián Ramírezand hisFastAPIproject,  some scripts and
documentation structure and parts were used from there.LicenseThis project is licensed under the terms of the MIT license.