fastkit-i18n
Laravel-style i18n for FastAPI. JSON translation files, automatic locale detection, and translatable SQLAlchemy/SQLModel models — without touching a framework. The standalone extraction of the i18n module from fastkit-core.
Introduction
Most FastAPI apps eventually need more than one language — validation messages, API responses, or content that comes straight from the database. fastkit-i18n covers all three with a single, small library built around three independent pieces:
_() / TranslationManagerJSON translation files with dot-notation keys, variable interpolation, and locale fallback. Zero dependencies.
A pure ASGI middleware — detects locale from headers, query params, or cookies. Works with FastAPI, Starlette, or Litestar. Zero dependencies.
Multi-language fields on SQLAlchemy or SQLModel models — a field reads and writes like a plain string, backed by JSON per-locale storage. Needs [sqlalchemy].
Each piece works completely on its own. Use just _() for a single-language app that wants centralized strings, or all three together for a fully multi-language API.
Quick Example
{
"messages": {
"welcome": "Welcome to the API!",
"hello": "Hello, {name}!"
}
}
{
"messages": {
"welcome": "¡Bienvenido a la API!",
"hello": "¡Hola, {name}!"
}
}
from fastapi import FastAPI
from fastkit_i18n import LocaleMiddleware, _
app = FastAPI()
app.add_middleware(LocaleMiddleware) # Accept-Language / ?lang= / cookie
@app.get("/")
def root():
return {"message": _("messages.welcome")}
@app.get("/hello/{name}")
def hello(name: str):
return {"message": _("messages.hello", name=name)}
curl http://localhost:8000/ -H "Accept-Language: es"
# {"message": "¡Bienvenido a la API!"}
Features
_() and LocaleMiddleware need nothing beyond the standard library. SQLAlchemy is an optional extra, only for TranslatableMixin.
LocaleMiddleware speaks raw ASGI — works identically on FastAPI, Starlette, and Litestar.
TranslatableMixin works with either — a SQLModel table class is a real SQLAlchemy mapped class underneath.
Combine TranslatableMixin with another base class in the wrong order and it raises a clear TypeError at class-definition time — never a silently empty field.
Missing keys and missing locales fall back automatically — partial translations never break your app.
Ships a py.typed marker — inline type hints throughout, no stub packages needed.
Use Cases
Return API responses in the caller's language, detected automatically from the request.
Blog posts, product catalogs, or CMS content stored once per model, in as many languages as you need.
Provide the translation primitive that fastkit-core's validation-error formatting builds on.
Centralize every user-facing string in JSON — change a label in one place instead of hunting through the codebase.
How it works
All three pieces share the same locale context. LocaleMiddleware reads the request once per call and sets the locale for that request only; _() and TranslatableMixin both read from that same shared context, so nothing needs to be wired together manually.
The context resets automatically once the request finishes, so a locale set for one request can never leak into another — even under concurrent requests.