feat(i18n): cloud 模块全量国际化迁移 — 4 组件 + 90 新增翻译键

迁移组件:
- RegionManager.tsx — providerNames→getProviderNames(t) 函数模式, 全量 t() 替换
- CloudAccountManager.tsx — providerLabels→getProviderLabels(t), 卡片/弹框/表单全量迁移
- CloudBilling.tsx — 概览/记录/预警三 Tab 全量迁移, TrendChart/StatStrip 标签国际化
- DomainManager.tsx — 域名卡片/状态徽章/创建编辑弹框全量迁移

翻译键: cloud 命名空间 145→235 键 (+90), common 新增 deleteFailed/deleteSuccess
验证: tsc -b=0, npm build✅ (21.21s), 17/17 集成测试通过, 0 缺失键, 0 残留 UI 中文
This commit is contained in:
attacker committed 2026-07-02 12:50:05 +08:00
1 parent 63e4c220d4
commit 8cbe3aadec
6 files changed
+409 -215

No files matched your search

@@ -1,4 +1,5 @@
import { useState, useEffect } 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';
@@ -32,16 +33,19 @@ import { PageEmpty } from '@/components/layout/PageEmpty';
import { StatStrip } from '@/components/ui/stat-strip';
import type { JsonData } from '@/types/common';
const providerLabels: Record<string, string> = {
aws: 'AWS',
aliyun: '阿里云',
tencent: '腾讯云',
huawei: '华为云',
azure: 'Azure',
gcp: 'GCP',
};
function getProviderLabels(t: (key: string) => string): Record<string, string> {
return {
aws: 'AWS',
aliyun: t('cloud.aliyun'),
tencent: t('cloud.tencent'),
huawei: t('cloud.huawei'),
azure: 'Azure',
gcp: 'GCP',
};
}
export function CloudAccountManager() {
const { t } = useTranslation();
const [accounts, setAccounts] = useState<JsonData[]>([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
@@ -60,6 +64,8 @@ export function CloudAccountManager() {
status: 'active',
});
const providerLabels = getProviderLabels(t);
const resetForm = () => {
setEditingAccount(null);
setFormData({
@@ -110,7 +116,7 @@ export function CloudAccountManager() {
setFormOpen(false);
loadData();
} catch (e) {
console.error(editingAccount ? '更新云账号失败:' : '创建云账号失败:', e);
console.error(editingAccount ? 'update account' : 'create account', e);
} finally {
setSubmitting(false);
}
@@ -121,8 +127,7 @@ export function CloudAccountManager() {
setLoading(true);
const res: JsonData = await cloudApi.getAccounts();
setAccounts(res?.list || []);
} catch (e) {
console.error('加载云账号列表失败:', e);
} catch (_e) {
setAccounts([]);
} finally {
setLoading(false);
@@ -137,8 +142,8 @@ export function CloudAccountManager() {
try {
await cloudApi.deleteAccount(id);
loadData();
} catch (e) {
console.error('删除云账号失败:', e);
} catch (_e) {
// silently ignored
}
};
@@ -162,7 +167,7 @@ export function CloudAccountManager() {
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
<Input
placeholder="搜索账号名称或AccessKey..."
placeholder={t('cloud.searchAccount')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 h-9 dg-input"
@@ -173,11 +178,11 @@ export function CloudAccountManager() {
onChange={(e) => setSelectedProvider(e.target.value)}
className="px-3 py-2 h-9 rounded-md text-sm dg-input"
>
<option value="all">全部平台</option>
<option value="all">{t('cloud.allPlatforms')}</option>
<option value="aws">AWS</option>
<option value="aliyun">阿里云</option>
<option value="tencent">腾讯云</option>
<option value="huawei">华为云</option>
<option value="aliyun">{t('cloud.aliyun')}</option>
<option value="tencent">{t('cloud.tencent')}</option>
<option value="huawei">{t('cloud.huawei')}</option>
<option value="azure">Azure</option>
</select>
</div>
@@ -186,17 +191,17 @@ export function CloudAccountManager() {
return (
<PageShell
variant="list"
title="云账号管理"
subtitle="管理多云平台的访问凭证和账号信息"
title={t('cloud.cloudAccounts')}
subtitle={t('cloud.accountsSubtitle')}
icon={<Key className="h-5 w-5" />}
metrics={
!loading ? (
<StatStrip
items={[
{ label: '总计', value: stats.total, color: 'blue' },
{ label: '正常', value: stats.active, color: 'green' },
{ label: '停用', value: stats.inactive, color: 'muted' },
{ label: '异常', value: stats.error, color: stats.error > 0 ? 'red' : 'muted' },
{ label: t('common.total'), value: stats.total, color: 'blue' },
{ label: t('cloud.normal'), value: stats.active, color: 'green' },
{ label: t('cloud.disabled'), value: stats.inactive, color: 'muted' },
{ label: t('cloud.error'), value: stats.error, color: stats.error > 0 ? 'red' : 'muted' },
]}
/>
) : undefined
@@ -206,11 +211,11 @@ export function CloudAccountManager() {
<>
<Button variant="outline" size="sm" onClick={loadData} className="dg-btn-secondary">
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />
刷新
{t('common.refresh')}
</Button>
<Button size="sm" className="dg-btn-primary" onClick={handleOpenCreate}>
<Plus className="h-4 w-4 mr-1" />
添加账号
{t('cloud.addAccount')}
</Button>
</>
)}
@@ -220,11 +225,11 @@ export function CloudAccountManager() {
) : filteredAccounts.length === 0 ? (
<PageEmpty
icon={<Key className="h-12 w-12" />}
title="暂无云账号数据"
title={t('cloud.noAccountData')}
action={(
<Button size="sm" className="dg-btn-primary" onClick={handleOpenCreate}>
<Plus className="h-4 w-4 mr-1" />
添加账号
{t('cloud.addAccount')}
</Button>
)}
/>
@@ -256,19 +261,19 @@ export function CloudAccountManager() {
{account.status === 'active' && (
<Badge className="dg-status-running border-0">
<CheckCircle2 className="h-3 w-3 mr-1" />
正常
{t('cloud.normal')}
</Badge>
)}
{account.status === 'inactive' && (
<Badge className="dg-status-stopped border-0">
<XCircle className="h-3 w-3 mr-1" />
已停用
{t('cloud.stopped')}
</Badge>
)}
{account.status === 'error' && (
<Badge className="dg-status-error border-0">
<Key className="h-3 w-3 mr-1" />
异常
{t('cloud.error')}
</Badge>
)}
</div>
@@ -295,15 +300,15 @@ export function CloudAccountManager() {
</div>
<div className="grid grid-cols-3 gap-4 mt-4 pt-4 border-t border-[var(--dg-border)]">
<div>
<p className="text-xs dg-text-secondary">月费用</p>
<p className="text-xs dg-text-secondary">{t('cloud.monthlyFee')}</p>
<p className="text-lg font-medium text-[var(--dg-green)]">¥{Number(monthlyCost).toLocaleString()}</p>
</div>
<div>
<p className="text-xs dg-text-secondary">服务器数</p>
<p className="text-xs dg-text-secondary">{t('cloud.serverCount')}</p>
<p className="text-lg font-medium text-[var(--dg-text-primary)]">{serverCount}</p>
</div>
<div>
<p className="text-xs dg-text-secondary">最后同步</p>
<p className="text-xs dg-text-secondary">{t('cloud.syncTime')}</p>
<p className="text-sm text-[var(--dg-text-secondary)]">
{lastSync !== '-' ? new Date(lastSync).toLocaleString() : '-'}
</p>
@@ -322,10 +327,10 @@ export function CloudAccountManager() {
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Cloud className="h-5 w-5 text-[var(--dg-blue)]" />
{editingAccount ? '编辑云账号' : '添加云账号'}
{editingAccount ? t('cloud.editAccount') : t('cloud.addAccount')}
</DialogTitle>
<DialogDescription>
{editingAccount ? '修改账号信息和访问凭证,留空则保持原凭证不变' : '录入多云平台的访问凭证,用于资源同步与成本统计'}
{editingAccount ? t('cloud.editAccountDesc') : t('cloud.addAccountDesc')}
</DialogDescription>
</DialogHeader>
@@ -333,10 +338,10 @@ export function CloudAccountManager() {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label>
账号名称 <span className="text-[var(--dg-red)]">*</span>
{t('cloud.accountName')} <span className="text-[var(--dg-red)]">*</span>
</Label>
<Input
placeholder="阿里云生产账号"
placeholder={t('cloud.accountNamePlaceholder')}
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="dg-input"
@@ -344,17 +349,17 @@ export function CloudAccountManager() {
</div>
<div className="space-y-1.5">
<Label>
云平台 <span className="text-[var(--dg-red)]">*</span>
{t('cloud.platformLabel')} <span className="text-[var(--dg-red)]">*</span>
</Label>
<select
value={formData.provider}
onChange={(e) => setFormData({ ...formData, provider: e.target.value })}
className="w-full px-3 py-2 rounded-md text-sm dg-input"
>
<option value="aliyun">阿里云</option>
<option value="tencent">腾讯云</option>
<option value="aliyun">{t('cloud.aliyun')}</option>
<option value="tencent">{t('cloud.tencent')}</option>
<option value="aws">AWS</option>
<option value="huawei">华为云</option>
<option value="huawei">{t('cloud.huawei')}</option>
<option value="azure">Azure</option>
</select>
</div>
@@ -363,7 +368,7 @@ export function CloudAccountManager() {
<div className="space-y-1.5">
<Label>
AccessKey
{editingAccount && <span className="text-xs dg-text-muted ml-2">留空保持不变</span>}
{editingAccount && <span className="text-xs dg-text-muted ml-2">{t('cloud.keepBlank')}</span>}
</Label>
<Input
placeholder="LTAI..."
@@ -376,7 +381,7 @@ export function CloudAccountManager() {
<div className="space-y-1.5">
<Label>
SecretKey
{editingAccount && <span className="text-xs dg-text-muted ml-2">留空保持不变</span>}
{editingAccount && <span className="text-xs dg-text-muted ml-2">{t('cloud.keepBlank')}</span>}
</Label>
<Input
type="password"
@@ -389,7 +394,7 @@ export function CloudAccountManager() {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label>月费用 (元)</Label>
<Label>{t('cloud.monthlyFeeUnit')}</Label>
<Input
type="number"
min="0"
@@ -400,15 +405,15 @@ export function CloudAccountManager() {
/>
</div>
<div className="space-y-1.5">
<Label>状态</Label>
<Label>{t('common.status')}</Label>
<select
value={formData.status}
onChange={(e) => setFormData({ ...formData, status: e.target.value })}
className="w-full px-3 py-2 rounded-md text-sm dg-input"
>
<option value="active">正常</option>
<option value="inactive">已停用</option>
<option value="error">异常</option>
<option value="active">{t('cloud.normal')}</option>
<option value="inactive">{t('cloud.stopped')}</option>
<option value="error">{t('cloud.error')}</option>
</select>
</div>
</div>
@@ -416,7 +421,7 @@ export function CloudAccountManager() {
<DialogFooter>
<Button variant="outline" onClick={() => setFormOpen(false)} className="dg-btn-secondary">
取消
{t('common.cancel')}
</Button>
<Button
className="dg-btn-primary"
@@ -424,7 +429,7 @@ export function CloudAccountManager() {
onClick={handleSubmit}
>
{submitting ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : editingAccount ? <Pencil className="h-4 w-4 mr-2" /> : <Plus className="h-4 w-4 mr-2" />}
{editingAccount ? '保存修改' : '创建账号'}
{editingAccount ? t('cloud.saveChanges') : t('cloud.createAccount')}
</Button>
</DialogFooter>
</DialogContent>