perf: Backend i18n

This commit is contained in:
fit2cloud-chenyw
2025-07-27 19:03:21 +08:00
parent 3a0213f6e5
commit 45ae892024
7 changed files with 107 additions and 43 deletions
+5 -4
View File
@@ -6,14 +6,14 @@ from fastapi import APIRouter, Query
from sqlmodel import func, select, update
from apps.system.models.system_model import AiModelDetail
from common.core.deps import SessionDep
from common.core.deps import SessionDep, Trans
from common.utils.time import get_timestamp
from common.utils.utils import SQLBotLogUtil
router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
@router.post("/status")
async def check_llm(info: AiModelCreator):
async def check_llm(info: AiModelCreator, trans: Trans):
try:
additional_params = {item.key: item.val for item in info.config_list}
config = LLMConfig(
@@ -28,7 +28,7 @@ async def check_llm(info: AiModelCreator):
SQLBotLogUtil.info(f"check_llm result: {result}")
except Exception as e:
SQLBotLogUtil.error(f"Error checking LLM: {e}")
raise e
raise Exception(trans('i18n_llm.validate_error', msg = str(e)))
@router.get("", response_model=list[AiModelGridItem])
async def query(
@@ -102,11 +102,12 @@ async def update_model(
@router.delete("/{id}")
async def delete_model(
session: SessionDep,
trans: Trans,
id: int
):
item = session.get(AiModelDetail, id)
if item.default_model:
raise RuntimeError(f"Can not delete [${item.name}], because it is default model!")
raise Exception(trans('i18n_llm.delete_default_error', key = item.name))
session.delete(item)
session.commit()
+5 -5
View File
@@ -2,7 +2,7 @@ from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from apps.system.schemas.system_schema import BaseUserDTO
from common.core.deps import SessionDep
from common.core.deps import SessionDep, Trans
from ..crud.user import authenticate
from common.core.security import create_access_token
from datetime import timedelta
@@ -13,16 +13,16 @@ router = APIRouter(tags=["login"], prefix="/login")
@router.post("/access-token")
def local_login(
session: SessionDep,
trans: Trans,
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
) -> Token:
user: BaseUserDTO = authenticate(session=session, account=form_data.username, password=form_data.password)
if not user:
raise HTTPException(status_code=400, detail="Incorrect account or password")
raise HTTPException(status_code=400, detail=trans('i18n_login.account_pwd_error'))
if not user.oid or user.oid == 0:
raise HTTPException(status_code=400, detail="No associated workspace, Please contact the administrator")
raise HTTPException(status_code=400, detail=trans('i18n_login.no_associated_ws', msg = trans('i18n_concat_admin')))
if user.status != 1:
raise HTTPException(status_code=400, detail="User is disabled, Please contact the administrator")
raise HTTPException(status_code=400, detail=trans('i18n_login.user_disable', msg = trans('i18n_concat_admin')))
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
user_dict = user.to_dict()
return Token(access_token=create_access_token(
+17 -17
View File
@@ -99,7 +99,7 @@ async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Tran
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
if not any(x.id == oid for x in ws_list):
raise HTTPException(f"oid [{oid}] is invalid!")
raise Exception(f"oid [{oid}] is invalid!")
user_model: UserModel = get_db_user(session = session, user_id = current_user.id)
user_model.oid = oid
session.add(user_model)
@@ -115,13 +115,13 @@ async def query(session: SessionDep, trans: Trans, id: int) -> UserEditor:
return result
@router.post("")
async def create(session: SessionDep, creator: UserCreator):
async def create(session: SessionDep, creator: UserCreator, trans: Trans):
if check_account_exists(session=session, account=creator.account):
raise Exception(f"Account [{creator.account}] already exists!")
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.account') [{creator.account}]}"))
if check_email_exists(session=session, email=creator.email):
raise Exception(f"Email [{creator.email}] already exists!")
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email') [{creator.email}]}"))
if not check_email_format(creator.email):
raise Exception(f"Email [{creator.email}] format is invalid!")
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email') [{creator.email}]}"))
data = creator.model_dump(exclude_unset=True)
user_model = UserModel.model_validate(data)
#user_model.create_time = get_timestamp()
@@ -144,16 +144,16 @@ async def create(session: SessionDep, creator: UserCreator):
@router.put("")
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id")
async def update(session: SessionDep, editor: UserEditor):
async def update(session: SessionDep, editor: UserEditor, trans: Trans):
user_model: UserModel = get_db_user(session = session, user_id = editor.id)
if not user_model:
raise Exception(f"User with id [{editor.id}] not found!")
if editor.account != user_model.account:
raise Exception(f"account cannot be changed!")
if editor.email != user_model.email and check_email_exists(session=session, account=editor.email):
raise Exception(f"Email [{editor.email}] already exists!")
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email') [{editor.email}]}"))
if not check_email_format(editor.email):
raise Exception(f"Email [{editor.email}] format is invalid!")
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email') [{editor.email}]}"))
origin_oid: int = user_model.oid
del_stmt = sqlmodel_delete(UserWsModel).where(UserWsModel.uid == editor.id)
session.exec(del_stmt)
@@ -188,10 +188,10 @@ async def batch_del(session: SessionDep, id_list: list[int]):
@router.put("/language")
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, language: UserLanguage):
lang = language.language
if lang not in ["zh-CN", "en"]:
return {"message": "Language not supported"}
raise Exception(trans('i18n_user.language_not_support', key = lang))
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
db_user.language = lang
session.add(db_user)
@@ -199,9 +199,9 @@ async def langChange(session: SessionDep, current_user: CurrentUser, language: U
@router.patch("/pwd/{id}")
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
async def pwdReset(session: SessionDep, current_user: CurrentUser, trans: Trans, id: int):
if not current_user.isAdmin:
raise HTTPException('only for admin')
raise Exception(trans('i18n_permission.no_permission', url = " patch[/user/pwd/id],", msg = trans('i18n_permission.only_admin')))
db_user: UserModel = get_db_user(session=session, user_id=id)
db_user.password = default_md5_pwd()
session.add(db_user)
@@ -209,22 +209,22 @@ async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
@router.put("/pwd")
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, editor: PwdEditor):
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, trans: Trans, editor: PwdEditor):
new_pwd = editor.new_pwd
if not check_pwd_format(new_pwd):
raise Exception("Password format is invalid!")
raise Exception(trans('i18n_format_invalid', key = trans('i18n_user.password')))
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
if not verify_md5pwd(editor.pwd, db_user.password):
raise Exception(f"pwd [{editor.pwd}] error")
raise Exception(trans('i18n_error', key = trans('i18n_user.password')))
db_user.password = md5pwd(new_pwd)
session.add(db_user)
session.commit()
@router.patch("/status")
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="statusDto.id")
async def langChange(session: SessionDep, current_user: CurrentUser, statusDto: UserStatus):
async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, statusDto: UserStatus):
if not current_user.isAdmin:
raise Exception("no permission to execute")
raise Exception(trans('i18n_permission.no_permission', url = ", ", msg = trans('i18n_permission.only_admin')))
status = statusDto.status
if status not in [0, 1]:
return {"message": "status not supported"}
+14 -11
View File
@@ -17,15 +17,16 @@ router = APIRouter(tags=["system/workspace"], prefix="/system/workspace")
async def option_pager(
session: SessionDep,
current_user: CurrentUser,
trans: Trans,
pageNum: int,
pageSize: int,
oid: int = Query(description="空间ID"),
keyword: Optional[str] = Query(None, description="搜索关键字(可选)"),
):
if not current_user.isAdmin:
raise RuntimeError('only for admin')
raise Exception(trans('i18n_permission.no_permission', url = ", ", msg = trans('i18n_permission.only_admin')))
if not oid:
raise RuntimeError('oid miss error')
raise Exception(trans('i18n_miss_args', key = '[oid]'))
pagination = PaginationParams(page=pageNum, size=pageSize)
paginator = Paginator(session)
stmt = select(UserModel.id, UserModel.account, UserModel.name).where(
@@ -50,12 +51,13 @@ async def option_pager(
async def option_user(
session: SessionDep,
current_user: CurrentUser,
trans: Trans,
keyword: str = Query(description="搜索关键字")
):
if not keyword:
raise HTTPException("keyword is required")
raise Exception(trans('i18n_miss_args', key = '[keyword]'))
if (not current_user.isAdmin) and current_user.weight == 0:
raise RuntimeError("no permission to execute this api")
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
oid = current_user.oid
stmt = select(UserModel.id, UserModel.account, UserModel.name).where(
@@ -76,13 +78,14 @@ async def option_user(
async def pager(
session: SessionDep,
current_user: CurrentUser,
trans: Trans,
pageNum: int,
pageSize: int,
keyword: Optional[str] = Query(None, description="搜索关键字(可选)"),
oid: Optional[int] = Query(None, description="空间ID(仅admin用户生效)"),
):
if not current_user.isAdmin and current_user.weight == 0:
raise HTTPException("no permission to execute")
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
if current_user.isAdmin:
workspace_id = oid if oid else current_user.oid
else:
@@ -112,9 +115,9 @@ async def pager(
@router.post("/uws")
async def create(session: SessionDep, current_user: CurrentUser, creator: UserWsDTO):
async def create(session: SessionDep, current_user: CurrentUser, trans: Trans, creator: UserWsDTO):
if not current_user.isAdmin and current_user.weight == 0:
raise HTTPException("no permission to execute")
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
oid: int = creator.oid if (current_user.isAdmin and creator.oid) else current_user.oid
weight = creator.weight if (current_user.isAdmin and creator.weight) else 0
# 判断uid_list以及oid合法性
@@ -134,9 +137,9 @@ async def create(session: SessionDep, current_user: CurrentUser, creator: UserWs
session.commit()
@router.put("/uws")
async def edit(session: SessionDep, editor: UserWsEditor):
async def edit(session: SessionDep, trans: Trans, editor: UserWsEditor):
if not editor.oid or not editor.uid:
raise HTTPException("param [oid, uid] miss")
raise Exception(trans('i18n_miss_args', key = '[oid, uid]'))
db_model = session.exec(select(UserWsModel).where(UserWsModel.uid == editor.uid, UserWsModel.oid == editor.oid)).first()
if not db_model:
raise HTTPException("uws not exist")
@@ -150,9 +153,9 @@ async def edit(session: SessionDep, editor: UserWsEditor):
session.commit()
@router.delete("/uws")
async def delete(session: SessionDep, current_user: CurrentUser, dto: UserWsBase):
async def delete(session: SessionDep, current_user: CurrentUser, trans: Trans, dto: UserWsBase):
if not current_user.isAdmin and current_user.weight == 0:
raise HTTPException("no permission to execute")
raise Exception(trans('i18n_permission.no_permission', url = '', msg = ''))
oid: int = dto.oid if (current_user.isAdmin and dto.oid) else current_user.oid
db_model_list: list[UserWsModel] = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(dto.uid_list), UserWsModel.oid == oid)).all()
if not db_model_list:
+15 -4
View File
@@ -1,16 +1,15 @@
from pathlib import Path
import json
from typing import Dict, Optional
from typing import Dict, Optional, Any
from fastapi import Request
class I18n:
def __init__(self, locale_dir: str = "locales"):
self.locale_dir = Path(locale_dir)
self.translations: Dict[str, Dict[str, str]] = {}
self.translations: Dict[str, Dict[str, Any]] = {}
self.load_translations()
def load_translations(self):
if not self.locale_dir.exists():
self.locale_dir.mkdir()
return
@@ -34,9 +33,21 @@ class I18nHelper:
self.request = request
self.lang = i18n.get_language(request)
def _get_nested_translation(self, data: Dict[str, Any], key_path: str) -> str:
keys = key_path.split('.')
current = data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return key_path # 如果找不到,返回原键
return current if isinstance(current, str) else key_path
def __call__(self, key: str, **kwargs) -> str:
lang_data = self.i18n.translations.get(self.lang, {})
text = lang_data.get(key, key) # 找不到则返回原key
text = self._get_nested_translation(lang_data, key)
if kwargs:
try:
+25 -1
View File
@@ -1,4 +1,28 @@
{
"i18n_default_workspace": "Default workspace",
"i18n_ds_name_exist": "Name exist"
"i18n_ds_name_exist": "Name already exists",
"i18n_concat_admin": "Please contact administrator!",
"i18n_exist": "{msg} already exists!",
"i18n_error": "{key} error!",
"i18n_miss_args": "Missing {key} parameter!",
"i18n_format_invalid": "{key} format is incorrect!",
"i18n_login": {
"account_pwd_error": "Account or password error!",
"no_associated_ws": "No associated workspace, {msg}",
"user_disable": "Account disabled, {msg}"
},
"i18n_user": {
"account": "Account",
"email": "Email",
"password": "Password",
"language_not_support": "System does not support [{key}] language!"
},
"i18n_permission": {
"only_admin": "Only administrators can call this!",
"no_permission": "No permission to access {url}{msg}"
},
"i18n_llm": {
"validate_error": "Validation failed [{msg}]",
"delete_default_error": "Cannot delete default model [{key}]!"
}
}
+26 -1
View File
@@ -1,4 +1,29 @@
{
"i18n_default_workspace": "默认工作空间",
"i18n_ds_name_exist": "名称已存在"
"i18n_ds_name_exist": "名称已存在",
"i18n_concat_admin": "请联系管理员!",
"i18n_exist": "{msg}已存在!",
"i18n_error": "{key}错误!",
"i18n_miss_args": "缺失{key}参数!",
"i18n_format_invalid": "{key}格式不正确!",
"i18n_login": {
"account_pwd_error": "账号或密码错误!",
"no_associated_ws": "没有关联的工作空间,{msg}",
"user_disable": "账号已禁用,{msg}"
},
"i18n_user": {
"account": "账号",
"email": "邮箱",
"password": "密码",
"language_not_support": "系统不支持[{key}]语言!"
},
"i18n_permission": {
"only_admin": "仅支持管理员调用!",
"no_permission": "无权调用{url}{msg}"
},
"i18n_llm": {
"validate_error": "校验失败[{msg}]",
"delete_default_error": "无法删除默认模型[{key}]!"
}
}