fix: localize provider source config UI (#4933)

* fix: localize provider source ui

* feat: localize provider metadata keys

* chore: add provider metadata translations

* chore: format provider i18n changes

* fix: preserve metadata fields in i18n conversion

* fix: internationalize platform config and dialog

* fix: add Weixin official account platform icon

---------

Co-authored-by: Soulter <905617992@qq.com>
This commit is contained in:
letr
2026-02-08 10:40:26 +08:00
committed by GitHub
parent 5e5207da95
commit e48950d260
13 changed files with 1621 additions and 125 deletions

View File

@@ -9,14 +9,14 @@
</div>
<div style="flex: 1;">
<h3>
选择消息平台类别
{{ tm('createDialog.step1Title') }}
</h3>
<small style="color: grey;">想把机器人接入到哪里 QQ企业微信飞书DiscordTelegram </small>
<small style="color: grey;">{{ tm('createDialog.step1Hint') }}</small>
<div>
<div v-if="!updatingMode">
<v-select v-model="selectedPlatformType" :items="Object.keys(platformTemplates)" item-title="name"
item-value="name" label="消息平台类别" variant="outlined" rounded="md" dense hide-details class="mt-6"
item-value="name" :label="tm('createDialog.platformTypeLabel')" variant="outlined" rounded="md" dense hide-details class="mt-6"
style="max-width: 30%; min-width: 300px;">
<template v-slot:item="{ props: itemProps, item }">
@@ -41,7 +41,7 @@
</div>
</div>
<div v-else>
<v-text-field label="消息平台类别" variant="outlined" rounded="md" dense hide-details class="mt-6"
<v-text-field :label="tm('createDialog.platformTypeLabel')" variant="outlined" rounded="md" dense hide-details class="mt-6"
style="max-width: 30%; min-width: 300px;" v-model="updatingPlatformConfig.type"
disabled></v-text-field>
<div class="mt-3">
@@ -65,13 +65,13 @@
<div>
<div class="d-flex align-center">
<h3>
配置文件
{{ tm('createDialog.configFileTitle') }}
</h3>
<v-chip size="x-small" color="primary" variant="tonal" rounded="sm" class="ml-2"
v-if="!updatingMode">可选</v-chip>
v-if="!updatingMode">{{ tm('createDialog.optional') }}</v-chip>
</div>
<small style="color: grey;">想如何配置机器人配置文件包含了聊天模型人格知识库插件范围等丰富的机器人配置项</small>
<small style="color: grey;" v-if="!updatingMode">默认使用默认配置文件 default您也可以稍后配置</small>
<small style="color: grey;">{{ tm('createDialog.configHint') }}</small>
<small style="color: grey;" v-if="!updatingMode">{{ tm('createDialog.configDefaultHint') }}</small>
</div>
<div>
<v-btn variant="plain" icon @click="toggleConfigSection" class="mt-2">
@@ -86,12 +86,12 @@
<v-radio-group class="mt-2" v-model="aBConfigRadioVal" hide-details="true">
<v-radio value="0">
<template v-slot:label>
<span>使用现有配置文件</span>
<span>{{ tm('createDialog.useExistingConfig') }}</span>
</template>
</v-radio>
<div class="d-flex align-center ml-10 my-2" v-if="aBConfigRadioVal === '0'">
<v-select v-model="selectedAbConfId" :items="configInfoList" item-title="name"
item-value="id" label="选择配置文件" variant="outlined" rounded="md" dense hide-details
item-value="id" :label="tm('createDialog.selectConfigLabel')" variant="outlined" rounded="md" dense hide-details
style="max-width: 30%; min-width: 200px;">
</v-select>
<v-btn icon variant="text" density="comfortable" class="ml-2"
@@ -99,10 +99,10 @@
<v-icon>mdi-arrow-top-right-thick</v-icon>
</v-btn>
</div>
<v-radio value="1" label="创建新配置文件">
<v-radio value="1" :label="tm('createDialog.createNewConfig')">
</v-radio>
<div class="d-flex align-center" v-if="aBConfigRadioVal === '1'">
<v-text-field v-model="selectedAbConfId" label="新配置文件名称" variant="outlined" rounded="md" dense
<v-text-field v-model="selectedAbConfId" :label="tm('createDialog.newConfigNameLabel')" variant="outlined" rounded="md" dense
hide-details style="max-width: 30%; min-width: 200px;" class="ml-10 my-2">
</v-text-field>
</div>
@@ -131,12 +131,12 @@
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</div>
<div v-else-if="newConfigData && newConfigMetadata" class="config-preview-container">
<h4 class="mb-3">使用新的配置文件</h4>
<h4 class="mb-3">{{ tm('createDialog.newConfigTitle') }}</h4>
<AstrBotCoreConfigWrapper :metadata="newConfigMetadata" :config_data="newConfigData" />
</div>
<div v-else class="text-center py-4 text-grey">
<v-icon>mdi-information-outline</v-icon>
<p class="mt-2">无法加载默认配置模板</p>
<p class="mt-2">{{ tm('createDialog.newConfigLoadFailed') }}</p>
</div>
</div>
@@ -147,18 +147,18 @@
<div>
<v-btn v-if="isEditingRoutes" color="primary" variant="tonal" @click="addNewRoute" size="small">
<v-icon start>mdi-plus</v-icon>
添加路由规则
{{ tm('createDialog.addRouteRule') }}
</v-btn>
</div>
<v-btn :color="isEditingRoutes ? 'grey' : 'primary'" variant="tonal" size="small"
@click="toggleEditMode">
<v-icon start>{{ isEditingRoutes ? 'mdi-eye' : 'mdi-pencil' }}</v-icon>
{{ isEditingRoutes ? '查看' : '编辑' }}
{{ isEditingRoutes ? tm('createDialog.viewMode') : tm('createDialog.editMode') }}
</v-btn>
</div>
<v-data-table :headers="routeTableHeaders" :items="platformRoutes" item-value="umop"
no-data-text="该平台暂无路由规则将使用默认配置文件" hide-default-footer :items-per-page="-1" class="mt-2"
:no-data-text="tm('createDialog.noRouteRules')" hide-default-footer :items-per-page="-1" class="mt-2"
variant="outlined">
<template v-slot:item.source="{ item }">
@@ -170,9 +170,9 @@
<small v-else>{{ getMessageTypeLabel(item.messageType) }}</small>
<small class="mx-1">:</small>
<v-text-field v-if="isEditingRoutes" v-model="item.sessionId" variant="outlined" density="compact"
hide-details placeholder="会话ID或*">
hide-details :placeholder="tm('createDialog.sessionIdPlaceholder')">
</v-text-field>
<small v-else>{{ item.sessionId === '*' ? '全部会话' : item.sessionId }}</small>
<small v-else>{{ item.sessionId === '*' ? tm('createDialog.allSessions') : item.sessionId }}</small>
</div>
</template>
@@ -191,7 +191,7 @@
</v-btn>
</div>
<small v-if="configInfoList.findIndex(c => c.id === item.configId) === -1" style="color: red;"
class="ml-2">配置文件不存在</small>
class="ml-2">{{ tm('createDialog.configMissing') }}</small>
</template>
<template v-slot:item.actions="{ item, index }">
@@ -211,8 +211,7 @@
</template>
</v-data-table>
<small class="ml-2 mt-2 d-block" style="color: grey">*消息下发时根据会话来源按顺序从上到下匹配首个符合条件的配置文件使用 * 表示匹配所有使用 /sid 指令获取会话
ID全部不匹配时将使用默认配置文件</small>
<small class="ml-2 mt-2 d-block" style="color: grey">{{ tm('createDialog.routeHint') }}</small>
</div>
</div>
@@ -266,10 +265,10 @@
<v-card-actions class="px-4 pb-4">
<v-spacer></v-spacer>
<v-btn color="error" @click="handleOneBotEmptyTokenWarningDismiss(true)">
无视警告并继续创建
{{ tm('createDialog.warningContinue') }}
</v-btn>
<v-btn color="primary" @click="handleOneBotEmptyTokenWarningDismiss(false)">
重新修改
{{ tm('createDialog.warningEditAgain') }}
</v-btn>
</v-card-actions>
</v-card>
@@ -286,9 +285,9 @@
<v-card class="config-drawer-card" elevation="12">
<div class="config-drawer-header">
<div>
<span class="text-h6">配置文件管理</span>
<span class="text-h6">{{ tm('createDialog.configDrawerTitle') }}</span>
<div v-if="configDrawerTargetId" class="text-caption text-grey">
ID: {{ configDrawerTargetId }}
{{ tm('createDialog.configDrawerIdLabel') }}: {{ configDrawerTargetId }}
</div>
</div>
<v-btn icon variant="text" @click="closeConfigDrawer">
@@ -359,23 +358,9 @@ export default {
// 平台配置文件表格(已弃用,改用 platformRoutes
platformConfigs: [],
configTableHeaders: [
{ title: '与此实例关联的配置文件 ID', key: 'name', sortable: false },
{ title: '在此实例下的应用范围', key: 'scope', sortable: false },
],
// 平台路由表
platformRoutes: [],
routeTableHeaders: [
{ title: '消息会话来源(消息类型:会话 ID)', key: 'source', sortable: false, width: '60%' },
{ title: '使用配置文件', key: 'configId', sortable: false, width: '20%' },
{ title: '操作', key: 'actions', sortable: false, align: 'center', width: '20%' },
],
messageTypeOptions: [
{ label: '全部消息', value: '*' },
{ label: '群组消息(GroupMessage)', value: 'GroupMessage' },
{ label: '私聊消息(FriendMessage)', value: 'FriendMessage' },
],
isEditingRoutes: false, // 编辑模式开关
// ID冲突确认对话框
@@ -437,6 +422,26 @@ export default {
}
return false;
},
configTableHeaders() {
return [
{ title: this.tm('createDialog.configTableHeaders.configId'), key: 'name', sortable: false },
{ title: this.tm('createDialog.configTableHeaders.scope'), key: 'scope', sortable: false },
];
},
routeTableHeaders() {
return [
{ title: this.tm('createDialog.routeTableHeaders.source'), key: 'source', sortable: false, width: '60%' },
{ title: this.tm('createDialog.routeTableHeaders.config'), key: 'configId', sortable: false, width: '20%' },
{ title: this.tm('createDialog.routeTableHeaders.actions'), key: 'actions', sortable: false, align: 'center', width: '20%' },
];
},
messageTypeOptions() {
return [
{ label: this.tm('createDialog.messageTypeOptions.all'), value: '*' },
{ label: this.tm('createDialog.messageTypeOptions.group'), value: 'GroupMessage' },
{ label: this.tm('createDialog.messageTypeOptions.friend'), value: 'FriendMessage' },
];
}
},
watch: {
@@ -603,7 +608,7 @@ export default {
const targetId = configId || 'default';
if (configId && this.configInfoList.findIndex(c => c.id === configId) === -1) {
this.showError('目标配置文件不存在,已打开配置页面以便检查。');
this.showError(this.tm('messages.configNotFoundOpenConfig'));
}
this.configDrawerTargetId = targetId;
@@ -637,7 +642,7 @@ export default {
const id = this.originalUpdatingPlatformId || this.updatingPlatformConfig.id;
if (!id) {
this.loading = false;
this.showError('更新失败,缺少平台 ID。');
this.showError(this.tm('messages.updateMissingPlatformId'));
return;
}
@@ -655,7 +660,7 @@ export default {
})
if (resp.data.status === 'error') {
throw new Error(resp.data.message || '平台更新失败');
throw new Error(resp.data.message || this.tm('messages.platformUpdateFailed'));
}
// 同时更新路由表
@@ -665,7 +670,7 @@ export default {
this.showDialog = false;
this.resetForm();
this.$emit('refresh-config');
this.showSuccess('更新成功');
this.showSuccess(this.tm('messages.updateSuccess'));
} catch (err) {
this.loading = false;
this.showError(err.response?.data?.message || err.message);
@@ -710,7 +715,7 @@ export default {
this.showDialog = false;
this.resetForm();
this.$emit('refresh-config');
this.showSuccess(res.data.message || '平台添加成功,配置文件已更新');
this.showSuccess(res.data.message || this.tm('messages.addSuccessWithConfig'));
} catch (err) {
this.loading = false;
this.showError(err.response?.data?.message || err.message);
@@ -738,7 +743,7 @@ export default {
}
if (!configId) {
throw new Error('无法获取配置文件ID');
throw new Error(this.tm('messages.configIdMissing'));
}
// 第二步:统一更新路由表
@@ -755,7 +760,8 @@ export default {
console.log(`成功更新路由表: ${umop} -> ${configId}`);
} catch (err) {
console.error('更新路由表失败:', err);
throw new Error(`更新路由表失败: ${err.response?.data?.message || err.message}`);
const errorMessage = err.response?.data?.message || err.message;
throw new Error(this.tm('messages.routingUpdateFailed', { message: errorMessage }));
}
},
@@ -778,7 +784,8 @@ export default {
return newConfigId;
} catch (err) {
console.error('创建新配置文件失败:', err);
throw new Error(`创建新配置文件失败: ${err.response?.data?.message || err.message}`);
const errorMessage = err.response?.data?.message || err.message;
throw new Error(this.tm('messages.createConfigFailed', { message: errorMessage }));
}
},
@@ -922,7 +929,7 @@ export default {
const newPlatformId = this.updatingPlatformConfig?.id || originalPlatformId;
if (!originalPlatformId && !newPlatformId) {
throw new Error('无法获取平台 ID');
throw new Error(this.tm('messages.platformIdMissing'));
}
try {
@@ -958,7 +965,8 @@ export default {
});
} catch (err) {
console.error('保存路由表失败:', err);
throw new Error(`保存路由表失败: ${err.response?.data?.message || err.message}`);
const errorMessage = err.response?.data?.message || err.message;
throw new Error(this.tm('messages.routingSaveFailed', { message: errorMessage }));
}
},
@@ -987,10 +995,10 @@ export default {
// 获取消息类型标签
getMessageTypeLabel(messageType) {
const typeMap = {
'*': '全部消息',
'': '全部消息',
'GroupMessage': '群组消息',
'FriendMessage': '私聊消息'
'*': this.tm('createDialog.messageTypeLabels.all'),
'': this.tm('createDialog.messageTypeLabels.all'),
'GroupMessage': this.tm('createDialog.messageTypeLabels.group'),
'FriendMessage': this.tm('createDialog.messageTypeLabels.friend')
};
return typeMap[messageType] || messageType;
},

View File

@@ -14,7 +14,7 @@
rounded="xl"
size="small"
>
新增
{{ tm('providerSources.add') }}
</v-btn>
</template>
<v-list density="compact">

View File

@@ -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) {
<template>
<div class="config-section" v-if="iterable && metadata[metadataKey]?.type === 'object'">
<v-list-item-title class="config-title">
{{ metadata[metadataKey]?.description }} <span class="metadata-key">({{ metadataKey }})</span>
{{ translateIfKey(metadata[metadataKey]?.description) }} <span class="metadata-key">({{ metadataKey }})</span>
</v-list-item-title>
<v-list-item-subtitle class="config-hint">
<span v-if="metadata[metadataKey]?.obvious_hint && metadata[metadataKey]?.hint" class="important-hint"></span>
{{ metadata[metadataKey]?.hint }}
{{ translateIfKey(metadata[metadataKey]?.hint) }}
</v-list-item-subtitle>
</div>
@@ -180,14 +186,14 @@ function hasVisibleItemsAfter(items, currentIndex) {
<div class="config-section mb-2">
<v-list-item-title class="config-title">
<span v-if="metadata[metadataKey].items[key]?.description">
{{ metadata[metadataKey].items[key]?.description }}
{{ translateIfKey(metadata[metadataKey].items[key]?.description) }}
<span class="property-key">({{ key }})</span>
</span>
<span v-else>{{ key }}</span>
</v-list-item-title>
<v-list-item-subtitle class="config-hint">
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint" class="important-hint"></span>
{{ metadata[metadataKey].items[key]?.hint }}
{{ translateIfKey(metadata[metadataKey].items[key]?.hint) }}
</v-list-item-subtitle>
</div>
<TemplateListEditor
@@ -205,7 +211,7 @@ function hasVisibleItemsAfter(items, currentIndex) {
<v-list-item density="compact">
<v-list-item-title class="property-name">
<span v-if="metadata[metadataKey].items[key]?.description">
{{ metadata[metadataKey].items[key]?.description }}
{{ translateIfKey(metadata[metadataKey].items[key]?.description) }}
<span class="property-key">({{ key }})</span>
</span>
<span v-else>{{ key }}</span>
@@ -214,7 +220,7 @@ function hasVisibleItemsAfter(items, currentIndex) {
<v-list-item-subtitle class="property-hint">
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint"
class="important-hint"></span>
{{ metadata[metadataKey].items[key]?.hint }}
{{ translateIfKey(metadata[metadataKey].items[key]?.hint) }}
</v-list-item-subtitle>
</v-list-item>
</v-col>