mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
perf: Backend i18n
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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}]!"
|
||||
}
|
||||
}
|
||||
@@ -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}]!"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user