diff --git a/docs/technical/how-to-i18n-en.md b/docs/technical/how-to-i18n-en.md index 861810dc6b..1bbf7edca8 100644 --- a/docs/technical/how-to-i18n-en.md +++ b/docs/technical/how-to-i18n-en.md @@ -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. diff --git a/docs/technical/how-to-i18n-zh.md b/docs/technical/how-to-i18n-zh.md index e4fd9c637d..5d0a93c369 100644 --- a/docs/technical/how-to-i18n-zh.md +++ b/docs/technical/how-to-i18n-zh.md @@ -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 +} ``` 通过避免模板字符串,可以获得更好的开发体验、更可靠的翻译检查以及更易维护的代码库。 diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 5e84c9bf4a..bb1cadb766 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -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 } diff --git a/src/renderer/src/services/ProviderService.ts b/src/renderer/src/services/ProviderService.ts index 48a20b0533..51d62c251d 100644 --- a/src/renderer/src/services/ProviderService.ts +++ b/src/renderer/src/services/ProviderService.ts @@ -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) {