Skip to content

Commit 81751d3

Browse files
perf: User Language
1 parent 0afccc9 commit 81751d3

File tree

12 files changed

+272
-64
lines changed

12 files changed

+272
-64
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""010_upgrade_user_language
2+
3+
Revision ID: 8dc3b1bdbfef
4+
Revises: 804b08ac329d
5+
Create Date: 2025-06-10 11:21:35.257604
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '8dc3b1bdbfef'
15+
down_revision = '804b08ac329d'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
23+
op.add_column('sys_user', sa.Column('language', sa.VARCHAR(length=255), server_default=sa.text("'zh-CN'::character varying"), nullable=False))
24+
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade():
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
31+
op.drop_column('sys_user', 'language')
32+
33+
# ### end Alembic commands ###

backend/apps/system/api/user.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from fastapi import APIRouter, Depends, Request
2+
from apps.system.crud.user import get_user_info
23
from apps.system.models.user import user_grid
4+
from apps.system.schemas.system_schema import UserLanguage
35
from common.core.deps import CurrentUser, SessionDep
46
from common.core.pagination import Paginator
57
from common.core.schemas import PaginatedResponse, PaginationParams
@@ -8,8 +10,12 @@
810

911

1012
@router.get("/info")
11-
async def user_info(current_user: CurrentUser):
12-
return current_user.to_dict()
13+
async def user_info(session: SessionDep, current_user: CurrentUser):
14+
db_user = get_user_info(session=session, user_id=current_user.id)
15+
if not db_user:
16+
return {"message": "User not found"}
17+
db_user.password = None
18+
return db_user
1319

1420

1521
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[user_grid])
@@ -24,4 +30,17 @@ async def pager(
2430
return await paginator.get_paginated_response(
2531
model=user_grid,
2632
pagination=pagination,
27-
**filters)
33+
**filters)
34+
35+
@router.put("/language")
36+
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
37+
lang = language.language
38+
if lang not in ["zh-CN", "en"]:
39+
return {"message": "Language not supported"}
40+
db_user = get_user_info(session=session, user_id=current_user.id)
41+
if not db_user:
42+
return {"message": "User not found"}
43+
db_user.language = lang
44+
session.add(db_user)
45+
session.commit()
46+
return {"message": "Language changed successfully", "language": lang}

backend/apps/system/crud/user.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ def get_user_by_account(*, session: Session, account: str) -> sys_user | None:
1010
result_user = sys_user.model_validate(session_user)
1111
return result_user
1212

13+
def get_user_info(*, session: Session, user_id: int) -> sys_user | None:
14+
db_user = session.get(user_grid, user_id)
15+
if not db_user:
16+
return None
17+
return db_user
18+
1319
def authenticate(*, session: Session, account: str, password: str) -> sys_user | None:
1420
db_user = get_user_by_account(session=session, account=account)
1521
if not db_user:

backend/apps/system/models/user.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ class user_grid(sys_user, table=True):
2121
email: str
2222
status: int
2323
create_time: int
24+
language: str = Field(max_length=255, default="zh-CN")
2425

backend/apps/system/schemas/system_schema.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22

33
class model_status(BaseModel):
44
status: bool
5-
ids: list[int]
5+
ids: list[int]
6+
7+
8+
class UserLanguage(BaseModel):
9+
language: str

frontend/src/api/auth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export const userApi = {
55
add: (data: any) => request.post('/settings/terminology', data),
66
edit: (data: any) => request.put('/settings/terminology', data),
77
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
8-
query: (id: number) => request.get(`/settings/terminology/${id}`)
8+
query: (id: number) => request.get(`/settings/terminology/${id}`),
9+
language: (data: any) => request.put('/user/language', data),
910
}

