Localize your FastAPI validation message
Tags: fastapi, webdev, python
Published: 2023-08-17 06:14:04

When working on website frontend development, a significant amount of time is spent on handling form validation. If your service caters to a global audience (not only within your country), it's advisable to incorporate internationalization (i18n) error messages into it.

If the situation involves frontend-backend separation, managing the validation i18n message communication becomes a troublesome task. 

The question arises: should the validation messages be managed on the backend or the frontend?

Generally, messages should be managed closer to the rendering (frontend mostly) interface for better organization. A common approach is to use error codes paired with corresponding messages.

However, there are numerous validation methods for forms, and if error codes were applied to forms, maintenance would become quite challenging. Therefore, it's best to centrally manage form-type validation by either the frontend or the backend.

**FastAPI** employs **Pydantic** to encapsulate most scenarios of form validation and provides corresponding error messages. This works well in a purely English environment, but if we require i18n, it's clear that this approach falls short.

This article will work through the whole concept of how to integrate i18n into FastAPI's request validation error messages with pseudo code.

## middleware
Firstly we need to know what locale it is, we can archive this using middleware
```py
class LocaleMiddleware:
   async def dispatch(req, call_next):
       req.locale = incoming_locale
       return await call_next(req)
app.add_middleware(LocaleMiddleware)
```

## exception handler
Then we register our own exception handler to handle `RequestValidationError`
```py
async def i18n_exception_handler(request, exc):
     msg = make_i18n_msg(exc, request.state.locale)
     return JSONResponse({"errors": exc}, status=422)

app.add_exception_handler(RequestValidationError, i18n_exception_handler)
```

## translate
Finally, we make translate action, the exc of FastAPI is object of object, so we need to extract the message recursively
```py
def make_i18n_msg(exc, locale):
   if isinstanceof(exc, wrapper):
     return make_i18n_msg(exc, locale)
   return Translator(locale).t('trans.file.key')
```
And we need a translation file looks like 
```json
// zh-TW.json
{
  "field required": "欄位必填",
  "extra fields not permitted": "不允許額外的欄位",
}
// ja-JP.json
{
  "field required": "フィールドは必須です",
  "extra fields not permitted": "余分なフィールドは許可されていません",
}
```
Then when you send with locale to your API pydantic validation, you will get something like
```
{
  "errors": [
    {
      "loc": [
        "body",
        "string"
      ],
      "msg": "フィールドは必須です",
      "type": "value_missing.field_required"
    },
   {
      "loc": [
        "body",
        "string"
      ],
      "msg": "余分なフィールドは許可されていません",
      "type": "value_error.extra_fields_are_not_permitted"
    }
}
```

That's the essence of it, and I've written a package for it.

> [**fastapi-validation-i18n**](https://pypi.org/project/fastapi-validation-i18n/)

You can accomplish this by adhering to the documentation, contributions are also encouraged and welcome.