|
| 1 | +# 国际化 (i18n) 模块 |
| 2 | + |
| 3 | +FastAPI 项目的完整国际化解决方案,支持多语言响应消息、验证错误消息和业务逻辑消息的自动翻译。 |
| 4 | + |
| 5 | +## 🌍 功能特性 |
| 6 | + |
| 7 | +- **自动语言检测**: 支持从 URL 参数、请求头、Accept-Language 等多种方式检测用户语言偏好 |
| 8 | +- **响应码国际化**: 自动翻译所有响应状态码消息 |
| 9 | +- **验证消息国际化**: 支持 100+ 条 Pydantic 验证错误消息的翻译 |
| 10 | +- **业务消息国际化**: 支持业务逻辑中的错误和成功消息翻译 |
| 11 | +- **灵活的翻译管理**: 基于 JSON 文件的翻译资源管理 |
| 12 | +- **上下文感知**: 支持参数格式化的动态翻译 |
| 13 | + |
| 14 | +## 📁 文件结构 |
| 15 | + |
| 16 | +``` |
| 17 | +backend/common/i18n/ |
| 18 | +├── __init__.py # 模块导出 |
| 19 | +├── manager.py # 国际化管理器 |
| 20 | +├── middleware.py # 国际化中间件 |
| 21 | +├── locales/ # 翻译文件目录 |
| 22 | +│ ├── zh-CN.json # 中文翻译 |
| 23 | +│ └── en-US.json # 英文翻译 |
| 24 | +├── usage_example.py # 使用示例 |
| 25 | +└── README.md # 文档说明 |
| 26 | +``` |
| 27 | + |
| 28 | +## 🚀 快速开始 |
| 29 | + |
| 30 | +### 1. 启用国际化中间件 |
| 31 | + |
| 32 | +在 `main.py` 中添加国际化中间件: |
| 33 | + |
| 34 | +```python |
| 35 | +from fastapi import FastAPI |
| 36 | +from backend.common.i18n import I18nMiddleware |
| 37 | + |
| 38 | +app = FastAPI() |
| 39 | + |
| 40 | +# 添加国际化中间件 |
| 41 | +app.add_middleware(I18nMiddleware, default_language='zh-CN') |
| 42 | +``` |
| 43 | + |
| 44 | +### 2. 基本使用 |
| 45 | + |
| 46 | +```python |
| 47 | +from backend.common.i18n.manager import t |
| 48 | +from backend.common.response.response_code import CustomResponseCode |
| 49 | + |
| 50 | +# 使用响应码(自动国际化) |
| 51 | +res = CustomResponseCode.HTTP_200 |
| 52 | +print(res.msg) # 根据当前语言显示 "请求成功" 或 "Request successful" |
| 53 | + |
| 54 | +# 手动翻译 |
| 55 | +message = t('error.user_not_found') |
| 56 | +formatted_msg = t('error.invalid_request_params', message="用户名") |
| 57 | +``` |
| 58 | + |
| 59 | +### 3. 语言切换方式 |
| 60 | + |
| 61 | +客户端可以通过以下方式指定语言: |
| 62 | + |
| 63 | +1. **URL 参数**: `GET /api/users?lang=en-US` |
| 64 | +2. **请求头**: `X-Language: en-US` |
| 65 | +3. **Accept-Language**: `Accept-Language: en-US,en;q=0.9` |
| 66 | + |
| 67 | +优先级: URL 参数 > X-Language 头 > Accept-Language 头 > 默认语言 |
| 68 | + |
| 69 | +## 📖 API 文档 |
| 70 | + |
| 71 | +### I18nManager |
| 72 | + |
| 73 | +国际化管理器,负责加载和管理翻译资源。 |
| 74 | + |
| 75 | +```python |
| 76 | +from backend.common.i18n.manager import get_i18n_manager, t |
| 77 | + |
| 78 | +# 获取管理器实例 |
| 79 | +i18n = get_i18n_manager() |
| 80 | + |
| 81 | +# 翻译方法 |
| 82 | +def t(key: str, language: str = None, **kwargs) -> str: |
| 83 | + """ |
| 84 | + 翻译函数 |
| 85 | + |
| 86 | + Args: |
| 87 | + key: 翻译键,支持点号分隔的嵌套键 |
| 88 | + language: 目标语言,None 则使用当前语言 |
| 89 | + **kwargs: 格式化参数 |
| 90 | + |
| 91 | + Returns: |
| 92 | + 翻译后的文本 |
| 93 | + """ |
| 94 | +``` |
| 95 | + |
| 96 | +### I18nMiddleware |
| 97 | + |
| 98 | +国际化中间件,自动检测和设置请求语言。 |
| 99 | + |
| 100 | +```python |
| 101 | +class I18nMiddleware(BaseHTTPMiddleware): |
| 102 | + def __init__(self, app, default_language: str = 'zh-CN'): |
| 103 | + """ |
| 104 | + Args: |
| 105 | + app: FastAPI 应用实例 |
| 106 | + default_language: 默认语言 |
| 107 | + """ |
| 108 | +``` |
| 109 | + |
| 110 | +## 🔧 翻译文件格式 |
| 111 | + |
| 112 | +翻译文件使用 JSON 格式,支持嵌套结构: |
| 113 | + |
| 114 | +```json |
| 115 | +{ |
| 116 | + "response": { |
| 117 | + "success": "请求成功", |
| 118 | + "error": "请求错误" |
| 119 | + }, |
| 120 | + "error": { |
| 121 | + "user_not_found": "用户不存在", |
| 122 | + "invalid_request_params": "请求参数非法: {message}" |
| 123 | + }, |
| 124 | + "validation": { |
| 125 | + "missing": "字段为必填项", |
| 126 | + "string_too_short": "字符串应至少有 {min_length} 个字符" |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +## 💡 使用示例 |
| 132 | + |
| 133 | +### 在 API 端点中使用 |
| 134 | + |
| 135 | +```python |
| 136 | +from fastapi import APIRouter |
| 137 | +from backend.common.i18n.manager import t |
| 138 | +from backend.common.response.response_code import CustomResponseCode |
| 139 | + |
| 140 | +router = APIRouter() |
| 141 | + |
| 142 | +@router.get("/users") |
| 143 | +async def get_users(): |
| 144 | + # 响应码会自动国际化 |
| 145 | + res = CustomResponseCode.HTTP_200 |
| 146 | + return { |
| 147 | + "code": res.code, |
| 148 | + "msg": res.msg, # 自动翻译 |
| 149 | + "data": [] |
| 150 | + } |
| 151 | + |
| 152 | +@router.post("/users") |
| 153 | +async def create_user(user_data: dict): |
| 154 | + if not user_data.get('username'): |
| 155 | + # 手动翻译错误消息 |
| 156 | + raise HTTPException( |
| 157 | + status_code=400, |
| 158 | + detail=t('error.user_not_found') |
| 159 | + ) |
| 160 | + |
| 161 | + return { |
| 162 | + "msg": t('success.create_success', name="用户") |
| 163 | + } |
| 164 | +``` |
| 165 | + |
| 166 | +### 在服务层中使用 |
| 167 | + |
| 168 | +```python |
| 169 | +from backend.common.exception.errors import CustomError |
| 170 | +from backend.common.response.response_code import CustomErrorCode |
| 171 | +from backend.common.i18n.manager import t |
| 172 | + |
| 173 | +class UserService: |
| 174 | + def get_user(self, user_id: int): |
| 175 | + user = self.user_repository.get(user_id) |
| 176 | + if not user: |
| 177 | + # 使用预定义的错误码 |
| 178 | + raise CustomError(error=CustomErrorCode.USER_NOT_FOUND) |
| 179 | + |
| 180 | + return user |
| 181 | + |
| 182 | + def validate_password(self, password: str): |
| 183 | + if len(password) < 8: |
| 184 | + # 使用动态翻译 |
| 185 | + raise ValueError(t('error.password_too_short', min_length=8)) |
| 186 | +``` |
| 187 | + |
| 188 | +### 在 Pydantic 模型中使用 |
| 189 | + |
| 190 | +```python |
| 191 | +from pydantic import BaseModel, Field, validator |
| 192 | +from backend.common.i18n.manager import t |
| 193 | + |
| 194 | +class UserCreateSchema(BaseModel): |
| 195 | + username: str = Field(..., description="用户名") |
| 196 | + password: str = Field(..., description="密码") |
| 197 | + |
| 198 | + @validator('username') |
| 199 | + def validate_username(cls, v): |
| 200 | + if not v or len(v) < 3: |
| 201 | + raise ValueError(t('validation.string_too_short', min_length=3)) |
| 202 | + return v |
| 203 | +``` |
| 204 | + |
| 205 | +## 🔄 扩展新语言 |
| 206 | + |
| 207 | +1. 在 `locales/` 目录下创建新的语言文件,如 `ja-JP.json` |
| 208 | +2. 复制现有翻译文件结构,翻译所有文本 |
| 209 | +3. 在 `I18nManager` 中添加新语言到 `supported_languages` 列表 |
| 210 | +4. 在中间件的 `_normalize_language` 方法中添加语言映射 |
| 211 | + |
| 212 | +## 📝 翻译键命名规范 |
| 213 | + |
| 214 | +- **响应码**: `response.{type}` (如: `response.success`) |
| 215 | +- **错误消息**: `error.{error_type}` (如: `error.user_not_found`) |
| 216 | +- **成功消息**: `success.{action}` (如: `success.login_success`) |
| 217 | +- **验证消息**: `validation.{validation_type}` (如: `validation.missing`) |
| 218 | +- **任务消息**: `task.{task_type}` (如: `task.execute_failed`) |
| 219 | + |
| 220 | +## ⚠️ 注意事项 |
| 221 | + |
| 222 | +1. **性能考虑**: 翻译文件在启动时加载到内存,避免频繁的文件 I/O |
| 223 | +2. **缓存机制**: 使用 `@lru_cache` 缓存管理器实例 |
| 224 | +3. **参数格式化**: 支持 Python 字符串格式化语法,如 `{name}`, `{count:d}` |
| 225 | +4. **回退机制**: 如果翻译不存在,会回退到默认语言或返回翻译键 |
| 226 | +5. **上下文变量**: 使用 `contextvars` 确保请求级别的语言隔离 |
| 227 | + |
| 228 | +## 🔍 故障排除 |
| 229 | + |
| 230 | +### 翻译不生效 |
| 231 | +- 检查翻译文件是否存在且格式正确 |
| 232 | +- 确认中间件已正确添加 |
| 233 | +- 验证翻译键是否正确 |
| 234 | + |
| 235 | +### 语言检测不准确 |
| 236 | +- 检查请求头格式 |
| 237 | +- 确认支持的语言列表包含目标语言 |
| 238 | +- 验证语言代码规范化映射 |
| 239 | + |
| 240 | +### 格式化参数错误 |
| 241 | +- 确保参数名与翻译文件中的占位符匹配 |
| 242 | +- 检查参数类型是否正确 |
| 243 | +- 验证格式化语法 |
| 244 | + |
| 245 | +## 🤝 贡献指南 |
| 246 | + |
| 247 | +1. 添加新的翻译键时,请同时更新所有语言文件 |
| 248 | +2. 保持翻译文件结构的一致性 |
| 249 | +3. 为新功能编写相应的使用示例 |
| 250 | +4. 更新文档说明 |
| 251 | + |
| 252 | +--- |
| 253 | + |
| 254 | +通过这个国际化模块,你的 FastAPI 项目可以轻松支持多语言,为全球用户提供本地化的体验。 |
0 commit comments