refactor(i18n): U34 System & User 模块 i18n 清理 — ProfilePage + ResetPasswordDialog + SystemRolesTab + LdapManagementSection 4 文件 UI 硬编码中文清零

- ProfilePage.tsx: 从零接入 useTranslation,基本信息/安全设置/密码修改/用户名邮箱手机/账号来源 15 处中文→t()
- ResetPasswordDialog.tsx: 从零接入 useTranslation,重置密码标题/描述/LDAP提示/新密码确认密码/确认重置 11 处中文→t()
- SystemRolesTab.tsx: 权限分组标签(云资源/自动化/审批/审计)+toast 校验+confirm description+角色计数+空状态+权限列表计数+编码标签+尾部文案 12 处中文→t()/i18n.t()
- LdapManagementSection.tsx: 验证按钮+根OU标签+创建按钮×2+导入按钮+编辑用户描述 8 处中文→t()
- 新增 user.profile.* 14键 + user.resetPwd.* 11键 + system.* 16键 共 41 翻译键
- zh/en 7524→7565 +41 对齐
- tsc -b=0, npm build✓ (16.05s), ESLint errors=0/any=0/unused=0, go build=0
- 17/17 集成回归测试通过
This commit is contained in:
attacker committed 2026-07-12 21:29:58 +08:00
1 parent ad9acea1e5
commit 0dbb85f5b8
7 files changed
+141 -46

No files matched your search

