From 544e716587f2da7a1ea51fb4461a97925e085ec7 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Tue, 9 Dec 2025 19:41:13 +0800 Subject: [PATCH] feat(tabs): implement default route titles for new tabs and navigation updates - Introduced `getDefaultRouteTitle` utility to provide translated titles for various routes. - Updated tab creation and navigation logic in `AppShell`, `Sidebar`, and `useTabs` to utilize the new title mapping. - Enhanced internationalization support by adding new keys for default tab titles in multiple language files. These changes improve the user experience by ensuring that tabs display meaningful titles based on their content, enhancing navigation clarity. --- src/renderer/src/components/app/Sidebar.tsx | 5 +- .../src/components/layout/AppShell.tsx | 7 ++- src/renderer/src/hooks/useTabs.ts | 7 ++- src/renderer/src/i18n/locales/en-us.json | 3 + src/renderer/src/i18n/locales/zh-cn.json | 3 + src/renderer/src/i18n/locales/zh-tw.json | 3 + src/renderer/src/i18n/translate/de-de.json | 3 + src/renderer/src/i18n/translate/el-gr.json | 3 + src/renderer/src/i18n/translate/es-es.json | 3 + src/renderer/src/i18n/translate/fr-fr.json | 3 + src/renderer/src/i18n/translate/ja-jp.json | 3 + src/renderer/src/i18n/translate/pt-pt.json | 3 + src/renderer/src/i18n/translate/ru-ru.json | 3 + src/renderer/src/utils/routeTitle.ts | 58 +++++++++++++++++++ 14 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/renderer/src/utils/routeTitle.ts diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index d332a4a78f..f187e62ee8 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -12,6 +12,7 @@ import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' import { useSettings } from '@renderer/hooks/useSettings' import { getSidebarIconLabel, getThemeModeLabel } from '@renderer/i18n/label' import { isEmoji } from '@renderer/utils' +import { getDefaultRouteTitle } from '@renderer/utils/routeTitle' import { ThemeMode } from '@shared/data/preference/preferenceTypes' import { Code, @@ -60,7 +61,7 @@ const Sidebar: FC = () => { const to = async (path: string) => { await modelGenerating() if (activeTabId) { - updateTab(activeTabId, { url: path }) + updateTab(activeTabId, { url: path, title: getDefaultRouteTitle(path) }) } } @@ -166,7 +167,7 @@ const MainMenus: FC = () => { const to = async (path: string) => { await modelGenerating() if (activeTabId) { - updateTab(activeTabId, { url: path }) + updateTab(activeTabId, { url: path, title: getDefaultRouteTitle(path) }) } } diff --git a/src/renderer/src/components/layout/AppShell.tsx b/src/renderer/src/components/layout/AppShell.tsx index 75e9e5a824..1b2e38dc0a 100644 --- a/src/renderer/src/components/layout/AppShell.tsx +++ b/src/renderer/src/components/layout/AppShell.tsx @@ -1,6 +1,7 @@ import '@renderer/databases' import { cn, Tabs, TabsList, TabsTrigger } from '@cherrystudio/ui' +import { getDefaultRouteTitle } from '@renderer/utils/routeTitle' import { Plus, X } from 'lucide-react' import { Activity } from 'react' import { v4 as uuid } from 'uuid' @@ -22,9 +23,9 @@ const WebviewContainer = ({ url, isActive }: { url: string; isActive: boolean }) export const AppShell = () => { const { tabs, activeTabId, setActiveTab, closeTab, updateTab, addTab } = useTabs() - // Sync internal navigation back to tab state + // Sync internal navigation back to tab state with default title const handleUrlChange = (tabId: string, url: string) => { - updateTab(tabId, { url }) + updateTab(tabId, { url, title: getDefaultRouteTitle(url) }) } // 新增 Tab(默认打开首页) @@ -33,7 +34,7 @@ export const AppShell = () => { id: uuid(), type: 'route', url: '/', - title: 'New Tab' + title: getDefaultRouteTitle('/') }) } diff --git a/src/renderer/src/hooks/useTabs.ts b/src/renderer/src/hooks/useTabs.ts index a1ca00308c..0bf2b9dc10 100644 --- a/src/renderer/src/hooks/useTabs.ts +++ b/src/renderer/src/hooks/useTabs.ts @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo } from 'react' import { usePersistCache } from '../data/hooks/useCache' import { uuid } from '../utils' +import { getDefaultRouteTitle } from '../utils/routeTitle' // Re-export types from shared schema export type { Tab, TabsState, TabType } from '@shared/data/cache/cacheSchemas' @@ -11,7 +12,7 @@ const DEFAULT_TAB: Tab = { id: 'home', type: 'route', url: '/', - title: 'Home' + title: getDefaultRouteTitle('/') } /** @@ -127,12 +128,12 @@ export function useTabs() { } } - // Create new tab + // Create new tab with default route title const newTab: Tab = { id: id || uuid(), type, url, - title: title || url.split('/').pop() || url + title: title || getDefaultRouteTitle(url) } addTab(newTab) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 4ebc57cb9b..51ee786108 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -4803,6 +4803,9 @@ "title": "Page Zoom" } }, + "tab": { + "new": "New Tab" + }, "title": { "apps": "Apps", "code": "Code", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 8829bfe08e..62a5868616 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -4803,6 +4803,9 @@ "title": "缩放" } }, + "tab": { + "new": "新标签页" + }, "title": { "apps": "小程序", "code": "Code", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 1a036a29e1..340a420386 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -4803,6 +4803,9 @@ "title": "縮放" } }, + "tab": { + "new": "新標籤頁" + }, "title": { "apps": "小程序", "code": "Code", diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 1300fbf6c7..ba79d67270 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -4803,6 +4803,9 @@ "title": "Zoom" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "Mini-Apps", "code": "Code", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 535a36489e..274977a5b8 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -4803,6 +4803,9 @@ "title": "Κλίμακα" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "Εφαρμογές", "code": "Κώδικας", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 43d5919f00..82cec64467 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -4803,6 +4803,9 @@ "title": "Escala" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "Aplicaciones", "code": "Código", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index ef7db8b3b3..93309fbd82 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -4803,6 +4803,9 @@ "title": "Zoom" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "Mini-programmes", "code": "Code", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 42c50c8827..275044e5ee 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -4803,6 +4803,9 @@ "title": "ページズーム" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "アプリ", "code": "Code", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index bc84fc99b1..7a443ab4c9 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -4803,6 +4803,9 @@ "title": "Escala" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "Miniaplicativos", "code": "Código", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 109bffb9b2..899aa778c7 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -4803,6 +4803,9 @@ "title": "Масштаб страницы" } }, + "tab": { + "new": "[to be translated]:New Tab" + }, "title": { "apps": "Приложения", "code": "Code", diff --git a/src/renderer/src/utils/routeTitle.ts b/src/renderer/src/utils/routeTitle.ts new file mode 100644 index 0000000000..b880683c01 --- /dev/null +++ b/src/renderer/src/utils/routeTitle.ts @@ -0,0 +1,58 @@ +import i18n from '@renderer/i18n' + +/** + * Route to i18n key mapping for default tab titles + */ +const routeTitleKeys: Record = { + '/': 'tab.new', + '/chat': 'assistants.title', + '/store': 'assistants.presets.title', + '/paintings': 'paintings.title', + '/translate': 'translate.title', + '/apps': 'minapp.title', + '/knowledge': 'knowledge.title', + '/files': 'files.title', + '/code': 'code.title', + '/notes': 'notes.title', + '/settings': 'settings.title' +} + +/** + * Get the default title for a route URL + * + * @param url - Route URL (e.g., '/settings', '/chat/123') + * @returns Translated title or URL path fallback + * + * @example + * getDefaultRouteTitle('/settings') // '设置' + * getDefaultRouteTitle('/chat/abc123') // '助手' + * getDefaultRouteTitle('/unknown') // 'unknown' + */ +export function getDefaultRouteTitle(url: string): string { + // Try exact match first + const exactKey = routeTitleKeys[url] + if (exactKey) { + return i18n.t(exactKey) + } + + // Try matching base path (e.g., '/chat/123' -> '/chat') + const basePath = '/' + url.split('/').filter(Boolean)[0] + const baseKey = routeTitleKeys[basePath] + if (baseKey) { + return i18n.t(baseKey) + } + + // Fallback to URL path + return url.split('/').pop() || url +} + +/** + * Get the i18n key for a route (without translating) + */ +export function getRouteTitleKey(url: string): string | undefined { + const exactKey = routeTitleKeys[url] + if (exactKey) return exactKey + + const basePath = '/' + url.split('/').filter(Boolean)[0] + return routeTitleKeys[basePath] +}