feat(frontend): 控制台 next-intl 中英双语

接入无 URL 前缀的 zh-CN/en,顶栏与外观设置可切换语言;选择写入 cookie 后刷新生效。
This commit is contained in:
ryan
2026-08-16 16:49:16 +08:00
parent 64fbaa7ef1
commit 5a8722ff07
219 changed files with 21437 additions and 5212 deletions
@@ -11,6 +11,11 @@ export const FLARED_WS_CONNECTED_LAST_SEEN =
export type StatusTone = 'success' | 'warning' | 'danger' | 'info';
export type NodeMessageT = (
key: string,
values?: Record<string, string | number | Date>,
) => string;
export function isWSConnectedLastSeen(value: string | null | undefined) {
return (
value === WS_CONNECTED_LAST_SEEN || value === FLARED_WS_CONNECTED_LAST_SEEN
@@ -27,7 +32,7 @@ export function isMeaningfulTime(
);
}
export function formatRelativeTime(value: string) {
export function formatRelativeTime(value: string, t: NodeMessageT) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
@@ -35,16 +40,16 @@ export function formatRelativeTime(value: string) {
const diffMs = Date.now() - date.getTime();
const diffMinutes = Math.floor(diffMs / 60_000);
if (diffMinutes < 1) return '刚刚';
if (diffMinutes < 60) return `${diffMinutes} 分钟前`;
if (diffMinutes < 1) return t('relative.justNow');
if (diffMinutes < 60) return t('relative.minutesAgo', { count: diffMinutes });
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) return `${diffHours} 小时前`;
if (diffHours < 24) return t('relative.hoursAgo', { count: diffHours });
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 30) return `${diffDays} 天前`;
if (diffDays < 30) return t('relative.daysAgo', { count: diffDays });
return `${Math.floor(diffDays / 30)} 个月前`;
return t('relative.monthsAgo', { count: Math.floor(diffDays / 30) });
}
export function getNodeStatusTone(status: NodeStatus): StatusTone {
@@ -53,10 +58,10 @@ export function getNodeStatusTone(status: NodeStatus): StatusTone {
return 'danger';
}
export function getNodeStatusLabel(status: NodeStatus) {
if (status === 'online') return '在线';
if (status === 'pending') return '待接入';
return '离线';
export function getNodeStatusLabel(status: NodeStatus, t: NodeMessageT) {
if (status === 'online') return t('status.online');
if (status === 'pending') return t('status.pending');
return t('status.offline');
}
export function getApplyTone(result: ApplyResult): StatusTone {
@@ -66,11 +71,11 @@ export function getApplyTone(result: ApplyResult): StatusTone {
return 'warning';
}
export function getApplyLabel(result: ApplyResult) {
if (result === 'success') return '成功';
if (result === 'warning') return '警告';
if (result === 'failed') return '失败';
return '暂无';
export function getApplyLabel(result: ApplyResult, t: NodeMessageT) {
if (result === 'success') return t('apply.success');
if (result === 'warning') return t('apply.warning');
if (result === 'failed') return t('apply.failed');
return t('apply.none');
}
export function getOpenrestyStatusTone(status: OpenrestyStatus): StatusTone {
@@ -79,10 +84,13 @@ export function getOpenrestyStatusTone(status: OpenrestyStatus): StatusTone {
return 'warning';
}
export function getOpenrestyStatusLabel(status: OpenrestyStatus) {
if (status === 'healthy') return '健康';
if (status === 'unhealthy') return '异常';
return '未知';
export function getOpenrestyStatusLabel(
status: OpenrestyStatus,
t: NodeMessageT,
) {
if (status === 'healthy') return t('health.healthy');
if (status === 'unhealthy') return t('health.unhealthy');
return t('health.unknown');
}
export function getRelayStatusTone(
@@ -93,10 +101,13 @@ export function getRelayStatusTone(
return 'warning';
}
export function getRelayStatusLabel(status: string | null | undefined) {
if (status === 'healthy') return '健康';
if (status === 'unhealthy') return '异常';
return '未知';
export function getRelayStatusLabel(
status: string | null | undefined,
t: NodeMessageT,
) {
if (status === 'healthy') return t('health.healthy');
if (status === 'unhealthy') return t('health.unhealthy');
return t('health.unknown');
}
export function getNodeTypeLabel(nodeType: NodeItem['node_type']) {
@@ -105,8 +116,8 @@ export function getNodeTypeLabel(nodeType: NodeItem['node_type']) {
return 'Edge';
}
export function getErrorMessage(error: unknown) {
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
export function getErrorMessage(error: unknown, fallback: string) {
return error instanceof Error ? error.message : fallback;
}
export function getServerUrl(value: string) {
@@ -264,7 +275,10 @@ export function formatUsageRatio(used?: number | null, total?: number | null) {
return Math.max(0, Math.min(100, (used / total) * 100));
}
export function formatUptime(seconds?: number | null) {
export function formatUptime(
seconds: number | null | undefined,
t: NodeMessageT,
) {
if (!seconds || seconds <= 0) {
return '—';
}
@@ -274,12 +288,12 @@ export function formatUptime(seconds?: number | null) {
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) {
return `${days} 天 ${hours} 小时`;
return t('uptime.daysHours', { days, hours });
}
if (hours > 0) {
return `${hours} 小时 ${minutes} 分钟`;
return t('uptime.hoursMinutes', { hours, minutes });
}
return `${minutes} 分钟`;
return t('uptime.minutes', { minutes });
}
export function getHealthEventTone(event: {
@@ -302,17 +316,17 @@ export function getHealthEventLabel(event: { event_type: string }) {
return event.event_type.replaceAll('_', ' ');
}
export function getFlaredStatusLabel(node: NodeItem) {
export function getFlaredStatusLabel(node: NodeItem, t: NodeMessageT) {
if (isWSConnectedLastSeen(node.last_seen_at)) {
return 'WS 已连接';
return t('status.wsConnected');
}
if (node.status === 'online') {
return '运行中';
return t('status.running');
}
if (node.status === 'pending') {
return '待接入';
return t('status.pending');
}
return '离线';
return t('status.offline');
}
export function getFlaredStatusTone(node: NodeItem): StatusTone {