Skip to content

Commit 45ae892

Browse files
perf: Backend i18n
1 parent 3a0213f commit 45ae892

File tree

7 files changed

+107
-43
lines changed

7 files changed

+107
-43
lines changed

backend/apps/system/api/aimodel.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
from sqlmodel import func, select, update
77

88
from apps.system.models.system_model import AiModelDetail
9-
from common.core.deps import SessionDep
9+
from common.core.deps import SessionDep, Trans
1010
from common.utils.time import get_timestamp
1111
from common.utils.utils import SQLBotLogUtil
1212

1313
router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
1414

1515
@router.post("/status")
16-
async def check_llm(info: AiModelCreator):
16+
async def check_llm(info: AiModelCreator, trans: Trans):
1717
try:
1818
additional_params = {item.key: item.val for item in info.config_list}
1919
config = LLMConfig(
@@ -28,7 +28,7 @@ async def check_llm(info: AiModelCreator):
2828
SQLBotLogUtil.info(f"check_llm result: {result}")
2929
except Exception as e:
3030
SQLBotLogUtil.error(f"Error checking LLM: {e}")
31-
raise e
31+
raise Exception(trans('i18n_llm.validate_error', msg = str(e)))
3232

3333
@router.get("", response_model=list[AiModelGridItem])
3434
async def query(
@@ -102,11 +102,12 @@ async def update_model(
102102
@router.delete("/{id}")
103103
async def delete_model(
104104
session: SessionDep,
105+
trans: Trans,
105106
id: int
106107
):
107108
item = session.get(AiModelDetail, id)
108109
if item.default_model:
109-
raise RuntimeError(f"Can not delete [${item.name}], because it is default model!")
110+
raise Exception(trans('i18n_llm.delete_default_error', key = item.name))
110111
session.delete(item)
111112
session.commit()
112113

backend/apps/system/api/login.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from fastapi import APIRouter, Depends, HTTPException
33
from fastapi.security import OAuth2PasswordRequestForm
44
from apps.system.schemas.system_schema import BaseUserDTO
5-
from common.core.deps import SessionDep
5+
from common.core.deps import SessionDep, Trans
66
from ..crud.user import authenticate
77
from common.core.security import create_access_token
88
from datetime import timedelta
@@ -13,16 +13,16 @@
1313
@router.post("/access-token")
1414
def local_login(
1515
session: SessionDep,
16+
trans: Trans,
1617
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
1718
) -> Token:
1819
user: BaseUserDTO = authenticate(session=session, account=form_data.username, password=form_data.password)
1920
if not user:
20-
raise HTTPException(status_code=400, detail="Incorrect account or password")
21-
21+
raise HTTPException(status_code=400, detail=trans('i18n_login.account_pwd_error'))
2222
if not user.oid or user.oid == 0:
23-
raise HTTPException(status_code=400, detail="No associated workspace, Please contact the administrator")
23+
raise HTTPException(status_code=400, detail=trans('i18n_login.no_associated_ws', msg = trans('i18n_concat_admin')))
2424
if user.status != 1:
25-
raise HTTPException(status_code=400, detail="User is disabled, Please contact the administrator")
25+
raise HTTPException(status_code=400, detail=trans('i18n_login.user_disable', msg = trans('i18n_concat_admin')))
2626
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
2727
user_dict = user.to_dict()
2828
return Token(access_token=create_access_token(

backend/apps/system/api/user.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Tran
9999
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
100100
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
101101
if not any(x.id == oid for x in ws_list):
102-
raise HTTPException(f"oid [{oid}] is invalid!")
102+
raise Exception(f"oid [{oid}] is invalid!")
103103
user_model: UserModel = get_db_user(session = session, user_id = current_user.id)
104104
user_model.oid = oid
105105
session.add(user_model)
@@ -115,13 +115,13 @@ async def query(session: SessionDep, trans: Trans, id: int) -> UserEditor:
115115
return result
116116

117117
@router.post("")
118-
async def create(session: SessionDep, creator: UserCreator):
118+
async def create(session: SessionDep, creator: UserCreator, trans: Trans):
119119
if check_account_exists(session=session, account=creator.account):
120-
raise Exception(f"Account [{creator.account}] already exists!")
120+
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.account') [{creator.account}]}"))
121121
if check_email_exists(session=session, email=creator.email):
122-
raise Exception(f"Email [{creator.email}] already exists!")
122+
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email') [{creator.email}]}"))
123123
if not check_email_format(creator.email):
124-
raise Exception(f"Email [{creator.email}] format is invalid!")
124+
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email') [{creator.email}]}"))
125125
data = creator.model_dump(exclude_unset=True)
126126
user_model = UserModel.model_validate(data)
127127
#user_model.create_time = get_timestamp()
@@ -144,16 +144,16 @@ async def create(session: SessionDep, creator: UserCreator):
144144

145145
@router.put("")
146146
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id")
147-
async def update(session: SessionDep, editor: UserEditor):
147+
async def update(session: SessionDep, editor: UserEditor, trans: Trans):
148148
user_model: UserModel = get_db_user(session = session, user_id = editor.id)
149149
if not user_model:
150150
raise Exception(f"User with id [{editor.id}] not found!")
151151
if editor.account != user_model.account:
152152
raise Exception(f"account cannot be changed!")
153153
if editor.email != user_model.email and check_email_exists(session=session, account=editor.email):
154-
raise Exception(f"Email [{editor.email}] already exists!")
154+
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email') [{editor.email}]}"))
155155
if not check_email_format(editor.email):
156-
raise Exception(f"Email [{editor.email}] format is invalid!")
156+
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email') [{editor.email}]}"))
157157
origin_oid: int = user_model.oid
158158
del_stmt = sqlmodel_delete(UserWsModel).where(UserWsModel.uid == editor.id)
159159
session.exec(del_stmt)
@@ -188,43 +188,43 @@ async def batch_del(session: SessionDep, id_list: list[int]):
188188

189189
@router.put("/language")
190190
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
191-
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
191+
async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, language: UserLanguage):
192192
lang = language.language
193193
if lang not in ["zh-CN", "en"]:
194-
return {"message": "Language not supported"}
194+
raise Exception(trans('i18n_user.language_not_support', key = lang))
195195
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
196196
db_user.language = lang
197197
session.add(db_user)
198198
session.commit()
199199

200200
@router.patch("/pwd/{id}")
201201
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
202-
async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
202+
async def pwdReset(session: SessionDep, current_user: CurrentUser, trans: Trans, id: int):
203203
if not current_user.isAdmin:
204-
raise HTTPException('only for admin')
204+
raise Exception(trans('i18n_permission.no_permission', url = " patch[/user/pwd/id],", msg = trans('i18n_permission.only_admin')))
205205
db_user: UserModel = get_db_user(session=session, user_id=id)
206206
db_user.password = default_md5_pwd()
207207
session.add(db_user)
208208
session.commit()
209209

210210
@router.put("/pwd")
211211
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
212-
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, editor: PwdEditor):
212+
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, trans: Trans, editor: PwdEditor):
213213
new_pwd = editor.new_pwd
214214
if not check_pwd_format(new_pwd):
215-
raise Exception("Password format is invalid!")
215+
raise Exception(trans('i18n_format_invalid', key = trans('i18n_user.password')))
216216
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
217217
if not verify_md5pwd(editor.pwd, db_user.password):
218-
raise Exception(f"pwd [{editor.pwd}] error")
218+
raise Exception(trans('i18n_error', key = trans('i18n_user.password')))
219219
db_user.password = md5pwd(new_pwd)
220220
session.add(db_user)
221221
session.commit()
222222

223223
@router.patch("/status")
224224
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="statusDto.id")
225-
async def langChange(session: SessionDep, current_user: CurrentUser, statusDto: UserStatus):
225+
async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, statusDto: UserStatus):
226226
if not current_user.isAdmin:
227-
raise Exception("no permission to execute")
227+
raise Exception(trans('i18n_permission.no_permission', url = ", ", msg = trans('i18n_permission.only_admin')))
228228
status = statusDto.status
229229
if status not in [0, 1]:
230230
return {"message": "status not supported"}

