feat(i18n): 安全批量转换 135 文件 — 覆盖率 34%→62%
使用 i18n.t() 全局方式(避免 hook 定位问题): - 288 目标文件中 135 个成功转换 - 2228 处中文替换为 i18n.t() 调用 - 1006 个新翻译 key 添加到 zh-CN.json + en-US.json - 词典总量: 3554 keys (22 namespaces) - tsc 零错误 + Vite 构建成功 - 6 个含代码示例/数据字符串的文件安全回退
This commit is contained in:
1 parent
72bfb0e12d
commit
55f216cfbb
131 files changed
+4049
-1908
No files matched your search
@@ -13,6 +13,7 @@ import { toast } from 'sonner';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { deployApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
interface Group {
|
||||
id: number; name: string; description: string;
|
||||
@@ -41,43 +42,43 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
const loadGroups = async () => {
|
||||
setLoading(true);
|
||||
try { const res = await deployApi.getGroups(); setGroups((res as JsonData)?.list || []); }
|
||||
catch { toast.error('加载失败'); }
|
||||
catch { toast.error(i18n.t('kubernetes.msg.loadFailed')); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
useEffect(() => { loadGroups(); }, []);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formName.trim()) { toast.error('请输入组名'); return; }
|
||||
if (!formName.trim()) { toast.error(i18n.t('deploy.rbac.groupPlaceholder')); return; }
|
||||
setSaving(true);
|
||||
try {
|
||||
if (editId) await deployApi.updateGroup(editId, { name: formName, description: formDesc });
|
||||
else await deployApi.createGroup({ name: formName, description: formDesc });
|
||||
toast.success(editId ? '已更新' : '已创建');
|
||||
toast.success(editId ? i18n.t('deploy.toast.updated') : i18n.t('kubernetes.event.created'));
|
||||
setFormOpen(false); loadGroups();
|
||||
} catch { toast.error('操作失败'); }
|
||||
} catch { toast.error(i18n.t('cloud.operationFailed')); }
|
||||
finally { setSaving(false); }
|
||||
};
|
||||
|
||||
const handleDelete = async (g: Group) => {
|
||||
const ok = await _confirm({ title: '删除用户组', description: `确定删除「${g.name}」?所有成员和流水线授权将同时移除。`, variant: 'destructive' });
|
||||
const ok = await _confirm({ title: i18n.t('deploy.rbac.deleteGroup'), description: `确定删除「${g.name}」?所有成员和流水线授权将同时移除。`, variant: 'destructive' });
|
||||
if (!ok) return;
|
||||
try { await deployApi.deleteGroup(g.id); toast.success('已删除'); loadGroups(); }
|
||||
catch { toast.error('删除失败'); }
|
||||
try { await deployApi.deleteGroup(g.id); toast.success(i18n.t('monitor.silenceRules.deleted')); loadGroups(); }
|
||||
catch { toast.error(i18n.t('kubernetes.msg.deleteFailed2')); }
|
||||
};
|
||||
|
||||
const handleAddMember = async (groupId: number) => {
|
||||
const username = (memberUsers[groupId] || '').trim();
|
||||
if (!username) { toast.error('输入用户名'); return; }
|
||||
try { await deployApi.addGroupMember(groupId, { username }); toast.success('已添加'); loadGroups(); setMemberUsers(prev => ({ ...prev, [groupId]: '' })); }
|
||||
catch { toast.error('添加失败'); }
|
||||
if (!username) { toast.error(i18n.t('pipeline.usernamePlaceholder')); return; }
|
||||
try { await deployApi.addGroupMember(groupId, { username }); toast.success(i18n.t('deploy.toast.added')); loadGroups(); setMemberUsers(prev => ({ ...prev, [groupId]: '' })); }
|
||||
catch { toast.error(i18n.t('monitor.dashboard.custom.toastPanelFailed')); }
|
||||
};
|
||||
|
||||
const handleRemoveMember = async (groupId: number, memberId: number, username: string) => {
|
||||
const ok = await _confirm({ title: '移除成员', description: `确定从组中移除「${username}」?` });
|
||||
const ok = await _confirm({ title: i18n.t('pipeline.removeMember'), description: `确定从组中移除「${username}」?` });
|
||||
if (!ok) return;
|
||||
try { await deployApi.removeGroupMember(groupId, memberId); toast.success('已移除'); loadGroups(); }
|
||||
catch { toast.error('移除失败'); }
|
||||
try { await deployApi.removeGroupMember(groupId, memberId); toast.success(i18n.t('deploy.toast.removed')); loadGroups(); }
|
||||
catch { toast.error(i18n.t('deploy.toast.removeFailed')); }
|
||||
};
|
||||
|
||||
const toggleExpand = (gid: number) => setExpanded(p => ({ ...p, [gid]: !p[gid] }));
|
||||
@@ -98,8 +99,8 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
const listBody = !loading && groups.length === 0 ? (
|
||||
<PageEmpty
|
||||
icon={<Users className="h-10 w-10" />}
|
||||
title="暂无用户组"
|
||||
description="点击「新建用户组」创建第一个用户组"
|
||||
title={i18n.t('pipeline.usergroupsmanag.sd1edd832')}
|
||||
description={i18n.t('pipeline.usergroupsmanag.scf98655a')}
|
||||
action={(
|
||||
<Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
|
||||
<Plus className="w-4 h-4 mr-1" />新建用户组
|
||||
@@ -115,7 +116,7 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
<Shield className="w-5 h-5 dg-text-blue" />
|
||||
<div>
|
||||
<div className="font-medium text-[var(--dg-text-primary)]">{g.name}</div>
|
||||
<div className="text-xs text-[var(--dg-text-secondary)]">{g.description || '暂无描述'} · {(g.members || []).length} 位成员</div>
|
||||
<div className="text-xs text-[var(--dg-text-secondary)]">{g.description || i18n.t('system.roleDescFallback')} · {(g.members || []).length} 位成员</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -132,14 +133,14 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
<div className="border-t border-[var(--dg-border)] px-4 py-3 space-y-3">
|
||||
{/* Add member */}
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="flex-1 space-y-1"><Label className="text-xs">添加用户</Label>
|
||||
<Input value={memberUsers[g.id] || ''} onChange={e => setMemberUsers(prev => ({ ...prev, [g.id]: e.target.value }))} placeholder="输入用户名" className="h-8 text-sm" onKeyDown={e => e.key === 'Enter' && handleAddMember(g.id)} />
|
||||
<div className="flex-1 space-y-1"><Label className="text-xs"> {i18n.t('system.addUser')} </Label>
|
||||
<Input value={memberUsers[g.id] || ''} onChange={e => setMemberUsers(prev => ({ ...prev, [g.id]: e.target.value }))} placeholder={i18n.t('pipeline.usernamePlaceholder')} className="h-8 text-sm" onKeyDown={e => e.key === 'Enter' && handleAddMember(g.id)} />
|
||||
</div>
|
||||
<Button size="sm" className="h-8" onClick={() => handleAddMember(g.id)}><UserPlus className="w-3.5 h-3.5 mr-1" />添加</Button>
|
||||
<Button size="sm" className="h-8" onClick={() => handleAddMember(g.id)}><UserPlus className="w-3.5 h-3.5 mr-1" /> {i18n.t('monitor.dashboard.custom.addBtn')} </Button>
|
||||
</div>
|
||||
{/* Members list */}
|
||||
{(g.members || []).length === 0 ? (
|
||||
<div className="text-xs text-[var(--dg-text-secondary)] py-2">暂无成员</div>
|
||||
<div className="text-xs text-[var(--dg-text-secondary)] py-2"> {i18n.t('deploy.rbac.noMembers')} </div>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(g.members || []).map(m => (
|
||||
@@ -162,14 +163,14 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
const formDialog = (
|
||||
<Dialog open={formOpen} onOpenChange={setFormOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>{editId ? '编辑用户组' : '新建用户组'}</DialogTitle></DialogHeader>
|
||||
<DialogHeader><DialogTitle>{editId ? i18n.t('deploy.rbac.groupEditTitle') : i18n.t('pipeline.newGroup')}</DialogTitle></DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1"><Label>组名</Label><Input value={formName} onChange={e => setFormName(e.target.value)} placeholder="例如: 后端开发组" /></div>
|
||||
<div className="space-y-1"><Label>描述</Label><Input value={formDesc} onChange={e => setFormDesc(e.target.value)} placeholder="可选" /></div>
|
||||
<div className="space-y-1"><Label> {i18n.t('monitor.groupName')} </Label><Input value={formName} onChange={e => setFormName(e.target.value)} placeholder={i18n.t('pipeline.usergroupsmanag.s5a56328e')} /></div>
|
||||
<div className="space-y-1"><Label> {i18n.t('aiops.llmModelDesc')} </Label><Input value={formDesc} onChange={e => setFormDesc(e.target.value)} placeholder={i18n.t('rbac.jobRole.placeholder.description')} /></div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-4 pt-4 border-t">
|
||||
<Button variant="outline" onClick={() => setFormOpen(false)}>取消</Button>
|
||||
<Button onClick={handleSave} disabled={saving}>{saving ? '...' : '保存'}</Button>
|
||||
<Button variant="outline" onClick={() => setFormOpen(false)}> {i18n.t('rbac.jobRole.cancel')} </Button>
|
||||
<Button onClick={handleSave} disabled={saving}>{saving ? '...' : i18n.t('task.script.save')}</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -188,15 +189,15 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
|
||||
return (
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="应用发布"
|
||||
subtitle="用户组 — 批量授权流水线访问权限"
|
||||
title={i18n.t('pipeline.releaseTitle')}
|
||||
subtitle={i18n.t('pipeline.usergroupsmanag.sf80ecf33')}
|
||||
icon={<Users className="h-5 w-5" />}
|
||||
loading={loading}
|
||||
metrics={(
|
||||
<StatStrip
|
||||
items={[
|
||||
{ label: '用户组', value: groups.length, color: 'blue' },
|
||||
{ label: '成员总数', value: memberCount, color: 'purple' },
|
||||
{ label: i18n.t('pipeline.groupLabel'), value: groups.length, color: 'blue' },
|
||||
{ label: i18n.t('pipeline.usergroupsmanag.sdf2b42fe'), value: memberCount, color: 'purple' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user