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.sortKeys": true, //
"i18n-ally.sourceLanguage": "zh-cn", //
"i18n-ally.usage.derivedKeyRules": ["{key}_one", "{key}_other"], //
"search.exclude": {
"**/dist/**": 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 { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
import { useProvider } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings'
import { useAppDispatch } from '@renderer/store'
import { setModel } from '@renderer/store/assistants'
@ -141,7 +142,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
{docsWebsite && (
<SettingHelpLink target="_blank" href={docsWebsite}>
{t(`provider.${provider.id}`) + ' '}
{getProviderLabel(provider.id) + ' '}
{t('common.docs')}
</SettingHelpLink>
)}

View File

@ -1,3 +1,4 @@
import { getProviderLabel } from '@renderer/i18n/label'
import { Provider } from '@renderer/types'
import { oauthWithAihubmix, oauthWithPPIO, oauthWithSiliconFlow, oauthWithTokenFlux } from '@renderer/utils/oauth'
import { Button, ButtonProps } from 'antd'
@ -39,7 +40,7 @@ const OAuthButton: FC<Props> = ({ provider, onSuccess, ...buttonProps }) => {
return (
<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>
)
}

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { getSidebarIconLabel, getThemeModeLabel } from '@renderer/i18n/label'
import { ThemeMode } from '@renderer/types'
import { isEmoji } from '@renderer/utils'
import { Avatar, Tooltip } from 'antd'
@ -104,7 +105,7 @@ const Sidebar: FC = () => {
</Icon>
</Tooltip>
<Tooltip
title={t('settings.theme.title') + ': ' + t(`settings.theme.${theme}`)}
title={t('settings.theme.title') + ': ' + getThemeModeLabel(theme)}
mouseEnterDelay={0.8}
placement="right">
<Icon theme={theme} onClick={() => setTheme(theme === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark)}>
@ -129,7 +130,6 @@ const Sidebar: FC = () => {
const MainMenus: FC = () => {
const { hideMinappPopup } = useMinappPopup()
const { t } = useTranslation()
const { pathname } = useLocation()
const { sidebarIcons, defaultPaintingProvider } = useSettings()
const { minappShow } = useRuntime()
@ -164,7 +164,7 @@ const MainMenus: FC = () => {
const isActive = path === '/' ? isRoute(path) : isRoutes(path)
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
onClick={async () => {
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": {
"create": "快速创建",
"importFrom": {
"connectionFailed": "連接失敗",
"connectionFailed": "连接失败",
"dxt": "导入 DXT 包",
"dxtFile": "DXT 包文件",
"dxtHelp": "选择包含 MCP 服务器的 .dxt 文件",

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import { getHttpMessageLabel } from '@renderer/i18n/label'
import type { ErrorMessageBlock } from '@renderer/types/newMessage'
import { Alert as AntdAlert } from 'antd'
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]
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) {

View File

@ -72,7 +72,7 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
<Tooltip
title={
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')
}
placement="top"

View File

@ -9,6 +9,7 @@ import {
} from '@hello-pangea/dnd'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { getMiniappsStatusLabel } from '@renderer/i18n/label'
import { MinAppType } from '@renderer/types'
import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
@ -115,7 +116,7 @@ const MiniAppIconsManager: FC<MiniAppManagerProps> = ({
<ProgramSection style={{ background: 'transparent' }}>
{(['visible', 'disabled'] as const).map((listType) => (
<ProgramColumn key={listType}>
<h4>{t(`settings.miniapps.${listType}`)}</h4>
<h4>{getMiniappsStatusLabel(listType)}</h4>
<Droppable droppableId={listType}>
{(provided: DroppableProvided) => (
<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 { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { getProviderLabel } from '@renderer/i18n/label'
import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store'
@ -58,9 +59,16 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders()
const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option)
if (provider) {
return {
label: t(`provider.${provider?.id}`),
value: provider?.id
label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
}
})
const dispatch = useAppDispatch()

View File

@ -9,6 +9,7 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { getProviderLabel } from '@renderer/i18n/label'
import FileManager from '@renderer/services/FileManager'
import { useAppDispatch } from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime'
@ -49,9 +50,16 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders()
const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option)
if (provider) {
return {
label: t(`provider.${provider?.id}`),
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 { useRuntime } from '@renderer/hooks/useRuntime'
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 { DEFAULT_PAINTING, MODELS, SUPPORTED_MODELS } from '@renderer/pages/paintings/config/NewApiConfig'
import FileManager from '@renderer/services/FileManager'
@ -53,9 +60,16 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
const providers = useAllProviders()
const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option)
if (provider) {
return {
label: t(`provider.${provider?.id}`),
value: provider?.id
label: getProviderLabel(provider.id),
value: provider.id
}
} else {
return {
label: 'Unknown Provider',
value: undefined
}
}
})
const dispatch = useAppDispatch()
@ -566,7 +580,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
<Select value={painting.size} onChange={handleSizeChange} style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.imageSizes.map((s) => (
<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>
@ -583,7 +597,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.quality.map((q) => (
<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>
@ -602,7 +616,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.moderation.map((m) => (
<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>
@ -621,7 +635,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: '100%', marginBottom: 15 }}>
{selectedModelConfig.background.map((b) => (
<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>

View File

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

View File

@ -10,6 +10,7 @@ import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { getProviderLabel } from '@renderer/i18n/label'
import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store'
@ -55,9 +56,16 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
const providerOptions = Options.map((option) => {
const provider = providers.find((p) => p.id === option)
if (provider) {
return {
label: t(`provider.${provider?.id}`),
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 = [
{ 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: 'local_backup', title: 'settings.data.local.title', icon: <FolderCog size={16} /> },
{ key: 'webdav', title: 'settings.data.webdav.title', icon: <CloudSyncOutlined style={{ fontSize: 16 }} /> },
{ key: 'nutstore', title: 'settings.data.nutstore.title', icon: <NutstoreIcon /> },
{ key: 's3', title: 'settings.data.s3.title', icon: <CloudServerOutlined style={{ fontSize: 16 }} /> },
{ key: 'local_backup', title: t('settings.data.local.title'), icon: <FolderCog size={16} /> },
{ key: 'webdav', title: t('settings.data.webdav.title'), icon: <CloudSyncOutlined style={{ fontSize: 16 }} /> },
{ key: 'nutstore', title: t('settings.data.nutstore.title'), icon: <NutstoreIcon /> },
{ 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: 'export_menu',
title: 'settings.data.export_menu.title',
title: t('settings.data.export_menu.title'),
icon: <FolderInput size={16} />
},
{
key: 'markdown_export',
title: 'settings.data.markdown_export.title',
title: t('settings.data.markdown_export.title'),
icon: <FileText size={16} />
},
{ 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',
title: 'settings.data.yuque.title',
title: t('settings.data.yuque.title'),
icon: <YuqueOutlined style={{ fontSize: 16 }} />
},
{
key: 'joplin',
title: 'settings.data.joplin.title',
title: t('settings.data.joplin.title'),
icon: <JoplinIcon />
},
{
key: 'obsidian',
title: 'settings.data.obsidian.title',
title: t('settings.data.obsidian.title'),
icon: <i className="iconfont icon-obsidian" />
},
{
key: 'siyuan',
title: 'settings.data.siyuan.title',
title: t('settings.data.siyuan.title'),
icon: <SiyuanIcon />
},
{
key: 'agentssubscribe_url',
title: 'agents.settings.title',
title: t('agents.settings.title'),
icon: <Sparkle size={16} className="icon" />
}
]
@ -568,7 +568,7 @@ const DataSettings: FC = () => {
) : (
<ListItem
key={item.key}
title={t(item.title || '')}
title={item.title}
active={menu === item.key}
onClick={() => setMenu(item.key)}
titleStyle={{ fontWeight: 500 }}

View File

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

View File

@ -236,7 +236,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
.catch((connError: any) => {
logger.error(`Connectivity check failed for ${newServer.name}:`, connError)
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'
})
})

View File

@ -1,5 +1,6 @@
import { CheckOutlined, PlusOutlined } from '@ant-design/icons'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { getMcpTypeLabel } from '@renderer/i18n/label'
import { builtinMCPServers } from '@renderer/store/mcp'
import { Button, Popover, Tag } from 'antd'
import { FC } from 'react'
@ -55,7 +56,7 @@ const BuiltinMCPServersSection: FC = () => {
</Popover>
<ServerFooter>
<Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
{t(`settings.mcp.types.${server.type || 'stdio'}`)}
{getMcpTypeLabel(server.type ?? 'stdio')}
</Tag>
{server.env && Object.keys(server.env).length > 0 && (
<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 Scrollbar from '@renderer/components/Scrollbar'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { getMcpTypeLabel } from '@renderer/i18n/label'
import { MCPServer } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error'
import { Badge, Button, Dropdown, Empty, Switch, Tag } from 'antd'
@ -221,7 +222,7 @@ const McpServersList: FC = () => {
<ServerDescription>{server.description}</ServerDescription>
<ServerFooter>
<Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
{t(`settings.mcp.types.${server.type || 'stdio'}`)}
{getMcpTypeLabel(server.type ?? 'stdio')}
</Tag>
{server.provider && (
<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 { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useProvider } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'
import { providerBills, providerCharge } from '@renderer/utils/oauth'
import { Button } from 'antd'
import { isEmpty } from 'lodash'
@ -44,7 +45,7 @@ const ProviderOAuth: FC<Props> = ({ providerId }) => {
<ProviderLogo src={PROVIDER_LOGO_MAP[provider.id]} />
{isEmpty(provider.apiKey) ? (
<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>
) : (
<HStack gap={10}>

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { HStack } from '@renderer/components/Layout'
import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useShortcuts } from '@renderer/hooks/useShortcuts'
import { getShortcutLabel } from '@renderer/i18n/label'
import { useAppDispatch } from '@renderer/store'
import { initialState, resetShortcuts, toggleShortcut, updateShortcut } from '@renderer/store/shortcuts'
import { Shortcut } from '@renderer/types'
@ -400,7 +401,7 @@ const ShortcutSettings: FC = () => {
<SettingDivider style={{ marginBottom: 0 }} />
<Table
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}
size="middle"
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 { Provider } from '@renderer/types'
@ -9,7 +9,7 @@ export function getProviderName(id: string) {
}
if (provider.isSystem) {
return i18n.t(`provider.${provider.id}`, { defaultValue: provider.name })
return getProviderLabel(provider.id) ?? provider.name
}
return provider?.name

View File

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

View File

@ -83,7 +83,7 @@ describe('match', () => {
})
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)
})
})
@ -108,8 +108,8 @@ describe('match', () => {
})
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('i18n:provider.sys', model, sysProvider)).toBe(true)
expect(matchKeywordsInModel('gpt-4.1 sys', model, sysProvider)).toBe(true)
expect(matchKeywordsInModel('sys', model, sysProvider)).toBe(true)
expect(matchKeywordsInModel('SystemProvider', model, sysProvider)).toBe(false)
})

View File

@ -1,6 +1,7 @@
import { loggerService } from '@logger'
import { Client } from '@notionhq/client'
import i18n from '@renderer/i18n'
import { getProviderLabel } from '@renderer/i18n/label'
import { getMessageTitle } from '@renderer/services/MessagesService'
import store from '@renderer/store'
import { setExportState } from '@renderer/store/runtime'
@ -56,7 +57,7 @@ export function getTitleFromString(str: string, length: number = 80) {
return title
}
const getRoleText = (role: string, modelName?: string, modelProvider?: string) => {
const getRoleText = (role: string, modelName?: string, providerId?: string) => {
const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings
if (role === 'user') {
@ -67,14 +68,14 @@ const getRoleText = (role: string, modelName?: string, modelProvider?: string) =
let assistantText = '🤖 '
if (showModelNameInMarkdown && modelName) {
assistantText += `${modelName}`
if (showModelProviderInMarkdown && modelProvider) {
const providerDisplayName = i18n.t(`provider.${modelProvider}`, { defaultValue: modelProvider })
if (showModelProviderInMarkdown && providerId) {
const providerDisplayName = getProviderLabel(providerId) ?? providerId
assistantText += ` | ${providerDisplayName}`
return assistantText
}
return assistantText
} else if (showModelProviderInMarkdown && modelProvider) {
const providerDisplayName = i18n.t(`provider.${modelProvider}`, { defaultValue: modelProvider })
} else if (showModelProviderInMarkdown && providerId) {
const providerDisplayName = getProviderLabel(providerId) ?? providerId
assistantText += `Assistant | ${providerDisplayName}`
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'
/**
@ -43,6 +43,8 @@ export function matchKeywordsInString(keywords: string | string[], value: string
* @returns true
*/
export function matchKeywordsInProvider(keywords: string | string[], provider: Provider): boolean {
console.log(keywords, provider.id)
console.log(getProviderSearchString(provider))
return includeKeywords(getProviderSearchString(provider), keywords)
}
@ -64,7 +66,7 @@ export function matchKeywordsInModel(keywords: string | string[], model: Model,
* @returns
*/
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'
/**
@ -82,7 +82,7 @@ export const getLowerBaseModelName = (id: string, delimiter: string = '/'): stri
* @returns
*/
export const getFancyProviderName = (provider: Provider) => {
return provider.isSystem ? i18n.t(`provider.${provider.id}`) : provider.name
return provider.isSystem ? getProviderLabel(provider.id) : provider.name
}
/**