fix(i18n): standardize i18n usage (#8525)

* fix(数据设置): 修复菜单项标题未使用翻译函数的问题

* refactor(i18n): 使用labelMap集中管理翻译键以提升维护性

将分散在各处的翻译键集中管理到labelMap中,统一通过映射获取翻译文本
替换直接使用i18n.t的调用为从labelMap获取,减少重复代码
修复部分未考虑Linux平台的翻译描述

* fix(NewApiPage): 将未知提供者的值设为undefined以保持一致

* refactor(i18n): 将labelMap替换为动态获取标签的函数

重构国际化标签的获取方式,从静态对象改为动态函数,以支持语言切换时实时更新标签内容

* feat(i18n): 添加文件字段标签的国际化支持

将文件字段的标签文本提取到i18n模块中统一管理,便于维护和翻译

* refactor(utils): 移除调试用的console.log语句
This commit is contained in:
Phantom 2025-07-25 22:03:31 +08:00 committed by GitHub
parent ff649b9d49
commit 36a22129a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 389 additions and 81 deletions

View File

@ -38,6 +38,7 @@
// "i18n-ally.namespace": true, // // "i18n-ally.namespace": true, //
"i18n-ally.sortKeys": true, // "i18n-ally.sortKeys": true, //
"i18n-ally.sourceLanguage": "zh-cn", // "i18n-ally.sourceLanguage": "zh-cn", //
"i18n-ally.usage.derivedKeyRules": ["{key}_one", "{key}_other"], //
"search.exclude": { "search.exclude": {
"**/dist/**": true, "**/dist/**": true,
".yarn/releases/**": true ".yarn/releases/**": true

View File

@ -8,6 +8,7 @@ import NewApiAddModelPopup from '@renderer/components/ModelList/NewApiAddModelPo
import { PROVIDER_CONFIG } from '@renderer/config/providers' import { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant' import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
import { useProvider } from '@renderer/hooks/useProvider' import { useProvider } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings' import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setModel } from '@renderer/store/assistants' import { setModel } from '@renderer/store/assistants'
@ -141,7 +142,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText> <SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
{docsWebsite && ( {docsWebsite && (
<SettingHelpLink target="_blank" href={docsWebsite}> <SettingHelpLink target="_blank" href={docsWebsite}>
{t(`provider.${provider.id}`) + ' '} {getProviderLabel(provider.id) + ' '}
{t('common.docs')} {t('common.docs')}
</SettingHelpLink> </SettingHelpLink>
)} )}

View File

@ -1,3 +1,4 @@
import { getProviderLabel } from '@renderer/i18n/label'
import { Provider } from '@renderer/types' import { Provider } from '@renderer/types'
import { oauthWithAihubmix, oauthWithPPIO, oauthWithSiliconFlow, oauthWithTokenFlux } from '@renderer/utils/oauth' import { oauthWithAihubmix, oauthWithPPIO, oauthWithSiliconFlow, oauthWithTokenFlux } from '@renderer/utils/oauth'
import { Button, ButtonProps } from 'antd' import { Button, ButtonProps } from 'antd'
@ -39,7 +40,7 @@ const OAuthButton: FC<Props> = ({ provider, onSuccess, ...buttonProps }) => {
return ( return (
<Button type="primary" onClick={onAuth} shape="round" {...buttonProps}> <Button type="primary" onClick={onAuth} shape="round" {...buttonProps}>
{t('settings.provider.oauth.button', { provider: t(`provider.${provider.id}`) })} {t('settings.provider.oauth.button', { provider: getProviderLabel(provider.id) })}
</Button> </Button>
) )
} }

View File

@ -1,4 +1,5 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { getProgressLabel } from '@renderer/i18n/label'
import { backup } from '@renderer/services/BackupService' import { backup } from '@renderer/services/BackupService'
import store from '@renderer/store' import store from '@renderer/store'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
@ -54,11 +55,11 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
if (!progressData) return '' if (!progressData) return ''
if (progressData.stage === 'copying_files') { if (progressData.stage === 'copying_files') {
return t(`backup.progress.${progressData.stage}`, { return t('backup.progress.copying_files', {
progress: Math.floor(progressData.progress) progress: Math.floor(progressData.progress)
}) })
} }
return t(`backup.progress.${progressData.stage}`) return getProgressLabel(progressData.stage)
} }
BackupPopup.hide = onCancel BackupPopup.hide = onCancel

View File

@ -1,3 +1,4 @@
import { getProgressLabel } from '@renderer/i18n/label'
import { restore } from '@renderer/services/BackupService' import { restore } from '@renderer/services/BackupService'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { Modal, Progress } from 'antd' import { Modal, Progress } from 'antd'
@ -48,11 +49,11 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
if (!progressData) return '' if (!progressData) return ''
if (progressData.stage === 'copying_files') { if (progressData.stage === 'copying_files') {
return t(`restore.progress.${progressData.stage}`, { return t('backup.progress.copying_files', {
progress: Math.floor(progressData.progress) progress: Math.floor(progressData.progress)
}) })
} }
return t(`restore.progress.${progressData.stage}`) return getProgressLabel(progressData.stage)
} }
RestorePopup.hide = onCancel RestorePopup.hide = onCancel

View File

