diff --git a/.vscode/settings.json b/.vscode/settings.json index 73d1c213ff..997c26aedf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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 diff --git a/src/renderer/src/components/ModelList/ModelList.tsx b/src/renderer/src/components/ModelList/ModelList.tsx index 5a3b12b9a0..4ff78f7128 100644 --- a/src/renderer/src/components/ModelList/ModelList.tsx +++ b/src/renderer/src/components/ModelList/ModelList.tsx @@ -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 = ({ providerId }) => { {t('settings.provider.docs_check')} {docsWebsite && ( - {t(`provider.${provider.id}`) + ' '} + {getProviderLabel(provider.id) + ' '} {t('common.docs')} )} diff --git a/src/renderer/src/components/OAuth/OAuthButton.tsx b/src/renderer/src/components/OAuth/OAuthButton.tsx index aafbd81c5a..ad0b806bf8 100644 --- a/src/renderer/src/components/OAuth/OAuthButton.tsx +++ b/src/renderer/src/components/OAuth/OAuthButton.tsx @@ -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 = ({ provider, onSuccess, ...buttonProps }) => { return ( ) } diff --git a/src/renderer/src/components/Popups/BackupPopup.tsx b/src/renderer/src/components/Popups/BackupPopup.tsx index f1e27b1cf2..703e3b4d57 100644 --- a/src/renderer/src/components/Popups/BackupPopup.tsx +++ b/src/renderer/src/components/Popups/BackupPopup.tsx @@ -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 = ({ 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 diff --git a/src/renderer/src/components/Popups/RestorePopup.tsx b/src/renderer/src/components/Popups/RestorePopup.tsx index 59b27fa3cb..27224a98c8 100644 --- a/src/renderer/src/components/Popups/RestorePopup.tsx +++ b/src/renderer/src/components/Popups/RestorePopup.tsx @@ -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 = ({ 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 diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index c821c0fb17..7abcb6bdf1 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -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 = ({ children }) => { - const { t } = useTranslation() const location = useLocation() const navigate = useNavigate() const dispatch = useAppDispatch() @@ -134,7 +133,7 @@ const TabsContainer: React.FC = ({ children }) => { navigate(tab.path)}> {tab.id && {getTabIcon(tab.id)}} - {t(`title.${tab.id}`)} + {getTitleLabel(tab.id)} {tab.id !== 'home' && ( { 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 ( - + { hideMinappPopup() diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts new file mode 100644 index 0000000000..5e84c9bf4a --- /dev/null +++ b/src/renderer/src/i18n/label.ts @@ -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 +} diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 1ccc98dc6c..68064e6466 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2536,7 +2536,7 @@ "addServer": { "create": "快速创建", "importFrom": { - "connectionFailed": "連接失敗", + "connectionFailed": "连接失败", "dxt": "导入 DXT 包", "dxtFile": "DXT 包文件", "dxtHelp": "选择包含 MCP 服务器的 .dxt 文件", diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx index 550f8d551c..3bd3be3c68 100644 --- a/src/renderer/src/pages/files/FilesPage.tsx +++ b/src/renderer/src/pages/files/FilesPage.tsx @@ -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 = () => { - {['created_at', 'size', 'name'].map((field) => ( + {(['created_at', 'size', 'name'] as const).map((field) => ( { setSortOrder('desc') } }}> - {t(`files.${field}`)} + {getFileFieldLabel(field)} {sortField === field && (sortOrder === 'desc' ? : )} ))} diff --git a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx index 7e49a0ba7f..d029d198e2 100644 --- a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx @@ -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 = ({ 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({ diff --git a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx index b20b62bbc9..9d4a68201b 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx @@ -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 + return } if (block?.error?.message) { diff --git a/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx b/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx index c185d509f1..917184f161 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroupModelList.tsx @@ -72,7 +72,7 @@ const MessageGroupModelList: FC = ({ messages, selec = ({ {(['visible', 'disabled'] as const).map((listType) => ( -

{t(`settings.miniapps.${listType}`)}

+

{getMiniappsStatusLabel(listType)}

{(provided: DroppableProvided) => ( diff --git a/src/renderer/src/pages/paintings/AihubmixPage.tsx b/src/renderer/src/pages/paintings/AihubmixPage.tsx index 2b6d8e27e1..4d5d8f0f75 100644 --- a/src/renderer/src/pages/paintings/AihubmixPage.tsx +++ b/src/renderer/src/pages/paintings/AihubmixPage.tsx @@ -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) - return { - label: t(`provider.${provider?.id}`), - value: provider?.id + if (provider) { + return { + label: getProviderLabel(provider.id), + value: provider.id + } + } else { + return { + label: 'Unknown Provider', + value: undefined + } } }) const dispatch = useAppDispatch() diff --git a/src/renderer/src/pages/paintings/DmxapiPage.tsx b/src/renderer/src/pages/paintings/DmxapiPage.tsx index 02961f0ef1..e4e9647981 100644 --- a/src/renderer/src/pages/paintings/DmxapiPage.tsx +++ b/src/renderer/src/pages/paintings/DmxapiPage.tsx @@ -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) - return { - label: t(`provider.${provider?.id}`), - value: provider?.id + if (provider) { + return { + label: getProviderLabel(provider.id), + value: provider.id + } + } else { + return { + label: 'Unknown Provider', + value: undefined + } } }) diff --git a/src/renderer/src/pages/paintings/NewApiPage.tsx b/src/renderer/src/pages/paintings/NewApiPage.tsx index 672e40a6c4..e1e1869c6a 100644 --- a/src/renderer/src/pages/paintings/NewApiPage.tsx +++ b/src/renderer/src/pages/paintings/NewApiPage.tsx @@ -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) - return { - label: t(`provider.${provider?.id}`), - value: provider?.id + if (provider) { + return { + 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 }) => { @@ -583,7 +597,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { style={{ width: '100%', marginBottom: 15 }}> {selectedModelConfig.quality.map((q) => ( - {t(`paintings.quality_options.${q.value}`, { defaultValue: q.value })} + {getPaintingsQualityOptionsLabel(q.value) ?? q.value} ))} @@ -602,7 +616,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { style={{ width: '100%', marginBottom: 15 }}> {selectedModelConfig.moderation.map((m) => ( - {t(`paintings.moderation_options.${m.value}`, { defaultValue: m.value })} + {getPaintingsModerationOptionsLabel(m.value) ?? m.value} ))} @@ -621,7 +635,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { style={{ width: '100%', marginBottom: 15 }}> {selectedModelConfig.background.map((b) => ( - {t(`paintings.background_options.${b.value}`, { defaultValue: b.value })} + {getPaintingsBackgroundOptionsLabel(b.value) ?? b.value} ))} diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index 1fc3a30633..1d09bcfb96 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -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) - return { - label: t(`provider.${provider?.id}`), - value: provider?.id + if (provider) { + return { + label: getProviderLabel(provider.id), + value: provider.id + } + } else { + return { + label: 'Unknown Provider', + value: undefined + } } }) const [currentImageIndex, setCurrentImageIndex] = useState(0) diff --git a/src/renderer/src/pages/paintings/TokenFluxPage.tsx b/src/renderer/src/pages/paintings/TokenFluxPage.tsx index e31b9827ff..100550e6c7 100644 --- a/src/renderer/src/pages/paintings/TokenFluxPage.tsx +++ b/src/renderer/src/pages/paintings/TokenFluxPage.tsx @@ -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) - return { - label: t(`provider.${provider?.id}`), - value: provider?.id + if (provider) { + return { + label: getProviderLabel(provider.id), + value: provider.id + } + } else { + return { + label: 'Unknown Provider', + value: undefined + } } }) diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx index 381a63d3b0..47c16859ea 100644 --- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx @@ -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: }, + { key: 'data', title: t('settings.data.data.title'), icon: }, { key: 'divider_1', isDivider: true, text: t('settings.data.divider.cloud_storage') }, - { key: 'local_backup', title: 'settings.data.local.title', icon: }, - { key: 'webdav', title: 'settings.data.webdav.title', icon: }, - { key: 'nutstore', title: 'settings.data.nutstore.title', icon: }, - { key: 's3', title: 'settings.data.s3.title', icon: }, + { key: 'local_backup', title: t('settings.data.local.title'), icon: }, + { key: 'webdav', title: t('settings.data.webdav.title'), icon: }, + { key: 'nutstore', title: t('settings.data.nutstore.title'), icon: }, + { key: 's3', title: t('settings.data.s3.title.label'), icon: }, { 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: }, { key: 'markdown_export', - title: 'settings.data.markdown_export.title', + title: t('settings.data.markdown_export.title'), icon: }, { key: 'divider_3', isDivider: true, text: t('settings.data.divider.third_party') }, - { key: 'notion', title: 'settings.data.notion.title', icon: }, + { key: 'notion', title: t('settings.data.notion.title'), icon: }, { key: 'yuque', - title: 'settings.data.yuque.title', + title: t('settings.data.yuque.title'), icon: }, { key: 'joplin', - title: 'settings.data.joplin.title', + title: t('settings.data.joplin.title'), icon: }, { key: 'obsidian', - title: 'settings.data.obsidian.title', + title: t('settings.data.obsidian.title'), icon: }, { key: 'siyuan', - title: 'settings.data.siyuan.title', + title: t('settings.data.siyuan.title'), icon: }, { key: 'agentssubscribe_url', - title: 'agents.settings.title', + title: t('agents.settings.title'), icon: } ] @@ -568,7 +568,7 @@ const DataSettings: FC = () => { ) : ( setMenu(item.key)} titleStyle={{ fontWeight: 500 }} diff --git a/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx b/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx index 89aa11ca4b..c19fabc70f 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx @@ -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 = ({ {renderIcon(icon)} - {t(`${icon}.title`)} + {getSidebarIconLabel(icon)} {icon !== 'assistants' && ( onMoveIcon(icon, 'visible')}> @@ -166,7 +167,7 @@ const SidebarIconsManager: FC = ({ {renderIcon(icon)} - {t(`${icon}.title`)} + {getSidebarIconLabel(icon)} onMoveIcon(icon, 'disabled')}> diff --git a/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx b/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx index 85e2dc4525..f060df5df5 100644 --- a/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx @@ -236,7 +236,7 @@ const AddMcpServerModal: FC = ({ .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' }) }) diff --git a/src/renderer/src/pages/settings/MCPSettings/BuiltinMCPServersSection.tsx b/src/renderer/src/pages/settings/MCPSettings/BuiltinMCPServersSection.tsx index bf64898733..25500fb16b 100644 --- a/src/renderer/src/pages/settings/MCPSettings/BuiltinMCPServersSection.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/BuiltinMCPServersSection.tsx @@ -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 = () => { - {t(`settings.mcp.types.${server.type || 'stdio'}`)} + {getMcpTypeLabel(server.type ?? 'stdio')} {server.env && Object.keys(server.env).length > 0 && ( diff --git a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx index 41c290cada..1cb7c7fab2 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx @@ -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 = () => { {server.description} - {t(`settings.mcp.types.${server.type || 'stdio'}`)} + {getMcpTypeLabel(server.type ?? 'stdio')} {server.provider && ( diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx index 9615b65ac2..86d151a27b 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx @@ -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 = ({ providerId }) => { {isEmpty(provider.apiKey) ? ( - {t('settings.provider.oauth.button', { provider: t(`provider.${provider.id}`) })} + {t('settings.provider.oauth.button', { provider: getProviderLabel(provider.id) })} ) : ( diff --git a/src/renderer/src/pages/settings/ProviderSettings/index.tsx b/src/renderer/src/pages/settings/ProviderSettings/index.tsx index 3d72d2fb65..351c5a58f2 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/index.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/index.tsx @@ -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 diff --git a/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx b/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx index 1707b10dd7..d9e284a588 100644 --- a/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx +++ b/src/renderer/src/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings.tsx @@ -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 = () => {
{t('selection.settings.toolbar.trigger_mode.title')}
- + {/* FIXME: 没有考虑Linux? */} +
diff --git a/src/renderer/src/pages/settings/ShortcutSettings.tsx b/src/renderer/src/pages/settings/ShortcutSettings.tsx index 635d63ec09..4eb91e9c05 100644 --- a/src/renderer/src/pages/settings/ShortcutSettings.tsx +++ b/src/renderer/src/pages/settings/ShortcutSettings.tsx @@ -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 = () => { } - 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} diff --git a/src/renderer/src/services/ProviderService.ts b/src/renderer/src/services/ProviderService.ts index 644f75104c..48a20b0533 100644 --- a/src/renderer/src/services/ProviderService.ts +++ b/src/renderer/src/services/ProviderService.ts @@ -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 diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index cb061de1bd..4853bbd0d0 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -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 }) diff --git a/src/renderer/src/utils/__tests__/match.test.ts b/src/renderer/src/utils/__tests__/match.test.ts index 37f70f08ba..e7263765af 100644 --- a/src/renderer/src/utils/__tests__/match.test.ts +++ b/src/renderer/src/utils/__tests__/match.test.ts @@ -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) }) diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 6cefff8cb8..9e9dc10014 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -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 } diff --git a/src/renderer/src/utils/match.ts b/src/renderer/src/utils/match.ts index 9957e22326..11e8af52b0 100644 --- a/src/renderer/src/utils/match.ts +++ b/src/renderer/src/utils/match.ts @@ -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 } /** diff --git a/src/renderer/src/utils/naming.ts b/src/renderer/src/utils/naming.ts index ab77327bc0..6e4eeba728 100644 --- a/src/renderer/src/utils/naming.ts +++ b/src/renderer/src/utils/naming.ts @@ -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 } /**