mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 13:31:32 +08:00
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.
This commit is contained in:
parent
283101ef8a
commit
544e716587
@ -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) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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('/')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Page Zoom"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Apps",
|
||||
"code": "Code",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "缩放"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "新标签页"
|
||||
},
|
||||
"title": {
|
||||
"apps": "小程序",
|
||||
"code": "Code",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "縮放"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "新標籤頁"
|
||||
},
|
||||
"title": {
|
||||
"apps": "小程序",
|
||||
"code": "Code",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Zoom"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Mini-Apps",
|
||||
"code": "Code",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Κλίμακα"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Εφαρμογές",
|
||||
"code": "Κώδικας",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Escala"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Aplicaciones",
|
||||
"code": "Código",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Zoom"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Mini-programmes",
|
||||
"code": "Code",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "ページズーム"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "アプリ",
|
||||
"code": "Code",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Escala"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Miniaplicativos",
|
||||
"code": "Código",
|
||||
|
||||
@ -4803,6 +4803,9 @@
|
||||
"title": "Масштаб страницы"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"new": "[to be translated]:New Tab"
|
||||
},
|
||||
"title": {
|
||||
"apps": "Приложения",
|
||||
"code": "Code",
|
||||
|
||||
58
src/renderer/src/utils/routeTitle.ts
Normal file
58
src/renderer/src/utils/routeTitle.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
|
||||
/**
|
||||
* Route to i18n key mapping for default tab titles
|
||||
*/
|
||||
const routeTitleKeys: Record<string, string> = {
|
||||
'/': '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]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user