新项目优先用 fastapi-i18n,它专为 fastapi 设计,自动处理语言探测、中间件封装干净,依赖少且与 pydantic v2 无缝集成;老项目或需深度控制流程的可选 gettext,但需手动实现缓存、加载和 fallback。

用 fastapi-i18n 还是 gettext?选哪个更稳
直接说结论:新项目优先用 fastapi-i18n,它专为 FastAPI 设计,自动处理请求头、路径参数、cookie 的语言探测,中间件封装干净;老项目或需深度控制翻译流程的,用原生 gettext 更灵活,但得自己写中间件和缓存逻辑。
fastapi-i18n 依赖少(只靠 pydantic 和 starlette),不引入额外 Web 框架耦合;gettext 则必须手动管理 .mo 文件加载、@lru_cache 翻译器实例、fallback 行为,稍一疏忽就出现 KeyError: 'zh-CN' 或缓存污染。
- 如果你用
pydantic v2以上,fastapi-i18n的Translation类型能无缝对接模型字段的错误消息翻译 - 若项目已有大量 Jinja2 模板,
gettext的_()函数可复用,迁移成本低 -
fastapi-i18n默认不支持动态切换语言后实时重载翻译——得配合reload=True启动或手动调用i18n.reload()
I18nMiddleware 怎么注册才不丢语言上下文
常见错误是把中间件加在 app.add_middleware() 之后,结果后续中间件(比如认证中间件)读不到 request.state.locale。必须确保 I18nMiddleware 是第一个被注册的中间件。
正确顺序:
app = FastAPI() app.add_middleware(I18nMiddleware, default_language="en", translation_directory="app/locales") app.add_middleware(AuthMiddleware) # 放它后面 app.add_middleware(CORSMiddleware) # 再后面
-
translation_directory必须是相对于当前工作目录的路径,不是相对于main.py—— 启动时 pwd 错了就会报FileNotFoundError: No translation file found - 如果用
uvicorn main:app --reload,记得把translation_directory设为绝对路径,否则热重载可能触发两次初始化,导致翻译器重复加载 - 中间件默认从
Accept-Language头取语言,但用户显式传?lang=zh-CN时不会自动 fallback——得自己在路由里手动调用i18n.set_locale(request, lang)
Pydantic 模型验证错误怎么按语言返回不同提示
FastAPI 的 ValidationException 默认错误消息是英文硬编码的,不走 i18n 流程。要让它支持多语言,必须重写 pydantic.BaseModel 的 __init__ 或用自定义 ValidationError 处理器。
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
最简方案:在全局异常处理器里拦截 RequestValidationError,用当前请求的 request.state.gettext 替换错误信息中的字段名和约束描述:
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
locale = getattr(request.state, "locale", "en")
_ = request.state.gettext if hasattr(request.state, "gettext") else lambda x: x
errors = []
for error in exc.errors():
msg = error["msg"]
# 手动映射常见 msg 到翻译键,例如:
if "field required" in msg.lower():
msg = _("Field is required")
elif "string too short" in msg.lower():
msg = _("String must be at least {min_length} characters").format(min_length=error.get("ctx", {}).get("min_length", 1))
errors.append({**error, "msg": msg})
return JSONResponse(
status_code=422,
content={"detail": errors},
)
- 不要依赖
error["msg"]的原始文本做字符串匹配——不同 Pydantic 版本返回的 msg 格式可能变,建议统一用error["type"](如"missing","string_too_short")做判断 - 字段名(
error["loc"][-1])也要翻译,比如把"username"映射成_("Username"),否则中文用户看到 “username 字段必填” 还是中英混杂 - 带参数的翻译(如
_("At least {count} items"))必须用.format(),不能用 f-string,否则翻译器无法提取占位符
messages.po 文件结构和编译容易踩哪些坑
生成 .po 文件别用 pybabel extract 直接扫整个 app/ 目录——它会把 Pydantic 模型注释、SQLAlchemy 字段 docstring 全扫进去,导致翻译文件臃肿且难以维护。应该只扫描明确标记了 _() 或 gettext() 的 Python 文件和 Jinja2 模板。
关键点:
-
msgid必须是纯英文字符串,不能含变量或格式化符号;msgstr才放对应语言的翻译。写成_("Hello {name}".format(name=user.name))会导致提取失败 - 编译前检查
msgfmt -c messages.po,常见错误如:duplicate message definition(重复 key)、unterminated string(中文引号没转义) - 语言代码必须严格匹配:FastAPI 默认识别
zh-CN,但messages.po文件夹名写成zh_CN就加载失败——得保持一致,推荐全用连字符zh-CN,避免下划线 - 修改
.po后必须运行msgfmt messages.po -o messages.mo,否则gettext加载的是旧二进制文件,改了也白改
最常被忽略的是:翻译文件权限。Linux 下如果 messages.mo 是 root 写的,而 uvicorn 以普通用户运行,就会静默失败——查日志只会看到 WARNING: No translations found for locale zh-CN,实际是 Permission Denied。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