@@ -204,7 +204,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
disabled={ldapUserTesting || !ldapTestUser}
>
{ldapUserTesting ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
验证
{t('system.testConnection')}
</Button>
</div>
</DialogContent>
@@ -221,7 +221,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
<SelectValue placeholder={t('system.selectOu')} />
</SelectTrigger>
<SelectContent className="dg-input">
<SelectItem value={ldapConfig.base_dn}>根 ({ldapConfig.base_dn})</SelectItem>
<SelectItem value={ldapConfig.base_dn}>{t('system.rootOu', { dn: ldapConfig.base_dn })}</SelectItem>
{ldapOUs.map((ou: JsonData) => (
<SelectItem key={ou.dn} value={ou.dn}>
{ou.description ? `${ou.name} (${ou.description})` : ou.name}
@@ -258,7 +258,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
</div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateOU} disabled={!ouForm.ou_name || ldapSaving}>
{ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
创建
{t('common.create')}
</Button>
</div>
</DialogContent>
@@ -300,7 +300,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
</div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateLDAPUser} disabled={!newUser.username || !newUser.password || ldapSaving}>
{ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
创建
{t('common.create')}
</Button>
</div>
</DialogContent>
@@ -315,7 +315,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
disabled={ldapUsers.length === 0 || importing}
>
{importing ? <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" /> : <Download className="h-3.5 w-3.5 mr-1" />}
{selectedLDAPUsers.length > 0 ? `导入 (${selectedLDAPUsers.length})` : `导入全部 (${ldapUsers.length})`}
{selectedLDAPUsers.length > 0 ? t('system.importSelected', { count: selectedLDAPUsers.length }) : t('system.importAll', { count: ldapUsers.length })}
</Button>
</div>
@@ -427,7 +427,7 @@ export function LdapManagementSection(props: LdapManagementSectionProps) {
{t('system.editLdapUser')}
</DialogTitle>
<DialogDescription className="dg-text-muted">
修改 {editLdapUser?.uid} 的属性
{t('system.editLdapUserDesc', { uid: editLdapUser?.uid })}
</DialogDescription>
</DialogHeader>
{editLdapUser && (
@@ -1,5 +1,6 @@
// 管理员重置用户密码弹窗 // Logan.Li
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -17,6 +18,7 @@ interface ResetPasswordDialogProps {
}
export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps) {
const { t } = useTranslation();
const { toast } = useToast();
const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState('');
@@ -30,12 +32,12 @@ export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps)
setSaving(true);
try {
await userApi.resetUserPassword(user.id, password);
toast({ title: '密码已重置', description: `用户 ${user.nickname || user.username} 的新密码已生效` });
toast({ title: t('user.resetPwd.success'), description: t('user.resetPwd.successDesc', { name: user.nickname || user.username }) });
setPassword('');
setConfirm('');
onClose();
} catch (e: unknown) {
toast({ title: '重置失败', description: getErrorMessage(e), variant: 'destructive' });
toast({ title: t('user.resetPwd.failed'), description: getErrorMessage(e), variant: 'destructive' });
} finally {
setSaving(false);
}
@@ -47,34 +49,34 @@ export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps)
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<Key className="h-5 w-5 text-[var(--dg-blue)]" />
重置密码
{t('user.resetPwd.title')}
</DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)]">
{user ? `为「${user.nickname || user.username}」设置新密码` : ''}
{user ? t('user.resetPwd.desc', { name: user.nickname || user.username }) : ''}
</DialogDescription>
</DialogHeader>
{isLdap ? (
<p className="text-sm dg-text-yellow/90 py-2">LDAP 用户请在 LDAP 目录中管理密码,不支持本地重置。</p>
<p className="text-sm dg-text-yellow/90 py-2">{t('user.resetPwd.ldapHint')}</p>
) : (
<div className="space-y-3 py-1">
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">新密码</Label>
<Label className="text-xs text-[var(--dg-text-muted)]">{t('user.resetPwd.newPwd')}</Label>
<Input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="至少 6 位"
placeholder={t('user.resetPwd.newPwdPlaceholder')}
className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]"
/>
</div>
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">确认密码</Label>
<Label className="text-xs text-[var(--dg-text-muted)]">{t('user.resetPwd.confirmPwd')}</Label>
<Input
type="password"
value={confirm}
onChange={e => setConfirm(e.target.value)}
placeholder="再次输入新密码"
placeholder={t('user.resetPwd.confirmPlaceholder')}
className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]"
/>
</div>
@@ -82,11 +84,11 @@ export function ResetPasswordDialog({ user, onClose }: ResetPasswordDialogProps)
)}
<DialogFooter>
<Button variant="outline" className="border-[var(--dg-border)]" onClick={onClose}>取消</Button>
<Button variant="outline" className="border-[var(--dg-border)]" onClick={onClose}>{t('common.cancel')}</Button>
{!isLdap && (
<Button onClick={handleReset} disabled={!canSubmit || saving} className="bg-[var(--dg-blue)] hover:opacity-90">
{saving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
确认重置
{t('user.resetPwd.confirm')}
</Button>
)}
</DialogFooter>
@@ -35,14 +35,14 @@ const ROLE_ICONS: Record<string, string> = {
const PERM_MODULE_GROUPS: { label: string; prefixes: string[] }[] = [
{ label: i18n.t('menu.dashboard'), prefixes: ['dashboard:'] },
{ label: i18n.t('menu.assets'), prefixes: ['server:'] },
{ label: '云资源', prefixes: ['cloud:'] },
{ label: i18n.t('system.permCloudResource'), prefixes: ['cloud:'] },
{ label: 'K8s', prefixes: ['k8s:'] },
{ label: i18n.t('common.publish'), prefixes: ['deploy:'] },
{ label: i18n.t('monitor.hostDashboard.monitor'), prefixes: ['monitor:', 'agent:'] },
{ label: '自动化', prefixes: ['auto:'] },
{ label: i18n.t('system.permAutomation'), prefixes: ['auto:'] },
{ label: 'AI', prefixes: ['aiops:'] },
{ label: '审批', prefixes: ['approval:'] },
{ label: '审计', prefixes: ['audit:'] },
{ label: i18n.t('system.permApproval'), prefixes: ['approval:'] },
{ label: i18n.t('system.permAudit'), prefixes: ['audit:'] },
{ label: i18n.t('common.system3'), prefixes: ['system:'] },
];
@@ -138,8 +138,8 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
};
const handleSubmit = async () => {
if (!form.name.trim()) { toast.error('请输入角色名称'); return; }
if (!form.code.trim()) { toast.error('请输入角色编码'); return; }
if (!form.name.trim()) { toast.error(t('system.roleNameRequired')); return; }
if (!form.code.trim()) { toast.error(t('system.roleCodeRequired')); return; }
setSaving(true);
try {
const payload = {
@@ -171,7 +171,7 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
}
const ok = await confirm({
title: t('system.deleteRole'),
description: `确认删除角色「${role.name}」吗?该操作不可恢复。`,
description: t('system.confirmDeleteRoleDesc', { name: role.name }),
confirmText: t('common.delete'),
variant: 'destructive',
});
@@ -192,12 +192,12 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
<ShieldCheck className="h-5 w-5 dg-text-blue shrink-0 mt-0.5" />
<div className="text-sm text-[var(--dg-text-secondary)] space-y-1">
<p className="font-medium text-[var(--dg-text-primary)]">{t('system.permHowEffect')}</p>
<p>{t('system.permEffectDesc')}<strong className="text-[var(--dg-text-primary)]">{t('system.permBackendAuth')}</strong>{t('monitor.logCenterArch.connector')}<strong className="text-[var(--dg-text-primary)]">{t('system.permEffectFrontend')}</strong>,修改后用户需重新登录生效。</p>
<p>{t('system.permEffectDesc')}<strong className="text-[var(--dg-text-primary)]">{t('system.permBackendAuth')}</strong>{t('monitor.logCenterArch.connector')}<strong className="text-[var(--dg-text-primary)]">{t('system.permEffectFrontend')}</strong>{t('system.permEffectTrailing')}</p>
</div>
</div>
<div className="flex items-center justify-end gap-2">
<span className="text-xs text-[var(--dg-text-muted)]">{roles.length} 个角色</span>
<span className="text-xs text-[var(--dg-text-muted)]">{t('system.roleCount', { count: roles.length })}</span>
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={openCreate}>
<Plus className="h-4 w-4 mr-2" />{t('pipeline.newRole')}
</Button>
@@ -210,7 +210,7 @@ export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTab
) : roles.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-[var(--dg-text-muted)]">
<Shield className="h-10 w-10 mb-3 opacity-40" />
<p className="text-sm">暂无角色数据</p>
<p className="text-sm">{t('system.noRoleData')}</p>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
@@ -321,7 +321,7 @@ function RoleDetailDialog({
</Badge>
)}
</DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)]">编码:{role?.code}</DialogDescription>
<DialogDescription className="text-[var(--dg-text-muted)]">{t('system.roleCodeLabel', { code: role?.code })}</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="rounded-lg bg-[var(--dg-bg-tertiary)] p-4">
@@ -330,7 +330,7 @@ function RoleDetailDialog({
</div>
<div>
<h4 className="text-sm font-medium text-[var(--dg-text-primary)] mb-2">
权限列表 ({role?.permissions?.length || 0} 项)
{t('system.permListCount', { count: role?.permissions?.length || 0 })}
</h4>
{role && (role.permissions as string[]).length > 0 ? (
<div className="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
+17 -15
View File
@@ -1,5 +1,6 @@
// ProfilePage — 个人中心(基本信息 / 安全 / API 访问密钥)// Logan.Li
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -29,6 +30,7 @@ type Profile = {
}
export default function ProfilePage() {
const { t } = useTranslation();
const { toast } = useToast();
const storeUser = useUserStore(s => s.user);
@@ -51,7 +53,7 @@ export default function ProfilePage() {
useEffect(() => { load(); }, [load]);
const displayName = profile?.real_name || profile?.nickname || storeUser?.nickname || storeUser?.username || '用户';
const displayName = profile?.real_name || profile?.nickname || storeUser?.nickname || storeUser?.username || t('user.profile.defaultUser');
const username = profile?.username || storeUser?.username || '';
const handleChangePassword = async () => {
@@ -59,11 +61,11 @@ export default function ProfilePage() {
setPwdSaving(true);
try {
await authApi.changePassword({ old_password: oldPwd, new_password: newPwd });
toast({ title: '密码已修改' });
toast({ title: t('user.profile.pwdChanged') });
setOldPwd('');
setNewPwd('');
} catch (e: unknown) {
toast({ title: '修改失败', description: getErrorMessage(e), variant: 'destructive' });
toast({ title: t('user.profile.pwdChangeFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally {
setPwdSaving(false);
}
@@ -77,7 +79,7 @@ export default function ProfilePage() {
subtitle={`@${username} · ${profile?.email || '—'}`}
icon={<User className="h-5 w-5" />}
loading={loading}
loadingLabel="加载中..."
loadingLabel={t('common.loading')}
actions={(
<div className="flex gap-2 flex-wrap">
{(profile?.roles || []).map((r) => (
@@ -92,15 +94,15 @@ export default function ProfilePage() {
<Card className="dg-card">
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]">
<User className="h-4 w-4 text-[var(--dg-blue)]" />基本信息
<User className="h-4 w-4 text-[var(--dg-blue)]" />{t('user.profile.basicInfo')}
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">用户名</span><span className="font-mono">{username}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">邮箱</span><span>{profile?.email || '—'}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">手机</span><span>{profile?.phone || '—'}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">账号来源</span>
<Badge variant="outline" className="text-xs">{profile?.auth_source === 'ldap' ? 'LDAP' : '本地'}</Badge>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.username')}</span><span className="font-mono">{username}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.email')}</span><span>{profile?.email || '—'}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.phone')}</span><span>{profile?.phone || '—'}</span></div>
<div className="flex justify-between"><span className="text-[var(--dg-text-muted)]">{t('user.profile.authSource')}</span>
<Badge variant="outline" className="text-xs">{profile?.auth_source === 'ldap' ? 'LDAP' : t('user.profile.local')}</Badge>
</div>
</CardContent>
</Card>
@@ -108,22 +110,22 @@ export default function ProfilePage() {
<Card className="dg-card">
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]">
<Shield className="h-4 w-4 dg-text-green" />安全设置
<Shield className="h-4 w-4 dg-text-green" />{t('user.profile.security')}
</CardTitle>
<CardDescription className="text-xs">修改登录密码</CardDescription>
<CardDescription className="text-xs">{t('user.profile.changePwd')}</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">当前密码</Label>
<Label className="text-xs text-[var(--dg-text-muted)]">{t('user.profile.currentPwd')}</Label>
<Input type="password" value={oldPwd} onChange={e => setOldPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
</div>
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">新密码</Label>
<Label className="text-xs text-[var(--dg-text-muted)]">{t('user.profile.newPwd')}</Label>
<Input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
</div>
<Button size="sm" onClick={handleChangePassword} disabled={pwdSaving || !oldPwd || newPwd.length < 6}>
{pwdSaving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
保存密码
{t('user.profile.savePwd')}
</Button>
</CardContent>
</Card>