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:
MyPrototypeWhat 2025-12-09 19:41:13 +08:00
parent 283101ef8a
commit 544e716587
14 changed files with 99 additions and 8 deletions

View File

@ -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) })
}
}

View File

@ -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('/')
})
}

View File

@ -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)

View File

@ -4803,6 +4803,9 @@
"title": "Page Zoom"
}
},
"tab": {
"new": "New Tab"
},
"title": {
"apps": "Apps",
"code": "Code",

View File

@ -4803,6 +4803,9 @@
"title": "缩放"
}
},
"tab": {
"new": "新标签页"
},
"title": {
"apps": "小程序",
"code": "Code",

View File

@ -4803,6 +4803,9 @@
"title": "縮放"
}
},
"tab": {
"new": "新標籤頁"
},
"title": {
"apps": "小程序",
"code": "Code",

View File

@ -4803,6 +4803,9 @@
"title": "Zoom"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "Mini-Apps",
"code": "Code",

View File

@ -4803,6 +4803,9 @@
"title": "Κλίμακα"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "Εφαρμογές",
"code": "Κώδικας",

View File

@ -4803,6 +4803,9 @@
"title": "Escala"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "Aplicaciones",
"code": "Código",

View File

@ -4803,6 +4803,9 @@
"title": "Zoom"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "Mini-programmes",
"code": "Code",

View File

@ -4803,6 +4803,9 @@
"title": "ページズーム"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "アプリ",
"code": "Code",

View File

@ -4803,6 +4803,9 @@
"title": "Escala"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "Miniaplicativos",
"code": "Código",

View File

@ -4803,6 +4803,9 @@
"title": "Масштаб страницы"
}
},
"tab": {
"new": "[to be translated]:New Tab"
},
"title": {
"apps": "Приложения",
"code": "Code",

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