@ -2,6 +2,7 @@ import { PlusOutlined } from '@ant-design/icons'
import { isLinux, isMac, isWin } from '@renderer/config/constant' import { isLinux, isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useFullscreen } from '@renderer/hooks/useFullscreen' import { useFullscreen } from '@renderer/hooks/useFullscreen'
import { getTitleLabel } from '@renderer/i18n/label'
import tabsService from '@renderer/services/TabsService' import tabsService from '@renderer/services/TabsService'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import type { Tab } from '@renderer/store/tabs' import type { Tab } from '@renderer/store/tabs'
@ -23,7 +24,6 @@ import {
X X
} from 'lucide-react' } from 'lucide-react'
import { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
@ -62,7 +62,6 @@ let lastSettingsPath = '/settings/provider'
const specialTabs = ['launchpad', 'settings'] const specialTabs = ['launchpad', 'settings']
const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => { const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
const { t } = useTranslation()
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@ -134,7 +133,7 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
<Tab key={tab.id} active={tab.id === activeTabId} onClick={() => navigate(tab.path)}> <Tab key={tab.id} active={tab.id === activeTabId} onClick={() => navigate(tab.path)}>
<TabHeader> <TabHeader>
{tab.id && <TabIcon>{getTabIcon(tab.id)}</TabIcon>} {tab.id && <TabIcon>{getTabIcon(tab.id)}</TabIcon>}
<TabTitle>{t(`title.${tab.id}`)}</TabTitle> <TabTitle>{getTitleLabel(tab.id)}</TabTitle>
</TabHeader> </TabHeader>
{tab.id !== 'home' && ( {tab.id !== 'home' && (
<CloseButton <CloseButton

View File

@ -10,6 +10,7 @@ import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { getSidebarIconLabel, getThemeModeLabel } from '@renderer/i18n/label'
import { ThemeMode } from '@renderer/types' import { ThemeMode } from '@renderer/types'
import { isEmoji } from '@renderer/utils' import { isEmoji } from '@renderer/utils'
import { Avatar, Tooltip } from 'antd' import { Avatar, Tooltip } from 'antd'
@ -104,7 +105,7 @@ const Sidebar: FC = () => {
</Icon> </Icon>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
title={t('settings.theme.title') + ': ' + t(`settings.theme.${theme}`)} title={t('settings.theme.title') + ': ' + getThemeModeLabel(theme)}
mouseEnterDelay={0.8} mouseEnterDelay={0.8}
placement="right"> placement="right">
<Icon theme={theme} onClick={() => setTheme(theme === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark)}> <Icon theme={theme} onClick={() => setTheme(theme === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark)}>
@ -129,7 +130,6 @@ const Sidebar: FC = () => {
const MainMenus: FC = () => { const MainMenus: FC = () => {
const { hideMinappPopup } = useMinappPopup() const { hideMinappPopup } = useMinappPopup()
const { t } = useTranslation()
const { pathname } = useLocation() const { pathname } = useLocation()
const { sidebarIcons, defaultPaintingProvider } = useSettings() const { sidebarIcons, defaultPaintingProvider } = useSettings()
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
@ -164,7 +164,7 @@ const MainMenus: FC = () => {
const isActive = path === '/' ? isRoute(path) : isRoutes(path) const isActive = path === '/' ? isRoute(path) : isRoutes(path)
return ( return (
<Tooltip key={icon} title={t(`${icon}.title`)} mouseEnterDelay={0.8} placement="right"> <Tooltip key={icon} title={getSidebarIconLabel(icon)} mouseEnterDelay={0.8} placement="right">
<StyledLink <StyledLink
onClick={async () => { onClick={async () => {
hideMinappPopup() hideMinappPopup()

View File

@ -0,0 +1,245 @@
import i18n from './index'
const t = i18n.t
/** 使用函数形式是为了动态获取,如果使用静态对象的话,导出的对象将不会随语言切换而改变 */
export const getProviderLabel = (key: string): string => {
const labelMap = {
'302ai': t('provider.302ai'),
aihubmix: t('provider.aihubmix'),
alayanew: t('provider.alayanew'),
anthropic: t('provider.anthropic'),
'azure-openai': t('provider.azure-openai'),
baichuan: t('provider.baichuan'),
'baidu-cloud': t('provider.baidu-cloud'),
burncloud: t('provider.burncloud'),
cephalon: t('provider.cephalon'),
copilot: t('provider.copilot'),
dashscope: t('provider.dashscope'),
deepseek: t('provider.deepseek'),
dmxapi: t('provider.dmxapi'),
doubao: t('provider.doubao'),
fireworks: t('provider.fireworks'),
gemini: t('provider.gemini'),
'gitee-ai': t('provider.gitee-ai'),
github: t('provider.github'),
gpustack: t('provider.gpustack'),
grok: t('provider.grok'),
groq: t('provider.groq'),
hunyuan: t('provider.hunyuan'),
hyperbolic: t('provider.hyperbolic'),
infini: t('provider.infini'),
jina: t('provider.jina'),
lanyun: t('provider.lanyun'),
lmstudio: t('provider.lmstudio'),
minimax: t('provider.minimax'),
mistral: t('provider.mistral'),
modelscope: t('provider.modelscope'),
moonshot: t('provider.moonshot'),
'new-api': t('provider.new-api'),
nvidia: t('provider.nvidia'),
o3: t('provider.o3'),
ocoolai: t('provider.ocoolai'),
ollama: t('provider.ollama'),
openai: t('provider.openai'),
openrouter: t('provider.openrouter'),
perplexity: t('provider.perplexity'),
ph8: t('provider.ph8'),
ppio: t('provider.ppio'),
qiniu: t('provider.qiniu'),
qwenlm: t('provider.qwenlm'),
silicon: t('provider.silicon'),
stepfun: t('provider.stepfun'),
'tencent-cloud-ti': t('provider.tencent-cloud-ti'),
together: t('provider.together'),
tokenflux: t('provider.tokenflux'),
vertexai: t('provider.vertexai'),
voyageai: t('provider.voyageai'),
xirang: t('provider.xirang'),
yi: t('provider.yi'),
zhinao: t('provider.zhinao'),
zhipu: t('provider.zhipu')
} as const
return labelMap[key] ?? key
}
export const getProgressLabel = (key: string): string => {
const labelMap = {
completed: t('backup.progress.completed'),
compressing: t('backup.progress.compressing'),
copying_files: t('backup.progress.copying_files'),
preparing: t('backup.progress.preparing'),
title: t('backup.progress.title'),
writing_data: t('backup.progress.writing_data')
} as const
return labelMap[key] ?? key
}
export const getTitleLabel = (key: string): string => {
const labelMap = {
agents: t('title.agents'),
apps: t('title.apps'),
files: t('title.files'),
home: t('title.home'),
knowledge: t('title.knowledge'),
launchpad: t('title.launchpad'),
'mcp-servers': t('title.mcp-servers'),
memories: t('title.memories'),
paintings: t('title.paintings'),
settings: t('title.settings'),
translate: t('title.translate')
} as const
return labelMap[key] ?? key
}
export const getThemeModeLabel = (key: string): string => {
const labelMap = {
dark: t('settings.theme.dark'),
light: t('settings.theme.light'),
system: t('settings.theme.system')
} as const
return labelMap[key] ?? key
}
export const getSidebarIconLabel = (key: string): string => {
const labelMap = {
assistants: t('assistants.title'),
agents: t('agents.title'),
paintings: t('paintings.title'),
translate: t('translate.title'),
minapp: t('minapp.title'),
knowledge: t('knowledge.title'),
files: t('files.title')
} as const
return labelMap[key] ?? key
}
export const getShortcutLabel = (key: string): string => {
const labelMap = {
action: t('settings.shortcuts.action'),
actions: t('settings.shortcuts.actions'),
clear_shortcut: t('settings.shortcuts.clear_shortcut'),
clear_topic: t('settings.shortcuts.clear_topic'),
copy_last_message: t('settings.shortcuts.copy_last_message'),
enabled: t('settings.shortcuts.enabled'),
exit_fullscreen: t('settings.shortcuts.exit_fullscreen'),
label: t('settings.shortcuts.label'),
mini_window: t('settings.shortcuts.mini_window'),
new_topic: t('settings.shortcuts.new_topic'),
press_shortcut: t('settings.shortcuts.press_shortcut'),
reset_defaults: t('settings.shortcuts.reset_defaults'),
reset_defaults_confirm: t('settings.shortcuts.reset_defaults_confirm'),
reset_to_default: t('settings.shortcuts.reset_to_default'),
search_message: t('settings.shortcuts.search_message'),
search_message_in_chat: t('settings.shortcuts.search_message_in_chat'),
selection_assistant_select_text: t('settings.shortcuts.selection_assistant_select_text'),
selection_assistant_toggle: t('settings.shortcuts.selection_assistant_toggle'),
show_app: t('settings.shortcuts.show_app'),
show_settings: t('settings.shortcuts.show_settings'),
title: t('settings.shortcuts.title'),
toggle_new_context: t('settings.shortcuts.toggle_new_context'),
toggle_show_assistants: t('settings.shortcuts.toggle_show_assistants'),
toggle_show_topics: t('settings.shortcuts.toggle_show_topics'),
zoom_in: t('settings.shortcuts.zoom_in'),
zoom_out: t('settings.shortcuts.zoom_out'),
zoom_reset: t('settings.shortcuts.zoom_reset')
} as const
return labelMap[key] ?? key
}
export const getSelectionDescriptionLabel = (key: string): string => {
const labelMap = {
mac: t('selection.settings.toolbar.trigger_mode.description_note.mac'),
windows: t('selection.settings.toolbar.trigger_mode.description_note.windows')
} as const
return labelMap[key] ?? key
}
export const getPaintingsImageSizeOptionsLabel = (key: string): string => {
const labelMap = {
auto: t('paintings.image_size_options.auto')
} as const
return labelMap[key] ?? key
}
export const getPaintingsQualityOptionsLabel = (key: string): string => {
const labelMap = {
auto: t('paintings.quality_options.auto'),
high: t('paintings.quality_options.high'),
low: t('paintings.quality_options.low'),
medium: t('paintings.quality_options.medium')
} as const
return labelMap[key] ?? key
}
export const getPaintingsModerationOptionsLabel = (key: string): string => {
const labelMap = {
auto: t('paintings.moderation_options.auto'),
low: t('paintings.moderation_options.low')
} as const
return labelMap[key] ?? key
}
export const getPaintingsBackgroundOptionsLabel = (key: string): string => {
const labelMap = {
auto: t('paintings.background_options.auto'),
opaque: t('paintings.background_options.opaque'),
transparent: t('paintings.background_options.transparent')
} as const
return labelMap[key] ?? key
}
export const getMcpTypeLabel = (key: string): string => {
const labelMap = {
inMemory: t('settings.mcp.types.inMemory'),
sse: t('settings.mcp.types.sse'),
stdio: t('settings.mcp.types.stdio'),
streamableHttp: t('settings.mcp.types.streamableHttp')
} as const
return labelMap[key] ?? key
}
export const getMiniappsStatusLabel = (key: string): string => {
const labelMap = {
visible: t('settings.miniapps.visible'),
disabled: t('settings.miniapps.disabled')
} as const
return labelMap[key] ?? key
}
export const getHttpMessageLabel = (key: string): string => {
const labelMap = {
'400': t('error.http.400'),
'401': t('error.http.401'),
'403': t('error.http.403'),
'404': t('error.http.404'),
'429': t('error.http.429'),
'500': t('error.http.500'),
'502': t('error.http.502'),
'503': t('error.http.503'),
'504': t('error.http.504')
} as const
return labelMap[key] ?? key
}
export const getReasoningEffortOptionsLabel = (key: string): string => {
const labelMap = {
auto: t('assistants.settings.reasoning_effort.default'),
high: t('assistants.settings.reasoning_effort.high'),
label: t('assistants.settings.reasoning_effort.label'),
low: t('assistants.settings.reasoning_effort.low'),
medium: t('assistants.settings.reasoning_effort.medium'),
off: t('assistants.settings.reasoning_effort.off')
} as const
return labelMap[key] ?? key
}
export const getFileFieldLabel = (key: string): string => {
const labelMap = {
created_at: t('files.created_at'),
size: t('files.size'),
name: t('files.name')
} as const
return labelMap[key] ?? key
}

View File

@ -2536,7 +2536,7 @@
"addServer": { "addServer": {
"create": "快速创建", "create": "快速创建",
"importFrom": { "importFrom": {
"connectionFailed": "連接失敗", "connectionFailed": "连接失败",
"dxt": "导入 DXT 包", "dxt": "导入 DXT 包",
"dxtFile": "DXT 包文件", "dxtFile": "DXT 包文件",
"dxtHelp": "选择包含 MCP 服务器的 .dxt 文件", "dxtHelp": "选择包含 MCP 服务器的 .dxt 文件",

View File

@ -8,6 +8,7 @@ import {
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import ListItem from '@renderer/components/ListItem' import ListItem from '@renderer/components/ListItem'
import db from '@renderer/databases' import db from '@renderer/databases'
import { getFileFieldLabel } from '@renderer/i18n/label'
import { handleDelete, handleRename, sortFiles, tempFilesSort } from '@renderer/services/FileAction' import { handleDelete, handleRename, sortFiles, tempFilesSort } from '@renderer/services/FileAction'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { FileMetadata, FileTypes } from '@renderer/types' import { FileMetadata, FileTypes } from '@renderer/types'
@ -94,7 +95,7 @@ const FilesPage: FC = () => {
</SideNav> </SideNav>
<MainContent> <MainContent>
<SortContainer> <SortContainer>
{['created_at', 'size', 'name'].map((field) => ( {(['created_at', 'size', 'name'] as const).map((field) => (
<SortButton <SortButton
key={field} key={field}
active={sortField === field} active={sortField === field}
@ -106,7 +107,7 @@ const FilesPage: FC = () => {
setSortOrder('desc') setSortOrder('desc')
} }
}}> }}>
{t(`files.${field}`)} {getFileFieldLabel(field)}
{sortField === field && (sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)} {sortField === field && (sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
</SortButton> </SortButton>
))} ))}

View File

@ -16,6 +16,7 @@ import {
isSupportedThinkingTokenQwenModel isSupportedThinkingTokenQwenModel
} from '@renderer/config/models' } from '@renderer/config/models'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label'
import { Assistant, Model, ReasoningEffortOptions } from '@renderer/types' import { Assistant, Model, ReasoningEffortOptions } from '@renderer/types'
import { Tooltip } from 'antd' import { Tooltip } from 'antd'
import { FC, ReactElement, useCallback, useEffect, useImperativeHandle, useMemo } from 'react' import { FC, ReactElement, useCallback, useEffect, useImperativeHandle, useMemo } from 'react'
@ -153,13 +154,13 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): Re
// 使用表中定义的选项创建UI选项 // 使用表中定义的选项创建UI选项
return supportedOptions.map((option) => ({ return supportedOptions.map((option) => ({
level: option, level: option,
label: t(`assistants.settings.reasoning_effort.${option === 'auto' ? 'default' : option}`), label: getReasoningEffortOptionsLabel(option),
description: '', description: '',
icon: createThinkingIcon(option), icon: createThinkingIcon(option),
isSelected: currentReasoningEffort === option, isSelected: currentReasoningEffort === option,
action: () => onThinkingChange(option) action: () => onThinkingChange(option)
})) }))
}, [t, createThinkingIcon, currentReasoningEffort, supportedOptions, onThinkingChange]) }, [createThinkingIcon, currentReasoningEffort, supportedOptions, onThinkingChange])
const openQuickPanel = useCallback(() => { const openQuickPanel = useCallback(() => {
quickPanel.open({ quickPanel.open({

View File

@ -1,3 +1,4 @@
import { getHttpMessageLabel } from '@renderer/i18n/label'
import type { ErrorMessageBlock } from '@renderer/types/newMessage' import type { ErrorMessageBlock } from '@renderer/types/newMessage'
import { Alert as AntdAlert } from 'antd' import { Alert as AntdAlert } from 'antd'
import React from 'react' import React from 'react'
@ -18,7 +19,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock }> = ({ block }) =>
const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504] const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504]
if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) { if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) {
return <Alert description={t(`error.http.${block.error.status}`)} message={block.error?.message} type="error" /> return <Alert description={getHttpMessageLabel(block.error.status)} message={block.error?.message} type="error" />
} }
if (block?.error?.message) { if (block?.error?.message) {

View File

@ -72,7 +72,7 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
<Tooltip <Tooltip
title={ title={
isCompact isCompact
? t(`message.message.multi_model_style.fold.expand`) ? t('message.message.multi_model_style.fold.expand')
: t('message.message.multi_model_style.fold.compress') : t('message.message.multi_model_style.fold.compress')
} }
placement="top" placement="top"

View File

@ -9,6 +9,7 @@ import {
} from '@hello-pangea/dnd' } from '@hello-pangea/dnd'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import { getMiniappsStatusLabel } from '@renderer/i18n/label'
import { MinAppType } from '@renderer/types' import { MinAppType } from '@renderer/types'
import { FC, useCallback } from 'react' import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -115,7 +116,7 @@ const MiniAppIconsManager: FC<MiniAppManagerProps> = ({
<ProgramSection style={{ background: 'transparent' }}> <ProgramSection style={{ background: 'transparent' }}>
{(['visible', 'disabled'] as const).map((listType) => ( {(['visible', 'disabled'] as const).map((listType) => (
<ProgramColumn key={listType}> <ProgramColumn key={listType}>
<h4>{t(`settings.miniapps.${listType}`)}</h4> <h4>{getMiniappsStatusLabel(listType)}</h4>
<Droppable droppableId={listType}> <Droppable droppableId={listType}>
{(provided: DroppableProvided) => ( {(provided: DroppableProvided) => (
<ProgramList ref={provided.innerRef} {...provided.droppableProps}> <ProgramList ref={provided.innerRef} {...provided.droppableProps}>

View File

@ -14,6 +14,7 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { getProviderLabel } from '@renderer/i18n/label'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
@ -58,9 +59,16 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders() const providers = useAllProviders()
const providerOptions = Options.map((option) => { const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option) const provider = providers.find((p) => p.id === option)
return { if (provider) {
label: t(`provider.${provider?.id}`), return {
value: provider?.id label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
} }
}) })
const dispatch = useAppDispatch() const dispatch = useAppDispatch()

View File

@ -9,6 +9,7 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { usePaintings } from '@renderer/hooks/usePaintings' import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { getProviderLabel } from '@renderer/i18n/label'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime' import { setGenerating } from '@renderer/store/runtime'
@ -49,9 +50,16 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders() const providers = useAllProviders()
const providerOptions = Options.map((option) => { const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option) const provider = providers.find((p) => p.id === option)
return { if (provider) {
label: t(`provider.${provider?.id}`), return {
value: provider?.id label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
} }
}) })

View File

@ -13,6 +13,13 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import {
getPaintingsBackgroundOptionsLabel,
getPaintingsImageSizeOptionsLabel,
getPaintingsModerationOptionsLabel,
getPaintingsQualityOptionsLabel,
getProviderLabel
} from '@renderer/i18n/label'
import PaintingsList from '@renderer/pages/paintings/components/PaintingsList' import PaintingsList from '@renderer/pages/paintings/components/PaintingsList'
import { DEFAULT_PAINTING, MODELS, SUPPORTED_MODELS } from '@renderer/pages/paintings/config/NewApiConfig' import { DEFAULT_PAINTING, MODELS, SUPPORTED_MODELS } from '@renderer/pages/paintings/config/NewApiConfig'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
@ -53,9 +60,16 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders() const providers = useAllProviders()
const providerOptions = Options.map((option) => { const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option) const provider = providers.find((p) => p.id === option)
return { if (provider) {
label: t(`provider.${provider?.id}`), return {
value: provider?.id label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
} }
}) })
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@ -566,7 +580,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
<Select value={painting.size} onChange={handleSizeChange} style={{ width: '100%', marginBottom: 15 }}> <Select value={painting.size} onChange={handleSizeChange} style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.imageSizes.map((s) => ( {selectedModelConfig.imageSizes.map((s) => (
<Select.Option value={s.value} key={s.value}> <Select.Option value={s.value} key={s.value}>
{t(`paintings.image_size_options.${s.value}`, { defaultValue: s.value })} {getPaintingsImageSizeOptionsLabel(s.value) ?? s.value}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
@ -583,7 +597,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: '100%', marginBottom: 15 }}> style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.quality.map((q) => ( {selectedModelConfig.quality.map((q) => (
<Select.Option value={q.value} key={q.value}> <Select.Option value={q.value} key={q.value}>
{t(`paintings.quality_options.${q.value}`, { defaultValue: q.value })} {getPaintingsQualityOptionsLabel(q.value) ?? q.value}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
@ -602,7 +616,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: '100%', marginBottom: 15 }}> style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.moderation.map((m) => ( {selectedModelConfig.moderation.map((m) => (
<Select.Option value={m.value} key={m.value}> <Select.Option value={m.value} key={m.value}>
{t(`paintings.moderation_options.${m.value}`, { defaultValue: m.value })} {getPaintingsModerationOptionsLabel(m.value) ?? m.value}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
@ -621,7 +635,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: '100%', marginBottom: 15 }}> style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.background.map((b) => ( {selectedModelConfig.background.map((b) => (
<Select.Option value={b.value} key={b.value}> <Select.Option value={b.value} key={b.value}>
{t(`paintings.background_options.${b.value}`, { defaultValue: b.value })} {getPaintingsBackgroundOptionsLabel(b.value) ?? b.value}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>

View File

@ -19,6 +19,7 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { getProviderLabel } from '@renderer/i18n/label'
import { getProviderByModel } from '@renderer/services/AssistantService' import { getProviderByModel } from '@renderer/services/AssistantService'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
@ -100,9 +101,16 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders() const providers = useAllProviders()
const providerOptions = Options.map((option) => { const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option) const provider = providers.find((p) => p.id === option)
return { if (provider) {
label: t(`provider.${provider?.id}`), return {
value: provider?.id label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
} }
}) })
const [currentImageIndex, setCurrentImageIndex] = useState(0) const [currentImageIndex, setCurrentImageIndex] = useState(0)

View File

@ -10,6 +10,7 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { getProviderLabel } from '@renderer/i18n/label'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
@ -55,9 +56,16 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
const providerOptions = Options.map((option) => { const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option) const provider = providers.find((p) => p.id === option)
return { if (provider) {
label: t(`provider.${provider?.id}`), return {
value: provider?.id label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
} }
}) })

View File

@ -87,49 +87,49 @@ const DataSettings: FC = () => {
const menuItems = [ const menuItems = [
{ key: 'divider_0', isDivider: true, text: t('settings.data.divider.basic') }, { key: 'divider_0', isDivider: true, text: t('settings.data.divider.basic') },
{ key: 'data', title: 'settings.data.data.title', icon: <FolderCog size={16} /> }, { key: 'data', title: t('settings.data.data.title'), icon: <FolderCog size={16} /> },
{ key: 'divider_1', isDivider: true, text: t('settings.data.divider.cloud_storage') }, { key: 'divider_1', isDivider: true, text: t('settings.data.divider.cloud_storage') },
{ key: 'local_backup', title: 'settings.data.local.title', icon: <FolderCog size={16} /> }, { key: 'local_backup', title: t('settings.data.local.title'), icon: <FolderCog size={16} /> },
{ key: 'webdav', title: 'settings.data.webdav.title', icon: <CloudSyncOutlined style={{ fontSize: 16 }} /> }, { key: 'webdav', title: t('settings.data.webdav.title'), icon: <CloudSyncOutlined style={{ fontSize: 16 }} /> },
{ key: 'nutstore', title: 'settings.data.nutstore.title', icon: <NutstoreIcon /> }, { key: 'nutstore', title: t('settings.data.nutstore.title'), icon: <NutstoreIcon /> },
{ key: 's3', title: 'settings.data.s3.title', icon: <CloudServerOutlined style={{ fontSize: 16 }} /> }, { key: 's3', title: t('settings.data.s3.title.label'), icon: <CloudServerOutlined style={{ fontSize: 16 }} /> },
{ key: 'divider_2', isDivider: true, text: t('settings.data.divider.export_settings') }, { key: 'divider_2', isDivider: true, text: t('settings.data.divider.export_settings') },
{ {
key: 'export_menu', key: 'export_menu',
title: 'settings.data.export_menu.title', title: t('settings.data.export_menu.title'),
icon: <FolderInput size={16} /> icon: <FolderInput size={16} />
}, },
{ {
key: 'markdown_export', key: 'markdown_export',
title: 'settings.data.markdown_export.title', title: t('settings.data.markdown_export.title'),
icon: <FileText size={16} /> icon: <FileText size={16} />
}, },
{ key: 'divider_3', isDivider: true, text: t('settings.data.divider.third_party') }, { key: 'divider_3', isDivider: true, text: t('settings.data.divider.third_party') },
{ key: 'notion', title: 'settings.data.notion.title', icon: <i className="iconfont icon-notion" /> }, { key: 'notion', title: t('settings.data.notion.title'), icon: <i className="iconfont icon-notion" /> },
{ {
key: 'yuque', key: 'yuque',
title: 'settings.data.yuque.title', title: t('settings.data.yuque.title'),
icon: <YuqueOutlined style={{ fontSize: 16 }} /> icon: <YuqueOutlined style={{ fontSize: 16 }} />
}, },
{ {
key: 'joplin', key: 'joplin',
title: 'settings.data.joplin.title', title: t('settings.data.joplin.title'),
icon: <JoplinIcon /> icon: <JoplinIcon />
}, },
{ {
key: 'obsidian', key: 'obsidian',
title: 'settings.data.obsidian.title', title: t('settings.data.obsidian.title'),
icon: <i className="iconfont icon-obsidian" /> icon: <i className="iconfont icon-obsidian" />
}, },
{ {
key: 'siyuan', key: 'siyuan',
title: 'settings.data.siyuan.title', title: t('settings.data.siyuan.title'),
icon: <SiyuanIcon /> icon: <SiyuanIcon />
}, },
{ {
key: 'agentssubscribe_url', key: 'agentssubscribe_url',
title: 'agents.settings.title', title: t('agents.settings.title'),
icon: <Sparkle size={16} className="icon" /> icon: <Sparkle size={16} className="icon" />
} }
] ]
@ -568,7 +568,7 @@ const DataSettings: FC = () => {
) : ( ) : (
<ListItem <ListItem
key={item.key} key={item.key}
title={t(item.title || '')} title={item.title}
active={menu === item.key} active={menu === item.key}
onClick={() => setMenu(item.key)} onClick={() => setMenu(item.key)}
titleStyle={{ fontWeight: 500 }} titleStyle={{ fontWeight: 500 }}

View File

@ -7,6 +7,7 @@ import {
DroppableProvided, DroppableProvided,
DropResult DropResult
} from '@hello-pangea/dnd' } from '@hello-pangea/dnd'
import { getSidebarIconLabel } from '@renderer/i18n/label'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setSidebarIcons } from '@renderer/store/settings' import { setSidebarIcons } from '@renderer/store/settings'
import { message } from 'antd' import { message } from 'antd'
@ -136,7 +137,7 @@ const SidebarIconsManager: FC<SidebarIconsManagerProps> = ({
<IconItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> <IconItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<IconContent> <IconContent>
{renderIcon(icon)} {renderIcon(icon)}
<span>{t(`${icon}.title`)}</span> <span>{getSidebarIconLabel(icon)}</span>
</IconContent> </IconContent>
{icon !== 'assistants' && ( {icon !== 'assistants' && (
<CloseButton onClick={() => onMoveIcon(icon, 'visible')}> <CloseButton onClick={() => onMoveIcon(icon, 'visible')}>
@ -166,7 +167,7 @@ const SidebarIconsManager: FC<SidebarIconsManagerProps> = ({
<IconItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> <IconItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<IconContent> <IconContent>
{renderIcon(icon)} {renderIcon(icon)}
<span>{t(`${icon}.title`)}</span> <span>{getSidebarIconLabel(icon)}</span>
</IconContent> </IconContent>
<CloseButton onClick={() => onMoveIcon(icon, 'disabled')}> <CloseButton onClick={() => onMoveIcon(icon, 'disabled')}>
<CloseOutlined /> <CloseOutlined />

View File

@ -236,7 +236,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
.catch((connError: any) => { .catch((connError: any) => {
logger.error(`Connectivity check failed for ${newServer.name}:`, connError) logger.error(`Connectivity check failed for ${newServer.name}:`, connError)
window.message.error({ window.message.error({
content: t(`${newServer.name} settings.mcp.addServer.importFrom.connectionFailed`), content: newServer.name + t('settings.mcp.addServer.importFrom.connectionFailed'),
key: 'mcp-quick-add-failed' key: 'mcp-quick-add-failed'
}) })
}) })

View File

@ -1,5 +1,6 @@
import { CheckOutlined, PlusOutlined } from '@ant-design/icons' import { CheckOutlined, PlusOutlined } from '@ant-design/icons'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { getMcpTypeLabel } from '@renderer/i18n/label'
import { builtinMCPServers } from '@renderer/store/mcp' import { builtinMCPServers } from '@renderer/store/mcp'
import { Button, Popover, Tag } from 'antd' import { Button, Popover, Tag } from 'antd'
import { FC } from 'react' import { FC } from 'react'
@ -55,7 +56,7 @@ const BuiltinMCPServersSection: FC = () => {
</Popover> </Popover>
<ServerFooter> <ServerFooter>
<Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}> <Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
{t(`settings.mcp.types.${server.type || 'stdio'}`)} {getMcpTypeLabel(server.type ?? 'stdio')}
</Tag> </Tag>
{server.env && Object.keys(server.env).length > 0 && ( {server.env && Object.keys(server.env).length > 0 && (
<Tag color="warning" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}> <Tag color="warning" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>

View File

@ -3,6 +3,7 @@ import { nanoid } from '@reduxjs/toolkit'
import { DraggableList } from '@renderer/components/DraggableList' import { DraggableList } from '@renderer/components/DraggableList'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { getMcpTypeLabel } from '@renderer/i18n/label'
import { MCPServer } from '@renderer/types' import { MCPServer } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error' import { formatMcpError } from '@renderer/utils/error'
import { Badge, Button, Dropdown, Empty, Switch, Tag } from 'antd' import { Badge, Button, Dropdown, Empty, Switch, Tag } from 'antd'
@ -221,7 +222,7 @@ const McpServersList: FC = () => {
<ServerDescription>{server.description}</ServerDescription> <ServerDescription>{server.description}</ServerDescription>
<ServerFooter> <ServerFooter>
<Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}> <Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
{t(`settings.mcp.types.${server.type || 'stdio'}`)} {getMcpTypeLabel(server.type ?? 'stdio')}
</Tag> </Tag>
{server.provider && ( {server.provider && (
<Tag color="success" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}> <Tag color="success" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>

View File

@ -6,6 +6,7 @@ import { HStack } from '@renderer/components/Layout'
import OAuthButton from '@renderer/components/OAuth/OAuthButton' import OAuthButton from '@renderer/components/OAuth/OAuthButton'
import { PROVIDER_CONFIG } from '@renderer/config/providers' import { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useProvider } from '@renderer/hooks/useProvider' import { useProvider } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'
import { providerBills, providerCharge } from '@renderer/utils/oauth' import { providerBills, providerCharge } from '@renderer/utils/oauth'
import { Button } from 'antd' import { Button } from 'antd'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
@ -44,7 +45,7 @@ const ProviderOAuth: FC<Props> = ({ providerId }) => {
<ProviderLogo src={PROVIDER_LOGO_MAP[provider.id]} /> <ProviderLogo src={PROVIDER_LOGO_MAP[provider.id]} />
{isEmpty(provider.apiKey) ? ( {isEmpty(provider.apiKey) ? (
<OAuthButton provider={provider} onSuccess={setApiKey}> <OAuthButton provider={provider} onSuccess={setApiKey}>
{t('settings.provider.oauth.button', { provider: t(`provider.${provider.id}`) })} {t('settings.provider.oauth.button', { provider: getProviderLabel(provider.id) })}
</OAuthButton> </OAuthButton>
) : ( ) : (
<HStack gap={10}> <HStack gap={10}>

View File

@ -4,6 +4,7 @@ import { loggerService } from '@logger'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { getProviderLogo } from '@renderer/config/providers' import { getProviderLogo } from '@renderer/config/providers'
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider' import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'
import ImageStorage from '@renderer/services/ImageStorage' import ImageStorage from '@renderer/services/ImageStorage'
import { INITIAL_PROVIDERS } from '@renderer/store/llm' import { INITIAL_PROVIDERS } from '@renderer/store/llm'
import { Provider, ProviderType } from '@renderer/types' import { Provider, ProviderType } from '@renderer/types'
@ -101,7 +102,7 @@ const ProvidersList: FC = () => {
} }
const providerDisplayName = existingProvider.isSystem const providerDisplayName = existingProvider.isSystem
? t(`provider.${existingProvider.id}`) ? getProviderLabel(existingProvider.id)
: existingProvider.name : existingProvider.name
// 检查是否已有 API Key // 检查是否已有 API Key

View File

@ -1,6 +1,7 @@
import { isMac, isWin } from '@renderer/config/constant' import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant' import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
import { getSelectionDescriptionLabel } from '@renderer/i18n/label'
import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes' import { FilterMode, TriggerMode } from '@renderer/types/selectionTypes'
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar' import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd' import { Button, Radio, Row, Slider, Switch, Tooltip } from 'antd'
@ -132,10 +133,8 @@ const SelectionAssistantSettings: FC = () => {
<SettingLabel> <SettingLabel>
<SettingRowTitle> <SettingRowTitle>
<div style={{ marginRight: '4px' }}>{t('selection.settings.toolbar.trigger_mode.title')}</div> <div style={{ marginRight: '4px' }}>{t('selection.settings.toolbar.trigger_mode.title')}</div>
<Tooltip {/* FIXME: 没有考虑Linux */}
placement="top" <Tooltip placement="top" title={getSelectionDescriptionLabel(isWin ? 'windows' : 'mac')} arrow>
title={t(`selection.settings.toolbar.trigger_mode.description_note.${isWin ? 'windows' : 'mac'}`)}
arrow>
<QuestionIcon size={14} /> <QuestionIcon size={14} />
</Tooltip> </Tooltip>
</SettingRowTitle> </SettingRowTitle>

View File

@ -3,6 +3,7 @@ import { HStack } from '@renderer/components/Layout'
import { isMac, isWin } from '@renderer/config/constant' import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useShortcuts } from '@renderer/hooks/useShortcuts' import { useShortcuts } from '@renderer/hooks/useShortcuts'
import { getShortcutLabel } from '@renderer/i18n/label'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { initialState, resetShortcuts, toggleShortcut, updateShortcut } from '@renderer/store/shortcuts' import { initialState, resetShortcuts, toggleShortcut, updateShortcut } from '@renderer/store/shortcuts'
import { Shortcut } from '@renderer/types' import { Shortcut } from '@renderer/types'
@ -400,7 +401,7 @@ const ShortcutSettings: FC = () => {
<SettingDivider style={{ marginBottom: 0 }} /> <SettingDivider style={{ marginBottom: 0 }} />
<Table <Table
columns={columns as ColumnsType<unknown>} columns={columns as ColumnsType<unknown>}
dataSource={shortcuts.map((s) => ({ ...s, name: t(`settings.shortcuts.${s.key}`) }))} dataSource={shortcuts.map((s) => ({ ...s, name: getShortcutLabel(s.key) }))}
pagination={false} pagination={false}
size="middle" size="middle"
showHeader={false} showHeader={false}

View File

@ -1,4 +1,4 @@
import i18n from '@renderer/i18n' import { getProviderLabel } from '@renderer/i18n/label'
import store from '@renderer/store' import store from '@renderer/store'
import { Provider } from '@renderer/types' import { Provider } from '@renderer/types'
@ -9,7 +9,7 @@ export function getProviderName(id: string) {
} }
if (provider.isSystem) { if (provider.isSystem) {
return i18n.t(`provider.${provider.id}`, { defaultValue: provider.name }) return getProviderLabel(provider.id) ?? provider.name
} }
return provider?.name return provider?.name

View File

@ -202,13 +202,14 @@ const migrateConfig = {
'8': (state: RootState) => { '8': (state: RootState) => {
try { try {
const fixAssistantName = (assistant: Assistant) => { const fixAssistantName = (assistant: Assistant) => {
// 2025/07/25 这俩键早没了,从远古版本迁移包出错的
if (isEmpty(assistant.name)) { if (isEmpty(assistant.name)) {
assistant.name = i18n.t(`assistant.${assistant.id}.name`) assistant.name = i18n.t('chat.default.name')
} }
assistant.topics = assistant.topics.map((topic) => { assistant.topics = assistant.topics.map((topic) => {
if (isEmpty(topic.name)) { if (isEmpty(topic.name)) {
topic.name = i18n.t(`assistant.${assistant.id}.topic.name`) topic.name = i18n.t('chat.default.topic.name')
} }
return topic return topic
}) })

View File

@ -83,7 +83,7 @@ describe('match', () => {
}) })
it('should match i18n name for system provider', () => { it('should match i18n name for system provider', () => {
expect(matchKeywordsInProvider('i18n:provider.sys', sysProvider)).toBe(true) expect(matchKeywordsInProvider('sys', sysProvider)).toBe(true)
expect(matchKeywordsInProvider('SystemProvider', sysProvider)).toBe(false) expect(matchKeywordsInProvider('SystemProvider', sysProvider)).toBe(false)
}) })
}) })
@ -108,8 +108,8 @@ describe('match', () => {
}) })
it('should match model name and i18n provider name for system provider', () => { it('should match model name and i18n provider name for system provider', () => {
expect(matchKeywordsInModel('gpt-4.1 i18n:provider.sys', model, sysProvider)).toBe(true) expect(matchKeywordsInModel('gpt-4.1 sys', model, sysProvider)).toBe(true)
expect(matchKeywordsInModel('i18n:provider.sys', model, sysProvider)).toBe(true) expect(matchKeywordsInModel('sys', model, sysProvider)).toBe(true)
expect(matchKeywordsInModel('SystemProvider', model, sysProvider)).toBe(false) expect(matchKeywordsInModel('SystemProvider', model, sysProvider)).toBe(false)
}) })

View File

@ -1,6 +1,7 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { Client } from '@notionhq/client' import { Client } from '@notionhq/client'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { getProviderLabel } from '@renderer/i18n/label'
import { getMessageTitle } from '@renderer/services/MessagesService' import { getMessageTitle } from '@renderer/services/MessagesService'
import store from '@renderer/store' import store from '@renderer/store'
import { setExportState } from '@renderer/store/runtime' import { setExportState } from '@renderer/store/runtime'
@ -56,7 +57,7 @@ export function getTitleFromString(str: string, length: number = 80) {
return title return title
} }
const getRoleText = (role: string, modelName?: string, modelProvider?: string) => { const getRoleText = (role: string, modelName?: string, providerId?: string) => {
const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings
if (role === 'user') { if (role === 'user') {
@ -67,14 +68,14 @@ const getRoleText = (role: string, modelName?: string, modelProvider?: string) =
let assistantText = '🤖 ' let assistantText = '🤖 '
if (showModelNameInMarkdown && modelName) { if (showModelNameInMarkdown && modelName) {
assistantText += `${modelName}` assistantText += `${modelName}`
if (showModelProviderInMarkdown && modelProvider) { if (showModelProviderInMarkdown && providerId) {
const providerDisplayName = i18n.t(`provider.${modelProvider}`, { defaultValue: modelProvider }) const providerDisplayName = getProviderLabel(providerId) ?? providerId
assistantText += ` | ${providerDisplayName}` assistantText += ` | ${providerDisplayName}`
return assistantText return assistantText
} }
return assistantText return assistantText
} else if (showModelProviderInMarkdown && modelProvider) { } else if (showModelProviderInMarkdown && providerId) {
const providerDisplayName = i18n.t(`provider.${modelProvider}`, { defaultValue: modelProvider }) const providerDisplayName = getProviderLabel(providerId) ?? providerId
assistantText += `Assistant | ${providerDisplayName}` assistantText += `Assistant | ${providerDisplayName}`
return assistantText return assistantText
} }

View File

@ -1,4 +1,4 @@
import i18n from '@renderer/i18n' import { getProviderLabel } from '@renderer/i18n/label'
import { Model, Provider } from '@renderer/types' import { Model, Provider } from '@renderer/types'
/** /**
@ -43,6 +43,8 @@ export function matchKeywordsInString(keywords: string | string[], value: string
* @returns true * @returns true
*/ */
export function matchKeywordsInProvider(keywords: string | string[], provider: Provider): boolean { export function matchKeywordsInProvider(keywords: string | string[], provider: Provider): boolean {
console.log(keywords, provider.id)
console.log(getProviderSearchString(provider))
return includeKeywords(getProviderSearchString(provider), keywords) return includeKeywords(getProviderSearchString(provider), keywords)
} }
@ -64,7 +66,7 @@ export function matchKeywordsInModel(keywords: string | string[], model: Model,
* @returns * @returns
*/ */
function getProviderSearchString(provider: Provider) { function getProviderSearchString(provider: Provider) {
return provider.isSystem ? `${i18n.t(`provider.${provider.id}`)} ${provider.id}` : provider.name return provider.isSystem ? `${getProviderLabel(provider.id)} ${provider.id}` : provider.name
} }
/** /**

View File

@ -1,4 +1,4 @@
import i18n from '@renderer/i18n' import { getProviderLabel } from '@renderer/i18n/label'
import { Provider } from '@renderer/types' import { Provider } from '@renderer/types'
/** /**
@ -82,7 +82,7 @@ export const getLowerBaseModelName = (id: string, delimiter: string = '/'): stri
* @returns * @returns
*/ */
export const getFancyProviderName = (provider: Provider) => { export const getFancyProviderName = (provider: Provider) => {
return provider.isSystem ? i18n.t(`provider.${provider.id}`) : provider.name return provider.isSystem ? getProviderLabel(provider.id) : provider.name
} }
/** /**