whchi
Posted on July 10, 2023
When dealing with i18n, the most common way is to add middleware to detect the specific locale in the input request. Fortunately, FastAPI supports middleware, making it pretty simple to do.
- i18n_middleware.py
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
class I18nMiddleware(BaseHTTPMiddleware):
WHITE_LIST = ['en', 'ja', 'zh-TW']
async def dispatch( # type: ignore
self, request: Request, call_next: RequestResponseEndpoint):
# 1. headers 2. path 3. query string
locale = request.headers.get('locale', None) or \
request.path_params.get('locale', None) or \
request.query_params.get('locale', None) or \
'zh-TW'
if locale not in self.WHITE_LIST:
locale = 'zh-TW'
request.state.locale = locale
return await call_next(request)
You can add the middleware to your application in main.py:
- main.py
app = FastAPI()
app.add_middleware(I18nMiddleware)
Then you can use it in your router:
@router.get('/my-resource')
def get_my_resource(request: Request):
print(request.state.locale)
However, there is no global helper like Laravel's __
or RoR's I18n.t
, so we need to create our own.
The idea is pretty simple: put translation scripts in Python and write your own importer using importlib
. Here's how you can do it:
- Write your translation scripts
locale = {
'greeting': 'Hi, {user_name}',
'title': 'hello world',
}
- Put your translation scripts in the following structure
app/lang
├── en
│ └── messages.py
├── ja
│ └── messages.py
└── zh-TW
└── messages.py
- Write a class to handle translation
import importlib
from typing import Any, Dict
class Translator:
_instances: Dict[str, 'Translator'] = {}
def __new__(cls, lang: str) -> 'Translator':
if lang not in cls._instances:
cls._instances[lang] = super(Translator, cls).__new__(cls)
return cls._instances[lang]
def __init__(self, lang: str):
self.lang = lang
def t(self, key: str, **kwargs: Dict[str, Any]) -> str:
file_key, *translation_keys = key.split('.')
locale_module = importlib.import_module(f'app.lang.{self.lang}.{file_key}')
translation = locale_module.locale
for translation_key in translation_keys:
translation = translation.get(translation_key, None)
if translation is None:
return f'Key {key} not found in {self.lang} locale'
if kwargs.keys():
translation = translation.format(**kwargs)
return translation
Then you can use it in your router
@router.get('/my-resource')
def get_my_resource(request: Request):
translator = Translator(request.state.locale)
# 'hello world'
print(translator.t('messages.title'))
# 'Hi, Jon Doe'
print(translator.t('messages.greeting'), user_name='Jon Doe')
That is, pretty easy.
💖 💪 🙅 🚩
whchi
Posted on July 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.