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 f3252ab16 fix(web): localize the remaining hardcoded strings in the 
main layout (#4806)
f3252ab16 is described below

commit f3252ab16f7dd7bcc342f8c173631af43c2ca093
Author: Wang1rrr <[email protected]>
AuthorDate: Thu Sep 24 18:17:44 2026 +0800

    fix(web): localize the remaining hardcoded strings in the main layout 
(#4806)
    
    fix(web): localize the remaining hardcoded strings in the main layout
    
    MainLayout pulls every navigation and command label through the language 
context but left
    seven strings as literals, so an English console mixed languages:
    
    - the warning shown when the server-side logout call fails
    - the /studio/users breadcrumb entry and the matching admin user-menu item
    - the "no matching pages" description in the navigation search palette
    - the three keyboard hints under that palette: navigate, open and close
    
    Two of the seven already had dictionary entries carrying the identical 
Chinese value, so
    they are pointed at the existing userMgmt.title and common.close rather 
than given
    duplicate keys. Of the rest, "open" joins the existing common.close as 
common.open, and
    the two palette strings become layout.noMatchingPage and 
layout.shortcutNavigate beside
    the other layout.* entries. Only user.logoutFailed is genuinely new copy.
    
    The language toggle that renders "En" / the Chinese glyph is deliberately 
untouched:
    showing the name of the language being switched to is the point of that 
control.
    
    Adds a case that puts the console in English, fails the logout call and 
asserts the toast
    arrives as the English dictionary value instead of a literal.
---
 web/src/i18n/translations.ts        |  7 +++++++
 web/src/layouts/MainLayout.test.tsx | 29 +++++++++++++++++++++++++++++
 web/src/layouts/MainLayout.tsx      | 16 +++++++++-------
 3 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index bb0261ad1..057924b79 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -68,6 +68,7 @@ const translations: Record<string, Record<Lang, string>> = {
   'common.export': { zh: '导出', en: 'Export' },
   'common.detail': { zh: '详情', en: 'Detail' },
   'common.close': { zh: '关闭', en: 'Close' },
+  'common.open': { zh: '打开', en: 'Open' },
   'common.noData': { zh: '暂无数据', en: 'No Data' },
   'common.autoRefresh': { zh: '自动刷新', en: 'Auto Refresh' },
   'common.liveRefresh': { zh: '实时刷新', en: 'Live Refresh' },
@@ -79,6 +80,8 @@ const translations: Record<string, Record<Lang, string>> = {
 
   // ─── Global layout controls ───
   'layout.skipToMain': { zh: '跳到主要内容', en: 'Skip to main content' },
+  'layout.noMatchingPage': { zh: '未找到匹配页面', en: 'No matching pages' },
+  'layout.shortcutNavigate': { zh: '切换', en: 'Navigate' },
   'layout.goHome': { zh: '返回首页', en: 'Go to home' },
   'layout.openSearch': { zh: '打开导航搜索', en: 'Open navigation search' },
   'layout.switchToRealData': { zh: '切换到真实数据', en: 'Switch to real data' },
@@ -2745,6 +2748,10 @@ const translations: Record<string, Record<Lang, string>> 
= {
   // ─── User Menu ───
   'user.profile': { zh: '个人中心', en: 'Profile' },
   'user.logout': { zh: '退出登录', en: 'Logout' },
+  'user.logoutFailed': {
+    zh: '服务端退出失败,已清除本地登录状态',
+    en: 'Signing out on the server failed. The local session has been 
cleared.',
+  },
 
   // ─── User Management ───
   'userMgmt.title': { zh: '用户管理', en: 'User Management' },
diff --git a/web/src/layouts/MainLayout.test.tsx 
b/web/src/layouts/MainLayout.test.tsx
index 71f42ca3b..8cfb846d9 100644
--- a/web/src/layouts/MainLayout.test.tsx
+++ b/web/src/layouts/MainLayout.test.tsx
@@ -16,10 +16,12 @@
  */
 
 import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { message } from 'antd';
 import { MemoryRouter, Route, Routes } from 'react-router-dom';
 import { beforeEach, describe, expect, it, vi } from 'vitest';
 import { logout } from '../api/auth';
 import { LangProvider } from '../i18n/LangContext';
+import translations from '../i18n/translations';
 import useAuthStore from '../stores/authStore';
 import { ThemeProvider } from '../theme/ThemeProvider';
 import MainLayout from './MainLayout';
@@ -135,6 +137,33 @@ describe('MainLayout authentication navigation', () => {
     expect(localStorage.getItem('token')).toBeNull();
   });
 
+  it('reports a failed server logout in the language the console is in', async 
() => {
+    localStorage.setItem('rocketmq-studio-language', 'en');
+    vi.mocked(message.warning).mockClear();
+    vi.mocked(logout).mockRejectedValueOnce(new Error('server gone'));
+    render(
+      <LangProvider>
+        <ThemeProvider>
+          <MemoryRouter initialEntries={['/instance/topic']}>
+            <Routes>
+              <Route path="/login" element={<div>login page</div>} />
+              <Route path="/" element={<MainLayout />}>
+                <Route path="instance/topic" element={<div>protected 
topic</div>} />
+              </Route>
+            </Routes>
+          </MemoryRouter>
+        </ThemeProvider>
+      </LangProvider>,
+    );
+
+    fireEvent.click(screen.getByRole('button', { name: 
translations['user.logout'].en }));
+    await waitFor(() => expect(screen.getByText('login 
page')).toBeInTheDocument());
+
+    // The toast has to come out of the dictionary in the active language: it 
used to be a
+    // literal, so an English console reported the failed server logout in 
Chinese.
+    
expect(vi.mocked(message.warning)).toHaveBeenCalledWith(translations['user.logoutFailed'].en);
+  });
+
   it('exposes global layout commands as localized semantic buttons', () => {
     render(
       <LangProvider>
diff --git a/web/src/layouts/MainLayout.tsx b/web/src/layouts/MainLayout.tsx
index 0a183d25e..1e6bbd75a 100644
--- a/web/src/layouts/MainLayout.tsx
+++ b/web/src/layouts/MainLayout.tsx
@@ -109,7 +109,7 @@ const MainLayout = () => {
     try {
       await requestLogout();
     } catch {
-      message.warning('服务端退出失败,已清除本地登录状态');
+      message.warning(t('user.logoutFailed'));
     } finally {
       clearAuth();
       navigate('/login', { replace: true });
@@ -271,7 +271,7 @@ const MainLayout = () => {
       '/ops/audit': t('nav.audit'),
       '/ai': t('nav.ai'),
       '/settings': t('nav.settings'),
-      '/studio/users': '用户管理',
+      '/studio/users': t('userMgmt.title'),
     }),
     [t],
   );
@@ -331,7 +331,9 @@ const MainLayout = () => {
     onClick: handleUserMenuClick,
     items: [
       { key: 'profile', icon: <UserGear size={14} />, label: t('user.profile') 
},
-      ...(admin ? [{ key: 'users', icon: <UserGear size={14} />, label: '用户管理' 
}] : []),
+      ...(admin
+        ? [{ key: 'users', icon: <UserGear size={14} />, label: 
t('userMgmt.title') }]
+        : []),
       {
         key: 'dataMode',
         icon: (
@@ -796,7 +798,7 @@ const MainLayout = () => {
           ) : (
             <Empty
               image={Empty.PRESENTED_IMAGE_SIMPLE}
-              description="未找到匹配页面"
+              description={t('layout.noMatchingPage')}
               style={{ padding: '24px 0' }}
             />
           )}
@@ -816,15 +818,15 @@ const MainLayout = () => {
           <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 
}}>
             <kbd style={kbdStyle}>↑</kbd>
             <kbd style={kbdStyle}>↓</kbd>
-            切换
+            {t('layout.shortcutNavigate')}
           </span>
           <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 
}}>
             <kbd style={kbdStyle}>↵</kbd>
-            打开
+            {t('common.open')}
           </span>
           <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 
}}>
             <kbd style={kbdStyle}>ESC</kbd>
-            关闭
+            {t('common.close')}
           </span>
         </div>
       </Modal>

Reply via email to