perf(i18n): improve performance when getting i18n text (#8548)

* refactor(i18n): 重构国际化标签映射为独立的键值映射对象

对象定义移出函数以优化性能

* docs(i18n): 更新国际化文档中的动态翻译推荐做法

修改中英文文档,添加通过维护键映射表来避免动态翻译键缺失的最佳实践

* chore(ProviderService): 添加注释

* chore: 移动注释位置

* refactor(ProviderService): 将获取提供者名称的逻辑移到utils中

移除冗余代码并使用统一的工具函数getFancyProviderName来获取提供者名称
This commit is contained in:
Phantom 2025-07-27 17:30:52 +08:00 committed by GitHub
parent 46d98c2b22
commit 8ffdb4d1c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 248 additions and 211 deletions

View File

@ -84,15 +84,21 @@ Since the plugin cannot track such usages, developers must manually verify the e
### Recommended Approach
To avoid missing keys, all dynamically translated texts should first maintain a `FooKeyMap`, then retrieve the translation text through a function.
For example:
```ts
const fruitLabels = {
apple: t('fruits.apple'),
banana: t('fruits.banana')
// src/renderer/src/i18n/label.ts
const themeModeKeyMap = {
dark: 'settings.theme.dark',
light: 'settings.theme.light',
system: 'settings.theme.system'
} as const
const fruit = getFruit()
const label = fruitLabels[fruit]
export const getThemeModeLabel = (key: string): string => {
return themeModeKeyMap[key] ? t(themeModeKeyMap[key]) : key
}
```
By avoiding template strings, you gain better developer experience, more reliable translation checks, and a more maintainable codebase.

View File

@ -78,15 +78,21 @@ i18n ally是一个强大的VSCode插件它能在开发阶段提供实时反
### 推荐做法
为了避免键的缺失,所有需要动态翻译的文本都应当先维护一个`FooKeyMap`,再通过函数获取翻译文本。
例如:
```ts
const fruitLabels = {
apple: t('fruits.apple'),
banana: t('fruits.banana')
// src/renderer/src/i18n/label.ts
const themeModeKeyMap = {
dark: 'settings.theme.dark',
light: 'settings.theme.light',
system: 'settings.theme.system'
} as const
const fruit = getFruit()
const label = fruitLabels[fruit]
export const getThemeModeLabel = (key: string): string => {
return themeModeKeyMap[key] ? t(themeModeKeyMap[key]) : key
}
```
通过避免模板字符串,可以获得更好的开发体验、更可靠的翻译检查以及更易维护的代码库。

View File

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

View File

@ -1,6 +1,6 @@
import { getProviderLabel } from '@renderer/i18n/label'
import store from '@renderer/store'
import { Provider } from '@renderer/types'
import { getFancyProviderName } from '@renderer/utils'
export function getProviderName(id: string) {
const provider = store.getState().llm.providers.find((p) => p.id === id)
@ -8,11 +8,7 @@ export function getProviderName(id: string) {
return ''
}
if (provider.isSystem) {
return getProviderLabel(provider.id) ?? provider.name
}
return provider?.name
return getFancyProviderName(provider)
}
export function isProviderSupportAuth(provider: Provider) {