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 04bedb02c fix(consumer): take the page subtitle, pagination totals and
import toasts through t() (#5238)
04bedb02c is described below
commit 04bedb02c680c0e9f70a342b394744ff63e52497
Author: 0 <[email protected]>
AuthorDate: Fri Oct 9 11:53:03 2026 +0800
fix(consumer): take the page subtitle, pagination totals and import toasts
through t() (#5238)
Six user-facing strings on the consumer page were hardcoded Chinese while
the rest of the page goes
through the translation table: the header subtitle, the pagination total,
and the four import result
toasts. They now use `consumer.pageSubtitle`, `consumer.totalGroups`,
`consumer.importDone`,
`consumer.importDoneSkipped`, `consumer.importDoneFailed` and
`consumer.importFailed`, with zh and en
entries; the interpolated zh text is unchanged.
Maintainer edit on top of the contribution: added the assertions these keys
were missing. The
existing subtitle case now also pins the pagination footer, and three new
cases drive the import
dialog through to the backend call in English and assert the all-created,
skipped-invalid-rows and
all-failed toasts. Each of the four assertions was checked against a
mutation - reverting a key to
its hardcoded literal turns its case red. The partially-failed toast was
already asserted by the
existing import case.
---
web/src/i18n/translations.ts | 23 ++++-
.../pages/instance/__tests__/ConsumerPage.test.tsx | 109 +++++++++++++++++++++
web/src/pages/instance/consumer.tsx | 16 +--
3 files changed, 141 insertions(+), 7 deletions(-)
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index 8b6d93e41..ad9145450 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -1709,7 +1709,10 @@ const translations: Record<string, Record<Lang, string>>
= {
zh: '会话已恢复到进行中',
en: 'Conversation moved back to active',
},
- 'ai.list.archiveFailed': { zh: '归档操作失败,请稍后重试', en: 'Archive action failed,
please retry later' },
+ 'ai.list.archiveFailed': {
+ zh: '归档操作失败,请稍后重试',
+ en: 'Archive action failed, please retry later',
+ },
'ai.list.deleteConfirm': { zh: '删除这条会话?', en: 'Delete this conversation?' },
'ai.list.deleteSelected': { zh: '删除 ({count})', en: 'Delete ({count})' },
'ai.list.deleteSelectedConfirm': {
@@ -2235,6 +2238,24 @@ const translations: Record<string, Record<Lang, string>>
= {
'topicCompare.export': { zh: '导出结果', en: 'Export results' },
// ─── Consumer Page ───
+ 'consumer.pageSubtitle': {
+ zh: '管理消费者组订阅关系与消费进度,共 {count} 个 Group',
+ en: 'Manage consumer-group subscriptions and progress, {count} groups in
total',
+ },
+ 'consumer.totalGroups': { zh: '共 {count} 个 Group', en: '{count} groups in
total' },
+ 'consumer.importDone': { zh: '已导入 {created} 个 Group', en: 'Imported
{created} groups' },
+ 'consumer.importDoneSkipped': {
+ zh: '已导入 {created} 个 Group,{invalid} 行无效已跳过',
+ en: 'Imported {created} groups; {invalid} invalid rows were skipped',
+ },
+ 'consumer.importDoneFailed': {
+ zh: '已导入 {created} 个 Group,{failed} 个失败',
+ en: 'Imported {created} groups; {failed} failed',
+ },
+ 'consumer.importFailed': {
+ zh: '{failed} 个 Group 导入失败',
+ en: 'Failed to import {failed} groups',
+ },
'consumer.name': { zh: 'Group 名称', en: 'Group Name' },
'consumer.subType': { zh: '订阅组类型', en: 'Sub Type' },
'consumer.subMode': { zh: '订阅模式', en: 'Sub Mode' },
diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index c8b28faa6..0c325a116 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -298,6 +298,20 @@ describe('Consumer page', () => {
]);
});
+ it('renders the page subtitle and pagination totals in the display
language', async () => {
+ window.localStorage.setItem('rocketmq-studio-language', 'en');
+ renderWithProviders(<ConsumerPage />);
+
+ expect(await screen.findByText('remote-cg')).toBeInTheDocument();
+ expect(
+ screen.getByText(/Manage consumer-group subscriptions and progress, \d+
groups in total/),
+ ).toBeInTheDocument();
+ expect(screen.queryByText(/共 \d+ 个 Group/)).not.toBeInTheDocument();
+ // Anchored: the subtitle above contains the same phrase, the pagination
footer is the node
+ // whose whole text is the total.
+ expect(screen.getByText(/^\d+ groups in total$/)).toBeInTheDocument();
+ });
+
it('loads consumer groups through the service layer', async () => {
renderWithProviders(<ConsumerPage />);
@@ -1510,6 +1524,101 @@ describe('Consumer page', () => {
);
});
+ type GroupImportResult = Awaited<ReturnType<typeof
consumerService.importConsumerGroups>>;
+
+ const IMPORT_HEADER =
+ '"Name","Subscription Mode","Consume Type","Retry Max Times","Subscription
Data Type","Delivery Order Type"';
+
+ /**
+ * Runs the CSV import through to the backend batch call in English, so the
toast assertions on
+ * the caller side can only pass if the text really comes from the
translation table - a
+ * hardcoded zh literal would not match. The dialog's own chrome is still zh
(not localized).
+ */
+ const driveGroupImport = async (rows: string[], result: GroupImportResult)
=> {
+ window.localStorage.setItem('rocketmq-studio-language', 'en');
+
vi.mocked(consumerService.listConsumerGroupPage).mockResolvedValue(groupPage([]));
+ vi.mocked(consumerService.importConsumerGroups).mockResolvedValue(result);
+ instanceServiceMocks.listInstances.mockResolvedValue([
+ {
+ id: 4,
+ name: 'instance-proxy-1',
+ remark: '',
+ type: 'PROXY_CLUSTER',
+ endpoint: '10.0.2.21:8080',
+ topicCount: 0,
+ consumerGroupCount: 0,
+ gmtCreate: '2026-01-01T00:00:00Z',
+ gmtModified: '2026-01-01T00:00:00Z',
+ },
+ ]);
+ const user = userEvent.setup({ pointerEventsCheck: 0 });
+ renderWithProviders(<ConsumerPage />,
'/instance/instance-proxy-1/consumer');
+ await waitFor(() =>
+ expect(consumerService.listConsumerGroupPage).toHaveBeenCalledWith(
+ expect.objectContaining({ instanceId: 'instance-proxy-1' }),
+ ),
+ );
+ await user.upload(
+ screen.getByTestId('consumer-group-import-file'),
+ new File([[IMPORT_HEADER, ...rows].join('\n')], 'groups.csv'),
+ );
+ await user.click(await screen.findByRole('button', { name: '开始导入' }));
+ await waitFor(() =>
expect(consumerService.importConsumerGroups).toHaveBeenCalledTimes(1));
+ };
+
+ it('reports a fully created import in the display language', async () => {
+ await driveGroupImport(['"cg-ok","Push","CLUSTERING","16","NORMAL",""'], {
+ imported: 1,
+ failed: 0,
+ groups: [{ ...group, name: 'cg-ok' }],
+ failures: [],
+ });
+
+ expect(await screen.findByText('Imported 1 groups')).toBeInTheDocument();
+ });
+
+ it('reports the skipped invalid rows of an import in the display language',
async () => {
+ await driveGroupImport(
+ [
+ '"cg-ok","Push","CLUSTERING","16","NORMAL",""',
+ '"cg-bad","Push","NOT_A_TYPE","16","NORMAL",""',
+ ],
+ {
+ imported: 1,
+ failed: 0,
+ groups: [{ ...group, name: 'cg-ok' }],
+ failures: [],
+ },
+ );
+
+ expect(
+ await screen.findByText('Imported 1 groups; 1 invalid rows were
skipped'),
+ ).toBeInTheDocument();
+
expect(consumerService.importConsumerGroups).toHaveBeenCalledWith('instance-proxy-1',
[
+ expect.objectContaining({ name: 'cg-ok' }),
+ ]);
+ });
+
+ it('reports an import where every row failed in the display language', async
() => {
+ await driveGroupImport(
+ [
+ '"cg-a","Push","CLUSTERING","16","NORMAL",""',
+ '"cg-b","Push","CLUSTERING","16","NORMAL",""',
+ ],
+ {
+ imported: 0,
+ failed: 2,
+ groups: [],
+ failures: [
+ { index: 0, name: 'cg-a', message: 'broker rejected group' },
+ { index: 1, name: 'cg-b', message: 'broker rejected group' },
+ ],
+ },
+ );
+
+ expect(await screen.findByText('Failed to import 2
groups')).toBeInTheDocument();
+ });
+
it('shows a spinner while the instance list is still resolving', async () =>
{
let resolveInstances!: (value: never[]) => void;
instanceServiceMocks.listInstances.mockReturnValue(
diff --git a/web/src/pages/instance/consumer.tsx
b/web/src/pages/instance/consumer.tsx
index 5559fd486..790e459a4 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -843,14 +843,18 @@ const ConsumerPageContent = ({
const invalidCount = nextRows.filter((row) => row.status ===
'invalid').length;
if (failedCount === 0) {
if (invalidCount > 0) {
- message.warning(`已导入 ${createdGroups.length} 个 Group,${invalidCount}
行无效已跳过`);
+ message.warning(
+ t('consumer.importDoneSkipped', { created: createdGroups.length,
invalid: invalidCount }),
+ );
} else {
- message.success(`已导入 ${createdGroups.length} 个 Group`);
+ message.success(t('consumer.importDone', { created:
createdGroups.length }));
}
} else if (createdGroups.length > 0) {
- message.warning(`已导入 ${createdGroups.length} 个 Group,${failedCount}
个失败`);
+ message.warning(
+ t('consumer.importDoneFailed', { created: createdGroups.length,
failed: failedCount }),
+ );
} else {
- message.error(`${failedCount} 个 Group 导入失败`);
+ message.error(t('consumer.importFailed', { failed: failedCount }));
}
};
@@ -1431,7 +1435,7 @@ const ConsumerPageContent = ({
{/* ─── Header ─── */}
<PageHeader
title={t('group.title')}
- subtitle={`管理消费者组订阅关系与消费进度,共 ${totalGroups} 个 Group`}
+ subtitle={t('consumer.pageSubtitle', { count: totalGroups })}
/>
{/* ─── Filter Bar ─── */}
@@ -1574,7 +1578,7 @@ const ConsumerPageContent = ({
pageSize,
total: totalGroups,
showSizeChanger: true,
- showTotal: (total) => `共 ${total} 个 Group`,
+ showTotal: (total) => t('consumer.totalGroups', { count: total }),
pageSizeOptions: [10, 20, 50, 100],
onChange: (nextPage, nextPageSize) => {
setSelectedRowKeys([]);