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] +}