backend/apps/system/api/workspace.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
async def option_pager(
1818
session: SessionDep,
1919
current_user: CurrentUser,
20+
trans: Trans,
2021
pageNum: int,
2122
pageSize: int,
2223
oid: int = Query(description="空间ID"),
2324
keyword: Optional[str] = Query(None, description="搜索关键字(可选)"),
2425
):
2526
if not current_user.isAdmin:
26-
raise RuntimeError('only for admin')
27+
raise Exception(trans('i18n_permission.no_permission', url = ", ", msg = trans('i18n_permission.only_admin')))
2728
if not oid:
28-
raise RuntimeError('oid miss error')
29+
raise Exception(trans('i18n_miss_args', key = '[oid]'))
2930
pagination = PaginationParams(page=pageNum, size=pageSize)
3031
paginator = Paginator(session)
3132
stmt = select(UserModel.id, UserModel.account, UserModel.name).where(
@@ -50,12 +51,13 @@ async def option_pager(
5051
async def option_user(
5152
session: SessionDep,
5253
current_user: CurrentUser,
54+
trans: Trans,
5355
keyword: str = Query(description="搜索关键字")
5456
):
5557
if not keyword:
56-
raise HTTPException("keyword is required")
58+
raise Exception(trans('i18n_miss_args', key = '[keyword]'))
5759
if (not current_user.isAdmin) and current_user.weight == 0:
58-
raise RuntimeError("no permission to execute this api")
60+
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
5961
oid = current_user.oid
6062

6163
stmt = select(UserModel.id, UserModel.account, UserModel.name).where(
@@ -76,13 +78,14 @@ async def option_user(
7678
async def pager(
7779
session: SessionDep,
7880
current_user: CurrentUser,
81+
trans: Trans,
7982
pageNum: int,
8083
pageSize: int,
8184
keyword: Optional[str] = Query(None, description="搜索关键字(可选)"),
8285
oid: Optional[int] = Query(None, description="空间ID(仅admin用户生效)"),
8386
):
8487
if not current_user.isAdmin and current_user.weight == 0:
85-
raise HTTPException("no permission to execute")
88+
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
8689
if current_user.isAdmin:
8790
workspace_id = oid if oid else current_user.oid
8891
else:
@@ -112,9 +115,9 @@ async def pager(
112115

113116

114117
@router.post("/uws")
115-
async def create(session: SessionDep, current_user: CurrentUser, creator: UserWsDTO):
118+
async def create(session: SessionDep, current_user: CurrentUser, trans: Trans, creator: UserWsDTO):
116119
if not current_user.isAdmin and current_user.weight == 0:
117-
raise HTTPException("no permission to execute")
120+
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
118121
oid: int = creator.oid if (current_user.isAdmin and creator.oid) else current_user.oid
119122
weight = creator.weight if (current_user.isAdmin and creator.weight) else 0
120123
# 判断uid_list以及oid合法性
@@ -134,9 +137,9 @@ async def create(session: SessionDep, current_user: CurrentUser, creator: UserWs
134137
session.commit()
135138

136139
@router.put("/uws")
137-
async def edit(session: SessionDep, editor: UserWsEditor):
140+
async def edit(session: SessionDep, trans: Trans, editor: UserWsEditor):
138141
if not editor.oid or not editor.uid:
139-
raise HTTPException("param [oid, uid] miss")
142+
raise Exception(trans('i18n_miss_args', key = '[oid, uid]'))
140143
db_model = session.exec(select(UserWsModel).where(UserWsModel.uid == editor.uid, UserWsModel.oid == editor.oid)).first()
141144
if not db_model:
142145
raise HTTPException("uws not exist")
@@ -150,9 +153,9 @@ async def edit(session: SessionDep, editor: UserWsEditor):
150153
session.commit()
151154

152155
@router.delete("/uws")
153-
async def delete(session: SessionDep, current_user: CurrentUser, dto: UserWsBase):
156+
async def delete(session: SessionDep, current_user: CurrentUser, trans: Trans, dto: UserWsBase):
154157
if not current_user.isAdmin and current_user.weight == 0:
155-
raise HTTPException("no permission to execute")
158+
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
156159
oid: int = dto.oid if (current_user.isAdmin and dto.oid) else current_user.oid
157160
db_model_list: list[UserWsModel] = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(dto.uid_list), UserWsModel.oid == oid)).all()
158161
if not db_model_list:

backend/common/utils/locale.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
from pathlib import Path
22
import json
3-
from typing import Dict, Optional
3+
from typing import Dict, Optional, Any
44
from fastapi import Request
55

66
class I18n:
77
def __init__(self, locale_dir: str = "locales"):
88
self.locale_dir = Path(locale_dir)
9-
self.translations: Dict[str, Dict[str, str]] = {}
9+
self.translations: Dict[str, Dict[str, Any]] = {}
1010
self.load_translations()
1111

1212
def load_translations(self):
13-
1413
if not self.locale_dir.exists():
1514
self.locale_dir.mkdir()
1615
return
@@ -34,9 +33,21 @@ def __init__(self, i18n: I18n, request: Request):
3433
self.request = request
3534
self.lang = i18n.get_language(request)
3635

36+
def _get_nested_translation(self, data: Dict[str, Any], key_path: str) -> str:
37+
keys = key_path.split('.')
38+
current = data
39+
40+
for key in keys:
41+
if isinstance(current, dict) and key in current:
42+
current = current[key]
43+
else:
44+
return key_path # 如果找不到,返回原键
45+
46+
return current if isinstance(current, str) else key_path
47+
3748
def __call__(self, key: str, **kwargs) -> str:
3849
lang_data = self.i18n.translations.get(self.lang, {})
39-
text = lang_data.get(key, key) # 找不到则返回原key
50+
text = self._get_nested_translation(lang_data, key)
4051

4152
if kwargs:
4253
try:

backend/locales/en.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
{
22
"i18n_default_workspace": "Default workspace",
3-
"i18n_ds_name_exist": "Name exist"
3+
"i18n_ds_name_exist": "Name already exists",
4+
"i18n_concat_admin": "Please contact administrator!",
5+
"i18n_exist": "{msg} already exists!",
6+
"i18n_error": "{key} error!",
7+
"i18n_miss_args": "Missing {key} parameter!",
8+
"i18n_format_invalid": "{key} format is incorrect!",
9+
"i18n_login": {
10+
"account_pwd_error": "Account or password error!",
11+
"no_associated_ws": "No associated workspace, {msg}",
12+
"user_disable": "Account disabled, {msg}"
13+
},
14+
"i18n_user": {
15+
"account": "Account",
16+
"email": "Email",
17+
"password": "Password",
18+
"language_not_support": "System does not support [{key}] language!"
19+
},
20+
"i18n_permission": {
21+
"only_admin": "Only administrators can call this!",
22+
"no_permission": "No permission to access {url}{msg}"
23+
},
24+
"i18n_llm": {
25+
"validate_error": "Validation failed [{msg}]",
26+
"delete_default_error": "Cannot delete default model [{key}]!"
27+
}
428
}

backend/locales/zh-CN.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
11
{
22
"i18n_default_workspace": "默认工作空间",
3-
"i18n_ds_name_exist": "名称已存在"
3+
"i18n_ds_name_exist": "名称已存在",
4+
"i18n_concat_admin": "请联系管理员!",
5+
"i18n_exist": "{msg}已存在!",
6+
"i18n_error": "{key}错误!",
7+
"i18n_miss_args": "缺失{key}参数!",
8+
"i18n_format_invalid": "{key}格式不正确!",
9+
"i18n_login": {
10+
"account_pwd_error": "账号或密码错误!",
11+
"no_associated_ws": "没有关联的工作空间,{msg}",
12+
"user_disable": "账号已禁用,{msg}"
13+
},
14+
"i18n_user": {
15+
"account": "账号",
16+
"email": "邮箱",
17+
"password": "密码",
18+
"language_not_support": "系统不支持[{key}]语言!"
19+
},
20+
"i18n_permission": {
21+
"only_admin": "仅支持管理员调用!",
22+
"no_permission": "无权调用{url}{msg}"
23+
24+
},
25+
"i18n_llm": {
26+
"validate_error": "校验失败[{msg}]",
27+
"delete_default_error": "无法删除默认模型[{key}]!"
28+
}
429
}

0 commit comments

Comments
 (0)