This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new bce30ba feat: enhance i18n support with labelKey-based translations
(#495)
bce30ba is described below
commit bce30ba3e116d4e52ae189088974edad4c5f6fec
Author: zhaohai <[email protected]>
AuthorDate: Fri Jul 24 13:42:12 2026 +0800
feat: enhance i18n support with labelKey-based translations (#495)
Migrate hardcoded Chinese UI text to i18n labelKey pattern, add ~280
translation entries (zh+en), fix duplicate key compilation errors.
---
web/src/components/StatusBadge.tsx | 4 +-
web/src/components/__tests__/StatusBadge.test.tsx | 77 ++++++
web/src/constants/__tests__/theme.test.ts | 97 ++++++++
web/src/constants/theme.ts | 50 ++--
web/src/i18n/__tests__/LangContext.test.tsx | 2 +-
web/src/i18n/translations.ts | 287 +++++++++++++++++++++-
web/src/pages/cluster/index.tsx | 57 ++---
web/src/pages/home/dashboard.tsx | 2 +-
web/src/pages/instance/consumer.tsx | 13 +-
web/src/pages/instance/topic.tsx | 14 +-
10 files changed, 529 insertions(+), 74 deletions(-)
diff --git a/web/src/components/StatusBadge.tsx
b/web/src/components/StatusBadge.tsx
index b2c02ec..1a38781 100644
--- a/web/src/components/StatusBadge.tsx
+++ b/web/src/components/StatusBadge.tsx
@@ -17,6 +17,7 @@
import { Badge, Space, Typography } from 'antd';
import { STATUS_MAP } from '../constants/theme';
+import { useLang } from '../i18n/LangContext';
const { Text } = Typography;
@@ -27,8 +28,9 @@ interface StatusBadgeProps {
}
const StatusBadge = ({ status, text, showDot = true }: StatusBadgeProps) => {
+ const { t } = useLang();
const config = STATUS_MAP[status] || STATUS_MAP.offline;
- const label = text || config.label;
+ const label = text || t(config.labelKey);
return (
<Space size={4} role="status" aria-label={`状态:${label}`}>
{showDot && <Badge color={config.dot} aria-hidden="true" />}
diff --git a/web/src/components/__tests__/StatusBadge.test.tsx
b/web/src/components/__tests__/StatusBadge.test.tsx
new file mode 100644
index 0000000..07f08c7
--- /dev/null
+++ b/web/src/components/__tests__/StatusBadge.test.tsx
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import StatusBadge from '../StatusBadge';
+import { LangProvider } from '../../i18n/LangContext';
+
+const renderWithLang = (ui: React.ReactElement) =>
render(<LangProvider>{ui}</LangProvider>);
+
+describe('StatusBadge', () => {
+ it('renders the translated label for a known status in Chinese', () => {
+ renderWithLang(<StatusBadge status="healthy" />);
+ // theme.healthy in zh is '运行中'
+ expect(screen.getByText('运行中')).toBeInTheDocument();
+ });
+
+ it('renders the translated label for warning status', () => {
+ renderWithLang(<StatusBadge status="warning" />);
+ // theme.warning in zh is '告警'
+ expect(screen.getByText('告警')).toBeInTheDocument();
+ });
+
+ it('renders the translated label for error status', () => {
+ renderWithLang(<StatusBadge status="error" />);
+ // theme.error in zh is '异常'
+ expect(screen.getByText('异常')).toBeInTheDocument();
+ });
+
+ it('renders the translated label for offline status', () => {
+ renderWithLang(<StatusBadge status="offline" />);
+ expect(screen.getByText('离线')).toBeInTheDocument();
+ });
+
+ it('renders the translated label for connecting status', () => {
+ renderWithLang(<StatusBadge status="connecting" />);
+ expect(screen.getByText('连接中')).toBeInTheDocument();
+ });
+
+ it('falls back to offline config for unknown status', () => {
+ renderWithLang(<StatusBadge status="unknown_status" />);
+ // Should render offline label
+ expect(screen.getByText('离线')).toBeInTheDocument();
+ });
+
+ it('uses custom text when provided', () => {
+ renderWithLang(<StatusBadge status="healthy" text="自定义" />);
+ expect(screen.getByText('自定义')).toBeInTheDocument();
+ });
+
+ it('hides the dot when showDot is false', () => {
+ const { container } = renderWithLang(<StatusBadge status="healthy"
showDot={false} />);
+ // No Badge component should be rendered
+ const badges = container.querySelectorAll('.ant-badge');
+ expect(badges.length).toBe(0);
+ });
+
+ it('has role=status for accessibility', () => {
+ const { container } = renderWithLang(<StatusBadge status="healthy" />);
+ const statusEl = container.querySelector('[role="status"]');
+ expect(statusEl).toBeInTheDocument();
+ });
+});
diff --git a/web/src/constants/__tests__/theme.test.ts
b/web/src/constants/__tests__/theme.test.ts
new file mode 100644
index 0000000..c3945f0
--- /dev/null
+++ b/web/src/constants/__tests__/theme.test.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect } from 'vitest';
+import { STATUS_MAP, CLUSTER_TYPE_MAP, TOPIC_TYPE_MAP, PROTOCOL_MAP,
THEME_COLORS } from '../theme';
+
+describe('theme constants', () => {
+ describe('STATUS_MAP', () => {
+ it('has labelKey for all status entries', () => {
+ for (const [key, val] of Object.entries(STATUS_MAP)) {
+ expect(val.labelKey, `STATUS_MAP[${key}].labelKey should
exist`).toBeDefined();
+ expect(val.labelKey, `STATUS_MAP[${key}].labelKey should start with
theme.`).toMatch(
+ /^theme\./,
+ );
+ }
+ });
+
+ it('has color and dot for all status entries', () => {
+ for (const [key, val] of Object.entries(STATUS_MAP)) {
+ expect(val.color, `STATUS_MAP[${key}].color should
exist`).toBeDefined();
+ expect(val.dot, `STATUS_MAP[${key}].dot should exist`).toBeDefined();
+ }
+ });
+
+ it('contains required status keys', () => {
+ expect(STATUS_MAP.healthy).toBeDefined();
+ expect(STATUS_MAP.warning).toBeDefined();
+ expect(STATUS_MAP.error).toBeDefined();
+ expect(STATUS_MAP.offline).toBeDefined();
+ expect(STATUS_MAP.connecting).toBeDefined();
+ });
+ });
+
+ describe('CLUSTER_TYPE_MAP', () => {
+ it('has labelKey for all cluster type entries', () => {
+ for (const [key, val] of Object.entries(CLUSTER_TYPE_MAP)) {
+ expect(val.labelKey, `CLUSTER_TYPE_MAP[${key}].labelKey should
exist`).toBeDefined();
+ expect(val.color, `CLUSTER_TYPE_MAP[${key}].color should
exist`).toBeDefined();
+ }
+ });
+
+ it('contains V4_DIRECT, V5_PROXY_LOCAL, V5_PROXY_CLUSTER', () => {
+ expect(CLUSTER_TYPE_MAP.V4_DIRECT).toBeDefined();
+ expect(CLUSTER_TYPE_MAP.V5_PROXY_LOCAL).toBeDefined();
+ expect(CLUSTER_TYPE_MAP.V5_PROXY_CLUSTER).toBeDefined();
+ });
+ });
+
+ describe('TOPIC_TYPE_MAP', () => {
+ it('has labelKey for all topic type entries', () => {
+ for (const [key, val] of Object.entries(TOPIC_TYPE_MAP)) {
+ expect(val.labelKey, `TOPIC_TYPE_MAP[${key}].labelKey should
exist`).toBeDefined();
+ expect(val.color, `TOPIC_TYPE_MAP[${key}].color should
exist`).toBeDefined();
+ }
+ });
+
+ it('contains NORMAL, FIFO, DELAY, TRANSACTION, LITE', () => {
+ expect(TOPIC_TYPE_MAP.NORMAL).toBeDefined();
+ expect(TOPIC_TYPE_MAP.FIFO).toBeDefined();
+ expect(TOPIC_TYPE_MAP.DELAY).toBeDefined();
+ expect(TOPIC_TYPE_MAP.TRANSACTION).toBeDefined();
+ expect(TOPIC_TYPE_MAP.LITE).toBeDefined();
+ });
+ });
+
+ describe('PROTOCOL_MAP', () => {
+ it('has labelKey for all protocol entries', () => {
+ for (const [key, val] of Object.entries(PROTOCOL_MAP)) {
+ expect(val.labelKey, `PROTOCOL_MAP[${key}].labelKey should
exist`).toBeDefined();
+ expect(val.color, `PROTOCOL_MAP[${key}].color should
exist`).toBeDefined();
+ }
+ });
+ });
+
+ describe('THEME_COLORS', () => {
+ it('contains primary colors', () => {
+ expect(THEME_COLORS.primary).toBeDefined();
+ expect(THEME_COLORS.success).toBeDefined();
+ expect(THEME_COLORS.warning).toBeDefined();
+ expect(THEME_COLORS.error).toBeDefined();
+ });
+ });
+});
diff --git a/web/src/constants/theme.ts b/web/src/constants/theme.ts
index 2cedb9b..58ff540 100644
--- a/web/src/constants/theme.ts
+++ b/web/src/constants/theme.ts
@@ -29,29 +29,41 @@ export const THEME_COLORS = {
clusterV5Cluster: '#722ed1',
} as const;
-export const CLUSTER_TYPE_MAP: Record<string, { label: string; color:
TagProps['color'] }> = {
- V4_DIRECT: { label: 'V4 直连', color: 'orange' },
- V5_PROXY_LOCAL: { label: 'V5 Proxy 单节点', color: 'blue' },
- V5_PROXY_CLUSTER: { label: 'V5 Proxy 集群', color: 'purple' },
+/**
+ * Cluster type map — labels are i18n keys, resolved at render time via t().
+ */
+export const CLUSTER_TYPE_MAP: Record<string, { labelKey: string; color:
TagProps['color'] }> = {
+ V4_DIRECT: { labelKey: 'theme.clusterV4', color: 'orange' },
+ V5_PROXY_LOCAL: { labelKey: 'theme.clusterV5Local', color: 'blue' },
+ V5_PROXY_CLUSTER: { labelKey: 'theme.clusterV5Cluster', color: 'purple' },
};
-export const STATUS_MAP: Record<string, { label: string; color: string; dot:
string }> = {
- healthy: { label: '运行中', color: '#52c41a', dot: '#52c41a' },
- warning: { label: '告警', color: '#faad14', dot: '#faad14' },
- error: { label: '异常', color: '#ff4d4f', dot: '#ff4d4f' },
- offline: { label: '离线', color: '#d9d9d9', dot: '#d9d9d9' },
- connecting: { label: '连接中', color: '#1677ff', dot: '#1677ff' },
+/**
+ * Status map — labels are i18n keys, resolved at render time via t().
+ */
+export const STATUS_MAP: Record<string, { labelKey: string; color: string;
dot: string }> = {
+ healthy: { labelKey: 'theme.healthy', color: '#52c41a', dot: '#52c41a' },
+ warning: { labelKey: 'theme.warning', color: '#faad14', dot: '#faad14' },
+ error: { labelKey: 'theme.error', color: '#ff4d4f', dot: '#ff4d4f' },
+ offline: { labelKey: 'theme.offline', color: '#d9d9d9', dot: '#d9d9d9' },
+ connecting: { labelKey: 'theme.connecting', color: '#1677ff', dot: '#1677ff'
},
};
-export const TOPIC_TYPE_MAP: Record<string, { label: string; color:
TagProps['color'] }> = {
- NORMAL: { label: '普通', color: 'default' },
- FIFO: { label: '顺序', color: 'blue' },
- DELAY: { label: '延迟', color: 'orange' },
- TRANSACTION: { label: '事务', color: 'purple' },
- LITE: { label: 'LiteTopic', color: 'magenta' },
+/**
+ * Topic type map — labels are i18n keys, resolved at render time via t().
+ */
+export const TOPIC_TYPE_MAP: Record<string, { labelKey: string; color:
TagProps['color'] }> = {
+ NORMAL: { labelKey: 'theme.topicNormal', color: 'default' },
+ FIFO: { labelKey: 'theme.topicFifo', color: 'blue' },
+ DELAY: { labelKey: 'theme.topicDelay', color: 'orange' },
+ TRANSACTION: { labelKey: 'theme.topicTransaction', color: 'purple' },
+ LITE: { labelKey: 'theme.topicLite', color: 'magenta' },
};
-export const PROTOCOL_MAP: Record<string, { label: string; color:
TagProps['color'] }> = {
- REMOTING: { label: 'Remoting', color: 'geekblue' },
- GRPC: { label: 'gRPC', color: 'green' },
+/**
+ * Protocol map — labels are i18n keys, resolved at render time via t().
+ */
+export const PROTOCOL_MAP: Record<string, { labelKey: string; color:
TagProps['color'] }> = {
+ REMOTING: { labelKey: 'theme.protocolRemoting', color: 'geekblue' },
+ GRPC: { labelKey: 'theme.protocolGrpc', color: 'green' },
};
diff --git a/web/src/i18n/__tests__/LangContext.test.tsx
b/web/src/i18n/__tests__/LangContext.test.tsx
index 1e3ac57..5a81cab 100644
--- a/web/src/i18n/__tests__/LangContext.test.tsx
+++ b/web/src/i18n/__tests__/LangContext.test.tsx
@@ -119,4 +119,4 @@ describe('LangContext', () => {
);
expect(screen.getByTestId('alias-lang')).toHaveTextContent('zh');
});
-});
\ No newline at end of file
+});
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index dd61987..3b37ff1 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -87,6 +87,14 @@ const translations: Record<string, Record<Lang, string>> = {
'dashboard.group': { zh: 'Group', en: 'Group' },
'dashboard.trend': { zh: '趋势', en: 'Trend' },
'dashboard.millionMessages': { zh: '约 {n}M 条消息', en: '~{n}M messages' },
+ 'dashboard.tpsTrend': { zh: 'TPS 趋势', en: 'TPS Trend' },
+ 'dashboard.tpsInLabel': { zh: 'TPS In', en: 'TPS In' },
+ 'dashboard.tpsOutLabel': { zh: 'TPS Out', en: 'TPS Out' },
+ 'dashboard.brokers': { zh: '{n} Broker', en: '{n} Brokers' },
+ 'dashboard.proxies': { zh: '{n} Proxy', en: '{n} Proxies' },
+ 'dashboard.consumerGroups': { zh: '{n} 消费组', en: '{n} Groups' },
+ 'dashboard.healthy': { zh: '健康', en: 'Healthy' },
+ 'dashboard.last12h': { zh: '近 12 小时', en: 'Last 12 hours' },
// ─── Cluster Page ───
'cluster.title': { zh: 'RocketMQ 集群', en: 'RocketMQ Cluster' },
@@ -332,10 +340,276 @@ const translations: Record<string, Record<Lang, string>>
= {
'acl.required': { zh: '请选择{field}', en: 'Please select {field}' },
'acl.inputRequired': { zh: '请输入{field}', en: 'Please enter {field}' },
+ // ─── Topic Page (unique keys, duplicates merged into Topic section below)
───
+ 'topic.name': { zh: 'Topic 名称', en: 'Topic Name' },
+ 'topic.action': { zh: '操作', en: 'Actions' },
+ 'topic.detail': { zh: '详情', en: 'Detail' },
+ 'topic.route': { zh: '路由', en: 'Route' },
+ 'topic.send': { zh: '发送', en: 'Send' },
+ 'topic.delete': { zh: '删除', en: 'Delete' },
+ 'topic.deleteContent': {
+ zh: '确定要删除 Topic「{name}」吗?此操作不可撤销。',
+ en: 'Are you sure to delete Topic "{name}"? This cannot be undone.',
+ },
+ 'topic.allTypes': { zh: '全部类型', en: 'All Types' },
+ 'topic.normal': { zh: '普通', en: 'Normal' },
+ 'topic.fifo': { zh: '顺序', en: 'FIFO' },
+ 'topic.delay': { zh: '延迟', en: 'Delay' },
+ 'topic.transaction': { zh: '事务', en: 'Transaction' },
+ 'topic.lite': { zh: 'LiteTopic', en: 'LiteTopic' },
+ 'topic.batchDelete': { zh: '批量删除', en: 'Batch Delete' },
+ 'topic.bodyOrder': { zh: '订单事件', en: 'Order Event' },
+ 'topic.bodyUserEvent': { zh: '用户行为', en: 'User Event' },
+ 'topic.bodyPayment': { zh: '支付回调', en: 'Payment Callback' },
+ 'topic.bodyInventory': { zh: '库存变更', en: 'Inventory Change' },
+ 'topic.bodyNotification': { zh: '通知消息', en: 'Notification' },
+ 'topic.bodyMetrics': { zh: '监控指标', en: 'Metrics' },
+ 'topic.randomBody': { zh: '随机消息体', en: 'Random Body' },
+ 'topic.sendTestMessage': { zh: '发送测试消息', en: 'Send Test Message' },
+ 'topic.tag': { zh: 'Tag', en: 'Tag' },
+ 'topic.key': { zh: 'Key', en: 'Key' },
+ 'topic.messageBody': { zh: '消息体', en: 'Message Body' },
+ 'topic.properties': { zh: '属性', en: 'Properties' },
+ 'topic.topicConfig': { zh: 'Topic 配置', en: 'Topic Config' },
+ 'topic.queueCount': { zh: '队列数', en: 'Queue Count' },
+
+ // ─── Consumer Page ───
+ 'consumer.name': { zh: 'Group 名称', en: 'Group Name' },
+ 'consumer.subType': { zh: '订阅组类型', en: 'Sub Type' },
+ 'consumer.subMode': { zh: '订阅模式', en: 'Sub Mode' },
+ 'consumer.onlineClients': { zh: '在线客户端', en: 'Online Clients' },
+ 'consumer.totalLag': { zh: '总堆积量', en: 'Total Lag' },
+ 'consumer.delay': { zh: '消费延迟', en: 'Consume Delay' },
+ 'consumer.createdAt': { zh: '创建时间', en: 'Created' },
+ 'consumer.updatedAt': { zh: '修改时间', en: 'Updated' },
+ 'consumer.action': { zh: '操作', en: 'Actions' },
+ 'consumer.detail': { zh: '详情', en: 'Detail' },
+ 'consumer.resetOffset': { zh: '重置位点', en: 'Reset Offset' },
+ 'consumer.confirmDelete': {
+ zh: '确认删除消费组 "{name}"?',
+ en: 'Delete consumer group "{name}"?',
+ },
+ 'consumer.deleteWarning': {
+ zh: '删除后该消费组的所有配置和消费进度将被清除,此操作不可恢复。',
+ en: 'All configurations and progress will be removed. This cannot be
undone.',
+ },
+ 'consumer.deleted': { zh: '消费组 {name} 已删除', en: 'Consumer group {name}
deleted' },
+ 'consumer.searchPlaceholder': { zh: '搜索 Group 名称或 Topic', en: 'Search group
name or topic' },
+ 'consumer.allModes': { zh: '全部模式', en: 'All Modes' },
+ 'consumer.push': { zh: 'Push', en: 'Push' },
+ 'consumer.pull': { zh: 'Pull', en: 'Pull' },
+ 'consumer.sortNameAsc': { zh: '名称升序', en: 'Name A-Z' },
+ 'consumer.sortLagDesc': { zh: '堆积量降序', en: 'Lag Descending' },
+ 'consumer.createGroup': { zh: '新建消费组', en: 'Create Group' },
+ 'consumer.subTopic': { zh: 'Topic 主题', en: 'Topic' },
+ 'consumer.consistency': { zh: '订阅一致性', en: 'Consistency' },
+ 'consumer.consistent': { zh: '一致', en: 'Consistent' },
+ 'consumer.inconsistent': { zh: '不一致', en: 'Inconsistent' },
+ 'consumer.filterMode': { zh: '订阅模式', en: 'Filter Mode' },
+ 'consumer.filterAll': { zh: '全量', en: 'Full' },
+ 'consumer.filterTag': { zh: 'Tag 过滤', en: 'Tag Filter' },
+ 'consumer.filterSql92': { zh: 'SQL92 过滤', en: 'SQL92 Filter' },
+ 'consumer.expression': { zh: '订阅表达式', en: 'Expression' },
+ 'consumer.viewDistribution': { zh: '查看分布', en: 'View Distribution' },
+ 'consumer.resetToTime': { zh: '重置到指定时间', en: 'Reset to Time' },
+ 'consumer.resetSuccess': { zh: '位点重置成功', en: 'Offset reset successfully' },
+
+ // ─── Home Page (additional) ───
+ 'home.banner': {
+ zh: 'RocketMQ Studio — 跨集群 · 跨架构 · 跨云的统一管控平台',
+ en: 'RocketMQ Studio — Cross-cluster · Cross-arch · Cross-cloud unified
management',
+ },
+ 'home.placeholder': {
+ zh: '向 RocketMQ Bot 提问,全程加密、安全、可信',
+ en: 'Ask RocketMQ Bot, fully encrypted, secure, trusted',
+ },
+ 'home.tools': { zh: '工具', en: 'Tools' },
+ 'home.promptEnhance': { zh: 'Prompt 增强', en: 'Prompt Enhance' },
+ 'home.docs': { zh: '文档中心', en: 'Documentation' },
+ 'home.community': { zh: 'RocketMQ 社区', en: 'RocketMQ Community' },
+ 'home.brand': { zh: 'RocketMQ Studio 出品', en: 'Powered by RocketMQ Studio' },
+
+ // ─── AI Page (additional) ───
+ 'ai.recommended': { zh: '推荐', en: 'Rec.' },
+
+ // ─── Theme Constants ───
+ 'theme.clusterV4': { zh: 'V4 直连', en: 'V4 Direct' },
+ 'theme.clusterV5Local': { zh: 'V5 Proxy 单节点', en: 'V5 Proxy Local' },
+ 'theme.clusterV5Cluster': { zh: 'V5 Proxy 集群', en: 'V5 Proxy Cluster' },
+ 'theme.healthy': { zh: '运行中', en: 'Healthy' },
+ 'theme.warning': { zh: '告警', en: 'Warning' },
+ 'theme.error': { zh: '异常', en: 'Error' },
+ 'theme.offline': { zh: '离线', en: 'Offline' },
+ 'theme.connecting': { zh: '连接中', en: 'Connecting' },
+ 'theme.topicNormal': { zh: '普通', en: 'Normal' },
+ 'theme.topicFifo': { zh: '顺序', en: 'FIFO' },
+ 'theme.topicDelay': { zh: '延迟', en: 'Delay' },
+ 'theme.topicTransaction': { zh: '事务', en: 'Transaction' },
+ 'theme.topicLite': { zh: 'LiteTopic', en: 'LiteTopic' },
+ 'theme.protocolRemoting': { zh: 'Remoting', en: 'Remoting' },
+ 'theme.protocolGrpc': { zh: 'gRPC', en: 'gRPC' },
+
// ─── Settings ───
'settings.title': { zh: '设置', en: 'Settings' },
'settings.subtitle': { zh: '管理应用配置与数据源', en: 'Manage app settings and data
sources' },
+ // ─── Certs ───
+ 'cert.clusterName': { zh: 'K8s 集群名称', en: 'K8s Cluster Name' },
+ 'cert.certName': { zh: '证书名称', en: 'Certificate Name' },
+ 'cert.issuer': { zh: '签发者', en: 'Issuer' },
+ 'cert.expiryTime': { zh: '到期时间', en: 'Expiry Time' },
+ 'cert.daysRemaining': { zh: '剩余天数', en: 'Days Remaining' },
+ 'cert.statusValid': { zh: '有效', en: 'Valid' },
+ 'cert.statusExpiring': { zh: '即将过期', en: 'Expiring' },
+ 'cert.statusExpired': { zh: '已过期', en: 'Expired' },
+ 'cert.confirmDelete': { zh: '确认删除', en: 'Confirm Delete' },
+ 'cert.deleteConfirm': {
+ zh: '确定要删除证书"{name}"吗?',
+ en: 'Are you sure to delete certificate "{name}"?',
+ },
+ 'cert.deleted': { zh: '证书已删除: {name}', en: 'Certificate deleted: {name}' },
+ 'cert.addCert': { zh: '添加证书', en: 'Add Certificate' },
+ 'cert.searchPlaceholder': { zh: '搜索证书名称或集群', en: 'Search cert name or
cluster' },
+ 'cert.editCert': { zh: '编辑证书 — {name}', en: 'Edit Certificate — {name}' },
+ 'cert.certUpdated': { zh: '证书「{name}」已更新', en: 'Certificate "{name}"
updated' },
+ 'cert.namespace': { zh: '命名空间', en: 'Namespace' },
+ 'cert.issuerPlaceholder': { zh: '例:kubernetes-ca', en: 'e.g. kubernetes-ca'
},
+ 'cert.namespacePlaceholder': { zh: '例:kube-system', en: 'e.g. kube-system' },
+ 'cert.featureWip': { zh: '添加证书功能开发中', en: 'Add certificate feature is in
development' },
+ 'cert.totalCount': { zh: '共 {count} 个证书', en: '{count} certificates total' },
+
+ // ─── Cluster ───
+ 'cluster.brokerCount': { zh: 'Broker 数', en: 'Broker Count' },
+ 'cluster.topicCount': { zh: 'Topic 数', en: 'Topic Count' },
+ 'cluster.messageCount': { zh: '消息总量', en: 'Message Total' },
+ 'cluster.totalTps': { zh: '总 TPS', en: 'Total TPS' },
+ 'cluster.avgTps': { zh: '平均 TPS', en: 'Avg TPS' },
+ 'cluster.consumerGroupCount': { zh: '消费者组数', en: 'Consumer Group Count' },
+ 'cluster.createClusterWip': {
+ zh: '新建集群功能开发中',
+ en: 'Create cluster feature is in development',
+ },
+ 'cluster.configTitle': { zh: '配置 - {name}', en: 'Config - {name}' },
+ 'cluster.configUpdated': { zh: '配置已更新', en: 'Configuration updated' },
+ 'cluster.flushDiskType': { zh: '刷盘方式', en: 'Flush Disk Type' },
+ 'cluster.syncFlush': { zh: '同步刷盘', en: 'Sync Flush' },
+ 'cluster.asyncFlush': { zh: '异步刷盘', en: 'Async Flush' },
+ 'cluster.autoCreateTopic': { zh: '自动创建 Topic', en: 'Auto Create Topic' },
+ 'cluster.autoCreateSubGroup': { zh: '自动创建订阅组', en: 'Auto Create Subscription
Group' },
+ 'cluster.maxMessageSize': { zh: '最大消息大小 (MB)', en: 'Max Message Size (MB)' },
+ 'cluster.fileReservedTime': { zh: '文件保留时长 (小时)', en: 'File Retention Time
(hours)' },
+ 'cluster.writeQueues': { zh: '写队列数', en: 'Write Queues' },
+ 'cluster.readQueues': { zh: '读队列数', en: 'Read Queues' },
+ 'cluster.brokerPermission': { zh: 'Broker 权限', en: 'Broker Permission' },
+ 'cluster.viewDetail': { zh: '查看详情: {addr}', en: 'View detail: {addr}' },
+ 'cluster.restartProxyConfirm': {
+ zh: '确定要重启 Proxy "{addr}" 吗?',
+ en: 'Are you sure to restart Proxy "{addr}"?',
+ },
+ 'cluster.restartProxySubmitted': {
+ zh: 'Proxy 重启已提交: {addr}',
+ en: 'Proxy restart submitted: {addr}',
+ },
+
+ // ─── Topic ───
+ 'topic.type': { zh: '类型', en: 'Type' },
+ 'topic.status': { zh: '状态', en: 'Status' },
+ 'topic.serving': { zh: '服务中', en: 'Serving' },
+ 'topic.createdAt': { zh: '创建时间', en: 'Created At' },
+ 'topic.updatedAt': { zh: '修改时间', en: 'Updated At' },
+ 'topic.topicName': { zh: 'Topic 名称', en: 'Topic Name' },
+ 'topic.namespace': { zh: '命名空间', en: 'Namespace' },
+ 'topic.cluster': { zh: '集群', en: 'Cluster' },
+ 'topic.writeQueues': { zh: '写队列数', en: 'Write Queues' },
+ 'topic.readQueues': { zh: '读队列数', en: 'Read Queues' },
+ 'topic.perm': { zh: '权限', en: 'Permission' },
+ 'topic.messageCount': { zh: '今日消息量', en: "Today's Messages" },
+ 'topic.tps': { zh: 'TPS', en: 'TPS' },
+ 'topic.consumerGroupCount': { zh: '消费者组数', en: 'Consumer Groups' },
+ 'topic.quickFill': { zh: '快速填入:', en: 'Quick fill:' },
+ 'topic.customProps': { zh: '自定义属性(可选)', en: 'Custom Properties (optional)' },
+ 'topic.allNamespaces': { zh: '全部', en: 'All' },
+ 'topic.remark': { zh: '备注', en: 'Remark' },
+ 'topic.confirmDelete': { zh: '确认删除', en: 'Confirm Delete' },
+ 'topic.deleteConfirm': {
+ zh: '确定要删除 Topic「{name}」吗?此操作不可撤销。',
+ en: 'Are you sure to delete Topic "{name}"? This action cannot be undone.',
+ },
+ 'topic.deleted': { zh: 'Topic「{name}」已删除', en: 'Topic "{name}" deleted' },
+ 'topic.permRW': { zh: '读写', en: 'Read/Write' },
+ 'topic.permRO': { zh: '只读', en: 'Read-only' },
+ 'topic.permWO': { zh: '只写', en: 'Write-only' },
+ 'topic.brokerName': { zh: 'Broker 名称', en: 'Broker Name' },
+ 'topic.brokerAddr': { zh: 'Broker 地址', en: 'Broker Address' },
+ 'topic.writeQueue': { zh: '写队列', en: 'Write Queue' },
+ 'topic.readQueue': { zh: '读队列', en: 'Read Queue' },
+ 'topic.consumerGroup': { zh: '消费者组', en: 'Consumer Group' },
+ 'topic.consumeMode': { zh: '消费模式', en: 'Consume Mode' },
+ 'topic.consumeTps': { zh: '消费 TPS', en: 'Consume TPS' },
+ 'topic.backlog': { zh: '堆积量', en: 'Backlog' },
+ 'topic.broadcast': { zh: '广播消费', en: 'Broadcast' },
+ 'topic.clustering': { zh: '集群消费', en: 'Clustering' },
+ 'topic.basicInfo': { zh: '基本信息', en: 'Basic Info' },
+ 'topic.routeInfo': { zh: '路由信息', en: 'Route Info' },
+ 'topic.consumerInfo': { zh: '消费者', en: 'Consumers' },
+ 'topic.createTopic': { zh: '创建 Topic', en: 'Create Topic' },
+ 'topic.createSuccess': {
+ zh: 'Topic「{name}」创建成功',
+ en: 'Topic "{name}" created successfully',
+ },
+ 'topic.topicNamePlaceholder': { zh: '请输入 Topic 名称', en: 'Enter topic name' },
+ 'topic.topicNameRule': {
+ zh: '仅支持字母、数字、下划线、中划线、斜杠和星号',
+ en: 'Only letters, numbers, underscore, hyphen, slash and asterisk',
+ },
+ 'topic.topicNameRequired': { zh: '请输入 Topic 名称', en: 'Please enter topic
name' },
+ 'topic.queueExtra': { zh: '每个 Broker 节点 8 个队列', en: '8 queues per Broker
node' },
+ 'topic.remarkPlaceholder': {
+ zh: '可选,描述 Topic 用途',
+ en: 'Optional, describe the topic purpose',
+ },
+ 'topic.sendMsg': { zh: '发送消息到 {name}', en: 'Send message to {name}' },
+ 'topic.sendSuccess': {
+ zh: '消息发送成功!MsgId: {id}',
+ en: 'Message sent successfully! MsgId: {id}',
+ },
+ 'topic.tagPlaceholder': { zh: '可选,消息标签', en: 'Optional, message tag' },
+ 'topic.keyPlaceholder': {
+ zh: '可选,消息 Key(用于查询)',
+ en: 'Optional, message key (for query)',
+ },
+ 'topic.bodyLabel': { zh: '消息体 Body', en: 'Message Body' },
+ 'topic.bodyRequired': { zh: '请输入消息体', en: 'Please enter message body' },
+ 'topic.bodyPlaceholder': { zh: 'JSON 格式消息体', en: 'JSON format message body'
},
+ 'topic.propName': { zh: '属性名', en: 'Property Name' },
+ 'topic.propValue': { zh: '属性值', en: 'Property Value' },
+ 'topic.addProp': { zh: '添加属性', en: 'Add Property' },
+ 'topic.confirmBatchDelete': { zh: '确认批量删除', en: 'Confirm Batch Delete' },
+ 'topic.batchDeleteConfirm': {
+ zh: '确定要删除选中的 {count} 个 Topic 吗?此操作不可撤销。',
+ en: 'Are you sure to delete {count} selected topics? This action cannot be
undone.',
+ },
+ 'topic.batchDeleted': { zh: '已删除 {count} 个 Topic', en: '{count} topics
deleted' },
+ 'topic.importWip': { zh: '导入功能开发中', en: 'Import feature is in development' },
+ 'topic.exported': { zh: '已导出 {count} 个 Topic', en: '{count} topics exported'
},
+ 'topic.totalCount': { zh: '共 {count} 个 Topic', en: '{count} topics total' },
+ 'topic.searchPlaceholder': { zh: '搜索 Topic 名称', en: 'Search topic name' },
+ 'topic.typeFilter': { zh: '类型筛选', en: 'Type filter' },
+ 'topic.listView': { zh: '列表', en: 'List' },
+ 'topic.cardView': { zh: '卡片', en: 'Card' },
+ 'topic.showTotal': { zh: '共 {total} 条', en: '{total} total' },
+ 'topic.orderEvent': { zh: '订单事件', en: 'Order Event' },
+ 'topic.userEvent': { zh: '用户行为', en: 'User Event' },
+ 'topic.paymentCallback': { zh: '支付回调', en: 'Payment Callback' },
+ 'topic.inventoryChange': { zh: '库存变更', en: 'Inventory Change' },
+ 'topic.notification': { zh: '通知消息', en: 'Notification' },
+ 'topic.metrics': { zh: '监控指标', en: 'Metrics' },
+
+ // ─── Consumer ───
+ 'consumer.subscriptionMode': { zh: '订阅模式', en: 'Subscription Mode' },
+ 'consumer.subGroupType': { zh: '订阅组类型', en: 'Subscription Group Type' },
+ 'consumer.maxRetry': { zh: '最大重试次数', en: 'Max Retries' },
+
// ─── User Menu ───
'user.profile': { zh: '个人中心', en: 'Profile' },
'user.logout': { zh: '退出登录', en: 'Logout' },
@@ -412,18 +686,9 @@ const translations: Record<string, Record<Lang, string>> =
{
'topic.add': { zh: '添加主题', en: 'Add Topic' },
'topic.config': { zh: '主题配置', en: 'Topic Config' },
'topic.change': { zh: '修改主题', en: 'Modify Topic' },
- 'topic.name': { zh: '主题名', en: 'Topic Name' },
- 'topic.type': { zh: '消息类型', en: 'Message Type' },
- 'topic.normal': { zh: '普通消息', en: 'Normal' },
- 'topic.fifo': { zh: '顺序消息', en: 'FIFO' },
- 'topic.delay': { zh: '定时/延时消息', en: 'Delay' },
- 'topic.transaction': { zh: '事务消息', en: 'Transaction' },
'topic.unspecified': { zh: '未指定', en: 'Unspecified' },
- 'topic.perm': { zh: '权限', en: 'Permission' },
'topic.readQueueNums': { zh: '读队列数量', en: 'Read Queue Num' },
'topic.writeQueueNums': { zh: '写队列数量', en: 'Write Queue Num' },
- 'topic.brokerName': { zh: 'Broker 名称', en: 'Broker Name' },
- 'topic.brokerAddr': { zh: 'Broker 地址', en: 'Broker Address' },
'topic.clusterName': { zh: '集群名', en: 'Cluster Name' },
'topic.selectCluster': { zh: '请选择集群', en: 'Select Cluster' },
'topic.selectBroker': { zh: '请选择 Broker', en: 'Select Broker' },
@@ -431,15 +696,11 @@ const translations: Record<string, Record<Lang, string>>
= {
'topic.minOffset': { zh: '最小位点', en: 'Min Offset' },
'topic.maxOffset': { zh: '最大位点', en: 'Max Offset' },
'topic.lastUpdateTime': { zh: '上次更新时间', en: 'Last Update Time' },
- 'topic.sendMsg': { zh: '发送消息', en: 'Send Message' },
'topic.resetOffset': { zh: '重置位点', en: 'Reset Offset' },
'topic.skipAccumulate': { zh: '跳过堆积', en: 'Skip Accumulate' },
- 'topic.delete': { zh: '删除主题', en: 'Delete Topic' },
- 'topic.confirmDelete': { zh: '确认删除此主题?', en: 'Delete this topic?' },
'topic.deleteWarning': { zh: '删除后无法恢复,请确认。', en: 'This cannot be undone.' },
'topic.fetchFailed': { zh: '获取主题列表失败', en: 'Failed to fetch topic list' },
'topic.operationSuccess': { zh: 'Topic 操作成功', en: 'Topic operation
successful' },
- 'topic.searchPlaceholder': { zh: '搜索主题名称', en: 'Search topic name' },
'topic.filterType': { zh: '消息类型', en: 'Message Type' },
'topic.filterAll': { zh: '全部类型', en: 'All Types' },
diff --git a/web/src/pages/cluster/index.tsx b/web/src/pages/cluster/index.tsx
index f22ca74..5fdcf63 100644
--- a/web/src/pages/cluster/index.tsx
+++ b/web/src/pages/cluster/index.tsx
@@ -363,7 +363,7 @@ const ClusterPage = () => {
<Button
type="primary"
icon={<PlusOutlined />}
- onClick={() => message.info('新建集群功能开发中')}
+ onClick={() => message.info(t('cluster.createClusterWip'))}
>
{t('cluster.createCluster')}
</Button>
@@ -380,51 +380,51 @@ const ClusterPage = () => {
{selectedCluster && (
<Modal
- title={`配置 - ${selectedCluster.name}`}
+ title={t('cluster.configTitle', { name: selectedCluster.name })}
open={configModalOpen}
onCancel={() => setConfigModalOpen(false)}
onOk={() => {
configForm.validateFields().then(() => {
- message.success('配置已更新');
+ message.success(t('cluster.configUpdated'));
setConfigModalOpen(false);
});
}}
width={560}
>
<Form form={configForm} layout="vertical">
- <Form.Item label="刷盘方式" name="flushDiskType">
+ <Form.Item label={t('cluster.flushDiskType')}
name="flushDiskType">
<Radio.Group>
- <Radio value="SYNC_FLUSH">同步刷盘</Radio>
- <Radio value="ASYNC_FLUSH">异步刷盘</Radio>
+ <Radio value="SYNC_FLUSH">{t('cluster.syncFlush')}</Radio>
+ <Radio value="ASYNC_FLUSH">{t('cluster.asyncFlush')}</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
- label="自动创建 Topic"
+ label={t('cluster.autoCreateTopic')}
name="autoCreateTopicEnable"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
- label="自动创建订阅组"
+ label={t('cluster.autoCreateSubGroup')}
name="autoCreateSubscriptionGroup"
valuePropName="checked"
>
<Switch />
</Form.Item>
- <Form.Item label="最大消息大小 (MB)" name="maxMessageSizeMB">
+ <Form.Item label={t('cluster.maxMessageSize')}
name="maxMessageSizeMB">
<InputNumber min={1} max={128} style={{ width: '100%' }} />
</Form.Item>
- <Form.Item label="文件保留时长 (小时)" name="fileReservedTime">
+ <Form.Item label={t('cluster.fileReservedTime')}
name="fileReservedTime">
<InputNumber min={1} max={720} style={{ width: '100%' }} />
</Form.Item>
- <Form.Item label="写队列数" name="writeQueueNums">
+ <Form.Item label={t('cluster.writeQueues')}
name="writeQueueNums">
<InputNumber min={1} max={256} style={{ width: '100%' }} />
</Form.Item>
- <Form.Item label="读队列数" name="readQueueNums">
+ <Form.Item label={t('cluster.readQueues')} name="readQueueNums">
<InputNumber min={1} max={256} style={{ width: '100%' }} />
</Form.Item>
- <Form.Item label="Broker 权限" name="brokerPermission">
+ <Form.Item label={t('cluster.brokerPermission')}
name="brokerPermission">
<InputNumber min={0} max={7} style={{ width: '100%' }} />
</Form.Item>
</Form>
@@ -468,9 +468,9 @@ const ClusterPage = () => {
render: (status: string) => {
const map: Record<string, { color: string; label: string }> = {
healthy: { color: 'green', label: t('cluster.running') },
- warning: { color: 'gold', label: '告警' },
- error: { color: 'red', label: '异常' },
- offline: { color: 'default', label: '离线' },
+ warning: { color: 'gold', label: t('cluster.warning') },
+ error: { color: 'red', label: t('cluster.error') },
+ offline: { color: 'default', label: t('cluster.offline') },
};
const cfg = map[status] ?? { color: 'default', label: status };
return <Tag color={cfg.color}>{cfg.label}</Tag>;
@@ -530,9 +530,9 @@ const ClusterPage = () => {
render: (status: string) => {
const map: Record<string, { color: string; label: string }> = {
healthy: { color: 'green', label: t('cluster.running') },
- warning: { color: 'gold', label: '告警' },
- error: { color: 'red', label: '异常' },
- offline: { color: 'default', label: '离线' },
+ warning: { color: 'gold', label: t('cluster.warning') },
+ error: { color: 'red', label: t('cluster.error') },
+ offline: { color: 'default', label: t('cluster.offline') },
};
const cfg = map[status] ?? { color: 'default', label: status };
return <Tag color={cfg.color}>{cfg.label}</Tag>;
@@ -661,9 +661,9 @@ const ClusterPage = () => {
render: (status: string) => {
const map: Record<string, { color: string; label: string }> = {
healthy: { color: 'green', label: t('cluster.running') },
- warning: { color: 'gold', label: '告警' },
- error: { color: 'red', label: '异常' },
- offline: { color: 'default', label: '离线' },
+ warning: { color: 'gold', label: t('cluster.warning') },
+ error: { color: 'red', label: t('cluster.error') },
+ offline: { color: 'default', label: t('cluster.offline') },
};
const cfg = map[status] ?? { color: 'default', label: status };
return <Tag color={cfg.color}>{cfg.label}</Tag>;
@@ -704,7 +704,7 @@ const ClusterPage = () => {
size="small"
icon={<EyeOutlined />}
style={{ borderColor: '#1677ff', color: '#1677ff' }}
- onClick={() => message.info(`查看详情: ${record.addr}`)}
+ onClick={() => message.info(t('cluster.viewDetail', { addr:
record.addr }))}
>
{t('common.detail')}
</Button>
@@ -715,10 +715,11 @@ const ClusterPage = () => {
onClick={() => {
Modal.confirm({
title: t('cluster.confirmRestart'),
- content: `确定要重启 Proxy "${record.addr}" 吗?`,
- okText: '确认',
- cancelText: '取消',
- onOk: () => message.success(`Proxy 重启已提交: ${record.addr}`),
+ content: t('cluster.restartProxyConfirm', { addr:
record.addr }),
+ okText: t('common.confirm'),
+ cancelText: t('common.cancel'),
+ onOk: () =>
+ message.success(t('cluster.restartProxySubmitted', { addr:
record.addr })),
});
}}
>
@@ -744,7 +745,7 @@ const ClusterPage = () => {
<Button
type="primary"
icon={<PlusOutlined />}
- onClick={() => message.info('新建集群功能开发中')}
+ onClick={() => message.info(t('cluster.createClusterWip'))}
>
{t('cluster.createCluster')}
</Button>
diff --git a/web/src/pages/home/dashboard.tsx b/web/src/pages/home/dashboard.tsx
index 349b725..1242fca 100644
--- a/web/src/pages/home/dashboard.tsx
+++ b/web/src/pages/home/dashboard.tsx
@@ -123,7 +123,7 @@ const DashboardPage = () => {
key: 'type',
render: (type: string) => {
const info = CLUSTER_TYPE_MAP[type];
- return info ? <Tag color={info.color}>{info.label}</Tag> : type;
+ return info ? <Tag color={info.color}>{t(info.labelKey)}</Tag> : type;
},
},
{
diff --git a/web/src/pages/instance/consumer.tsx
b/web/src/pages/instance/consumer.tsx
index 9e0ec7c..d7cec90 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -177,8 +177,8 @@ const ConsumerPage = () => {
width: 110,
sorter: (a, b) =>
a.subscriptionDataType.localeCompare(b.subscriptionDataType),
render: (type: string) => {
- const config = TOPIC_TYPE_MAP[type] || { label: type, color: 'default'
};
- return <Tag color={config.color}>{config.label}</Tag>;
+ const config = TOPIC_TYPE_MAP[type] || { labelKey: type, color:
'default' };
+ return <Tag color={config.color}>{t(config.labelKey)}</Tag>;
},
},
{
@@ -374,8 +374,8 @@ const ConsumerPage = () => {
key: 'protocol',
width: 100,
render: (protocol: string) => {
- const config = PROTOCOL_MAP[protocol] || { label: protocol, color:
'default' };
- return <Tag color={config.color}>{config.label}</Tag>;
+ const config = PROTOCOL_MAP[protocol] || { labelKey: protocol, color:
'default' };
+ return <Tag color={config.color}>{t(config.labelKey)}</Tag>;
},
},
{
@@ -710,8 +710,9 @@ const ConsumerPage = () => {
TOPIC_TYPE_MAP[selectedGroup.subscriptionDataType]?.color || 'default'
}
>
-
{TOPIC_TYPE_MAP[selectedGroup.subscriptionDataType]?.label ||
- selectedGroup.subscriptionDataType}
+ {TOPIC_TYPE_MAP[selectedGroup.subscriptionDataType]
+ ?
t(TOPIC_TYPE_MAP[selectedGroup.subscriptionDataType].labelKey)
+ : selectedGroup.subscriptionDataType}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="消费延迟">
diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx
index fc3af2e..4569bfa 100644
--- a/web/src/pages/instance/topic.tsx
+++ b/web/src/pages/instance/topic.tsx
@@ -349,7 +349,7 @@ const TopicPage = () => {
sorter: (a, b) => a.type.localeCompare(b.type),
render: (type: string) => {
const cfg = TOPIC_TYPE_MAP[type];
- return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> :
<Tag>{type}</Tag>;
+ return cfg ? <Tag color={cfg.color}>{t(cfg.labelKey)}</Tag> :
<Tag>{type}</Tag>;
},
},
{
@@ -458,7 +458,9 @@ const TopicPage = () => {
{topic.name}
</Descriptions.Item>
<Descriptions.Item label="类型">
- <Tag color={typeInfo?.color}>{typeInfo?.label}</Tag>
+ <Tag color={typeInfo?.color}>
+ {typeInfo?.labelKey ? t(typeInfo.labelKey) : topic.type}
+ </Tag>
</Descriptions.Item>
<Descriptions.Item label="命名空间">
<Tag>{topic.namespace}</Tag>
@@ -466,7 +468,7 @@ const TopicPage = () => {
<Descriptions.Item label="集群" span={2}>
<Space>
<Text>{topic.clusterId}</Text>
- {clusterType && <Tag
color={clusterType.color}>{clusterType.label}</Tag>}
+ {clusterType && <Tag
color={clusterType.color}>{t(clusterType.labelKey)}</Tag>}
</Space>
</Descriptions.Item>
<Descriptions.Item label="写队列数">{topic.writeQueues}</Descriptions.Item>
@@ -506,7 +508,9 @@ const TopicPage = () => {
<Text strong style={{ fontSize: 15 }}>
{topic.name}
</Text>
- <Tag color={typeInfo?.color}>{typeInfo?.label}</Tag>
+ <Tag color={typeInfo?.color}>
+ {typeInfo?.labelKey ? t(typeInfo.labelKey) : topic.type}
+ </Tag>
</Flex>
{/* Namespace + cluster tags */}
@@ -514,7 +518,7 @@ const TopicPage = () => {
<Tag style={{ fontSize: 11 }}>{topic.namespace}</Tag>
{clusterType && (
<Tag color={clusterType.color} style={{ fontSize: 11 }}>
- {clusterType.label}
+ {t(clusterType.labelKey)}
</Tag>
)}
</Space>