frontend/src/api/login.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export const AuthApi = {
88
'Content-Type': 'application/x-www-form-urlencoded'
99
}
1010
}),
11-
logout: () => request.post('/auth/logout')
11+
logout: () => request.post('/auth/logout'),
12+
info: () => request.get('/user/info')
1213
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<el-dropdown @command="changeLanguage" trigger="hover">
3+
<div class="lang-switch">
4+
<span>{{ selectedLanguage === 'zh-CN' ? '中文' : 'English' }}</span>
5+
<el-icon class="el-icon--right">
6+
<ArrowDown />
7+
</el-icon>
8+
</div>
9+
<template #dropdown>
10+
<el-dropdown-menu>
11+
<el-dropdown-item command="en" :class="{ 'selected-lang': selectedLanguage === 'en' }">
12+
English
13+
</el-dropdown-item>
14+
<el-dropdown-item command="zh-CN" :class="{ 'selected-lang': selectedLanguage === 'zh-CN' }">
15+
中文
16+
</el-dropdown-item>
17+
</el-dropdown-menu>
18+
</template>
19+
</el-dropdown>
20+
</template>
21+
22+
<script setup lang="ts">
23+
import { computed } from 'vue'
24+
import { useI18n } from 'vue-i18n'
25+
import { useUserStore } from '@/stores/user'
26+
import { ArrowDown } from '@element-plus/icons-vue'
27+
import { userApi } from '@/api/auth'
28+
29+
const { locale } = useI18n()
30+
const userStore = useUserStore()
31+
32+
const selectedLanguage = computed(() => {
33+
return userStore.language
34+
})
35+
36+
const changeLanguage = (lang: string) => {
37+
locale.value = lang
38+
userStore.setLanguage(lang)
39+
40+
const param = {
41+
language: lang
42+
}
43+
userApi.language(param)
44+
}
45+
</script>
46+
47+
<style scoped lang="less">
48+
.lang-switch {
49+
display: flex;
50+
align-items: center;
51+
cursor: pointer;
52+
color: var(--el-text-color-primary);
53+
54+
.el-icon--right {
55+
margin-left: 8px;
56+
font-size: 12px;
57+
}
58+
}
59+
60+
.selected-lang {
61+
color: var(--el-color-primary);
62+
}
63+
</style>

frontend/src/components/layout/index.vue

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,7 @@
7777
<el-dropdown-menu>
7878
<el-dropdown-item @click="switchLayout">Switch Layout</el-dropdown-item>
7979
<el-dropdown-item @click="logout">Logout</el-dropdown-item>
80-
81-
<el-dropdown @command="changeLanguage">
82-
<div class="lang-switch">
83-
Language
84-
<el-icon>
85-
<i class="el-icon-arrow-down"/>
86-
</el-icon>
87-
</div>
88-
<template #dropdown>
89-
<el-dropdown-menu>
90-
<el-dropdown-item command="en">English</el-dropdown-item>
91-
<el-dropdown-item command="zh-CN">中文</el-dropdown-item>
92-
</el-dropdown-menu>
93-
</template>
94-
</el-dropdown>
80+
<el-dropdown-item><language-selector /></el-dropdown-item>
9581
</el-dropdown-menu>
9682
</template>
9783
</el-dropdown>
@@ -121,6 +107,8 @@
121107
<el-dropdown-menu>
122108
<el-dropdown-item @click="switchLayout">Switch Layout</el-dropdown-item>
123109
<el-dropdown-item @click="logout">Logout</el-dropdown-item>
110+
<el-dropdown-item><language-selector /></el-dropdown-item>
111+
124112
</el-dropdown-menu>
125113
</template>
126114
</el-dropdown>
@@ -170,8 +158,9 @@ import icon_ai from '@/assets/svg/icon_ai.svg'
170158
import {ArrowLeftBold} from '@element-plus/icons-vue'
171159
import {useCache} from '@/utils/useCache'
172160
import { useI18n } from 'vue-i18n'
161+
import LanguageSelector from '@/components/Language-selector/index.vue'
173162
174-
const { locale, t } = useI18n()
163+
const { t } = useI18n()
175164
const {wsCache} = useCache()
176165
const topLayout = ref(false)
177166
const router = useRouter()
@@ -234,9 +223,6 @@ const switchLayout = () => {
234223
wsCache.set('sqlbot-topbar-layout', topLayout.value)
235224
}
236225
237-
const changeLanguage = (lang: string) => {
238-
locale.value = lang
239-
}
240226
onMounted(() => {
241227
topLayout.value = wsCache.get('sqlbot-topbar-layout') || true
242228
})
@@ -558,12 +544,4 @@ onMounted(() => {
558544
}
559545
}
560546
}
561-
.lang-switch {
562-
cursor: pointer;
563-
padding: 0 12px;
564-
565-
&:hover {
566-
color: var(--el-color-primary);
567-
}
568-
}
569547
</style>

frontend/src/i18n/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import en from './en.json'
33
import zhCN from './zh-CN.json'
44
import elementEnLocale from 'element-plus-secondary/es/locale/lang/en'
55
import elementZhLocale from 'element-plus-secondary/es/locale/lang/zh-cn'
6+
import { useCache } from '@/utils/useCache'
7+
const { wsCache } = useCache()
68

79
const getDefaultLocale = () => {
8-
/* const savedLang = localStorage.getItem('lang')
9-
return savedLang || 'zh-CN' */
10-
return 'zh-CN'
10+
const language = wsCache.get('user.language')
11+
return language || 'zh-CN'
1112
}
1213

1314
const messages = {

0 commit comments

Comments
 (0)