From aad4b4d5514caf727fdf4485bf9e4d4c6b8226c9 Mon Sep 17 00:00:00 2001 From: letr Date: Sat, 7 Feb 2026 02:26:44 +0800 Subject: [PATCH 1/7] fix: localize provider source ui --- dashboard/src/components/provider/ProviderSourcesPanel.vue | 2 +- dashboard/src/composables/useProviderSources.ts | 4 ++-- dashboard/src/i18n/locales/en-US/features/provider.json | 3 ++- dashboard/src/i18n/locales/zh-CN/features/provider.json | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dashboard/src/components/provider/ProviderSourcesPanel.vue b/dashboard/src/components/provider/ProviderSourcesPanel.vue index 6f65af67b2..adca108325 100644 --- a/dashboard/src/components/provider/ProviderSourcesPanel.vue +++ b/dashboard/src/components/provider/ProviderSourcesPanel.vue @@ -14,7 +14,7 @@ rounded="xl" size="small" > - 新增 + {{ tm('providerSources.add') }} diff --git a/dashboard/src/composables/useProviderSources.ts b/dashboard/src/composables/useProviderSources.ts index 07ac5aa150..dc573818ab 100644 --- a/dashboard/src/composables/useProviderSources.ts +++ b/dashboard/src/composables/useProviderSources.ts @@ -59,14 +59,14 @@ export function useProviderSources(options: UseProviderSourcesOptions) { let suppressSourceWatch = false - const providerTypes = [ + const providerTypes = computed(() => [ { value: 'chat_completion', label: tm('providers.tabs.chatCompletion'), icon: 'mdi-message-text' }, { value: 'agent_runner', label: tm('providers.tabs.agentRunner'), icon: 'mdi-robot' }, { value: 'speech_to_text', label: tm('providers.tabs.speechToText'), icon: 'mdi-microphone-message' }, { value: 'text_to_speech', label: tm('providers.tabs.textToSpeech'), icon: 'mdi-volume-high' }, { value: 'embedding', label: tm('providers.tabs.embedding'), icon: 'mdi-code-json' }, { value: 'rerank', label: tm('providers.tabs.rerank'), icon: 'mdi-compare-vertical' } - ] + ]) // ===== Computed ===== const availableSourceTypes = computed(() => { diff --git a/dashboard/src/i18n/locales/en-US/features/provider.json b/dashboard/src/i18n/locales/en-US/features/provider.json index 8d80d0b0b8..505e5b729a 100644 --- a/dashboard/src/i18n/locales/en-US/features/provider.json +++ b/dashboard/src/i18n/locales/en-US/features/provider.json @@ -91,6 +91,7 @@ }, "providerSources": { "title": "Provider Sources", + "add": "Add", "empty": "No provider sources", "selectHint": "Please select a provider source", "save": "Save Configuration", @@ -141,4 +142,4 @@ "modelId": "Model ID" } } -} \ No newline at end of file +} diff --git a/dashboard/src/i18n/locales/zh-CN/features/provider.json b/dashboard/src/i18n/locales/zh-CN/features/provider.json index 10d8edc7c0..76cd347db9 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/provider.json +++ b/dashboard/src/i18n/locales/zh-CN/features/provider.json @@ -92,6 +92,7 @@ }, "providerSources": { "title": "提供商源", + "add": "新增", "empty": "暂无提供商源", "selectHint": "请选择一个提供商源", "save": "保存配置", @@ -142,4 +143,4 @@ "modelId": "模型 ID" } } -} \ No newline at end of file +} From 79297396991348918615d28eb5ea13b75c0817ea Mon Sep 17 00:00:00 2001 From: letr Date: Sat, 7 Feb 2026 02:26:59 +0800 Subject: [PATCH 2/7] feat: localize provider metadata keys --- astrbot/core/config/i18n_utils.py | 95 +++++++++++-------- astrbot/dashboard/routes/config.py | 14 ++- .../src/components/shared/AstrBotConfig.vue | 20 ++-- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/astrbot/core/config/i18n_utils.py b/astrbot/core/config/i18n_utils.py index aa441c0c16..6a2048b52a 100644 --- a/astrbot/core/config/i18n_utils.py +++ b/astrbot/core/config/i18n_utils.py @@ -42,6 +42,55 @@ def convert_to_i18n_keys(metadata: dict[str, Any]) -> dict[str, Any]: """ result = {} + def convert_items( + group: str, section: str, items: dict[str, Any], prefix: str = "" + ) -> dict[str, Any]: + items_result: dict[str, Any] = {} + + for field_key, field_data in items.items(): + if not isinstance(field_data, dict): + items_result[field_key] = field_data + continue + + field_name = field_key + field_path = f"{prefix}.{field_name}" if prefix else field_name + + field_result = { + key: value + for key, value in field_data.items() + if key not in {"description", "hint", "labels", "name"} + } + + if "description" in field_data: + field_result["description"] = ( + f"{group}.{section}.{field_path}.description" + ) + if "hint" in field_data: + field_result["hint"] = f"{group}.{section}.{field_path}.hint" + if "labels" in field_data: + field_result["labels"] = f"{group}.{section}.{field_path}.labels" + if "name" in field_data: + field_result["name"] = f"{group}.{section}.{field_path}.name" + + if "items" in field_data and isinstance(field_data["items"], dict): + field_result["items"] = convert_items( + group, section, field_data["items"], field_path + ) + + if "template_schema" in field_data and isinstance( + field_data["template_schema"], dict + ): + field_result["template_schema"] = convert_items( + group, + section, + field_data["template_schema"], + f"{field_path}.template_schema", + ) + + items_result[field_key] = field_result + + return items_result + for group_key, group_data in metadata.items(): group_result = { "name": f"{group_key}.name", @@ -54,55 +103,17 @@ def convert_to_i18n_keys(metadata: dict[str, Any]) -> dict[str, Any]: "type": section_data.get("type"), } - # 复制其他属性 - for key in ["items", "condition", "_special", "invisible"]: + for key in ["items", "condition", "_special", "invisible", "config_template"]: if key in section_data: section_result[key] = section_data[key] - # 处理 hint if "hint" in section_data: section_result["hint"] = f"{group_key}.{section_key}.hint" - # 处理 items 中的字段 if "items" in section_data and isinstance(section_data["items"], dict): - items_result = {} - for field_key, field_data in section_data["items"].items(): - # 处理嵌套的点号字段名(如 provider_settings.enable) - field_name = field_key - - field_result = {} - - # 复制基本属性 - for attr in [ - "type", - "condition", - "_special", - "invisible", - "options", - "slider", - ]: - if attr in field_data: - field_result[attr] = field_data[attr] - - # 转换文本属性为国际化键 - if "description" in field_data: - field_result["description"] = ( - f"{group_key}.{section_key}.{field_name}.description" - ) - - if "hint" in field_data: - field_result["hint"] = ( - f"{group_key}.{section_key}.{field_name}.hint" - ) - - if "labels" in field_data: - field_result["labels"] = ( - f"{group_key}.{section_key}.{field_name}.labels" - ) - - items_result[field_key] = field_result - - section_result["items"] = items_result + section_result["items"] = convert_items( + group_key, section_key, section_data["items"] + ) group_result["metadata"][section_key] = section_result diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index c5998682c8..8b09ccc698 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -58,6 +58,7 @@ def try_cast(value: Any, type_: str): return None + def _expect_type(value, expected_type, path_key, errors, expected_name=None): if not isinstance(value, expected_type): errors.append( @@ -407,8 +408,19 @@ async def update_provider_source(self): return Response().ok(message="更新 provider source 成功").__dict__ async def get_provider_template(self): + provider_metadata = ConfigMetadataI18n.convert_to_i18n_keys( + { + "provider_group": { + "metadata": { + "provider": CONFIG_METADATA_2["provider_group"]["metadata"][ + "provider" + ] + } + } + } + ) config_schema = { - "provider": CONFIG_METADATA_2["provider_group"]["metadata"]["provider"] + "provider": provider_metadata["provider_group"]["metadata"]["provider"] } data = { "config_schema": config_schema, diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 22dc03807e..12cb0bee1c 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -3,7 +3,7 @@ import { VueMonacoEditor } from '@guolao/vue-monaco-editor' import { ref, computed } from 'vue' import ConfigItemRenderer from './ConfigItemRenderer.vue' import TemplateListEditor from './TemplateListEditor.vue' -import { useI18n } from '@/i18n/composables' +import { useI18n, useModuleI18n } from '@/i18n/composables' import axios from 'axios' import { useToast } from '@/utils/toast' @@ -35,6 +35,12 @@ const props = defineProps({ }) const { t } = useI18n() +const { tm, getRaw } = useModuleI18n('features/config-metadata') + +const translateIfKey = (value) => { + if (!value || typeof value !== 'string') return value + return getRaw(value) ? tm(value) : value +} const filteredIterable = computed(() => { if (!props.iterable) return {} @@ -134,11 +140,11 @@ function hasVisibleItemsAfter(items, currentIndex) {