Pārlūkot izejas kodu

feat: 更新多语言内容

jiaxing.liao 3 dienas atpakaļ
vecāks
revīzija
442a5d3410
34 mainītis faili ar 3458 papildinājumiem un 1181 dzēšanām
  1. 3 3
      apps/web/src/components/Chat/ChatInput.vue
  2. 13 5
      apps/web/src/components/Chat/MessageList.vue
  3. 1074 7
      apps/web/src/i18n/locales/en-us.ts
  4. 1158 79
      apps/web/src/i18n/locales/zh-cn.ts
  5. 3 3
      apps/web/src/views/Statistics.vue
  6. 1 1
      apps/web/src/views/WorkflowExecution.vue
  7. 21 21
      apps/web/src/views/agent/components/DetailModal.vue
  8. 170 168
      apps/web/src/views/agent/components/EditModal.vue
  9. 18 11
      apps/web/src/views/chat/components/AddKbModal.vue
  10. 5 5
      apps/web/src/views/chat/components/ChatSidebar.vue
  11. 12 10
      apps/web/src/views/chat/index.vue
  12. 5 4
      apps/web/src/views/editor/Editor.vue
  13. 3 3
      apps/web/src/views/editor/NodeView.vue
  14. 2 2
      apps/web/src/views/editor/PublishBtn.vue
  15. 11 9
      apps/web/src/views/editor/StartNodeGuide.vue
  16. 14 14
      apps/web/src/views/flow/index.vue
  17. 79 76
      apps/web/src/views/knowledge/DocumentManage.vue
  18. 18 15
      apps/web/src/views/knowledge/KnowledgeBaseSidebar.vue
  19. 94 91
      apps/web/src/views/knowledge/QaManage.vue
  20. 15 12
      apps/web/src/views/knowledge/WikiGraph.vue
  21. 14 11
      apps/web/src/views/knowledge/WikiManage.vue
  22. 149 153
      apps/web/src/views/knowledge/components/KnowledgeBaseEditModal.vue
  23. 67 64
      apps/web/src/views/knowledge/components/StorageModal.vue
  24. 10 7
      apps/web/src/views/knowledge/index.vue
  25. 75 72
      apps/web/src/views/mcp/index.vue
  26. 43 19
      apps/web/src/views/model/components/ModelDetailDialog.vue
  27. 44 42
      apps/web/src/views/model/components/ModelEditDrawer.vue
  28. 106 80
      apps/web/src/views/model/index.vue
  29. 37 38
      apps/web/src/views/ollama/index.vue
  30. 53 50
      apps/web/src/views/prompt/index.vue
  31. 9 6
      apps/web/src/views/skills/index.vue
  32. 36 33
      apps/web/src/views/storage/index.vue
  33. 1 1
      apps/web/src/views/vector/index.vue
  34. 95 66
      apps/web/src/views/web-search/index.vue

+ 3 - 3
apps/web/src/components/Chat/ChatInput.vue

@@ -8,7 +8,7 @@
 			:auto-size="{ minRows: 2, maxRows: 5 }"
 			clearable
 			allow-speech
-			placeholder="请输入问题..."
+			:placeholder="t('pages.chat.senderPlaceholder')"
 			@submit="emit('submit', modelValue!)"
 			:loading="loading"
 			@cancel="emit('cancel')"
@@ -29,7 +29,7 @@
 			<template #action-list>
 				<div style="display: flex; align-items: center; gap: 8px">
 					<slot name="action" />
-					<el-tooltip v-if="!loading" content="发送">
+					<el-tooltip v-if="!loading" :content="t('pages.chat.send')">
 						<el-button
 							round
 							color="#626aef"
@@ -41,7 +41,7 @@
 							</el-icon>
 						</el-button>
 					</el-tooltip>
-					<el-tooltip v-else content="停止">
+					<el-tooltip v-else :content="t('pages.chat.stop')">
 						<el-button round type="danger" @click="emit('cancel')">
 							<Icon icon="lucide:circle-stop" />
 						</el-button>

+ 13 - 5
apps/web/src/components/Chat/MessageList.vue

@@ -92,7 +92,11 @@
 												size="small"
 												effect="dark"
 											>
-												{{ toolResult.success === false ? t('pages.chat.failedTag') : 'OK' }}
+												{{
+													toolResult.success === false
+														? t('pages.chat.failedTag')
+														: t('pages.chat.successTag')
+												}}
 											</el-tag>
 											<span class="tool-card__name">{{ getToolName(toolResult.toolName) }}</span>
 											<span v-if="toolResult.toolCallId" class="tool-card__id">{{
@@ -152,7 +156,9 @@
 						need-view-code-btn
 						@card-submit="(val) => emit('card-submit', val)"
 					/>
-					<div v-if="item?.stopped" class="msg-stop-indicator">(已停止)</div>
+					<div v-if="item?.stopped" class="msg-stop-indicator">
+						{{ t('pages.chat.stopped') }}
+					</div>
 				</div>
 			</template>
 
@@ -178,7 +184,7 @@
 								circle
 								@click="handleRetry(item)"
 							/> -->
-						<el-tooltip content="复制">
+						<el-tooltip :content="t('pages.chat.copy')">
 							<el-button
 								v-if="props.copy"
 								color="#626aef"
@@ -188,7 +194,7 @@
 								@click="handleCopy(item)"
 							/>
 						</el-tooltip>
-						<el-tooltip content="添加到知识库">
+						<el-tooltip :content="t('pages.chat.addToKnowledgeBase')">
 							<el-button
 								v-if="props.canAddToKb"
 								type="success"
@@ -201,7 +207,9 @@
 					</div>
 					<div class="footer-time">
 						{{ item.updateTime }}
-						<span v-if="item.total_duration_ms">共耗时: {{ item.total_duration_ms }}ms</span>
+						<span v-if="item.total_duration_ms">{{
+							t('pages.chat.totalDuration', { duration: item.total_duration_ms })
+						}}</span>
 					</div>
 				</div>
 			</template>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1074 - 7
apps/web/src/i18n/locales/en-us.ts


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1158 - 79
apps/web/src/i18n/locales/zh-cn.ts


+ 3 - 3
apps/web/src/views/Statistics.vue

@@ -23,7 +23,7 @@
 				:range-separator="t('common.date.rangeSeparator')"
 				:start-placeholder="t('common.date.startDate')"
 				:end-placeholder="t('common.date.endDate')"
-				format="YYYY年MM月DD日"
+				:format="t('statisticsPage.dateFormat')"
 			/>
 		</div>
 	</div>
@@ -225,7 +225,7 @@ const cardDataMap = computed(() => {
 
 // 当前选中卡片的图表标题
 const currentChartTitle = computed(() => {
-	return cardDataMap[selectedCardIdx.value]?.chartTitle || '统计图表'
+	return cardDataMap[selectedCardIdx.value]?.chartTitle || t('statisticsPage.defaultChartTitle')
 })
 
 // 当前选中卡片的图表数据
@@ -235,7 +235,7 @@ const currentChartData = computed(() => {
 
 // 当前选中卡片的表格标题
 const currentTableTitle = computed(() => {
-	return cardDataMap[selectedCardIdx.value]?.tableTitle || '统计明细'
+	return cardDataMap[selectedCardIdx.value]?.tableTitle || t('statisticsPage.defaultTableTitle')
 })
 
 // 当前选中卡片的表格数据

+ 1 - 1
apps/web/src/views/WorkflowExecution.vue

@@ -54,7 +54,7 @@
 			<div class="filters-left">
 				<el-input
 					v-model="keyword"
-					placeholder="关键词搜索"
+					:placeholder="t('pages.execution.filters.keyword')"
 					clearable
 					class="filter-item"
 					@keyup.enter="handleSearch"

+ 21 - 21
apps/web/src/views/agent/components/DetailModal.vue

@@ -31,7 +31,7 @@
 			</div>
 
 			<div class="section">
-				<div class="section__title">基础信息</div>
+				<div class="section__title">{{ t('pages.agent.detailModal.basicInfo') }}</div>
 				<div class="info-grid">
 					<div v-for="item in basicItems" :key="item.label" class="info-card">
 						<div class="info-card__label">{{ item.label }}</div>
@@ -41,7 +41,7 @@
 			</div>
 
 			<div class="section">
-				<div class="section__title">配置摘要</div>
+				<div class="section__title">{{ t('pages.agent.detailModal.configSummary') }}</div>
 				<div class="info-grid">
 					<div v-for="item in configItems" :key="item.label" class="info-card">
 						<div class="info-card__label">{{ item.label }}</div>
@@ -51,7 +51,7 @@
 			</div>
 
 			<div v-if="promptItems.length" class="section">
-				<div class="section__title">提示词</div>
+				<div class="section__title">{{ t('pages.agent.detailModal.prompts') }}</div>
 				<div class="prompt-list">
 					<div v-for="item in promptItems" :key="item.label" class="prompt-card">
 						<div class="prompt-card__head">{{ item.label }}</div>
@@ -94,7 +94,7 @@ function formatTypeLabel(type?: string) {
 }
 
 function formatBoolean(value?: boolean) {
-	return value ? '开启' : '关闭'
+	return value ? t('pages.agent.detailModal.on') : t('pages.agent.detailModal.off')
 }
 
 function formatModelName(id?: string) {
@@ -107,12 +107,12 @@ const basicItems = computed(() => {
 	if (!item) return []
 
 	return [
-		{ label: '模型', value: formatModelName(item.config?.model_config?.model_id) },
-		{ label: '重排模型', value: formatModelName(item.config?.model_config?.rerank_model_id) },
-		{ label: 'VLM 模型', value: formatModelName(item.config?.img_vlm_config?.vlm_model_id) },
-		{ label: 'ASR 模型', value: formatModelName(item.config?.img_vlm_config?.asr_model_id) },
-		{ label: '创建时间', value: item.creationTime || '-' },
-		{ label: '更新时间', value: item.updateTime || '-' }
+		{ label: t('pages.agent.detailModal.modelLabel'), value: formatModelName(item.config?.model_config?.model_id) },
+		{ label: t('pages.agent.detailModal.rerankModelLabel'), value: formatModelName(item.config?.model_config?.rerank_model_id) },
+		{ label: t('pages.agent.detailModal.vlmModelLabel'), value: formatModelName(item.config?.img_vlm_config?.vlm_model_id) },
+		{ label: t('pages.agent.detailModal.asrModelLabel'), value: formatModelName(item.config?.img_vlm_config?.asr_model_id) },
+		{ label: t('pages.agent.detailModal.creationTime'), value: item.creationTime || '-' },
+		{ label: t('pages.agent.detailModal.updateTime'), value: item.updateTime || '-' }
 	]
 })
 
@@ -121,22 +121,22 @@ const configItems = computed(() => {
 	if (!item) return []
 
 	return [
-		{ label: '温度', value: `${item.config?.model_config?.temperature ?? '-'}` },
+		{ label: t('pages.agent.detailModal.temperature'), value: `${item.config?.model_config?.temperature ?? '-'}` },
 		{
-			label: '最大 Token',
+			label: t('pages.agent.detailModal.maxTokens'),
 			value: `${item.config?.model_config?.max_completion_tokens ?? '-'}`
 		},
-		{ label: '思考模式', value: formatBoolean(item.config?.model_config?.thinking) },
+		{ label: t('pages.agent.detailModal.thinkingMode'), value: formatBoolean(item.config?.model_config?.thinking) },
 		{
-			label: '多轮对话',
+			label: t('pages.agent.detailModal.multiTurn'),
 			value: formatBoolean(item.config?.multiple_config?.multi_turn_enabled)
 		},
 		{
-			label: '知识库数量',
+			label: t('pages.agent.detailModal.kbCount'),
 			value: `${item.config?.kb_config?.knowledge_bases?.length ?? 0}`
 		},
 		{
-			label: '工具数量',
+			label: t('pages.agent.detailModal.toolCount'),
 			value: `${item.config?.setting_config?.allowed_tools?.length ?? 0}`
 		}
 	]
@@ -148,23 +148,23 @@ const promptItems = computed(() => {
 
 	return [
 		{
-			label: '系统提示词',
+			label: t('pages.agent.detailModal.systemPrompt'),
 			value: item.config?.basic_config?.system_prompt || ''
 		},
 		{
-			label: '上下文模板',
+			label: t('pages.agent.detailModal.contextTemplate'),
 			value: item.config?.basic_config?.context_template || ''
 		},
 		{
-			label: '改写系统提示词',
+			label: t('pages.agent.detailModal.rewriteSystemPrompt'),
 			value: item.config?.advanced_config?.rewrite_prompt_system || ''
 		},
 		{
-			label: '改写用户提示词',
+			label: t('pages.agent.detailModal.rewriteUserPrompt'),
 			value: item.config?.advanced_config?.rewrite_prompt_user || ''
 		},
 		{
-			label: '兜底提示词',
+			label: t('pages.agent.detailModal.fallbackPrompt'),
 			value: item.config?.advanced_config?.fallback_prompt || ''
 		}
 	].filter((entry) => entry.value.trim())

+ 170 - 168
apps/web/src/views/agent/components/EditModal.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-dialog
 		v-model="visible"
-		:title="configOnly ? '配置智能体' : formId ? '编辑智能体' : '新建智能体'"
+		:title="configOnly ? t('pages.agent.editModal.configAgent') : formId ? t('pages.agent.editModal.editAgent') : t('pages.agent.editModal.createAgent')"
 		class="agent-modal"
 		append-to-body
 		fullscreen
@@ -16,58 +16,58 @@
 				label-width="120px"
 			>
 				<el-tabs v-model="activeTab" tab-position="left" class="settings-tabs">
-					<el-tab-pane label="基础信息" name="basic">
+					<el-tab-pane :label="t('pages.agent.editModal.tabBasic')" name="basic">
 						<div class="tab-intro">
-							{{ configOnly ? '配置智能体的运行模式和参数' : '配置智能体的基本信息' }}
+							{{ configOnly ? t('pages.agent.editModal.configAgentDesc') : t('pages.agent.editModal.createAgentDesc') }}
 						</div>
 						<div class="collapse-body">
-							<el-form-item label="运行模式" prop="mode">
+							<el-form-item :label="t('pages.agent.editModal.modeLabel')" prop="mode">
 								<div class="switch-wrap flex items-center gap-2">
 									<el-segmented
 										v-model="form.mode"
 										class="selection-segmented"
 										:options="[
-											{ label: '快速问答', value: 'quick-answer' },
-											{ label: '智能推理', value: 'smart-reasoning' }
+											{ label: t('pages.agent.editModal.modeQuickAnswer'), value: 'quick-answer' },
+											{ label: t('pages.agent.editModal.modeSmartReasoning'), value: 'smart-reasoning' }
 										]"
 									/>
-									<div class="field-tip">多步思考,深度分析复杂问题。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.modeTip') }}</div>
 								</div>
 							</el-form-item>
 							<div class="mode-tip ml-120px">
-								<div class="mode-tip__label">当前说明</div>
+								<div class="mode-tip__label">{{ t('pages.agent.editModal.modeTipLabel') }}</div>
 								<p v-if="form.mode === 'quick-answer'">
-									适合问答与知识检索场景,可继续完善上下文模板、查询扩展和多轮对话。
+									{{ t('pages.agent.editModal.quickAnswerDesc') }}
 								</p>
-								<p v-else>适合工具调用与多步推理场景,可继续配置工具、MCP 服务和 Skills。</p>
+								<p v-else>{{ t('pages.agent.editModal.smartReasoningDesc') }}</p>
 							</div>
-							<el-form-item v-if="!configOnly" label="名称" prop="name">
-								<el-input v-model="form.name" placeholder="请输入智能体名称" />
-								<div class="field-tip">为智能体设置一个易于识别的名称。</div>
+							<el-form-item v-if="!configOnly" :label="t('pages.agent.editModal.nameLabel')" prop="name">
+								<el-input v-model="form.name" :placeholder="t('pages.agent.editModal.namePlaceholder')" />
+								<div class="field-tip">{{ t('pages.agent.editModal.nameTip') }}</div>
 							</el-form-item>
-							<el-form-item v-if="!configOnly" label="图标" prop="avatar">
+							<el-form-item v-if="!configOnly" :label="t('pages.agent.editModal.avatarLabel')" prop="avatar">
 								<div class="emoji-field">
 									<el-button class="emoji-trigger" @click="emojiDialogVisible = true">
 										<span class="emoji-preview">{{ selectedEmoji }}</span>
-										<span>{{ form.avatar ? '更换 Emoji' : '选择 Emoji' }}</span>
+										<span>{{ form.avatar ? t('pages.agent.editModal.changeEmoji') : t('pages.agent.editModal.selectEmoji') }}</span>
 									</el-button>
-									<el-button v-if="form.avatar" text @click="clearEmoji">清空</el-button>
+									<el-button v-if="form.avatar" text @click="clearEmoji">{{ t('pages.agent.editModal.clearEmoji') }}</el-button>
 								</div>
 							</el-form-item>
-							<el-form-item v-if="!configOnly" label="描述" prop="description">
+							<el-form-item v-if="!configOnly" :label="t('pages.agent.editModal.descriptionLabel')" prop="description">
 								<el-input
 									v-model="form.description"
 									type="textarea"
 									:rows="3"
-									placeholder="请输入智能体描述"
+									:placeholder="t('pages.agent.editModal.descriptionPlaceholder')"
 								/>
-								<div class="field-tip">简要描述智能体的用途和特点。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.descriptionTip') }}</div>
 							</el-form-item>
-							<el-form-item label="系统提示词" prop="config.basic_config.system_prompt">
+							<el-form-item :label="t('pages.agent.editModal.systemPromptLabel')" prop="config.basic_config.system_prompt">
 								<div class="prompt-field">
 									<div class="prompt-toolbar">
 										<div class="prompt-variable-row">
-											<span class="prompt-variable-label">支持变量</span>
+											<span class="prompt-variable-label">{{ t('pages.agent.editModal.supportVariables') }}</span>
 											<el-tag
 												v-for="variable in systemPromptVariables"
 												:key="variable"
@@ -90,7 +90,7 @@
 												)
 											"
 										>
-											模板选择
+											{{ t('pages.agent.editModal.templateSelect') }}
 										</el-button>
 									</div>
 									<el-input
@@ -99,20 +99,20 @@
 										mode="prompt"
 										type="textarea"
 										:rows="15"
-										placeholder="请输入系统提示词,可直接编写角色、目标、约束和回答风格"
+										:placeholder="t('pages.agent.editModal.systemPromptPlaceholder')"
 									/>
 								</div>
-								<div class="field-tip">自定义系统提示词,定义智能体的行为和角色。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.systemPromptTip') }}</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.mode === 'quick-answer'"
-								label="上下文模板"
+								:label="t('pages.agent.editModal.contextTemplateLabel')"
 								prop="config.basic_config.context_template"
 							>
 								<div class="prompt-field">
 									<div class="prompt-toolbar">
 										<div class="prompt-variable-row">
-											<span class="prompt-variable-label">支持变量</span>
+											<span class="prompt-variable-label">{{ t('pages.agent.editModal.supportVariables') }}</span>
 											<el-tag
 												v-for="variable in quickAnswerVariables"
 												:key="variable"
@@ -130,7 +130,7 @@
 											:icon="DocumentCopy"
 											@click="openPromptTemplatePicker('contextTemplate', 'context-template')"
 										>
-											模板选择
+											{{ t('pages.agent.editModal.templateSelect') }}
 										</el-button>
 									</div>
 									<el-input
@@ -139,18 +139,18 @@
 										mode="prompt"
 										type="textarea"
 										:rows="15"
-										placeholder="请输入上下文模板,用于约束问答模式下的上下文组织方式"
+										:placeholder="t('pages.agent.editModal.contextTemplatePlaceholder')"
 									/>
 								</div>
-								<div class="field-tip">定义如何将检索到的内容格式化后传递给模型。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.contextTemplateTip') }}</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="模型配置" name="model">
-						<div class="tab-intro">配置智能体的模型参数</div>
+					<el-tab-pane :label="t('pages.agent.editModal.tabModel')" name="model">
+						<div class="tab-intro">{{ t('pages.agent.editModal.modelTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="模型" prop="config.model_config.model_id">
+							<el-form-item :label="t('pages.agent.editModal.modelLabel')" prop="config.model_config.model_id">
 								<el-select
 									v-model="form.config.model_config.model_id"
 									filterable
@@ -164,9 +164,9 @@
 										:value="model.id"
 									/>
 								</el-select>
-								<div class="field-tip">选择智能体使用的大语言模型。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.modelTip') }}</div>
 							</el-form-item>
-							<el-form-item label="温度" prop="config.model_config.temperature">
+							<el-form-item :label="t('pages.agent.editModal.temperatureLabel')" prop="config.model_config.temperature">
 								<div class="switch-wrap w-full flex items-center gap-2">
 									<el-slider
 										v-model="form.config.model_config.temperature"
@@ -175,30 +175,30 @@
 										:step="0.1"
 										style="width: 50%"
 									/>
-									<div class="field-tip">控制输出的随机性,0 最确定,1 最随机。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.temperatureTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="最大 Token 数" prop="config.model_config.max_completion_tokens">
+							<el-form-item :label="t('pages.agent.editModal.maxTokensLabel')" prop="config.model_config.max_completion_tokens">
 								<el-input-number
 									v-model="form.config.model_config.max_completion_tokens"
 									:min="1"
 									style="width: 100%"
 								/>
-								<div class="field-tip">限制模型单次回复可生成的最大 Token 数。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.maxTokensTip') }}</div>
 							</el-form-item>
-							<el-form-item label="思考模式" prop="config.model_config.thinking">
+							<el-form-item :label="t('pages.agent.editModal.thinkingModeLabel')" prop="config.model_config.thinking">
 								<div class="switch-wrap flex items-center gap-2">
 									<el-switch v-model="form.config.model_config.thinking" />
-									<div class="field-tip">启用模型的扩展思考能力,需要模型本身支持。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.thinkingModeTip') }}</div>
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="知识库" name="knowledge">
-						<div class="tab-intro">配置智能体可访问的知识库</div>
+					<el-tab-pane :label="t('pages.agent.editModal.tabKnowledge')" name="knowledge">
+						<div class="tab-intro">{{ t('pages.agent.editModal.kbTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="关联知识库" prop="config.kb_config.knowledge_bases">
+							<el-form-item :label="t('pages.agent.editModal.kbRelationLabel')" prop="config.kb_config.knowledge_bases">
 								<div class="selection-panel">
 									<el-segmented
 										v-model="form.config.kb_config.kb_selection_mode"
@@ -219,15 +219,15 @@
 										</el-checkbox>
 									</el-checkbox-group>
 								</div>
-								<div class="field-tip">选择智能体可访问的知识库范围。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.kbTip') }}</div>
 								<div
 									v-if="form.config.kb_config.kb_selection_mode === 'selected'"
 									class="field-tip"
 								>
-									选择要关联的知识库,包括协作知识库。
+									{{ t('pages.agent.editModal.kbSelectTip') }}
 								</div>
 							</el-form-item>
-							<el-form-item label="知识的文件类型" prop="config.kb_config.supported_file_types">
+							<el-form-item :label="t('pages.agent.editModal.fileTypeLabel')" prop="config.kb_config.supported_file_types">
 								<el-select
 									v-model="form.config.kb_config.supported_file_types"
 									multiple
@@ -242,9 +242,9 @@
 										:value="item"
 									/>
 								</el-select>
-								<div class="field-tip">限制可选择的文件类型,留空表示支持所有类型。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.fileTypeTip') }}</div>
 							</el-form-item>
-							<el-form-item label="ReRank 模型" prop="config.model_config.rerank_model_id">
+							<el-form-item :label="t('pages.agent.editModal.rerankModelLabel')" prop="config.model_config.rerank_model_id">
 								<el-select
 									v-model="form.config.model_config.rerank_model_id"
 									filterable
@@ -257,21 +257,21 @@
 										:value="model.id"
 									/>
 								</el-select>
-								<div class="field-tip">用于对知识库检索结果进行重排序,提高回答准确性。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.rerankModelTip') }}</div>
 							</el-form-item>
 							<div class="subsection ml-120px">
-								<div class="subsection-title">FAQ 优化策略</div>
+								<div class="subsection-title">{{ t('pages.agent.editModal.faqTitle') }}</div>
 								<div class="subsection-tip">
-									当知识库中包含 FAQ(问答对)时,可以启用此策略让 FAQ 答案优先于普通文档。
+									{{ t('pages.agent.editModal.faqTip') }}
 								</div>
-								<el-form-item label="启动 FAQ 优先" prop="config.faq_config.faq_priority_enabled">
+								<el-form-item :label="t('pages.agent.editModal.faqPriorityLabel')" prop="config.faq_config.faq_priority_enabled">
 									<div class="switch-wrap flex items-center gap-2">
 										<el-switch v-model="form.config.faq_config.faq_priority_enabled" />
-										<div class="field-tip">FAQ 答案将优先于普通文档被引用,提高回答准确性。</div>
+										<div class="field-tip">{{ t('pages.agent.editModal.faqPriorityTip') }}</div>
 									</div>
 								</el-form-item>
 								<el-form-item
-									label="直接问答阈值"
+									:label="t('pages.agent.editModal.directAnswerThresholdLabel')"
 									prop="config.faq_config.faq_direct_answer_threshold"
 								>
 									<div class="switch-wrap w-full flex items-center gap-3">
@@ -282,10 +282,10 @@
 											:step="0.1"
 											style="width: 50%"
 										/>
-										<div class="field-tip">当问题与 FAQ 相似度超过此值时,直接使用 FAQ 答案。</div>
+										<div class="field-tip">{{ t('pages.agent.editModal.directAnswerThresholdTip') }}</div>
 									</div>
 								</el-form-item>
-								<el-form-item label="FAQ 分数加权" prop="config.faq_config.faq_score_boost">
+								<el-form-item :label="t('pages.agent.editModal.faqScoreBoostLabel')" prop="config.faq_config.faq_score_boost">
 									<div class="switch-wrap w-full flex items-center gap-3">
 										<el-slider
 											v-model="form.config.faq_config.faq_score_boost"
@@ -294,17 +294,17 @@
 											:step="0.1"
 											style="width: 50%"
 										/>
-										<div class="field-tip">FAQ 结果的相关性分数乘以此系数,使其排序更靠前。</div>
+										<div class="field-tip">{{ t('pages.agent.editModal.faqScoreBoostTip') }}</div>
 									</div>
 								</el-form-item>
 							</div>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="form.mode === 'smart-reasoning'" label="工具配置" name="tools">
-						<div class="tab-intro">配置 Agent 可以使用的工具</div>
+					<el-tab-pane v-if="form.mode === 'smart-reasoning'" :label="t('pages.agent.editModal.tabTools')" name="tools">
+						<div class="tab-intro">{{ t('pages.agent.editModal.toolsTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="允许的工具" prop="config.setting_config.allowed_tools">
+							<el-form-item :label="t('pages.agent.editModal.allowedToolsLabel')" prop="config.setting_config.allowed_tools">
 								<div class="selection-panel">
 									<el-checkbox-group
 										v-model="form.config.setting_config.allowed_tools"
@@ -331,28 +331,28 @@
 										</div>
 									</el-checkbox-group>
 								</div>
-								<div class="field-tip">选择 Agent 可以使用的工具。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.allowedToolsTip') }}</div>
 							</el-form-item>
-							<el-form-item label="最大迭代次数" prop="config.setting_config.max_iterations">
+							<el-form-item :label="t('pages.agent.editModal.maxIterationsLabel')" prop="config.setting_config.max_iterations">
 								<el-input-number
 									v-model="form.config.setting_config.max_iterations"
 									:min="1"
 									:max="50"
 									style="width: 100%"
 								/>
-								<div class="field-tip">Agent 执行任务时的最大推理步骤数。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.maxIterationsTip') }}</div>
 							</el-form-item>
-							<el-form-item label="LLM 调用超时" prop="config.setting_config.llm_call_timeout">
+							<el-form-item :label="t('pages.agent.editModal.llmTimeoutLabel')" prop="config.setting_config.llm_call_timeout">
 								<el-input-number
 									v-model="form.config.setting_config.llm_call_timeout"
 									:min="0"
 									style="width: 100%"
 								/>
 								<div class="field-tip">
-									单次 LLM 调用的最大等待时间(秒),超过此时间后调用将被中止。
+									{{ t('pages.agent.editModal.llmTimeoutTip') }}
 								</div>
 							</el-form-item>
-							<el-form-item label="MCP 服务" prop="config.setting_config.mcp_services">
+							<el-form-item :label="t('pages.agent.editModal.mcpServiceLabel')" prop="config.setting_config.mcp_services">
 								<div class="selection-panel">
 									<el-segmented
 										v-model="form.config.setting_config.mcp_selection_mode"
@@ -373,23 +373,23 @@
 										</el-checkbox>
 									</el-checkbox-group>
 								</div>
-								<div class="field-tip">选择 Agent 可以调用的 MCP 服务。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.mcpTip') }}</div>
 								<div
 									v-if="form.config.setting_config.mcp_selection_mode === 'selected'"
 									class="field-tip"
 								>
-									选择要启用的 MCP 服务。
+									{{ t('pages.agent.editModal.mcpSelectTip') }}
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="form.mode === 'smart-reasoning'" label="技能 Skills" name="skills">
+					<el-tab-pane v-if="form.mode === 'smart-reasoning'" :label="t('pages.agent.editModal.tabSkills')" name="skills">
 						<div class="tab-intro">
-							配置 Agent 可以使用的预装 Skills,提供专业领域知识和工作流程
+							{{ t('pages.agent.editModal.skillsTabIntro') }}
 						</div>
 						<div class="collapse-body">
-							<el-form-item label="Skills 选择" prop="config.setting_config.selected_skills">
+							<el-form-item :label="t('pages.agent.editModal.skillsSelectLabel')" prop="config.setting_config.selected_skills">
 								<div class="selection-panel">
 									<el-segmented
 										v-model="form.config.setting_config.skills_selection_mode"
@@ -415,30 +415,30 @@
 										</el-checkbox>
 									</el-checkbox-group>
 								</div>
-								<div class="field-tip">选择 Agent 可以使用的 Skills 范围。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.skillsTip') }}</div>
 								<div
 									v-if="form.config.setting_config.skills_selection_mode === 'selected'"
 									class="field-tip"
 								>
-									选择要启用的 Skills。
+									{{ t('pages.agent.editModal.skillsSelectTip') }}
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="搜索策略" name="search">
-						<div class="tab-intro">配置知识库检索和排序的参数</div>
+					<el-tab-pane :label="t('pages.agent.editModal.tabSearch')" name="search">
+						<div class="tab-intro">{{ t('pages.agent.editModal.searchTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="向量召回数量" prop="config.search_config.embedding_top_k">
+							<el-form-item :label="t('pages.agent.editModal.embeddingTopKLabel')" prop="config.search_config.embedding_top_k">
 								<el-input-number
 									v-model="form.config.search_config.embedding_top_k"
 									:min="1"
 									:max="50"
 									style="width: 100%"
 								/>
-								<div class="field-tip">向量检索返回的最大结果数量。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.embeddingTopKTip') }}</div>
 							</el-form-item>
-							<el-form-item label="关键词阈值" prop="config.search_config.keyword_threshold">
+							<el-form-item :label="t('pages.agent.editModal.keywordThresholdLabel')" prop="config.search_config.keyword_threshold">
 								<div class="switch-wrap w-full flex items-center gap-3">
 									<el-slider
 										v-model="form.config.search_config.keyword_threshold"
@@ -447,10 +447,10 @@
 										:step="0.1"
 										style="width: 50%"
 									/>
-									<div class="field-tip">关键词检索的最低相关性分数。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.keywordThresholdTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="向量阈值" prop="config.search_config.vector_threshold">
+							<el-form-item :label="t('pages.agent.editModal.vectorThresholdLabel')" prop="config.search_config.vector_threshold">
 								<div class="switch-wrap w-full flex items-center gap-3">
 									<el-slider
 										v-model="form.config.search_config.vector_threshold"
@@ -459,19 +459,19 @@
 										:step="0.1"
 										style="width: 50%"
 									/>
-									<div class="field-tip">向量检索的最低相似度分数。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.vectorThresholdTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="重排数量" prop="config.search_config.rerank_top_k">
+							<el-form-item :label="t('pages.agent.editModal.rerankTopKLabel')" prop="config.search_config.rerank_top_k">
 								<el-input-number
 									v-model="form.config.search_config.rerank_top_k"
 									:min="1"
 									:max="20"
 									style="width: 100%"
 								/>
-								<div class="field-tip">重排序后保留的最大结果数量。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.rerankTopKTip') }}</div>
 							</el-form-item>
-							<el-form-item label="重排阈值" prop="config.search_config.rerank_threshold">
+							<el-form-item :label="t('pages.agent.editModal.rerankThresholdLabel')" prop="config.search_config.rerank_threshold">
 								<div class="switch-wrap w-full flex items-center gap-3">
 									<el-slider
 										v-model="form.config.search_config.rerank_threshold"
@@ -480,28 +480,28 @@
 										:step="0.1"
 										style="width: 50%"
 									/>
-									<div class="field-tip">重排序的最低相关性分数。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.rerankThresholdTip') }}</div>
 								</div>
 							</el-form-item>
 							<div v-if="form.mode === 'quick-answer'" class="subsection">
-								<div class="subsection-title">问答</div>
-								<el-form-item label="查询扩展" prop="config.advanced_config.enable_query_expansion">
+								<div class="subsection-title">{{ t('pages.agent.editModal.qaSectionTitle') }}</div>
+								<el-form-item :label="t('pages.agent.editModal.queryExpansionLabel')" prop="config.advanced_config.enable_query_expansion">
 									<div class="switch-wrap flex items-center gap-2">
 										<el-switch v-model="form.config.advanced_config.enable_query_expansion" />
-										<div class="field-tip">自动扩展查询词以提高召回率。</div>
+										<div class="field-tip">{{ t('pages.agent.editModal.queryExpansionTip') }}</div>
 									</div>
 								</el-form-item>
-								<el-form-item label="兜底策略" prop="config.advanced_config.fallback_strategy">
+								<el-form-item :label="t('pages.agent.editModal.fallbackStrategyLabel')" prop="config.advanced_config.fallback_strategy">
 									<el-segmented
 										v-model="form.config.advanced_config.fallback_strategy"
 										class="selection-segmented"
 										:options="fallbackStrategyOptions"
 									/>
-									<div class="field-tip">当无法从知识库找到相关内容时的处理方式。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.fallbackStrategyTip') }}</div>
 								</el-form-item>
 								<el-form-item
 									v-if="form.config.advanced_config.fallback_strategy === 'model'"
-									label="兜底提示词"
+									:label="t('pages.agent.editModal.fallbackPromptLabel')"
 									prop="config.advanced_config.fallback_prompt"
 								>
 									<div class="prompt-field">
@@ -512,49 +512,49 @@
 												:icon="DocumentCopy"
 												@click="openPromptTemplatePicker('fallbackPrompt', 'fall-back')"
 											>
-												模板选择
+												{{ t('pages.agent.editModal.templateSelect') }}
 											</el-button>
 										</div>
 										<el-input
 											v-model="form.config.advanced_config.fallback_prompt"
 											type="textarea"
 											:rows="15"
-											placeholder="请输入模型兜底时使用的提示词"
+											:placeholder="t('pages.agent.editModal.fallbackPromptPlaceholder')"
 										/>
 									</div>
 									<div class="field-tip">
-										当无法从知识库找到相关内容时,用于引导模型生成兜底回复。
+										{{ t('pages.agent.editModal.fallbackPromptTip') }}
 									</div>
 								</el-form-item>
 								<el-form-item
 									v-else
-									label="固定提示词"
+									:label="t('pages.agent.editModal.fixedPromptLabel')"
 									prop="config.advanced_config.fallback_response"
 								>
 									<el-input
 										v-model="form.config.advanced_config.fallback_response"
 										type="textarea"
 										:rows="3"
-										placeholder="请输入固定兜底内容"
+										:placeholder="t('pages.agent.editModal.fixedResponsePlaceholder')"
 									/>
-									<div class="field-tip">当无法回答时返回的固定文本。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.fixedResponseTip') }}</div>
 								</el-form-item>
 							</div>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="网络搜索" name="web-search">
-						<div class="tab-intro">配置智能体的网络搜索能力</div>
+					<el-tab-pane :label="t('pages.agent.editModal.tabWebSearch')" name="web-search">
+						<div class="tab-intro">{{ t('pages.agent.editModal.webSearchTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="网络搜索开启" prop="config.web_search_config.web_search_enabled">
+							<el-form-item :label="t('pages.agent.editModal.webSearchEnableLabel')" prop="config.web_search_config.web_search_enabled">
 								<div class="switch-wrap flex items-center gap-2">
 									<el-switch v-model="form.config.web_search_config.web_search_enabled" />
-									<div class="field-tip">启用后智能体可以搜索互联网获取信息。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.webSearchEnableTip') }}</div>
 								</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.web_search_config.web_search_enabled"
-								label="选择搜索引擎"
+								:label="t('pages.agent.editModal.searchEngineLabel')"
 								prop="config.web_search_config.web_search_provider_id"
 							>
 								<el-select
@@ -562,7 +562,7 @@
 									filterable
 									:loading="webSearchProviderLoading"
 									clearable
-									placeholder="请输入关键词搜索"
+									:placeholder="t('pages.agent.editModal.searchEnginePlaceholder')"
 									style="width: 100%"
 								>
 									<el-option
@@ -572,11 +572,11 @@
 										:value="item.value"
 									/>
 								</el-select>
-								<div class="field-tip">为此智能体指定搜索引擎,留空则使用默认搜索引擎。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.searchEngineTip') }}</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.web_search_config.web_search_enabled"
-								label="最大搜索结果数"
+								:label="t('pages.agent.editModal.maxResultsLabel')"
 								prop="config.web_search_config.web_search_max_results"
 							>
 								<div class="switch-wrap w-full flex items-center gap-3">
@@ -586,25 +586,25 @@
 										:max="10"
 										style="width: 50%"
 									/>
-									<div class="field-tip">每次搜索返回的最大结果数量。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.maxResultsTip') }}</div>
 								</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.web_search_config.web_search_enabled"
 								label-width="130px"
-								label="自动抓取页面内容"
+								:label="t('pages.agent.editModal.webFetchLabel')"
 								prop="config.web_search_config.web_fetch_enabled"
 							>
 								<div class="switch-wrap flex items-center gap-2">
 									<el-switch v-model="form.config.web_search_config.web_fetch_enabled" />
 									<div class="field-tip">
-										ReRank 后自动抓取排名靠前的网页完整内容,提升回答质量。
+										{{ t('pages.agent.editModal.webFetchTip') }}
 									</div>
 								</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.web_search_config.web_fetch_enabled"
-								label="抓取页面数"
+								:label="t('pages.agent.editModal.fetchCountLabel')"
 								prop="config.web_search_config.web_fetch_top_n"
 							>
 								<div class="switch-wrap w-full flex items-center gap-3">
@@ -614,24 +614,24 @@
 										:max="10"
 										style="width: 50%"
 									/>
-									<div class="field-tip">Rerank 后最多抓取几个网页的完整内容。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.fetchCountTip') }}</div>
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="多模态" name="multimodal">
-						<div class="tab-intro">配置图片上传和视觉语言模型,启用后用户可在对话中上传图片</div>
+					<el-tab-pane :label="t('pages.agent.editModal.tabMultimodal')" name="multimodal">
+						<div class="tab-intro">{{ t('pages.agent.editModal.multimodalTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="图片上传" prop="config.img_vlm_config.image_upload_enabled">
+							<el-form-item :label="t('pages.agent.editModal.imageUploadLabel')" prop="config.img_vlm_config.image_upload_enabled">
 								<div class="switch-wrap flex items-center gap-2">
 									<el-switch v-model="form.config.img_vlm_config.image_upload_enabled" />
-									<div class="field-tip">启用后用户可在对话中上传图片进行多模态问答。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.imageUploadTip') }}</div>
 								</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.img_vlm_config.image_upload_enabled"
-								label="VLM 模型设置"
+								:label="t('pages.agent.editModal.vlmModelLabel')"
 								prop="config.img_vlm_config.vlm_model_id"
 							>
 								<el-select
@@ -646,19 +646,19 @@
 										:value="model.id"
 									/>
 								</el-select>
-								<div class="field-tip">用于图片分析的视觉语言模型。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.vlmModelTip') }}</div>
 							</el-form-item>
-							<el-form-item label="语音上传" prop="config.img_vlm_config.audio_upload_enabled">
+							<el-form-item :label="t('pages.agent.editModal.audioUploadLabel')" prop="config.img_vlm_config.audio_upload_enabled">
 								<div class="switch-wrap flex items-center gap-2">
 									<el-switch v-model="form.config.img_vlm_config.audio_upload_enabled" />
 									<div class="field-tip">
-										启用后用户可在对话中上传音频文件,系统将使用 ASR 模型自动转录为文字。
+										{{ t('pages.agent.editModal.audioUploadTip') }}
 									</div>
 								</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.img_vlm_config.audio_upload_enabled"
-								label="ASR 模型设置"
+								:label="t('pages.agent.editModal.asrModelLabel')"
 								prop="config.img_vlm_config.asr_model_id"
 							>
 								<el-select
@@ -675,24 +675,24 @@
 									/>
 								</el-select>
 								<div class="field-tip">
-									用于音频转录的语音识别模型,未配置时音频文件将以占位符形式传递。
+									{{ t('pages.agent.editModal.asrModelTip') }}
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="form.mode === 'quick-answer'" label="多轮对话" name="multi-turn">
-						<div class="tab-intro">配置多轮对话和问题改写相关参数</div>
+					<el-tab-pane v-if="form.mode === 'quick-answer'" :label="t('pages.agent.editModal.tabMultiTurn')" name="multi-turn">
+						<div class="tab-intro">{{ t('pages.agent.editModal.multiTurnTabIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="多轮对话" prop="config.multiple_config.multi_turn_enabled">
+							<el-form-item :label="t('pages.agent.editModal.multiTurnLabel')" prop="config.multiple_config.multi_turn_enabled">
 								<div class="switch-wrap flex items-center gap-2">
 									<el-switch v-model="form.config.multiple_config.multi_turn_enabled" />
-									<div class="field-tip">开启后将保留历史对话上下文。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.multiTurnTip') }}</div>
 								</div>
 							</el-form-item>
 							<el-form-item
 								v-if="form.config.multiple_config.multi_turn_enabled"
-								label="保留轮数"
+								:label="t('pages.agent.editModal.historyTurnsLabel')"
 								prop="config.multiple_config.history_turns"
 							>
 								<el-input-number
@@ -701,24 +701,24 @@
 									:max="20"
 									style="width: 100%"
 								/>
-								<div class="field-tip">保留最近几轮对话作为上下文。</div>
+								<div class="field-tip">{{ t('pages.agent.editModal.historyTurnsTip') }}</div>
 							</el-form-item>
 							<div v-if="form.mode === 'quick-answer'">
-								<el-form-item label="问题改写" prop="config.advanced_config.enable_rewrite">
+								<el-form-item :label="t('pages.agent.editModal.rewriteLabel')" prop="config.advanced_config.enable_rewrite">
 									<div class="switch-wrap flex items-center gap-2">
 										<el-switch v-model="form.config.advanced_config.enable_rewrite" />
-										<div class="field-tip">多轮对话时自动改写用户问题,消解指代和补全省略。</div>
+										<div class="field-tip">{{ t('pages.agent.editModal.rewriteTip') }}</div>
 									</div>
 								</el-form-item>
 								<el-form-item
 									v-if="form.config.advanced_config.enable_rewrite"
-									label="改写系统提示词"
+									:label="t('pages.agent.editModal.rewriteSystemPromptLabel')"
 									prop="config.advanced_config.rewrite_prompt_system"
 								>
 									<div class="prompt-field">
 										<div class="prompt-toolbar">
 											<div class="prompt-variable-row">
-												<span class="prompt-variable-label">可用变量</span>
+												<span class="prompt-variable-label">{{ t('pages.agent.editModal.availableVariables') }}</span>
 												<el-tag
 													v-for="variable in rewriteVariables"
 													:key="variable"
@@ -736,7 +736,7 @@
 												:icon="DocumentCopy"
 												@click="openPromptTemplatePicker('rewritePromptSystem', 'rewrite')"
 											>
-												模板选择
+												{{ t('pages.agent.editModal.templateSelect') }}
 											</el-button>
 										</div>
 										<el-input
@@ -744,20 +744,20 @@
 											v-model="form.config.advanced_config.rewrite_prompt_system"
 											type="textarea"
 											:rows="15"
-											placeholder="请输入问题改写时的系统提示词"
+											:placeholder="t('pages.agent.editModal.rewriteSystemPromptPlaceholder')"
 										/>
 									</div>
-									<div class="field-tip">用于问题改写的系统提示词。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.rewriteSystemPromptTip') }}</div>
 								</el-form-item>
 								<el-form-item
 									v-if="form.config.advanced_config.enable_rewrite"
-									label="改写用户提示词"
+									:label="t('pages.agent.editModal.rewriteUserPromptLabel')"
 									prop="config.advanced_config.rewrite_prompt_user"
 								>
 									<div class="prompt-field">
 										<div class="prompt-toolbar">
 											<div class="prompt-variable-row">
-												<span class="prompt-variable-label">可用变量</span>
+												<span class="prompt-variable-label">{{ t('pages.agent.editModal.availableVariables') }}</span>
 												<el-tag
 													v-for="variable in rewriteVariables"
 													:key="variable"
@@ -775,7 +775,7 @@
 												:icon="DocumentCopy"
 												@click="openPromptTemplatePicker('rewritePromptUser', 'rewrite')"
 											>
-												模板选择
+												{{ t('pages.agent.editModal.templateSelect') }}
 											</el-button>
 										</div>
 										<el-input
@@ -783,10 +783,10 @@
 											v-model="form.config.advanced_config.rewrite_prompt_user"
 											type="textarea"
 											:rows="15"
-											placeholder="请输入问题改写时的用户提示词"
+											:placeholder="t('pages.agent.editModal.rewriteUserPromptPlaceholder')"
 										/>
 									</div>
-									<div class="field-tip">用于问题改写的用户提示词模板。</div>
+									<div class="field-tip">{{ t('pages.agent.editModal.rewriteUserPromptTip') }}</div>
 								</el-form-item>
 							</div>
 						</div>
@@ -795,14 +795,14 @@
 			</el-form>
 		</el-scrollbar>
 
-		<el-dialog v-model="emojiDialogVisible" title="选择 Emoji 图标" width="520px" append-to-body>
+		<el-dialog v-model="emojiDialogVisible" :title="t('pages.agent.editModal.emojiPickerTitle')" width="520px" append-to-body>
 			<div class="emoji-picker-wrap">
 				<EmojiPicker
 					:native="true"
 					:hide-search="false"
 					:disable-skin-tones="false"
 					:display-recent="true"
-					:static-texts="{ placeholder: '搜索 Emoji' }"
+					:static-texts="{ placeholder: t('pages.agent.editModal.emojiSearchPlaceholder') }"
 					@select="handleEmojiSelect"
 				/>
 			</div>
@@ -818,8 +818,8 @@
 
 		<template #footer>
 			<div class="drawer-footer">
-				<el-button @click="visible = false">取消</el-button>
-				<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
+				<el-button @click="visible = false">{{ t('pages.agent.editModal.cancel') }}</el-button>
+				<el-button type="primary" :loading="submitLoading" @click="handleSubmit">{{ t('pages.agent.editModal.save') }}</el-button>
 			</div>
 		</template>
 	</el-dialog>
@@ -827,6 +827,7 @@
 
 <script setup lang="ts">
 import { computed, nextTick, reactive, ref, watch } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { ElMessage } from 'element-plus'
 import { DocumentCopy } from '@element-plus/icons-vue'
 import { agentApplication, aiModel, knowledge, resource } from '@repo/api-service'
@@ -847,6 +848,7 @@ import type {
 } from '../type'
 
 const visible = defineModel<boolean>({ required: true })
+const { t } = useI18n()
 
 const props = defineProps<{
 	agentId?: string
@@ -892,20 +894,20 @@ const promptTemplateDialogVisible = ref(false)
 const currentPromptField = ref<PromptFieldKey>('systemPrompt')
 const currentPromptTemplateType = ref<PromptTemplateType>('system-prompt')
 const supportedFileTypes = ['pdf', 'docx', 'txt', 'md', 'csv', 'xlsx', 'jpg']
-const selectionModeOptions = [
-	{ label: '全部知识库', value: 'all' },
-	{ label: '指定知识库', value: 'selected' },
-	{ label: '不适用知识库', value: 'none' }
-]
-const selectionMCPOptions = [
-	{ label: '全部', value: 'all' },
-	{ label: '指定', value: 'selected' },
-	{ label: '禁用', value: 'none' }
-]
-const fallbackStrategyOptions = [
-	{ label: '模型生成', value: 'model' },
-	{ label: '固定回复', value: 'fixed' }
-]
+const selectionModeOptions = computed(() => [
+	{ label: t('pages.agent.editModal.kbAll'), value: 'all' },
+	{ label: t('pages.agent.editModal.kbSelected'), value: 'selected' },
+	{ label: t('pages.agent.editModal.kbNone'), value: 'none' }
+])
+const selectionMCPOptions = computed(() => [
+	{ label: t('pages.agent.editModal.selectAll'), value: 'all' },
+	{ label: t('pages.agent.editModal.selectSpecified'), value: 'selected' },
+	{ label: t('pages.agent.editModal.selectDisabled'), value: 'none' }
+])
+const fallbackStrategyOptions = computed(() => [
+	{ label: t('pages.agent.editModal.fallbackModel'), value: 'model' },
+	{ label: t('pages.agent.editModal.fallbackFixed'), value: 'fixed' }
+])
 const smartReasoningSystemPromptVariables = [
 	'knowledge_bases',
 	'web_search_status',
@@ -990,25 +992,25 @@ const form = reactive<AgentFormData>({
 	}
 })
 
-const rules = {
+const rules = computed(() => ({
 	name: [
 		{
 			required: !props.configOnly,
-			message: '名称不能为空',
+			message: t('pages.agent.editModal.nameRequired'),
 			trigger: 'blur'
 		}
 	],
-	mode: [{ required: true, message: '请选择模式', trigger: 'change' }],
+	mode: [{ required: true, message: t('pages.agent.editModal.modeRequired'), trigger: 'change' }],
 	'config.basic_config.system_prompt': [
-		{ required: true, message: '请输入系统提示词', trigger: 'blur' }
+		{ required: true, message: t('pages.agent.editModal.systemPromptRequired'), trigger: 'blur' }
 	],
 	'config.model_config.model_id': [
-		{ required: true, message: '请选择对话模型', trigger: 'change' }
+		{ required: true, message: t('pages.agent.editModal.modelRequired'), trigger: 'change' }
 	],
 	'config.model_config.rerank_model_id': [
-		{ required: true, message: '请选择重排序模型', trigger: 'change' }
+		{ required: true, message: t('pages.agent.editModal.rerankModelRequired'), trigger: 'change' }
 	]
-}
+}))
 
 const chatModels = computed(() => modelOptions.value.filter((item) => item.type === 'KnowledgeQA'))
 const rerankModels = computed(() => modelOptions.value.filter((item) => item.type === 'Rerank'))
@@ -1564,7 +1566,7 @@ async function loadSkills() {
 async function loadDetail(id: string) {
 	const res = await agentApplication.postAiAgentInfo({ id })
 	if (!res.isSuccess || !res.result) {
-		ElMessage.error(res?.error || '详情加载失败')
+		ElMessage.error(res?.error || t('pages.agent.editModal.detailLoadFailed'))
 		return
 	}
 	applyDetail(res.result as AgentItem)
@@ -1629,22 +1631,22 @@ async function handleSubmit() {
 			})
 
 			if (!res.isSuccess) {
-				ElMessage.error(res?.error || '更新失败')
+				ElMessage.error(res?.error || t('pages.agent.editModal.updateFailed'))
 				return
 			}
-			ElMessage.success('更新成功')
+			ElMessage.success(t('pages.agent.editModal.updateSuccess'))
 		} else {
 			const res = await agentApplication.postAiAgentCreate(payload as any)
 			if (!res.isSuccess) {
-				ElMessage.error(res?.error || '创建失败')
+				ElMessage.error(res?.error || t('pages.agent.editModal.createFailed'))
 				return
 			}
-			ElMessage.success('创建成功')
+			ElMessage.success(t('pages.agent.editModal.createSuccess'))
 		}
 		visible.value = false
 		emit('saved')
 	} catch {
-		ElMessage.error('Save failed')
+		ElMessage.error(t('pages.agent.editModal.saveFailed'))
 	} finally {
 		submitLoading.value = false
 	}

+ 18 - 11
apps/web/src/views/chat/components/AddKbModal.vue

@@ -1,26 +1,28 @@
 <template>
-	<el-dialog v-model="visible" title="添加到知识库" width="600px">
+	<el-dialog v-model="visible" :title="t('pages.chat.addKbTitle')" width="600px">
 		<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
-			<el-form-item label="知识库" prop="knowledge_base_id">
+			<el-form-item :label="t('pages.chat.knowledgeBase')" prop="knowledge_base_id">
 				<el-select
 					v-model="formData.knowledge_base_id"
 					:options="knowledgeOptions"
-					placeholder="请选择知识库"
+					:placeholder="t('pages.chat.selectKnowledgeBase')"
 					filterable
 					remote
 					:remote-method="getKbOptions"
 				/>
 			</el-form-item>
-			<el-form-item label="标题" prop="title">
+			<el-form-item :label="t('pages.chat.title')" prop="title">
 				<el-input v-model="formData.title"></el-input>
 			</el-form-item>
-			<el-form-item label="内容" prop="content">
+			<el-form-item :label="t('pages.chat.content')" prop="content">
 				<el-input v-model="formData.content" :rows="10" type="textarea"></el-input>
 			</el-form-item>
 		</el-form>
 		<template #footer>
-			<el-button @click="handleClose">取消</el-button>
-			<el-button type="primary" :loading="loading" @click="handleSubmit">保存</el-button>
+			<el-button @click="handleClose">{{ t('common.cancel') }}</el-button>
+			<el-button type="primary" :loading="loading" @click="handleSubmit">{{
+				t('common.save')
+			}}</el-button>
 		</template>
 	</el-dialog>
 </template>
@@ -30,6 +32,9 @@ import { ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { knowledge } from '@repo/api-service'
 import type { FormInstance } from 'element-plus'
+import { useI18n } from '@/composables/useI18n'
+
+const { t } = useI18n()
 
 const visible = ref(false)
 const formRef = ref<FormInstance>()
@@ -44,9 +49,11 @@ const formData = ref({
 })
 
 const rules = {
-	knowledge_base_id: [{ required: true, message: '请选择知识库', trigger: 'change' }],
-	title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-	content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
+	knowledge_base_id: [
+		{ required: true, message: t('pages.chat.selectKnowledgeBase'), trigger: 'change' }
+	],
+	title: [{ required: true, message: t('pages.chat.titleRequired'), trigger: 'blur' }],
+	content: [{ required: true, message: t('pages.chat.contentRequired'), trigger: 'blur' }]
 }
 
 const getKbOptions = async (keyword: string = '') => {
@@ -81,7 +88,7 @@ const handleSubmit = async () => {
 			})
 
 			if (res.isSuccess) {
-				ElMessage.success('添加完成')
+				ElMessage.success(t('pages.chat.addKbSuccess'))
 				handleClose()
 			}
 		} finally {

+ 5 - 5
apps/web/src/views/chat/components/ChatSidebar.vue

@@ -21,7 +21,7 @@
 					@click="$emit('select-conversation', conv.id)"
 				>
 					<div class="conv-info">
-						<div class="conv-title">{{ conv.title || '新对话' }}</div>
+						<div class="conv-title">{{ conv.title || t('pages.chat.newConversation') }}</div>
 						<div class="conv-time">{{ formatTime(conv.updatedAt) }}</div>
 					</div>
 
@@ -92,10 +92,10 @@ const formatTime = (timestamp: number) => {
 	const hours = Math.floor(diff / 3600000)
 	const days = Math.floor(diff / 86400000)
 
-	if (minutes < 1) return t('common.date.justNow') || '刚刚'
-	if (minutes < 60) return t('common.date.minutesAgo', { count: minutes }) || `${minutes}分钟前`
-	if (hours < 24) return t('common.date.hoursAgo', { count: hours }) || `${hours}小时前`
-	if (days < 7) return t('common.date.daysAgo', { count: days }) || `${days}天前`
+	if (minutes < 1) return t('common.date.justNow')
+	if (minutes < 60) return t('common.date.minutesAgo', { count: minutes })
+	if (hours < 24) return t('common.date.hoursAgo', { count: hours })
+	if (days < 7) return t('common.date.daysAgo', { count: days })
 	return date.toLocaleDateString()
 }
 

+ 12 - 10
apps/web/src/views/chat/index.vue

@@ -18,7 +18,7 @@
 				<template #actions>
 					<el-button v-if="!isPresetMode" @click="shareConversation">
 						<Icon icon="lucide:share-2" />
-						<span>生成链接</span>
+						<span>{{ t('pages.chat.generateLink') }}</span>
 					</el-button>
 				</template>
 			</ChatHeader>
@@ -160,16 +160,18 @@
 			</template>
 		</el-dialog>
 
-		<el-dialog v-model="shareDialogVisible" title="生成访问链接" width="620px">
+		<el-dialog v-model="shareDialogVisible" :title="t('pages.chat.shareDialogTitle')" width="620px">
 			<div class="share-dialog">
 				<div class="share-dialog__tip">
-					通过该链接进入对话时会自动使用当前智能体、模型、知识库等配置,无需再次选择。
+					{{ t('pages.chat.shareDialogTip') }}
 				</div>
 				<el-input v-model="shareUrl" type="textarea" :rows="4" readonly />
 			</div>
 			<template #footer>
-				<el-button @click="shareDialogVisible = false">关闭</el-button>
-				<el-button type="primary" @click="copyShareUrl">复制链接</el-button>
+				<el-button @click="shareDialogVisible = false">{{ t('common.close') }}</el-button>
+				<el-button type="primary" @click="copyShareUrl">{{
+					t('pages.chat.copyShareLink')
+				}}</el-button>
 			</template>
 		</el-dialog>
 	</div>
@@ -398,7 +400,7 @@ const parsePresetConfigFromRoute = () => {
 		return normalizeConfig(rawConfig)
 	} catch (error) {
 		console.error('Failed to parse chat preset config', error)
-		ElMessage.error('访问链接参数无效')
+		ElMessage.error(t('pages.chat.invalidShareLink'))
 		return null
 	}
 }
@@ -423,9 +425,9 @@ const copyShareUrl = async () => {
 	if (!shareUrl.value) return
 	try {
 		await navigator.clipboard.writeText(shareUrl.value)
-		ElMessage.success('链接已复制')
+		ElMessage.success(t('pages.chat.shareLinkCopied'))
 	} catch {
-		ElMessage.warning('复制失败,请手动复制链接')
+		ElMessage.warning(t('pages.chat.shareLinkCopyFailed'))
 	}
 }
 
@@ -883,7 +885,7 @@ const applyStructuredEventToMessage = (message: BubbleMessage, event: ChatSseMes
 			syncMessageIdentity(message, event)
 			message.inlineErrors = [
 				...(message.inlineErrors || []),
-				event.content || data.error || data.message || '未知错误'
+				event.content || data.error || data.message || t('pages.chat.unknownError')
 			]
 
 			break
@@ -1244,7 +1246,7 @@ const handleRename = async () => {
  */
 const handleSend = async (content?: string) => {
 	if (!content || !content.trim()) {
-		ElMessage.warning('请输入对话内容')
+		ElMessage.warning(t('pages.chat.inputRequired'))
 		return
 	}
 

+ 5 - 4
apps/web/src/views/editor/Editor.vue

@@ -79,6 +79,7 @@ import NodeView from './NodeView.vue'
 import PublishBtn from './PublishBtn.vue'
 import { nodeMap } from '@/nodes'
 import { IconButton, Input } from '@repo/ui'
+import i18n from '@/i18n'
 import { useI18n } from '@/composables/useI18n'
 
 import type { IWorkflow } from '@repo/workflow'
@@ -113,7 +114,7 @@ const workflow = ref<IWorkflow>(
 		: {
 				id,
 				name: 'workflow_1',
-				created: dayjs().format('MM 月DD 日'),
+				created: dayjs().format(i18n.locale.value === 'zh-cn' ? 'MM月DD日' : 'MMM DD'),
 				nodes: [],
 				edges: []
 			}
@@ -243,7 +244,7 @@ const loadAgentWorkflow = async (agentId: string) => {
 		const response = await agent.postAgentGetAgentInfo({ id: agentId })
 		const result = response?.result
 		if (!response?.isSuccess || !result) {
-			throw new Error('获取智能体信息失败')
+			throw new Error(t('pages.editor.loadAgentInfoFailed'))
 		}
 
 		const normalizedNodes = (result.nodes || []).map(toWorkflowNode)
@@ -406,12 +407,12 @@ const handleDelete = () => {
 			id: id
 		})
 		if (!response?.isSuccess) {
-			ElMessage.error('删除失败')
+			ElMessage.error(t('pages.editor.deleteFailed'))
 			return
 		}
 		delete projectMap[id]
 		localStorage.setItem('workflow-map', JSON.stringify(projectMap))
-		ElMessage.success('删除成功')
+		ElMessage.success(t('pages.editor.deleteSuccess'))
 		await closeEditorAndRefreshWorkflow()
 	})
 }

+ 3 - 3
apps/web/src/views/editor/NodeView.vue

@@ -106,11 +106,11 @@
 		>
 			<button type="button" class="workflow-context-menu__item" @click="handleContextAddNode">
 				<Icon icon="lucide:plus" class="workflow-context-menu__icon">+</Icon>
-				<span>添加节点</span>
+				<span>{{ t('pages.editor.addNode') }}</span>
 			</button>
 			<button type="button" class="workflow-context-menu__item" @click="handleContextAddStickyNote">
 				<Icon icon="lucide:file-plus-corner" class="workflow-context-menu__icon"></Icon>
-				<span>添加注释</span>
+				<span>{{ t('pages.editor.addNote') }}</span>
 			</button>
 			<button
 				type="button"
@@ -119,7 +119,7 @@
 				@click="handleContextRunNode"
 			>
 				<Icon icon="lucide:play" class="workflow-context-menu__icon"></Icon>
-				<span>测试运行</span>
+				<span>{{ t('pages.editor.testRun') }}</span>
 			</button>
 		</div>
 	</Teleport>

+ 2 - 2
apps/web/src/views/editor/PublishBtn.vue

@@ -9,7 +9,7 @@
 		<div class="publish-selector">
 			<div class="publish-selector__title">{{ t('pages.editor.selectPublishNode') }}</div>
 			<div class="flex items-center gap-2">
-				<el-input v-model="search" placeholder="搜索" />
+				<el-input v-model="search" :placeholder="t('pages.editor.publishSearchPlaceholder')" />
 			</div>
 			<el-divider class="my-0" />
 			<el-scrollbar height="280px">
@@ -31,7 +31,7 @@
 
 					<span class="publish-selector__type">{{ nodeMap[node.nodeType]?.displayName }}</span>
 				</button>
-				<el-empty v-if="!publishNodes.length" :image-size="120" description="无节点数据" />
+				<el-empty v-if="!publishNodes.length" :image-size="120" :description="t('pages.editor.publishEmpty')" />
 			</el-scrollbar>
 		</div>
 		<template #reference>

+ 11 - 9
apps/web/src/views/editor/StartNodeGuide.vue

@@ -10,10 +10,10 @@
 	>
 		<template #header>
 			<div class="start-node-guide__header">
-				<h2>选择开始节点来开始</h2>
+				<h2>{{ t('pages.editor.startGuideTitle') }}</h2>
 			</div>
 		</template>
-		<p>不同的开始节点具有不同的功能,您随时可以更改它们。</p>
+		<p>{{ t('pages.editor.startGuideSubtitle') }}</p>
 		<div class="start-node-guide__cards">
 			<button
 				v-for="item in startNodes"
@@ -34,32 +34,34 @@
 
 <script setup lang="ts">
 import { Icon } from '@repo/ui'
+import { useI18n } from '@/composables/useI18n'
 
 defineEmits<{
 	'create-node': [type: string]
 	close: []
 }>()
 
+const { t } = useI18n()
+
 const startNodes = [
 	{
 		type: 'start',
-		title: '用户输入(原始开始节点)',
-		description:
-			'允许设置用户输入变量的开始节点,具有 Web 应用程序、服务 API、MCP 服务器和工作流即工具功能。',
+		title: t('pages.editor.startGuideStartNodeTitle'),
+		description: t('pages.editor.startGuideStartNodeDesc'),
 		icon: 'lucide:home',
 		color: '#0b74ff'
 	},
 	{
 		type: 'trigger-schedule',
-		title: '定时触发',
-		description: '按小时、每天、每周或每月定时运行工作流,适合周期任务和定时同步。',
+		title: t('nodes.meta.trigger-schedule.displayName'),
+		description: t('pages.editor.startGuideScheduleNodeDesc'),
 		icon: 'lucide:clock',
 		color: '#7c3aed'
 	},
 	{
 		type: 'trigger-webhook',
-		title: 'Webhook 触发',
-		description: '通过自定义 webhook 接收外部请求并启动工作流,适合与其他应用程序集成。',
+		title: t('nodes.meta.trigger-webhook.displayName'),
+		description: t('pages.editor.startGuideWebhookNodeDesc'),
 		icon: 'lucide:webhook',
 		color: '#0b74ff'
 	}

+ 14 - 14
apps/web/src/views/flow/index.vue

@@ -2,7 +2,7 @@
 	<div class="management-container">
 		<div class="page-header">
 			<div class="header-title">
-				<h1>智能编排</h1>
+				<h1>{{ t('sidebar.menu.orchestration') }}</h1>
 			</div>
 		</div>
 
@@ -13,7 +13,7 @@
 				</div>
 				<div>
 					<div class="stat-value">{{ pageData.totalCount }}</div>
-					<div class="stat-label">流程总数</div>
+					<div class="stat-label">{{ t('pages.management.totalWorkflowCount') }}</div>
 				</div>
 			</div>
 			<div class="stat-card">
@@ -48,7 +48,7 @@
 		<div class="panel">
 			<div class="toolbar">
 				<div class="toolbar-left">
-					<el-input v-model="keyword" placeholder="输入关键字搜索" clearable class="search-input">
+					<el-input v-model="keyword" :placeholder="t('pages.management.searchPlaceholder')" clearable class="search-input">
 						<template #prefix>
 							<el-icon><Search /></el-icon>
 						</template>
@@ -56,18 +56,18 @@
 					<div class="toolbar-actions">
 						<el-button type="primary" @click="loadAgents(pageIndex)">
 							<el-icon><Search /></el-icon>
-							查询
+							{{ t('common.search') }}
 						</el-button>
 						<el-button :loading="loading" @click="loadAgents(pageIndex)">
 							<el-icon><RefreshRight /></el-icon>
-							刷新
+							{{ t('common.refresh') }}
 						</el-button>
 					</div>
 				</div>
 				<div class="toolbar-right">
 					<el-button type="primary" @click="openCreateWorkflow">
 						<el-icon><Plus /></el-icon>
-						新建编排
+						{{ t('shared.createWorkflow.title') }}
 					</el-button>
 				</div>
 			</div>
@@ -88,9 +88,9 @@
 							</span>
 							<template #dropdown>
 								<el-dropdown-menu>
-									<el-dropdown-item @click="openEditWorkflow(row)"> 编辑 </el-dropdown-item>
+									<el-dropdown-item @click="openEditWorkflow(row)">{{ t('common.edit') }}</el-dropdown-item>
 									<el-dropdown-item @click="confirmDeleteWorkflow(row.id)" divided>
-										<span class="danger-text">删除</span>
+										<span class="danger-text">{{ t('common.delete') }}</span>
 									</el-dropdown-item>
 								</el-dropdown-menu>
 							</template>
@@ -202,7 +202,7 @@ const fallbackText = computed(
 const emptyText = computed(
 	() =>
 		({
-			default: '暂无编排数据',
+			default: t('pages.management.emptyDefault'),
 			filtered: t('pages.management.empty.filtered')
 		}) as const
 )
@@ -269,20 +269,20 @@ const openEditWorkflow = async (row: AgentListItem) => {
 		createWorkflowVisible.value = true
 	} catch (error) {
 		console.error('openEditWorkflow error', error)
-		ElMessage.error('加载编排详情失败')
+		ElMessage.error(t('pages.management.loadFailed'))
 	}
 }
 
 const confirmDeleteWorkflow = async (id: string) => {
 	try {
-		await ElMessageBox.confirm('确定要删除该编排吗?删除后不可恢复。', '删除确认', {
+		await ElMessageBox.confirm(t('pages.management.deleteConfirmMessage'), t('common.confirmDelete.title'), {
 			type: 'warning',
-			confirmButtonText: '删除',
-			cancelButtonText: '取消'
+			confirmButtonText: t('common.delete'),
+			cancelButtonText: t('common.cancel')
 		})
 		const res = await agent.postAgentDoDeleteAgent({ id })
 		if (!res.isSuccess) throw new Error('delete failed')
-		ElMessage.success('删除成功')
+		ElMessage.success(t('pages.management.deleteSuccess'))
 		await loadAgents(pageIndex.value)
 	} catch (error) {
 		if (error !== 'cancel' && error !== 'close') {

+ 79 - 76
apps/web/src/views/knowledge/DocumentManage.vue

@@ -5,7 +5,7 @@
 				<el-input
 					v-model="keyword"
 					clearable
-					placeholder="搜索知识标题"
+					:placeholder="t('pages.knowledge.document.searchPlaceholder')"
 					style="width: 240px"
 					@keyup.enter="fetchKnowledgeList"
 					@blur="fetchKnowledgeList"
@@ -14,7 +14,7 @@
 				<el-input
 					v-model="fileType"
 					clearable
-					placeholder="文件类型,如: txt / manual"
+					:placeholder="t('pages.knowledge.document.fileTypePlaceholder')"
 					style="width: 180px"
 					@keyup.enter="fetchKnowledgeList"
 					@blur="fetchKnowledgeList"
@@ -25,7 +25,7 @@
 					<el-icon>
 						<Refresh />
 					</el-icon>
-					刷新
+					{{ t('pages.knowledge.document.refresh') }}
 				</el-button>
 			</div>
 			<div class="action-bar__right">
@@ -33,13 +33,13 @@
 					<el-icon>
 						<Upload />
 					</el-icon>
-					上传文件
+					{{ t('pages.knowledge.document.uploadFile') }}
 				</el-button>
 				<el-button v-permission="{ buttonCode: 'add_md', menuCode: 'sys_ai_knowledge_base' }" type="primary" @click="openManualDrawer">
 					<el-icon>
 						<Plus />
 					</el-icon>
-					自定义
+					{{ t('pages.knowledge.document.custom') }}
 				</el-button>
 			</div>
 		</div>
@@ -47,7 +47,7 @@
 		<el-card>
 			<template #header>
 				<div class="card-header">
-					<span>知识列表</span>
+					<span>{{ t('pages.knowledge.document.listTitle') }}</span>
 				</div>
 			</template>
 
@@ -57,37 +57,37 @@
 				v-loading="loading"
 				border
 			>
-				<el-table-column prop="title" label="标题" min-width="220" />
-				<el-table-column prop="type" label="类型" width="100">
+				<el-table-column prop="title" :label="t('pages.knowledge.document.title')" min-width="220" />
+				<el-table-column prop="type" :label="t('pages.knowledge.document.type')" width="100">
 					<template #default="{ row }">
-						<el-tag v-if="row.type === 'file'">文件上传</el-tag>
-						<el-tag v-if="row.type === 'manual'">自定义</el-tag>
+						<el-tag v-if="row.type === 'file'">{{ t('pages.knowledge.document.fileUpload') }}</el-tag>
+						<el-tag v-if="row.type === 'manual'">{{ t('pages.knowledge.document.custom') }}</el-tag>
 					</template>
 				</el-table-column>
-				<el-table-column prop="file_name" label="文件名" min-width="180" />
-				<el-table-column prop="file_type" label="文件类型" width="100" />
-				<el-table-column prop="source" label="来源" width="110" />
-				<el-table-column prop="parse_status" label="解析状态" width="120">
+				<el-table-column prop="file_name" :label="t('pages.knowledge.document.fileName')" min-width="180" />
+				<el-table-column prop="file_type" :label="t('pages.knowledge.document.fileType')" width="100" />
+				<el-table-column prop="source" :label="t('pages.knowledge.document.source')" width="110" />
+				<el-table-column prop="parse_status" :label="t('pages.knowledge.document.parseStatus')" width="120">
 					<template #default="{ row }">
 						<el-tag :type="getStatusTagType(row.parse_status)">
 							{{ row.parse_status ? stateMap?.[row.parse_status] : '-' }}
 						</el-tag>
 					</template>
 				</el-table-column>
-				<el-table-column prop="summary_status" label="摘要状态" width="120">
+				<el-table-column prop="summary_status" :label="t('pages.knowledge.document.summaryStatus')" width="120">
 					<template #default="{ row }">
 						<el-tag :type="getStatusTagType(row.summary_status)">
 							{{ row.parse_status ? stateMap?.[row.summary_status] : '-' }}
 						</el-tag>
 					</template>
 				</el-table-column>
-				<el-table-column label="错误信息" min-width="180">
+				<el-table-column :label="t('pages.knowledge.document.errorMessage')" min-width="180">
 					<template #default="{ row }">
 						<span class="truncate-text">{{ row.error_message || '-' }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column prop="creationTime" label="创建时间" width="180" />
-				<el-table-column label="操作" width="220" fixed="right">
+				<el-table-column prop="creationTime" :label="t('pages.knowledge.document.creationTime')" width="180" />
+				<el-table-column :label="t('pages.knowledge.document.actions')" width="220" fixed="right">
 					<template #default="{ row }">
 						<el-button
 							v-permission="{ buttonCode: 'edit_doc', menuCode: 'sys_ai_knowledge_base' }"
@@ -96,61 +96,61 @@
 							type="primary"
 							@click="openEditDrawer(row)"
 						>
-							更新
+							{{ t('pages.knowledge.document.update') }}
 						</el-button>
-						<el-button v-permission="{ buttonCode: 'rebuild_index', menuCode: 'sys_ai_knowledge_base' }" link type="warning" @click="reparseKnowledge(row.id)">重新解析</el-button>
-						<el-button v-permission="{ buttonCode: 'del_doc', menuCode: 'sys_ai_knowledge_base' }" link type="danger" @click="removeKnowledge(row.id)">删除</el-button>
+						<el-button v-permission="{ buttonCode: 'rebuild_index', menuCode: 'sys_ai_knowledge_base' }" link type="warning" @click="reparseKnowledge(row.id)">{{ t('pages.knowledge.document.reparse') }}</el-button>
+						<el-button v-permission="{ buttonCode: 'del_doc', menuCode: 'sys_ai_knowledge_base' }" link type="danger" @click="removeKnowledge(row.id)">{{ t('common.delete') }}</el-button>
 					</template>
 				</el-table-column>
-				<template #empty>暂无知识内容</template>
+				<template #empty>{{ t('pages.knowledge.document.noContent') }}</template>
 			</el-table>
-			<el-empty v-else description="暂无知识内容" class="page-empty" />
+			<el-empty v-else :description="t('pages.knowledge.document.noContent')" class="page-empty" />
 		</el-card>
 		<!-- 新建 -->
-		<el-drawer v-model="manualDrawerVisible" title="新建知识" direction="rtl" size="640px">
+		<el-drawer v-model="manualDrawerVisible" :title="t('pages.knowledge.document.createTitle')" direction="rtl" size="640px">
 			<el-form ref="manualFormRef" :model="manualForm" :rules="manualRules" label-position="top">
-				<el-form-item label="标题" prop="title">
-					<el-input v-model="manualForm.title" placeholder="请输入标题" />
+				<el-form-item :label="t('pages.knowledge.document.title')" prop="title">
+					<el-input v-model="manualForm.title" :placeholder="t('pages.knowledge.document.titlePlaceholder')" />
 				</el-form-item>
-				<el-form-item label="知识内容" prop="content">
-					<el-input v-model="manualForm.content" type="textarea" placeholder="请输入知识正文" />
+				<el-form-item :label="t('pages.knowledge.document.contentLabel')" prop="content">
+					<el-input v-model="manualForm.content" type="textarea" :placeholder="t('pages.knowledge.document.contentPlaceholder')" />
 				</el-form-item>
-				<el-form-item label="发布">
+				<el-form-item :label="t('pages.knowledge.document.publish')">
 					<el-switch v-model="manualForm.publish" />
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="manualDrawerVisible = false">取消</el-button>
+					<el-button @click="manualDrawerVisible = false">{{ t('common.cancel') }}</el-button>
 					<el-button type="primary" :loading="submitLoading" @click="submitManualKnowledge">
-						提交
+						{{ t('common.submit') }}
 					</el-button>
 				</div>
 			</template>
 		</el-drawer>
 		<!-- 导入模版 -->
-		<el-drawer v-model="fileDrawerVisible" title="导入文件知识" direction="rtl" size="640px">
+		<el-drawer v-model="fileDrawerVisible" :title="t('pages.knowledge.document.importTitle')" direction="rtl" size="640px">
 			<el-form :model="fileForm" label-position="top">
-				<el-form-item label="上传文件">
+				<el-form-item :label="t('pages.knowledge.document.uploadFile')">
 					<FileUploadInput
 						v-model="uploadFile"
 						:multiple="false"
 						:file-types="['document', 'image', 'audio']"
 						:allow-link-input="false"
-						tip="支持文档、图片、音频格式,上传后会按知识内容处理。"
+						:tip="t('pages.knowledge.document.uploadTip')"
 						style="width: 100%"
 					/>
 				</el-form-item>
 				<el-form-item>
 					<div class="metadata-config">
 						<div class="metadata-config__top">
-							<span>元数据(可选)</span>
+							<span>{{ t('pages.knowledge.document.metadataOptional') }}</span>
 							<el-button type="primary" link @click="addMetadataItem">
-								<el-icon> <Plus /> </el-icon>添加元数据
+								<el-icon> <Plus /> </el-icon>{{ t('pages.knowledge.document.addMetadata') }}
 							</el-button>
 						</div>
 						<div class="metadata-config__desc">
-							导入文件知识时附加的元数据,常用于业务分类、标签标记、来源标识等场景
+							{{ t('pages.knowledge.document.metadataDesc') }}
 						</div>
 						<div class="metadata-config__rows">
 							<div
@@ -158,29 +158,29 @@
 								:key="`metadata-${index}`"
 								class="metadata-config__row"
 							>
-								<el-input v-model="item.key" placeholder="元数据名称" />
-								<el-input v-model="item.value" placeholder="元数据值" />
+								<el-input v-model="item.key" :placeholder="t('pages.knowledge.document.metadataKeyPlaceholder')" />
+								<el-input v-model="item.value" :placeholder="t('pages.knowledge.document.metadataValuePlaceholder')" />
 								<el-button
 									link
 									type="danger"
 									:disabled="fileForm.metadata.length === 1"
 									@click="removeMetadataItem(index)"
 								>
-									删除
+									{{ t('common.delete') }}
 								</el-button>
 							</div>
 						</div>
 					</div>
 				</el-form-item>
-				<el-form-item label="启用多模型解析">
+				<el-form-item :label="t('pages.knowledge.document.enableMultiModel')">
 					<el-switch v-model="fileForm.enable_multi_model" />
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="fileDrawerVisible = false">取消</el-button>
+					<el-button @click="fileDrawerVisible = false">{{ t('common.cancel') }}</el-button>
 					<el-button type="primary" :loading="submitLoading" @click="submitFileKnowledge">
-						提交
+						{{ t('common.submit') }}
 					</el-button>
 				</div>
 			</template>
@@ -188,7 +188,7 @@
 		<!-- 编辑 -->
 		<el-drawer
 			v-model="editDrawerVisible"
-			title="更新知识"
+			:title="t('pages.knowledge.document.editTitle')"
 			direction="rtl"
 			size="560px"
 			class="edit-knowledge-drawer"
@@ -201,10 +201,10 @@
 				label-position="top"
 				class="edit-knowledge-form"
 			>
-				<el-form-item label="标题" prop="title">
+				<el-form-item :label="t('pages.knowledge.document.title')" prop="title">
 					<el-input v-model="editForm.title" />
 				</el-form-item>
-				<el-form-item label="描述(点击编辑,支持Markdown语法)" class="edit-description-item">
+				<el-form-item :label="t('pages.knowledge.document.descriptionLabel')" class="edit-description-item">
 					<!-- <markdown v-if="!isEdit" :content="editForm.description" @click="isEdit = true" /> -->
 					<el-input
 						v-model="editForm.description"
@@ -216,9 +216,9 @@
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="editDrawerVisible = false">取消</el-button>
+					<el-button @click="editDrawerVisible = false">{{ t('common.cancel') }}</el-button>
 					<el-button type="primary" :loading="submitLoading" @click="submitEditKnowledge">
-						保存
+						{{ t('common.save') }}
 					</el-button>
 				</div>
 			</template>
@@ -227,7 +227,8 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, watch } from 'vue'
+import { computed, reactive, ref, watch } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Refresh, Upload } from '@element-plus/icons-vue'
 import { knowledge } from '@repo/api-service'
@@ -245,6 +246,8 @@ import type {
 
 const props = defineProps<{ currentBase: KnowledgeBaseItem }>()
 
+const { t } = useI18n()
+
 const loading = ref(false)
 const submitLoading = ref(false)
 const keyword = ref('')
@@ -283,12 +286,12 @@ const editForm = reactive<KnowledgeEditForm>({
 })
 
 const manualRules = {
-	title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-	content: [{ required: true, message: '请输入知识内容', trigger: 'blur' }]
+	title: [{ required: true, message: t('pages.knowledge.document.titleRequired'), trigger: 'blur' }],
+	content: [{ required: true, message: t('pages.knowledge.document.contentRequired'), trigger: 'blur' }]
 }
 
 const editRules = {
-	title: [{ required: true, message: '请输入标题', trigger: 'blur' }]
+	title: [{ required: true, message: t('pages.knowledge.document.titleRequired'), trigger: 'blur' }]
 }
 
 function getStatusTagType(status?: string) {
@@ -300,19 +303,19 @@ function getStatusTagType(status?: string) {
 	return 'info'
 }
 
-const stateMap = {
-	done: '完成',
-	completed: '完成',
-	failed: '失败',
-	finished: '结束',
-	parsing: '解析中',
-	error: '错误',
-	running: '运行中',
-	processing: '处理中',
-	pending: '处理中',
-	finalizing: '结束中',
-	none: '无'
-}
+const stateMap = computed(() => ({
+	done: t('pages.knowledge.document.stateDone'),
+	completed: t('pages.knowledge.document.stateDone'),
+	failed: t('pages.knowledge.document.stateFailed'),
+	finished: t('pages.knowledge.document.stateFinished'),
+	parsing: t('pages.knowledge.document.stateParsing'),
+	error: t('pages.knowledge.document.stateError'),
+	running: t('pages.knowledge.document.stateRunning'),
+	processing: t('pages.knowledge.document.stateProcessing'),
+	pending: t('pages.knowledge.document.stateProcessing'),
+	finalizing: t('pages.knowledge.document.stateFinalizing'),
+	none: t('pages.knowledge.document.stateNone')
+}))
 
 async function fetchKnowledgeList() {
 	if (!props.currentBase.id) return
@@ -398,11 +401,11 @@ async function submitManualKnowledge() {
 			content: manualForm.content,
 			publish: manualForm.publish
 		})
-		ElMessage.success('知识已创建')
+		ElMessage.success(t('pages.knowledge.document.createSuccess'))
 		manualDrawerVisible.value = false
 		await fetchKnowledgeList()
 	} catch {
-		ElMessage.error('创建失败')
+		ElMessage.error(t('pages.knowledge.document.createFailed'))
 	} finally {
 		submitLoading.value = false
 	}
@@ -420,7 +423,7 @@ function buildMetadataPayload() {
 async function submitFileKnowledge() {
 	const fileId = uploadFile.value?.path || ''
 	if (!fileId) {
-		ElMessage.warning('请先上传文件')
+		ElMessage.warning(t('pages.knowledge.document.uploadFileFirst'))
 		return
 	}
 
@@ -433,14 +436,14 @@ async function submitFileKnowledge() {
 			enable_multi_model: fileForm.enable_multi_model
 		})
 		if (!res?.isSuccess) {
-			ElMessage.error((res as any)?.error || '导入失败')
+			ElMessage.error((res as any)?.error || t('pages.knowledge.document.importFailed'))
 		} else {
-			ElMessage.success('文件知识已导入')
+			ElMessage.success(t('pages.knowledge.document.importSuccess'))
 			fileDrawerVisible.value = false
 			await fetchKnowledgeList()
 		}
 	} catch {
-		ElMessage.error('导入失败')
+		ElMessage.error(t('pages.knowledge.document.importFailed'))
 	} finally {
 		submitLoading.value = false
 	}
@@ -457,11 +460,11 @@ async function submitEditKnowledge() {
 			title: editForm.title,
 			description: editForm.description
 		})
-		ElMessage.success('知识已更新')
+		ElMessage.success(t('pages.knowledge.document.updateSuccess'))
 		editDrawerVisible.value = false
 		await fetchKnowledgeList()
 	} catch {
-		ElMessage.error('更新失败')
+		ElMessage.error(t('pages.knowledge.document.updateFailed'))
 	} finally {
 		submitLoading.value = false
 	}
@@ -470,13 +473,13 @@ async function submitEditKnowledge() {
 async function reparseKnowledge(id?: string) {
 	if (!id) return
 	await knowledge.postAiKnowledgeReparse({ id })
-	ElMessage.success('已触发重新解析')
+	ElMessage.success(t('pages.knowledge.document.reparseSuccess'))
 	await fetchKnowledgeList()
 }
 
 async function removeKnowledge(id?: string) {
 	if (!id) return
-	const confirmed = await ElMessageBox.confirm('确定删除该知识吗?删除后不可恢复。', '删除确认', {
+	const confirmed = await ElMessageBox.confirm(t('pages.knowledge.document.confirmDelete'), t('pages.knowledge.document.deleteTitle'), {
 		type: 'warning'
 	})
 		.then(() => true)
@@ -484,7 +487,7 @@ async function removeKnowledge(id?: string) {
 	if (!confirmed) return
 
 	await knowledge.postAiKnowledgeOpenApiDelete({ id })
-	ElMessage.success('知识已删除')
+	ElMessage.success(t('pages.knowledge.document.deleteSuccess'))
 	await fetchKnowledgeList()
 }
 

+ 18 - 15
apps/web/src/views/knowledge/KnowledgeBaseSidebar.vue

@@ -2,14 +2,14 @@
 	<aside class="knowledge-sidebar">
 		<div class="sidebar-header">
 			<div>
-				<div class="sidebar-title">知识库列表</div>
+				<div class="sidebar-title">{{ t('pages.knowledge.sidebar.listTitle') }}</div>
 			</div>
 			<div class="sidebar-actions">
 				<el-button v-permission="{ buttonCode: 'add_base', menuCode: 'sys_ai_knowledge_base' }" type="primary" @click="openCreateDrawer">
 					<el-icon>
 						<Plus />
 					</el-icon>
-					新建
+					{{ t('pages.knowledge.sidebar.create') }}
 				</el-button>
 			</div>
 		</div>
@@ -18,7 +18,7 @@
 			<el-input
 				v-model="keyword"
 				clearable
-				placeholder="搜索知识库名称"
+				:placeholder="t('pages.knowledge.sidebar.searchPlaceholder')"
 				@keyup.enter="refreshKnowledgeBases"
 				@clear="refreshKnowledgeBases"
 			>
@@ -31,9 +31,9 @@
 				</template>
 			</el-input>
 			<el-radio-group v-model="typeFilter" size="small" @change="refreshKnowledgeBases">
-				<el-radio-button label="">全部</el-radio-button>
-				<el-radio-button label="document">知识</el-radio-button>
-				<el-radio-button label="faq">问答</el-radio-button>
+				<el-radio-button label="">{{ t('pages.knowledge.sidebar.all') }}</el-radio-button>
+				<el-radio-button label="document">{{ t('pages.knowledge.sidebar.knowledge') }}</el-radio-button>
+				<el-radio-button label="faq">{{ t('pages.knowledge.sidebar.faq') }}</el-radio-button>
 			</el-radio-group>
 		</div>
 
@@ -58,23 +58,23 @@
 					<div class="base-card__top">
 						<div class="base-card__title">{{ item.name }}</div>
 						<el-tag :type="item.type === 'faq' ? 'warning' : 'success'" size="small">
-							{{ item.type === 'faq' ? '问答' : '知识' }}
+							{{ item.type === 'faq' ? t('pages.knowledge.sidebar.faq') : t('pages.knowledge.sidebar.knowledge') }}
 						</el-tag>
 					</div>
-					<div class="base-card__desc">{{ item.description || '暂无描述' }}</div>
+					<div class="base-card__desc">{{ item.description || t('pages.knowledge.sidebar.noDescription') }}</div>
 
 					<div class="base-card__footer">
 						<span>{{ item.updateTime || item.creationTime || '-' }}</span>
 						<div class="base-card__actions" @click.stop>
-							<el-button v-permission="{ buttonCode: 'edit_base', menuCode: 'sys_ai_knowledge_base' }" link type="primary" @click="openEditDrawer(item.id)">编辑</el-button>
-							<el-button v-permission="{ buttonCode: 'del_base', menuCode: 'sys_ai_knowledge_base' }" link type="danger" @click="removeKnowledgeBase(item.id)">删除</el-button>
+							<el-button v-permission="{ buttonCode: 'edit_base', menuCode: 'sys_ai_knowledge_base' }" link type="primary" @click="openEditDrawer(item.id)">{{ t('common.edit') }}</el-button>
+							<el-button v-permission="{ buttonCode: 'del_base', menuCode: 'sys_ai_knowledge_base' }" link type="danger" @click="removeKnowledgeBase(item.id)">{{ t('common.delete') }}</el-button>
 						</div>
 					</div>
 				</div>
-				<div v-if="loadingMore" class="list-status">加载中...</div>
-				<div v-else-if="!hasNextPage" class="list-status">没有更多了</div>
+				<div v-if="loadingMore" class="list-status">{{ t('pages.knowledge.sidebar.loading') }}</div>
+				<div v-else-if="!hasNextPage" class="list-status">{{ t('pages.knowledge.sidebar.noMore') }}</div>
 			</div>
-			<el-empty v-else description="暂无知识库" />
+			<el-empty v-else :description="t('pages.knowledge.sidebar.noKnowledge')" />
 		</el-scrollbar>
 		<EditModal ref="editDrawerRef" @refresh="refreshKnowledgeBases" />
 	</aside>
@@ -82,6 +82,7 @@
 
 <script setup lang="ts">
 import { nextTick, onMounted, ref, watch } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search } from '@element-plus/icons-vue'
 import { knowledge } from '@repo/api-service'
@@ -91,6 +92,8 @@ import EditModal from './components/KnowledgeBaseEditModal.vue'
 const props = defineProps<{ currentBaseId: string }>()
 const emit = defineEmits<{ (e: 'select-base', value?: KnowledgeBaseItem): void }>()
 
+const { t } = useI18n()
+
 const PAGE_SIZE = 20
 
 const loading = ref(false)
@@ -200,7 +203,7 @@ function openEditDrawer(id: string) {
 }
 
 async function removeKnowledgeBase(id: string) {
-	const confirmed = await ElMessageBox.confirm('确定删除该知识库吗?删除后不可恢复。', '删除确认', {
+	const confirmed = await ElMessageBox.confirm(t('pages.knowledge.sidebar.confirmDelete'), t('pages.knowledge.sidebar.deleteTitle'), {
 		type: 'warning'
 	})
 		.then(() => true)
@@ -208,7 +211,7 @@ async function removeKnowledgeBase(id: string) {
 	if (!confirmed) return
 
 	await knowledge.postAiKnowledgeBaseOpenApiDelete({ id })
-	ElMessage.success('知识库已删除')
+	ElMessage.success(t('pages.knowledge.sidebar.deleteSuccess'))
 	await refreshKnowledgeBases()
 }
 

+ 94 - 91
apps/web/src/views/knowledge/QaManage.vue

@@ -5,24 +5,24 @@
 				<el-input
 					v-model="keyword"
 					clearable
-					placeholder="搜索问题"
+					:placeholder="t('pages.knowledge.qa.searchPlaceholder')"
 					style="width: 280px"
 					@keyup.enter="refreshFaqList"
 					@clear="refreshFaqList"
 				/>
 				<el-button @click="refreshFaqList">
 					<el-icon><Search /></el-icon>
-					查询
+					{{ t('pages.knowledge.qa.search') }}
 				</el-button>
 			</div>
 			<div class="flex">
 				<el-button v-permission="{ buttonCode: 'import_qa', menuCode: 'sys_ai_knowledge_base' }" @click="openImportModal">
 					<el-icon><Upload /></el-icon>
-					模版导入
+					{{ t('pages.knowledge.qa.templateImport') }}
 				</el-button>
 				<el-button v-permission="{ buttonCode: 'add_qa', menuCode: 'sys_ai_knowledge_base' }" type="primary" @click="openCreateDrawer">
 					<el-icon><Plus /></el-icon>
-					新增问答
+					{{ t('pages.knowledge.qa.addQa') }}
 				</el-button>
 			</div>
 		</div>
@@ -30,44 +30,44 @@
 		<el-card>
 			<template #header>
 				<div class="card-header">
-					<span>问答列表</span>
+					<span>{{ t('pages.knowledge.qa.listTitle') }}</span>
 					<!-- <span class="card-header__meta">面向 FAQ 召回和问答命中的问答管理</span> -->
 				</div>
 			</template>
 
 			<el-table v-if="faqList.length || loading" :data="faqList" v-loading="loading" border>
-				<el-table-column prop="standard_question" label="标准问题" min-width="260" />
-				<el-table-column label="答案" min-width="260">
+				<el-table-column prop="standard_question" :label="t('pages.knowledge.qa.standardQuestion')" min-width="260" />
+				<el-table-column :label="t('pages.knowledge.qa.answer')" min-width="260">
 					<template #default="{ row }">
 						<span class="answers-preview">{{ formatAnswers(row.answers) }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column label="相似问数量" width="110">
+				<el-table-column :label="t('pages.knowledge.qa.similarCount')" width="110">
 					<template #default="{ row }">
 						{{ row.similar_questions?.length || 0 }}
 					</template>
 				</el-table-column>
-				<el-table-column label="反例数量" width="110">
+				<el-table-column :label="t('pages.knowledge.qa.negativeCount')" width="110">
 					<template #default="{ row }">
 						{{ row.negative_questions?.length || 0 }}
 					</template>
 				</el-table-column>
-				<el-table-column label="启用" width="90">
+				<el-table-column :label="t('pages.knowledge.qa.enabled')" width="90">
 					<template #default="{ row }">
 						<el-switch :model-value="!!row.is_enabled" @change="toggleFaqStatus(row, $event)" />
 					</template>
 				</el-table-column>
-				<el-table-column prop="creationTime" label="创建时间" width="180" />
-				<el-table-column label="操作" width="160" fixed="right">
+				<el-table-column prop="creationTime" :label="t('pages.knowledge.qa.creationTime')" width="180" />
+				<el-table-column :label="t('pages.knowledge.qa.actions')" width="160" fixed="right">
 					<template #default="{ row }">
-						<el-button link type="primary" @click="openDetailDialog(row.id)">详情</el-button>
-						<el-button v-permission="{ buttonCode: 'edit_qa', menuCode: 'sys_ai_knowledge_base' }" link type="primary" @click="openEditDrawer(row)">编辑</el-button>
-						<el-button v-permission="{ buttonCode: 'del_qa', menuCode: 'sys_ai_knowledge_base' }" link type="danger" @click="removeFaq(row.id)">删除</el-button>
+						<el-button link type="primary" @click="openDetailDialog(row.id)">{{ t('pages.knowledge.qa.detail') }}</el-button>
+						<el-button v-permission="{ buttonCode: 'edit_qa', menuCode: 'sys_ai_knowledge_base' }" link type="primary" @click="openEditDrawer(row)">{{ t('common.edit') }}</el-button>
+						<el-button v-permission="{ buttonCode: 'del_qa', menuCode: 'sys_ai_knowledge_base' }" link type="danger" @click="removeFaq(row.id)">{{ t('common.delete') }}</el-button>
 					</template>
 				</el-table-column>
-				<template #empty>暂无问答条目</template>
+				<template #empty>{{ t('pages.knowledge.qa.noContent') }}</template>
 			</el-table>
-			<el-empty v-else description="暂无问答条目" class="page-empty" />
+			<el-empty v-else :description="t('pages.knowledge.qa.noContent')" class="page-empty" />
 
 			<div v-if="faqList.length || pagination.totalCount" class="pagination-wrap">
 				<el-pagination
@@ -84,25 +84,25 @@
 		<!-- 编辑抽屉 -->
 		<el-drawer
 			v-model="drawerVisible"
-			:title="form.id ? '编辑问答' : '新增问答'"
+			:title="form.id ? t('pages.knowledge.qa.editTitle') : t('pages.knowledge.qa.createTitle')"
 			direction="rtl"
 			size="680px"
 		>
 			<el-form ref="formRef" :model="form" :rules="rules" label-position="top" label-width="100%">
-				<el-form-item label="标准问法" prop="standard_question">
-					<el-input v-model="form.standard_question" placeholder="请输入标准问题" />
+				<el-form-item :label="t('pages.knowledge.qa.standardQuestionLabel')" prop="standard_question">
+					<el-input v-model="form.standard_question" :placeholder="t('pages.knowledge.qa.standardQuestionPlaceholder')" />
 				</el-form-item>
 
 				<el-form-item>
 					<template #label>
 						<div class="array-field__header">
-							<span>相似问法</span>
+							<span>{{ t('pages.knowledge.qa.similarQuestions') }}</span>
 							<el-button link type="primary" @click="addArrayItem('similar_questions')">
-								<el-icon><Plus /></el-icon>添加
+								<el-icon><Plus /></el-icon>{{ t('pages.knowledge.qa.add') }}
 							</el-button>
 						</div>
 						<div class="field-tip">
-							添加与标准问意思相同但表述不同的问题,帮助系统更好地匹配用户查询。
+							{{ t('pages.knowledge.qa.similarQuestionsTip') }}
 						</div>
 					</template>
 
@@ -114,7 +114,7 @@
 						>
 							<el-input
 								v-model="form.similar_questions[index]"
-								placeholder="请输入与标准问语义相同但表述不同的问题"
+								:placeholder="t('pages.knowledge.qa.similarQuestionPlaceholder')"
 							/>
 							<el-button
 								link
@@ -122,7 +122,7 @@
 								:disabled="form.similar_questions.length <= 1"
 								@click="removeArrayItem('similar_questions', index)"
 							>
-								删除
+								{{ t('common.delete') }}
 							</el-button>
 						</div>
 					</div>
@@ -131,12 +131,12 @@
 				<el-form-item>
 					<template #label>
 						<div class="array-field__header">
-							<span>反例</span>
+							<span>{{ t('pages.knowledge.qa.negativeExamples') }}</span>
 							<el-button link type="primary" @click="addArrayItem('negative_questions')">
-								<el-icon><Plus /></el-icon>添加
+								<el-icon><Plus /></el-icon>{{ t('pages.knowledge.qa.add') }}
 							</el-button>
 						</div>
-						<div class="field-tip">添加不应匹配此答案的问题,用于排除误匹配。</div>
+						<div class="field-tip">{{ t('pages.knowledge.qa.negativeExamplesTip') }}</div>
 					</template>
 					<div class="array-field">
 						<div
@@ -146,7 +146,7 @@
 						>
 							<el-input
 								v-model="form.negative_questions[index]"
-								placeholder="请输入不应匹配此答案的问题"
+								:placeholder="t('pages.knowledge.qa.negativeExamplePlaceholder')"
 							/>
 							<el-button
 								link
@@ -154,7 +154,7 @@
 								:disabled="form.negative_questions.length <= 1"
 								@click="removeArrayItem('negative_questions', index)"
 							>
-								删除
+								{{ t('common.delete') }}
 							</el-button>
 						</div>
 					</div>
@@ -163,12 +163,12 @@
 				<el-form-item prop="answers">
 					<template #label>
 						<div class="array-field__header">
-							<span>答案</span>
+							<span>{{ t('pages.knowledge.qa.answer') }}</span>
 							<el-button link type="primary" @click="addArrayItem('answers')">
-								<el-icon><Plus /></el-icon>添加
+								<el-icon><Plus /></el-icon>{{ t('pages.knowledge.qa.add') }}
 							</el-button>
 						</div>
-						<div class="field-tip">提供完整准确的答案内容,可添加多个答案以覆盖不同场景。</div>
+						<div class="field-tip">{{ t('pages.knowledge.qa.answerTip') }}</div>
 					</template>
 					<div class="array-field">
 						<div
@@ -180,7 +180,7 @@
 								v-model="form.answers[index]"
 								type="textarea"
 								:rows="3"
-								placeholder="请输入答案内容"
+								:placeholder="t('pages.knowledge.qa.answerPlaceholder')"
 							/>
 							<el-button
 								link
@@ -188,29 +188,29 @@
 								:disabled="form.answers.length <= 1"
 								@click="removeArrayItem('answers', index)"
 							>
-								删除
+								{{ t('common.delete') }}
 							</el-button>
 						</div>
 					</div>
 				</el-form-item>
 
-				<el-form-item label="启用">
+				<el-form-item :label="t('pages.knowledge.qa.enabled')">
 					<el-switch v-model="form.is_enabled" />
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="drawerVisible = false">取消</el-button>
-					<el-button type="primary" :loading="submitLoading" @click="submitForm">保存</el-button>
+					<el-button @click="drawerVisible = false">{{ t('common.cancel') }}</el-button>
+					<el-button type="primary" :loading="submitLoading" @click="submitForm">{{ t('common.save') }}</el-button>
 				</div>
 			</template>
 		</el-drawer>
 		<!-- 详情弹窗 -->
-		<el-dialog v-model="detailVisible" title="问答详情" width="720px">
+		<el-dialog v-model="detailVisible" :title="t('pages.knowledge.qa.detailTitle')" width="720px">
 			<div v-loading="detailLoading" class="detail-panel">
 				<template v-if="detailData">
 					<div class="detail-item">
-						<div class="detail-item__label">标准问题</div>
+						<div class="detail-item__label">{{ t('pages.knowledge.qa.standardQuestion') }}</div>
 						<div class="detail-item__value">
 							<el-tag type="primary" effect="plain">
 								{{ detailData.standard_question || '-' }}
@@ -219,7 +219,7 @@
 					</div>
 
 					<div class="detail-item">
-						<div class="detail-item__label">相似问</div>
+						<div class="detail-item__label">{{ t('pages.knowledge.qa.similarQuestions') }}</div>
 						<div class="detail-tag-list">
 							<el-tag
 								v-for="item in detailData.similar_questions || []"
@@ -234,7 +234,7 @@
 						</div>
 					</div>
 					<div class="detail-item">
-						<div class="detail-item__label">反例</div>
+						<div class="detail-item__label">{{ t('pages.knowledge.qa.negativeExamples') }}</div>
 						<div class="detail-tag-list">
 							<el-tag
 								v-for="item in detailData.negative_questions || []"
@@ -250,7 +250,7 @@
 						</div>
 					</div>
 					<div class="detail-item">
-						<div class="detail-item__label">答案</div>
+						<div class="detail-item__label">{{ t('pages.knowledge.qa.answer') }}</div>
 						<div class="detail-answer-list">
 							<div
 								v-for="(item, index) in detailData.answers || []"
@@ -264,21 +264,21 @@
 					</div>
 					<div class="detail-grid">
 						<div class="detail-item">
-							<div class="detail-item__label">启用状态</div>
+							<div class="detail-item__label">{{ t('pages.knowledge.qa.enabledStatus') }}</div>
 							<div class="detail-item__value">
-								<el-tag :type="detailData.is_enabled ? 'success' : 'warning'">{{
-									detailData.is_enabled ? '启用' : '停用'
-								}}</el-tag>
+								<el-tag :type="detailData.is_enabled ? 'success' : 'warning'">
+									{{ detailData.is_enabled ? t('pages.knowledge.qa.enabledOn') : t('pages.knowledge.qa.enabledOff') }}
+								</el-tag>
 							</div>
 						</div>
 						<div class="detail-item">
-							<div class="detail-item__label">索引方式</div>
+							<div class="detail-item__label">{{ t('pages.knowledge.qa.indexMode') }}</div>
 							<div class="detail-item__value">
 								{{ detailData.index_mode }}
 							</div>
 						</div>
 						<div class="detail-item">
-							<div class="detail-item__label">创建时间</div>
+							<div class="detail-item__label">{{ t('pages.knowledge.qa.creationTime') }}</div>
 							<div class="detail-item__value">{{ detailData.creationTime || '-' }}</div>
 						</div>
 					</div>
@@ -286,66 +286,66 @@
 			</div>
 		</el-dialog>
 		<!-- 导入弹窗 -->
-		<el-dialog v-model="importDialogVisible" title="模版导入" width="500px">
+		<el-dialog v-model="importDialogVisible" :title="t('pages.knowledge.qa.importTitle')" width="500px">
 			<el-form> </el-form>
 			<div class="import-modal-content">
 				<div class="import-step">
-					<div class="step-title">上传文件</div>
-					<div class="step-desc">上传填写好的 Excel 文件。</div>
+					<div class="step-title">{{ t('pages.knowledge.qa.uploadFile') }}</div>
+					<div class="step-desc">{{ t('pages.knowledge.qa.uploadFileDesc') }}</div>
 					<FileUploadInput
 						v-model="importFile"
 						:fileExtensions="['.xlsx', '.xls']"
-						placeholder="点击或拖拽上传 Excel 文件"
+						:placeholder="t('pages.knowledge.qa.uploadPlaceholder')"
 					/>
 				</div>
 
 				<div class="import-step">
-					<div class="step-title">导入模式</div>
-					<el-select v-model="importFormData.mode" placeholder="请选择">
-						<el-option label="追加" value="append"></el-option>
-						<el-option label="覆盖" value="replace"></el-option>
+					<div class="step-title">{{ t('pages.knowledge.qa.importMode') }}</div>
+					<el-select v-model="importFormData.mode" :placeholder="t('pages.knowledge.qa.selectPlaceholder')">
+						<el-option :label="t('pages.knowledge.qa.importAppend')" value="append"></el-option>
+						<el-option :label="t('pages.knowledge.qa.importReplace')" value="replace"></el-option>
 					</el-select>
 				</div>
 
 				<div class="import-step">
-					<div class="step-title">下载模版</div>
-					<div class="step-desc">请下载标准模版,并按照格式填写问答数据。</div>
+					<div class="step-title">{{ t('pages.knowledge.qa.downloadTemplate') }}</div>
+					<div class="step-desc">{{ t('pages.knowledge.qa.downloadTemplateDesc') }}</div>
 					<div>
 						<el-button type="primary" link @click="downloadTemplate">
 							<el-icon><Download /></el-icon>
-							下载 faq_example.xlsx
+							{{ t('pages.knowledge.qa.downloadFaqTemplate') }}
 						</el-button>
 					</div>
 				</div>
 			</div>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="importDialogVisible = false">取消</el-button>
+					<el-button @click="importDialogVisible = false">{{ t('common.cancel') }}</el-button>
 					<el-button
 						type="primary"
 						:loading="importLoading"
 						:disabled="!importFile"
 						@click="submitImport"
 					>
-						开始导入
+						{{ t('pages.knowledge.qa.startImport') }}
 					</el-button>
 				</div>
 			</template>
 		</el-dialog>
 		<el-dialog
 			v-model="importTaskDialogVisible"
-			title="导入任务状态"
+			:title="t('pages.knowledge.qa.importTaskTitle')"
 			width="560px"
 			:close-on-click-modal="false"
 		>
 			<div class="import-task">
 				<div v-if="importTaskInfo" class="import-task__meta">
 					<div class="import-task__row">
-						<span class="import-task__label">任务编号</span>
+						<span class="import-task__label">{{ t('pages.knowledge.qa.taskCode') }}</span>
 						<span class="import-task__value">{{ importTaskInfo.code || '-' }}</span>
 					</div>
 					<div class="import-task__row">
-						<span class="import-task__label">创建时间</span>
+						<span class="import-task__label">{{ t('pages.knowledge.qa.creationTime') }}</span>
 						<span class="import-task__value">{{ importTaskInfo.creationTime || '-' }}</span>
 					</div>
 				</div>
@@ -354,12 +354,12 @@
 					<el-tag :type="getImportTaskStatusTag(importTaskInfo?.status)">
 						{{ getImportTaskStatusText(importTaskInfo?.status) }}
 					</el-tag>
-					<span class="import-task__progress"> 进度:{{ importTaskInfo?.progress || '-' }} </span>
+					<span class="import-task__progress"> {{ t('pages.knowledge.qa.progress') }}:{{ importTaskInfo?.progress || '-' }} </span>
 				</div>
 			</div>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="handleImportTaskDialogClose">关闭</el-button>
+					<el-button @click="handleImportTaskDialogClose">{{ t('pages.knowledge.qa.close') }}</el-button>
 				</div>
 			</template>
 		</el-dialog>
@@ -368,6 +368,7 @@
 
 <script setup lang="ts">
 import { onBeforeUnmount, reactive, ref, watch } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search, Upload, Download } from '@element-plus/icons-vue'
 import { knowledge } from '@repo/api-service'
@@ -398,6 +399,8 @@ interface AsyncTaskInfo {
 
 const props = defineProps<{ currentBaseId: string }>()
 
+const { t } = useI18n()
+
 const loading = ref(false)
 const submitLoading = ref(false)
 const drawerVisible = ref(false)
@@ -449,7 +452,7 @@ const createDefaultForm = (): FaqForm => ({
 const form = reactive<FaqForm>(createDefaultForm())
 
 const rules = {
-	standard_question: [{ required: true, message: '请输入标准问题', trigger: 'blur' }],
+	standard_question: [{ required: true, message: t('pages.knowledge.qa.standardQuestionRequired'), trigger: 'blur' }],
 	answers: [
 		{
 			validator: (_rule: unknown, value: string[], callback: (error?: Error) => void) => {
@@ -457,7 +460,7 @@ const rules = {
 					callback()
 					return
 				}
-				callback(new Error('请至少填写一个答案'))
+				callback(new Error(t('pages.knowledge.qa.answerRequired')))
 			},
 			trigger: 'change'
 		}
@@ -554,7 +557,7 @@ async function openDetailDialog(id?: string) {
 async function openEditDrawer(row: FaqItem) {
 	const detail = await fetchFaqDetail(row.id)
 	if (!detail) {
-		ElMessage.error('问答详情加载失败')
+		ElMessage.error(t('pages.knowledge.qa.detailLoadFailed'))
 		return
 	}
 	applyFaqFormData(detail)
@@ -585,18 +588,18 @@ async function submitForm() {
 		}
 		if (form.id) {
 			await knowledge.postAiFaqUpdate({ id: form.id, ...payload })
-			ElMessage.success('问答已更新')
+			ElMessage.success(t('pages.knowledge.qa.updateSuccess'))
 		} else {
 			await knowledge.postAiFaqCreate({
 				knowledge_base_id: props.currentBaseId,
 				...payload
 			})
-			ElMessage.success('问答已创建')
+			ElMessage.success(t('pages.knowledge.qa.createSuccess'))
 		}
 		drawerVisible.value = false
 		await refreshFaqList()
 	} catch {
-		ElMessage.error('保存失败')
+		ElMessage.error(t('pages.knowledge.qa.saveFailed'))
 	} finally {
 		if (!form.similar_questions.length) form.similar_questions = ['']
 		if (!form.negative_questions.length) form.negative_questions = ['']
@@ -609,7 +612,7 @@ async function toggleFaqStatus(row: FaqItem, value: string | number | boolean) {
 	if (!row.id) return
 	const detail = await fetchFaqDetail(row.id)
 	if (!detail) {
-		ElMessage.error('问答详情加载失败')
+		ElMessage.error(t('pages.knowledge.qa.detailLoadFailed'))
 		return
 	}
 	await knowledge.postAiFaqUpdate({
@@ -620,13 +623,13 @@ async function toggleFaqStatus(row: FaqItem, value: string | number | boolean) {
 		answers: normalizeStringArray(detail.answers),
 		is_enabled: Boolean(value)
 	})
-	ElMessage.success('状态已更新')
+	ElMessage.success(t('pages.knowledge.qa.statusUpdated'))
 	await fetchFaqList()
 }
 
 async function removeFaq(id?: string) {
 	if (!id) return
-	const confirmed = await ElMessageBox.confirm('确定删除该问答吗?删除后不可恢复。', '删除确认', {
+	const confirmed = await ElMessageBox.confirm(t('pages.knowledge.qa.confirmDelete'), t('pages.knowledge.qa.deleteTitle'), {
 		type: 'warning'
 	})
 		.then(() => true)
@@ -634,7 +637,7 @@ async function removeFaq(id?: string) {
 	if (!confirmed) return
 
 	await knowledge.postAiFaqOpenApiDelete({ id })
-	ElMessage.success('问答已删除')
+	ElMessage.success(t('pages.knowledge.qa.deleteSuccess'))
 	await refreshFaqList()
 }
 
@@ -669,17 +672,17 @@ function clearImportTaskTimer() {
 function getImportTaskStatusText(status?: number) {
 	switch (status) {
 		case 0:
-			return '已创建'
+			return t('pages.knowledge.qa.statusCreated')
 		case 1:
-			return '运行中'
+			return t('pages.knowledge.qa.statusRunning')
 		case 2:
-			return '成功'
+			return t('pages.knowledge.qa.statusSuccess')
 		case 3:
-			return '失败'
+			return t('pages.knowledge.qa.statusFailed')
 		case 4:
-			return '挂起'
+			return t('pages.knowledge.qa.statusPending')
 		default:
-			return '处理中'
+			return t('pages.knowledge.qa.statusProcessing')
 	}
 }
 
@@ -713,7 +716,7 @@ function extractAsyncTaskId(payload: any) {
 async function fetchImportTaskInfo(taskId: string) {
 	const res = await knowledge.postBpmGetAsynTaskInfo({ id: taskId })
 	if (!res?.isSuccess || !res.result) {
-		throw new Error((res as any)?.error || '异步任务状态查询失败')
+		throw new Error((res as any)?.error || t('pages.knowledge.qa.taskQueryFailed'))
 	}
 
 	importTaskInfo.value = res.result as AsyncTaskInfo
@@ -728,7 +731,7 @@ async function pollImportTask(taskId: string) {
 
 		if (status === 2) {
 			clearImportTaskTimer()
-			ElMessage.success('导入成功')
+			ElMessage.success(t('pages.knowledge.qa.importSuccess'))
 			await refreshFaqList()
 			importTaskDialogVisible.value = false
 			importTaskInfo.value = null
@@ -737,7 +740,7 @@ async function pollImportTask(taskId: string) {
 
 		if (status === 3) {
 			clearImportTaskTimer()
-			ElMessage.error('导入失败,请查看任务状态信息')
+			ElMessage.error(t('pages.knowledge.qa.importFailedCheckStatus'))
 			return
 		}
 
@@ -748,7 +751,7 @@ async function pollImportTask(taskId: string) {
 	} catch (error) {
 		clearImportTaskTimer()
 		console.error('Fetch import task failed:', error)
-		ElMessage.error('异步任务状态查询失败,请稍后重试')
+		ElMessage.error(t('pages.knowledge.qa.taskQueryFailedRetry'))
 	} finally {
 		importTaskLoading.value = false
 	}
@@ -766,11 +769,11 @@ function downloadTemplate() {
 
 async function submitImport() {
 	if (!importFile.value) {
-		ElMessage.warning('请先上传文件')
+		ElMessage.warning(t('pages.knowledge.qa.uploadFileFirst'))
 		return
 	}
 	if (!props.currentBaseId) {
-		ElMessage.error('知识库ID缺失')
+		ElMessage.error(t('pages.knowledge.qa.knowledgeBaseIdMissing'))
 		return
 	}
 
@@ -794,7 +797,7 @@ async function submitImport() {
 		resetImportForm()
 
 		if (!taskId) {
-			ElMessage.success('导入成功')
+			ElMessage.success(t('pages.knowledge.qa.importSuccess'))
 			await refreshFaqList()
 			return
 		}
@@ -808,7 +811,7 @@ async function submitImport() {
 		await pollImportTask(taskId)
 	} catch (error) {
 		console.error('Import failed:', error)
-		ElMessage.error('导入失败,请检查文件格式或联系管理员')
+		ElMessage.error(t('pages.knowledge.qa.importFailedContactAdmin'))
 	} finally {
 		importLoading.value = false
 	}

+ 15 - 12
apps/web/src/views/knowledge/WikiGraph.vue

@@ -2,8 +2,8 @@
 	<div class="wiki-graph">
 		<div class="wiki-graph__header">
 			<div>
-				<div class="wiki-graph__title">知识图谱</div>
-				<div class="wiki-graph__desc">基于 Wiki 页面与关系边构建的图谱视图,可拖拽、缩放。</div>
+				<div class="wiki-graph__title">{{ t('pages.knowledge.graph.title') }}</div>
+				<div class="wiki-graph__desc">{{ t('pages.knowledge.graph.description') }}</div>
 				<div class="wiki-graph__summary">
 					<div v-for="item in graphSummary" :key="item.label" class="wiki-graph-stat">
 						<span class="wiki-graph-stat__label">{{ item.label }}</span>
@@ -26,6 +26,7 @@
 
 <script setup lang="ts">
 import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import * as d3 from 'd3'
 import { wiki } from '@repo/api-service'
 import type { KnowledgeBaseItem } from './types'
@@ -69,6 +70,8 @@ const props = defineProps<{
 	currentBase: KnowledgeBaseItem
 }>()
 
+const { t } = useI18n()
+
 const graphContainerRef = ref<HTMLDivElement>()
 const graphLoading = ref(false)
 const selectedSlug = ref('')
@@ -81,13 +84,13 @@ const graphData = ref<{ nodes: WikiGraphNode[]; edges: WikiGraphEdge[] }>({
 	edges: []
 })
 
-const legendItems = [
-	{ type: 'summary', label: '摘要', color: '#1d6ff2' },
-	{ type: 'entity', label: '实体', color: '#2cb67d' },
-	{ type: 'concept', label: '概念', color: '#f08c24' },
-	{ type: 'index', label: '索引', color: '#3fa7ff' },
-	{ type: 'log', label: '日志', color: '#eb5757' }
-]
+const legendItems = computed(() => [
+	{ type: 'summary', label: t('pages.knowledge.graph.legendSummary'), color: '#1d6ff2' },
+	{ type: 'entity', label: t('pages.knowledge.graph.legendEntity'), color: '#2cb67d' },
+	{ type: 'concept', label: t('pages.knowledge.graph.legendConcept'), color: '#f08c24' },
+	{ type: 'index', label: t('pages.knowledge.graph.legendIndex'), color: '#3fa7ff' },
+	{ type: 'log', label: t('pages.knowledge.graph.legendLog'), color: '#eb5757' }
+])
 
 const graphSummary = computed(() => {
 	const duplicateCount =
@@ -96,9 +99,9 @@ const graphSummary = computed(() => {
 			: 0
 
 	return [
-		{ label: '节点数', value: graphData.value.nodes.length },
-		{ label: '关系数', value: graphData.value.edges.length },
-		{ label: '合并重复边', value: duplicateCount }
+		{ label: t('pages.knowledge.graph.nodeCount'), value: graphData.value.nodes.length },
+		{ label: t('pages.knowledge.graph.edgeCount'), value: graphData.value.edges.length },
+		{ label: t('pages.knowledge.graph.mergedDuplicates'), value: duplicateCount }
 	]
 })
 

+ 14 - 11
apps/web/src/views/knowledge/WikiManage.vue

@@ -44,7 +44,7 @@
 					</div>
 				</template>
 				<template v-else>
-					<el-empty description="暂无wiki内容" />
+					<el-empty :description="t('pages.knowledge.wiki.noContent')" />
 				</template>
 			</div>
 		</el-scrollbar>
@@ -55,7 +55,7 @@
 				<div class="wiki-detail__header">
 					<el-breadcrumb separator="/" class="wiki-detail__breadcrumb">
 						<el-breadcrumb-item>
-							<button type="button" class="wiki-detail__crumb" @click="backToIndex">索引</button>
+							<button type="button" class="wiki-detail__crumb" @click="backToIndex">{{ t('pages.knowledge.wiki.index') }}</button>
 						</el-breadcrumb-item>
 						<el-breadcrumb-item v-for="(item, index) in pageTrail" :key="`${item.slug}-${index}`">
 							<button
@@ -81,7 +81,7 @@
 						<XMarkdown :markdown="renderedSelectedPageContent" />
 					</div>
 				</template>
-				<el-empty v-else description="页面加载失败" />
+				<el-empty v-else :description="t('pages.knowledge.wiki.pageLoadFailed')" />
 			</div>
 		</el-scrollbar>
 	</div>
@@ -89,6 +89,7 @@
 
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { XMarkdown } from 'vue-element-plus-x'
 import { wiki } from '@repo/api-service'
 import type { KnowledgeBaseItem } from './types'
@@ -126,6 +127,8 @@ const props = defineProps<{
 	currentBase: KnowledgeBaseItem
 }>()
 
+const { t } = useI18n()
+
 const indexLoading = ref(false)
 const detailLoading = ref(false)
 const catalogRef = ref<HTMLDivElement>()
@@ -143,23 +146,23 @@ const renderedIndexContent = computed(() =>
 )
 
 const renderedSelectedPageContent = computed(() =>
-	transformWikiLinks(selectedPage.value?.content || selectedPage.value?.summary || '暂无内容')
+	transformWikiLinks(selectedPage.value?.content || selectedPage.value?.summary || t('pages.knowledge.wiki.noPageContent'))
 )
 
 const catalogSections = computed(() => [
 	{
 		key: 'summary',
-		label: '摘要',
+		label: t('pages.knowledge.wiki.summary'),
 		items: wikiIndexData.value?.summary_pages || []
 	},
 	{
 		key: 'entity',
-		label: '实体',
+		label: t('pages.knowledge.wiki.entity'),
 		items: wikiIndexData.value?.entity_pages || []
 	},
 	{
 		key: 'concept',
-		label: '概念',
+		label: t('pages.knowledge.wiki.concept'),
 		items: wikiIndexData.value?.concept_pages || []
 	}
 ])
@@ -196,13 +199,13 @@ function getItemDescription(item: WikiPageItem) {
 function getPageTypeLabel(pageType: WikiPageType) {
 	switch (pageType) {
 		case 'summary':
-			return '摘要'
+			return t('pages.knowledge.wiki.summary')
 		case 'entity':
-			return '实体'
+			return t('pages.knowledge.wiki.entity')
 		case 'concept':
-			return '概念'
+			return t('pages.knowledge.wiki.concept')
 		case 'index':
-			return '索引'
+			return t('pages.knowledge.wiki.index')
 		default:
 			return pageType
 	}

+ 149 - 153
apps/web/src/views/knowledge/components/KnowledgeBaseEditModal.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-dialog
 		v-model="drawerVisible"
-		:title="editingId ? '编辑知识库' : '新建知识库'"
+		:title="editingId ? t('pages.knowledge.editModal.editTitle') : t('pages.knowledge.editModal.createTitle')"
 		@open="handleOpen"
 		class="knowledge-modal"
 		fullscreen
@@ -16,10 +16,10 @@
 				class="knowledge-form"
 			>
 				<el-tabs v-model="activeTab" tab-position="left" class="settings-tabs">
-					<el-tab-pane label="基础信息" name="basic">
-						<div class="tab-intro">设置知识库的名称、类型和描述信息</div>
+					<el-tab-pane :label="t('pages.knowledge.editModal.tabBasic')" name="basic">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.basicIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="知识库类型" prop="type">
+							<el-form-item :label="t('pages.knowledge.editModal.kbType')" prop="type">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.type"
@@ -28,28 +28,28 @@
 										@change="handleKnowledgeBaseTypeChange"
 									/>
 									<div class="field-tip">
-										FAQ 类型适合结构化问答数据;文档型支持文件解析与分块。
+										{{ t('pages.knowledge.editModal.kbTypeTip') }}
 									</div>
 								</div>
 							</el-form-item>
 
 							<div v-if="isDocumentType" class="subsection ml-120px mb-16px">
-								<div class="subsection-title">索引策略</div>
+								<div class="subsection-title">{{ t('pages.knowledge.editModal.indexStrategy') }}</div>
 								<div class="subsection-tip">
-									配置文档上传后的处理管道。关键词和向量检索为 RAG 必需项,系统会保持开启。
+								{{ t('pages.knowledge.editModal.indexStrategyTip') }}
 								</div>
 								<div class="index-strategy-list">
-									<el-form-item label="知识图谱启用">
+									<el-form-item :label="t('pages.knowledge.editModal.graphEnabled')">
 										<div class="switch-wrap">
 											<el-segmented
 												v-model="form.graph_enabled"
 												class="selection-segmented"
 												:options="booleanSegmentedOptions"
 											/>
-											<div class="field-tip">开启后可使用图谱关系辅助检索。</div>
+											<div class="field-tip">{{ t('pages.knowledge.editModal.graphEnabledTip') }}</div>
 										</div>
 									</el-form-item>
-									<el-form-item label="关键字启用">
+									<el-form-item :label="t('pages.knowledge.editModal.keywordEnabled')">
 										<div class="switch-wrap">
 											<el-segmented
 												v-model="form.keyword_enabled"
@@ -57,10 +57,10 @@
 												:options="booleanSegmentedOptions"
 												disabled
 											/>
-											<div class="field-tip">RAG 检索必须开启,系统会自动保持为开启状态。</div>
+											<div class="field-tip">{{ t('pages.knowledge.editModal.keywordEnabledTip') }}</div>
 										</div>
 									</el-form-item>
-									<el-form-item label="向量启用">
+									<el-form-item :label="t('pages.knowledge.editModal.vectorEnabled')">
 										<div class="switch-wrap">
 											<el-segmented
 												v-model="form.vector_enabled"
@@ -68,42 +68,42 @@
 												:options="booleanSegmentedOptions"
 												disabled
 											/>
-											<div class="field-tip">RAG 检索必须开启,系统会自动保持为开启状态。</div>
+											<div class="field-tip">{{ t('pages.knowledge.editModal.vectorEnabledTip') }}</div>
 										</div>
 									</el-form-item>
-									<el-form-item label="维基启用">
+									<el-form-item :label="t('pages.knowledge.editModal.wikiEnabled')">
 										<div class="switch-wrap">
 											<el-segmented
 												v-model="form.wiki_enabled"
 												class="selection-segmented"
 												:options="booleanSegmentedOptions"
 											/>
-											<div class="field-tip">开启后可从 Wiki 内容中补充知识来源。</div>
+											<div class="field-tip">{{ t('pages.knowledge.editModal.wikiEnabledTip') }}</div>
 										</div>
 									</el-form-item>
 								</div>
 							</div>
 
-							<el-form-item label="知识库名称" prop="name">
-								<el-input v-model="form.name" placeholder="请输入知识库名称" />
+							<el-form-item :label="t('pages.knowledge.editModal.kbName')" prop="name">
+								<el-input v-model="form.name" :placeholder="t('pages.knowledge.editModal.kbNamePlaceholder')" />
 							</el-form-item>
 
-							<el-form-item label="知识库描述">
+							<el-form-item :label="t('pages.knowledge.editModal.kbDescription')">
 								<el-input
 									v-model="form.description"
 									type="textarea"
 									:rows="3"
-									placeholder="请输入知识库描述"
+									:placeholder="t('pages.knowledge.editModal.kbDescriptionPlaceholder')"
 								/>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="模型配置" name="model">
-						<div class="tab-intro">为知识库选择合适的 AI 模型</div>
+					<el-tab-pane :label="t('pages.knowledge.editModal.tabModel')" name="model">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.modelIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="LLM大语言模型" prop="summary_model_id">
-								<el-select v-model="form.summary_model_id" placeholder="请选择">
+							<el-form-item :label="t('pages.knowledge.editModal.llmModel')" prop="summary_model_id">
+								<el-select v-model="form.summary_model_id" :placeholder="t('pages.knowledge.editModal.selectPlaceholder')">
 									<el-option
 										v-for="item in summaryModels"
 										:key="item.id"
@@ -111,10 +111,10 @@
 										:value="item.id"
 									/>
 								</el-select>
-								<div class="field-tip">用于总结和摘要的大语言模型。</div>
+								<div class="field-tip">{{ t('pages.knowledge.editModal.llmModelTip') }}</div>
 							</el-form-item>
-							<el-form-item label="Embedding 嵌入模型" prop="embedding_model_id">
-								<el-select v-model="form.embedding_model_id" placeholder="请选择">
+							<el-form-item :label="t('pages.knowledge.editModal.embeddingModel')" prop="embedding_model_id">
+								<el-select v-model="form.embedding_model_id" :placeholder="t('pages.knowledge.editModal.selectPlaceholder')">
 									<el-option
 										v-for="item in embeddingModels"
 										:key="item.id"
@@ -122,18 +122,18 @@
 										:value="item.id"
 									/>
 								</el-select>
-								<div class="field-tip">用于文本向量化的嵌入模型。</div>
+								<div class="field-tip">{{ t('pages.knowledge.editModal.embeddingModelTip') }}</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane label="向量存储" name="vectorStore">
-						<div class="tab-intro">从全局向量存储配置中选择, 可为空, 默认/为空:系统默认。</div>
+					<el-tab-pane :label="t('pages.knowledge.editModal.tabVectorStore')" name="vectorStore">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.vectorStoreIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="向量存储">
+							<el-form-item :label="t('pages.knowledge.editModal.vectorStore')">
 								<el-select
 									v-model="form.vector_store_id"
-									placeholder="系统默认"
+									:placeholder="t('pages.knowledge.editModal.vectorStoreDefault')"
 									clearable
 									:disabled="!!editingId"
 								>
@@ -145,16 +145,16 @@
 									/>
 								</el-select>
 								<div class="field-tip">
-									创建后不可更改。如需迁移,请创建一个绑定到目标存储的新 KB 并重新索引。
+									{{ t('pages.knowledge.editModal.vectorStoreTip') }}
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="isDocumentType" label="解析引擎" name="parser">
-						<div class="tab-intro">为不同文件类型选择文档解析引擎</div>
+					<el-tab-pane v-if="isDocumentType" :label="t('pages.knowledge.editModal.tabParser')" name="parser">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.parserIntro') }}</div>
 						<div class="collapse-body">
-							<div class="field-tip compact-tip">未配置的文件类型将使用内置解析引擎。</div>
+							<div class="field-tip compact-tip">{{ t('pages.knowledge.editModal.parserTip') }}</div>
 							<div class="parser-rule-list">
 								<div
 									v-for="rule in form.parser_engine_rules"
@@ -162,7 +162,7 @@
 									class="parser-rule-item"
 								>
 									<div class="parser-rule-item__types">{{ rule.file_types.join(', ') }}</div>
-									<el-select v-model="rule.engine" placeholder="请选择">
+									<el-select v-model="rule.engine" :placeholder="t('pages.knowledge.editModal.selectPlaceholder')">
 										<el-option
 											v-for="item in parserEngineOptions"
 											:key="item"
@@ -175,21 +175,21 @@
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="isDocumentType" label="图像处理" name="image">
-						<div class="tab-intro">配置图像内容理解能力</div>
+					<el-tab-pane v-if="isDocumentType" :label="t('pages.knowledge.editModal.tabImage')" name="image">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.imageIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="多模态功能">
+							<el-form-item :label="t('pages.knowledge.editModal.vlmEnabled')">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.vlm_enabled"
 										class="selection-segmented"
 										:options="booleanSegmentedOptions"
 									/>
-									<div class="field-tip">启用图片等多模态内容的理解能力。</div>
+									<div class="field-tip">{{ t('pages.knowledge.editModal.vlmEnabledTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item v-if="form.vlm_enabled" label="VLLM视觉模型">
-								<el-select v-model="form.vlm_model_id" placeholder="请选择">
+							<el-form-item v-if="form.vlm_enabled" :label="t('pages.knowledge.editModal.vlmModel')">
+								<el-select v-model="form.vlm_model_id" :placeholder="t('pages.knowledge.editModal.selectPlaceholder')">
 									<el-option
 										v-for="item in vlmModels"
 										:key="item.id"
@@ -197,19 +197,18 @@
 										:value="item.id"
 									/>
 								</el-select>
-								<div class="field-tip">用于多模态理解的视觉语言模型(必选)。</div>
+								<div class="field-tip">{{ t('pages.knowledge.editModal.vlmModelTip') }}</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="isDocumentType" label="音频处理" name="audio">
-						<div class="tab-intro">配置语音识别(ASR)</div>
+					<el-tab-pane v-if="isDocumentType" :label="t('pages.knowledge.editModal.tabAudio')" name="audio">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.audioIntro') }}</div>
 						<div class="collapse-body">
 							<div class="field-tip compact-tip">
-								启用后可上传音频文件并整段转写为文本(常见格式:mp3、wav、m4a、flac、ogg
-								等)。暂不支持视频上传。
+							{{ t('pages.knowledge.editModal.audioTip') }}
 							</div>
-							<el-form-item label="音频语音识别">
+							<el-form-item :label="t('pages.knowledge.editModal.asrEnabled')">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.asr_enabled"
@@ -217,12 +216,12 @@
 										:options="booleanSegmentedOptions"
 									/>
 									<div class="field-tip">
-										启用后可上传音频到知识库,系统自动将语音转写为文本并参与解析与检索。
+										{{ t('pages.knowledge.editModal.asrEnabledTip') }}
 									</div>
 								</div>
 							</el-form-item>
-							<el-form-item v-if="form.asr_enabled" label="ASR模型">
-								<el-select v-model="form.asr_model_id" placeholder="请选择">
+							<el-form-item v-if="form.asr_enabled" :label="t('pages.knowledge.editModal.asrModel')">
+								<el-select v-model="form.asr_model_id" :placeholder="t('pages.knowledge.editModal.selectPlaceholder')">
 									<el-option
 										v-for="item in asrModels"
 										:key="item.id"
@@ -230,16 +229,16 @@
 										:value="item.id"
 									/>
 								</el-select>
-								<div class="field-tip">用于音频中语音转文本的识别模型(如 OpenAI Whisper)。</div>
+								<div class="field-tip">{{ t('pages.knowledge.editModal.asrModelTip') }}</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="isDocumentType" label="存储引擎" name="storage">
-						<div class="tab-intro">选择文档上传使用的文件存储引擎</div>
+					<el-tab-pane v-if="isDocumentType" :label="t('pages.knowledge.editModal.tabStorage')" name="storage">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.storageIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="存储引擎">
-								<el-select v-model="form.storage_provider" placeholder="请选择">
+							<el-form-item :label="t('pages.knowledge.editModal.storageEngine')">
+								<el-select v-model="form.storage_provider" :placeholder="t('pages.knowledge.editModal.selectPlaceholder')">
 									<el-option
 										v-for="item in storageProviderOptions"
 										:key="item.value"
@@ -248,19 +247,19 @@
 									/>
 								</el-select>
 								<div class="field-tip">
-									选择该知识库使用的存储引擎,需在全局设置中已配置对应引擎。
+									{{ t('pages.knowledge.editModal.storageEngineTip') }}
 								</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="isDocumentType" label="分块设置" name="chunk">
-						<div class="tab-intro">配置文档分块参数,优化检索效果</div>
+					<el-tab-pane v-if="isDocumentType" :label="t('pages.knowledge.editModal.tabChunk')" name="chunk">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.chunkIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="分块策略">
+							<el-form-item :label="t('pages.knowledge.editModal.chunkStrategy')">
 								<div class="slider-wrap">
 									<div class="field-tip mb-4px mt-0">
-										选择文档的分块方式。自动模式会分析每个文档的结构并选择最佳策略。
+										{{ t('pages.knowledge.editModal.chunkStrategyTip') }}
 									</div>
 									<el-segmented
 										v-model="form.chunk_strategy"
@@ -271,7 +270,7 @@
 										v-model="form.chunk_strategy"
 										filterable
 										default-first-option
-										placeholder="请选择"
+										:placeholder="t('pages.knowledge.editModal.selectPlaceholder')"
 										:options="chunkStrategyOptions"
 									/> -->
 									<div class="field-tip">
@@ -279,7 +278,7 @@
 									</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="分块大小">
+							<el-form-item :label="t('pages.knowledge.editModal.chunkSize')">
 								<div class="slider-wrap">
 									<el-slider
 										v-model="form.chunk_size"
@@ -288,10 +287,10 @@
 										:step="50"
 										show-input
 									/>
-									<div class="field-tip">控制每个文档分块的字符数(100-4000)。</div>
+									<div class="field-tip">{{ t('pages.knowledge.editModal.chunkSizeTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="分块重叠">
+							<el-form-item :label="t('pages.knowledge.editModal.chunkOverlap')">
 								<div class="slider-wrap">
 									<el-slider
 										v-model="form.chunk_overlap"
@@ -300,17 +299,17 @@
 										:step="10"
 										show-input
 									/>
-									<div class="field-tip">相邻文档块之间的重叠字符数(0-500)。</div>
+									<div class="field-tip">{{ t('pages.knowledge.editModal.chunkOverlapTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="分隔符">
+							<el-form-item :label="t('pages.knowledge.editModal.separators')">
 								<el-select
 									v-model="form.separators"
 									multiple
 									allow-create
 									filterable
 									default-first-option
-									placeholder="请输入或选择分隔符"
+									:placeholder="t('pages.knowledge.editModal.separatorsPlaceholder')"
 								>
 									<el-option
 										v-for="item in separatorOptions"
@@ -319,10 +318,10 @@
 										:value="item.value"
 									/>
 								</el-select>
-								<div class="field-tip">文档分块时使用的分隔符。</div>
+								<div class="field-tip">{{ t('pages.knowledge.editModal.separatorsTip') }}</div>
 							</el-form-item>
 
-							<el-form-item label="父子分块">
+							<el-form-item :label="t('pages.knowledge.editModal.parentChild')">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.enable_parent_child"
@@ -330,15 +329,15 @@
 										:options="booleanSegmentedOptions"
 									/>
 									<div class="field-tip">
-										启用两级父子分块策略。大的父块提供上下文,小的子块用于向量匹配检索。
+										{{ t('pages.knowledge.editModal.parentChildTip') }}
 									</div>
 								</div>
 							</el-form-item>
 
 							<div v-if="form.enable_parent_child" class="subsection ml-120px">
-								<div class="subsection-title">父子分块参数</div>
+								<div class="subsection-title">{{ t('pages.knowledge.editModal.parentChildParams') }}</div>
 
-								<el-form-item label="父块大小">
+								<el-form-item :label="t('pages.knowledge.editModal.parentChunkSize')">
 									<div class="slider-wrap">
 										<el-slider
 											v-model="form.parent_chunk_size"
@@ -347,10 +346,10 @@
 											:step="128"
 											show-input
 										/>
-										<div class="field-tip">提供上下文的父块字符数(512-8192)。</div>
+										<div class="field-tip">{{ t('pages.knowledge.editModal.parentChunkSizeTip') }}</div>
 									</div>
 								</el-form-item>
-								<el-form-item label="子块大小">
+								<el-form-item :label="t('pages.knowledge.editModal.childChunkSize')">
 									<div class="slider-wrap">
 										<el-slider
 											v-model="form.child_chunk_size"
@@ -359,15 +358,15 @@
 											:step="64"
 											show-input
 										/>
-										<div class="field-tip">用于向量匹配的子块字符数(64-2048)。</div>
+										<div class="field-tip">{{ t('pages.knowledge.editModal.childChunkSizeTip') }}</div>
 									</div>
 								</el-form-item>
 							</div>
 
 							<el-collapse class="chunk-advanced-collapse mt-12px" expand-icon-position="left">
-								<el-collapse-item title="高级" name="advanced">
+								<el-collapse-item :title="t('pages.knowledge.editModal.advanced')" name="advanced">
 									<div class="chunk-advanced-panel">
-										<el-form-item label="Token 上限">
+										<el-form-item :label="t('pages.knowledge.editModal.tokenLimit')">
 											<div class="slider-wrap">
 												<el-input-number
 													v-model="form.token_limit"
@@ -377,15 +376,12 @@
 													style="width: 100%"
 												/>
 												<div class="field-tip">
-													每个分块的硬性 Token 上限(0-8192)。0 = 关闭(仅按字符数)。当嵌入模型
-													Token 上限较小时启用:MiniLM (256 tok) 用 200,BGE/Cohere (512 tok) 用
-													400。现代嵌入器(OpenAI、Voyage、Jina-v3)支持 &gt;2000 tokens,保持 0
-													即可。
+													{{ t('pages.knowledge.editModal.tokenLimitTip') }}
 												</div>
 											</div>
 										</el-form-item>
 
-										<el-form-item label="语言提示">
+										<el-form-item :label="t('pages.knowledge.editModal.languageHint')">
 											<div class="switch-wrap">
 												<el-select
 													v-model="form.languages"
@@ -393,7 +389,7 @@
 													clearable
 													collapse-tags
 													collapse-tags-tooltip
-													placeholder="选择语言提示"
+													:placeholder="t('pages.knowledge.editModal.languageHintPlaceholder')"
 												>
 													<el-option
 														v-for="item in languageOptions"
@@ -403,8 +399,7 @@
 													/>
 												</el-select>
 												<div class="field-tip">
-													限制启发式模式只识别选定的语言(DE/EN/ZH)。留空 =
-													自动检测。同质化语料库可显式设置以避免跨语言误匹配。
+													{{ t('pages.knowledge.editModal.languageHintTip') }}
 												</div>
 											</div>
 										</el-form-item>
@@ -414,10 +409,10 @@
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="isDocumentType" label="高级设置" name="advanced">
-						<div class="tab-intro">配置问题生成等高级功能</div>
+					<el-tab-pane v-if="isDocumentType" :label="t('pages.knowledge.editModal.tabAdvanced')" name="advanced">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.advancedIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="AI 问题生成">
+							<el-form-item :label="t('pages.knowledge.editModal.questionGeneration')">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.question_generation_enabled"
@@ -425,36 +420,36 @@
 										:options="booleanSegmentedOptions"
 									/>
 									<div class="field-tip">
-										解析文档时调用大模型为每个分块生成相关问题,提高检索召回率。启用后会增加文档解析耗时。
+										{{ t('pages.knowledge.editModal.questionGenerationTip') }}
 									</div>
 								</div>
 							</el-form-item>
-							<el-form-item v-if="form.question_generation_enabled" label="生成问题数量">
+							<el-form-item v-if="form.question_generation_enabled" :label="t('pages.knowledge.editModal.questionCount')">
 								<el-input-number
 									v-model="form.question_count"
 									:min="1"
 									:max="10"
 									style="width: 100%"
 								/>
-								<div class="field-tip">每个文档分块生成的问题数量(1-10)。</div>
+								<div class="field-tip">{{ t('pages.knowledge.editModal.questionCountTip') }}</div>
 							</el-form-item>
 						</div>
 					</el-tab-pane>
 
-					<el-tab-pane v-if="!isDocumentType" label="FAQ设置" name="faq">
-						<div class="tab-intro">设置 FAQ 知识库的索引策略和问答组织方式</div>
+					<el-tab-pane v-if="!isDocumentType" :label="t('pages.knowledge.editModal.tabFaq')" name="faq">
+						<div class="tab-intro">{{ t('pages.knowledge.editModal.faqIntro') }}</div>
 						<div class="collapse-body">
-							<el-form-item label="索引方式">
+							<el-form-item :label="t('pages.knowledge.editModal.faqIndexMode')">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.faq_index_mode"
 										class="selection-segmented"
 										:options="faqIndexModeOptions"
 									/>
-									<div class="field-tip">仅索引问题可提升精度,索引问答可提高召回率。</div>
+									<div class="field-tip">{{ t('pages.knowledge.editModal.faqIndexModeTip') }}</div>
 								</div>
 							</el-form-item>
-							<el-form-item label="问题索引方式">
+							<el-form-item :label="t('pages.knowledge.editModal.faqQuestionIndexMode')">
 								<div class="switch-wrap">
 									<el-segmented
 										v-model="form.faq_question_index_mode"
@@ -462,7 +457,7 @@
 										:options="faqQuestionIndexModeOptions"
 									/>
 									<div class="field-tip">
-										合并索引:标准问和相似问合并索引;分别索引:标准问和每个相似问独立索引,检索更精确但需要更多存储。
+										{{ t('pages.knowledge.editModal.faqQuestionIndexModeTip') }}
 									</div>
 								</div>
 							</el-form-item>
@@ -474,8 +469,8 @@
 
 		<template #footer>
 			<div class="drawer-footer">
-				<el-button @click="drawerVisible = false">取消</el-button>
-				<el-button type="primary" :loading="submitLoading" @click="submitForm">保存</el-button>
+				<el-button @click="drawerVisible = false">{{ t('common.cancel') }}</el-button>
+				<el-button type="primary" :loading="submitLoading" @click="submitForm">{{ t('common.save') }}</el-button>
 			</div>
 		</template>
 	</el-dialog>
@@ -483,12 +478,15 @@
 
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { ElMessage } from 'element-plus'
 import { aiModel, knowledge, storageProvider, vector } from '@repo/api-service'
 import type { KnowledgeBaseForm, KnowledgeModelOption, ParserEngineRule } from '../types'
 
 const emit = defineEmits<{ (e: 'refresh'): void }>()
 
+const { t } = useI18n()
+
 const submitLoading = ref(false)
 const drawerVisible = ref(false)
 const editingId = ref('')
@@ -509,42 +507,42 @@ const DEFAULT_PARSER_RULES: ParserEngineRule[] = [
 	{ engine: 'simple', file_types: ['mp3', 'wav', 'm4a', 'flac', 'ogg'] }
 ]
 
-const separatorOptions = [
-	{ label: '双换行', value: '\n\n', displayValue: '\\n\\n' },
-	{ label: '单换行', value: '\n', displayValue: '\\n' },
-	{ label: '中文句号', value: '。', displayValue: '。' },
-	{ label: '感叹号', value: '!', displayValue: '!' },
-	{ label: '问号', value: '?', displayValue: '?' },
-	{ label: '英文分号', value: ';', displayValue: ';' },
-	{ label: '中文分号', value: ';', displayValue: ';' },
-	{ label: '空格', value: ' ', displayValue: ' ' }
-]
+const separatorOptions = computed(() => [
+	{ label: t('pages.knowledge.editModal.separatorDoubleNewline'), value: '\n\n', displayValue: '\\n\\n' },
+	{ label: t('pages.knowledge.editModal.separatorSingleNewline'), value: '\n', displayValue: '\\n' },
+	{ label: t('pages.knowledge.editModal.separatorChinesePeriod'), value: '。', displayValue: '。' },
+	{ label: t('pages.knowledge.editModal.separatorExclamation'), value: '!', displayValue: '!' },
+	{ label: t('pages.knowledge.editModal.separatorQuestion'), value: '?', displayValue: '?' },
+	{ label: t('pages.knowledge.editModal.separatorEnglishSemicolon'), value: ';', displayValue: ';' },
+	{ label: t('pages.knowledge.editModal.separatorChineseSemicolon'), value: ';', displayValue: ';' },
+	{ label: t('pages.knowledge.editModal.separatorSpace'), value: ' ', displayValue: ' ' }
+])
 // 默认不选空格
-const DEFAULT_SEPARATORS = separatorOptions.map((item) => item.value).filter((item) => item !== ' ')
+const DEFAULT_SEPARATORS = separatorOptions.value.map((item) => item.value).filter((item) => item !== ' ')
 const parserEngineOptions = ['builtin', 'markitdown', 'simple']
-const languageOptions = [
-	{ label: '中文', value: 'zh' },
-	{ label: '英语', value: 'en' },
-	{ label: '德语', value: 'de' }
-]
+const languageOptions = computed(() => [
+	{ label: t('pages.knowledge.editModal.langChinese'), value: 'zh' },
+	{ label: t('pages.knowledge.editModal.langEnglish'), value: 'en' },
+	{ label: t('pages.knowledge.editModal.langGerman'), value: 'de' }
+])
 const storageProviderOptions = ref<{ label: string; value: string }[]>([])
 const vectorStoreOptions = ref<{ label: string; value: string }[]>([])
-const knowledgeTypeOptions = [
-	{ label: '文档', value: 'document' },
-	{ label: '问答', value: 'faq' }
-]
-const booleanSegmentedOptions = [
-	{ label: '开启', value: true },
-	{ label: '关闭', value: false }
-]
-const faqIndexModeOptions = [
-	{ label: '仅索引问题', value: 'question_only' },
-	{ label: '索引问答', value: 'question_answer' }
-]
-const faqQuestionIndexModeOptions = [
-	{ label: '合并索引', value: 'combined' },
-	{ label: '分别索引', value: 'separate' }
-]
+const knowledgeTypeOptions = computed(() => [
+	{ label: t('pages.knowledge.editModal.typeDocument'), value: 'document' },
+	{ label: t('pages.knowledge.editModal.typeFaq'), value: 'faq' }
+])
+const booleanSegmentedOptions = computed(() => [
+	{ label: t('pages.knowledge.editModal.enabled'), value: true },
+	{ label: t('pages.knowledge.editModal.disabled'), value: false }
+])
+const faqIndexModeOptions = computed(() => [
+	{ label: t('pages.knowledge.editModal.faqIndexQuestionOnly'), value: 'question_only' },
+	{ label: t('pages.knowledge.editModal.faqIndexQuestionAnswer'), value: 'question_answer' }
+])
+const faqQuestionIndexModeOptions = computed(() => [
+	{ label: t('pages.knowledge.editModal.faqQuestionCombined'), value: 'combined' },
+	{ label: t('pages.knowledge.editModal.faqQuestionSeparate'), value: 'separate' }
+])
 
 function cloneParserRules(rules?: ParserEngineRule[]) {
 	return (rules || DEFAULT_PARSER_RULES).map((rule) => ({
@@ -589,33 +587,31 @@ const createDefaultForm = (): KnowledgeBaseForm => ({
 	wiki_synthesis_model_id: ''
 })
 
-const chunkStrategyOptions = [
+const chunkStrategyOptions = computed(() => [
 	{
-		label: '自动',
+		label: t('pages.knowledge.editModal.chunkAuto'),
 		value: 'auto'
 	},
 	{
-		label: '按标题切分',
+		label: t('pages.knowledge.editModal.chunkHeading'),
 		value: 'heading'
 	},
 	{
-		label: '结构感知',
+		label: t('pages.knowledge.editModal.chunkHeuristic'),
 		value: 'heuristic'
 	},
 	{
-		label: '按长度切分',
+		label: t('pages.knowledge.editModal.chunkLegacy'),
 		value: 'legacy'
 	}
-]
+])
 
-const chunkSrategyTip = {
-	auto: '文档分析器根据内容结构自动在「按标题切分」「结构感知」「按长度切分」之间选择。',
-	heading:
-		'在 Markdown 标题(#、##、###)边界处切分,每块自动带上所在标题路径。适合结构清晰的 Markdown 文档。',
-	heuristic:
-		'识别分页符、编号章节、多语言章节标记(DE/EN/ZH)、全大写标题等结构信号进行切分。适合没有 Markdown 标题的 PDF / 扫描件。',
-	legacy: '忽略结构,仅按字符数和分隔符递归切分——原始行为。当上述策略对你的内容效果不佳时使用。'
-}
+const chunkSrategyTip = computed(() => ({
+	auto: t('pages.knowledge.editModal.chunkTipAuto'),
+	heading: t('pages.knowledge.editModal.chunkTipHeading'),
+	heuristic: t('pages.knowledge.editModal.chunkTipHeuristic'),
+	legacy: t('pages.knowledge.editModal.chunkTipLegacy')
+}))
 
 const form = reactive<KnowledgeBaseForm>(createDefaultForm())
 
@@ -715,10 +711,10 @@ async function hydrateSelectedModels() {
 }
 
 const rules = {
-	name: [{ required: true, message: '请输入知识库名称', trigger: 'blur' }],
-	type: [{ required: true, message: '请选择知识库类型', trigger: 'change' }],
-	embedding_model_id: [{ required: true, message: '请选择 Embedding 模型', trigger: 'change' }],
-	summary_model_id: [{ required: true, message: '请选择摘要模型', trigger: 'change' }]
+	name: [{ required: true, message: t('pages.knowledge.editModal.kbNameRequired'), trigger: 'blur' }],
+	type: [{ required: true, message: t('pages.knowledge.editModal.kbTypeRequired'), trigger: 'change' }],
+	embedding_model_id: [{ required: true, message: t('pages.knowledge.editModal.embeddingModelRequired'), trigger: 'change' }],
+	summary_model_id: [{ required: true, message: t('pages.knowledge.editModal.summaryModelRequired'), trigger: 'change' }]
 }
 
 function handleKnowledgeBaseTypeChange(type: KnowledgeBaseForm['type']) {
@@ -942,15 +938,15 @@ async function submitForm() {
 				id: editingId.value,
 				...(payload as any)
 			} as any)
-			ElMessage.success('知识库已更新')
+			ElMessage.success(t('pages.knowledge.editModal.updateSuccess'))
 		} else {
 			await knowledge.postAiKnowledgeBaseCreate(payload as any)
-			ElMessage.success('知识库已创建')
+			ElMessage.success(t('pages.knowledge.editModal.createSuccess'))
 		}
 		drawerVisible.value = false
 		emit('refresh')
 	} catch {
-		ElMessage.error('保存失败')
+		ElMessage.error(t('pages.knowledge.editModal.saveFailed'))
 	} finally {
 		submitLoading.value = false
 	}

+ 67 - 64
apps/web/src/views/knowledge/components/StorageModal.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-dialog
 		v-model="visible"
-		title="存储服务管理"
+		:title="t('pages.storagePage.storageServiceTitle')"
 		width="1100px"
 		top="6vh"
 		destroy-on-close
@@ -9,7 +9,7 @@
 	>
 		<div class="storage-modal" v-loading="pageLoading">
 			<el-alert
-				title="选择引擎查看配置,支持修改保存和连通测试。"
+				:title="t('pages.storagePage.storageServiceAlert')"
 				type="info"
 				:closable="false"
 				show-icon
@@ -17,14 +17,14 @@
 
 			<div class="storage-actions">
 				<el-button type="primary" :loading="initLoading" @click="handleInit">
-					初始化存储
+					{{ t('pages.storagePage.initStorage') }}
 				</el-button>
-				<el-button :loading="engineLoading" @click="loadEngines">刷新列表</el-button>
+				<el-button :loading="engineLoading" @click="loadEngines">{{ t('pages.storagePage.refreshList') }}</el-button>
 			</div>
 
 			<div class="storage-layout">
 				<aside class="engine-list">
-					<div class="engine-list__header">引擎列表</div>
+					<div class="engine-list__header">{{ t('pages.storagePage.engineList') }}</div>
 					<div v-if="engines.length" class="engine-list__body">
 						<div
 							v-for="item in engines"
@@ -38,18 +38,18 @@
 							</div>
 							<div class="engine-card__meta">
 								<el-tag size="small" :type="item.allowed ? 'success' : 'info'" effect="plain">
-									{{ item.allowed ? '允许' : '不允许' }}
+									{{ item.allowed ? t('pages.storagePage.allowed') : t('pages.storagePage.notAllowed') }}
 								</el-tag>
 								<el-tag size="small" :type="item.available ? 'primary' : 'warning'" effect="plain">
-									{{ item.available ? '可用' : '不可用' }}
+									{{ item.available ? t('pages.storagePage.available') : t('pages.storagePage.unavailable') }}
 								</el-tag>
 							</div>
 							<div class="engine-card__desc">
-								{{ item.description || '点击加载配置信息' }}
+								{{ item.description || t('pages.storagePage.clickToLoad') }}
 							</div>
 						</div>
 					</div>
-					<el-empty v-else description="暂无引擎" />
+					<el-empty v-else :description="t('pages.storagePage.noEngine')" />
 				</aside>
 
 				<section class="config-panel">
@@ -57,63 +57,63 @@
 						<div class="config-panel__header">
 							<div>
 								<div class="config-panel__title">
-									{{ formatProviderLabel(selectedProvider) }} 配置
+									{{ formatProviderLabel(selectedProvider) }} {{ t('pages.storagePage.config') }}
 								</div>
-								<div class="config-panel__desc">点击左侧引擎后加载配置信息,修改后保存。</div>
+								<div class="config-panel__desc">{{ t('pages.storagePage.configDesc') }}</div>
 							</div>
 						</div>
 
 						<el-form :model="form" label-position="top">
 							<template v-if="selectedProvider === 'local'">
-								<el-form-item label="存储前缀">
-									<el-input v-model="form.local.path_prefix" placeholder="例如 knowledge/files" />
+								<el-form-item :label="t('pages.storagePage.storagePrefix')">
+									<el-input v-model="form.local.path_prefix" :placeholder="t('pages.storagePage.pathPrefixPlaceholder')" />
 								</el-form-item>
 							</template>
 
 							<template v-else-if="selectedProvider === 'minio'">
 								<div class="form-grid">
-									<el-form-item label="Endpoint">
+									<el-form-item :label="t('pages.storagePage.endpoint')">
 										<el-input v-model="form.minio.endpoint" placeholder="127.0.0.1:9000" />
 									</el-form-item>
-									<el-form-item label="Mode">
+									<el-form-item :label="t('pages.storagePage.mode')">
 										<el-input v-model="form.minio.mode" placeholder="docker" />
 									</el-form-item>
-									<el-form-item label="Access Key ID">
+									<el-form-item :label="t('pages.storagePage.accessKeyId')">
 										<el-input v-model="form.minio.access_key_id" />
 									</el-form-item>
-									<el-form-item label="Secret Access Key">
+									<el-form-item :label="t('pages.storagePage.secretAccessKey')">
 										<el-input v-model="form.minio.secret_access_key" show-password />
 									</el-form-item>
-									<el-form-item label="Bucket">
+									<el-form-item :label="t('pages.storagePage.bucket')">
 										<el-input v-model="form.minio.bucket_name" />
 									</el-form-item>
-									<el-form-item label="Path Prefix">
+									<el-form-item :label="t('pages.storagePage.pathPrefix')">
 										<el-input v-model="form.minio.path_prefix" />
 									</el-form-item>
 								</div>
-								<el-form-item label="Use SSL">
+								<el-form-item :label="t('pages.storagePage.useSsl')">
 									<el-switch v-model="form.minio.use_ssl" />
 								</el-form-item>
 							</template>
 
 							<template v-else-if="selectedProvider === 'cos'">
 								<div class="form-grid">
-									<el-form-item label="App ID">
+									<el-form-item :label="t('pages.storagePage.appId')">
 										<el-input v-model="form.cos.app_id" />
 									</el-form-item>
-									<el-form-item label="Region">
+									<el-form-item :label="t('pages.storagePage.region')">
 										<el-input v-model="form.cos.region" />
 									</el-form-item>
-									<el-form-item label="Secret ID">
+									<el-form-item :label="t('pages.storagePage.secretId')">
 										<el-input v-model="form.cos.secret_id" />
 									</el-form-item>
-									<el-form-item label="Secret Key">
+									<el-form-item :label="t('pages.storagePage.secretKey')">
 										<el-input v-model="form.cos.secret_key" show-password />
 									</el-form-item>
-									<el-form-item label="Bucket">
+									<el-form-item :label="t('pages.storagePage.bucket')">
 										<el-input v-model="form.cos.bucket_name" />
 									</el-form-item>
-									<el-form-item label="Path Prefix">
+									<el-form-item :label="t('pages.storagePage.pathPrefix')">
 										<el-input v-model="form.cos.path_prefix" />
 									</el-form-item>
 								</div>
@@ -121,22 +121,22 @@
 
 							<template v-else-if="selectedProvider === 'tos'">
 								<div class="form-grid">
-									<el-form-item label="Endpoint">
+									<el-form-item :label="t('pages.storagePage.endpoint')">
 										<el-input v-model="form.tos.endpoint" />
 									</el-form-item>
-									<el-form-item label="Region">
+									<el-form-item :label="t('pages.storagePage.region')">
 										<el-input v-model="form.tos.region" />
 									</el-form-item>
-									<el-form-item label="Access Key">
+									<el-form-item :label="t('pages.storagePage.accessKey')">
 										<el-input v-model="form.tos.access_key" />
 									</el-form-item>
-									<el-form-item label="Secret Key">
+									<el-form-item :label="t('pages.storagePage.secretKey')">
 										<el-input v-model="form.tos.secret_key" show-password />
 									</el-form-item>
-									<el-form-item label="Bucket">
+									<el-form-item :label="t('pages.storagePage.bucket')">
 										<el-input v-model="form.tos.bucket_name" />
 									</el-form-item>
-									<el-form-item label="Path Prefix">
+									<el-form-item :label="t('pages.storagePage.pathPrefix')">
 										<el-input v-model="form.tos.path_prefix" />
 									</el-form-item>
 								</div>
@@ -144,22 +144,22 @@
 
 							<template v-else-if="selectedProvider === 's3'">
 								<div class="form-grid">
-									<el-form-item label="Endpoint">
+									<el-form-item :label="t('pages.storagePage.endpoint')">
 										<el-input v-model="form.s3.endpoint" />
 									</el-form-item>
-									<el-form-item label="Region">
+									<el-form-item :label="t('pages.storagePage.region')">
 										<el-input v-model="form.s3.region" />
 									</el-form-item>
-									<el-form-item label="Access Key">
+									<el-form-item :label="t('pages.storagePage.accessKey')">
 										<el-input v-model="form.s3.access_key" />
 									</el-form-item>
-									<el-form-item label="Secret Key">
+									<el-form-item :label="t('pages.storagePage.secretKey')">
 										<el-input v-model="form.s3.secret_key" show-password />
 									</el-form-item>
-									<el-form-item label="Bucket">
+									<el-form-item :label="t('pages.storagePage.bucket')">
 										<el-input v-model="form.s3.bucket_name" />
 									</el-form-item>
-									<el-form-item label="Path Prefix">
+									<el-form-item :label="t('pages.storagePage.pathPrefix')">
 										<el-input v-model="form.s3.path_prefix" />
 									</el-form-item>
 								</div>
@@ -167,32 +167,32 @@
 
 							<template v-else-if="selectedProvider === 'oss'">
 								<div class="form-grid">
-									<el-form-item label="Endpoint">
+									<el-form-item :label="t('pages.storagePage.endpoint')">
 										<el-input v-model="form.oss.endpoint" />
 									</el-form-item>
-									<el-form-item label="Region">
+									<el-form-item :label="t('pages.storagePage.region')">
 										<el-input v-model="form.oss.region" />
 									</el-form-item>
-									<el-form-item label="Access Key">
+									<el-form-item :label="t('pages.storagePage.accessKey')">
 										<el-input v-model="form.oss.access_key" />
 									</el-form-item>
-									<el-form-item label="Secret Key">
+									<el-form-item :label="t('pages.storagePage.secretKey')">
 										<el-input v-model="form.oss.secret_key" show-password />
 									</el-form-item>
-									<el-form-item label="Bucket">
+									<el-form-item :label="t('pages.storagePage.bucket')">
 										<el-input v-model="form.oss.bucket_name" />
 									</el-form-item>
-									<el-form-item label="Path Prefix">
+									<el-form-item :label="t('pages.storagePage.pathPrefix')">
 										<el-input v-model="form.oss.path_prefix" />
 									</el-form-item>
-									<el-form-item label="Temp Bucket">
+									<el-form-item :label="t('pages.storagePage.tempBucket')">
 										<el-input v-model="form.oss.temp_bucket_name" />
 									</el-form-item>
-									<el-form-item label="Temp Region">
+									<el-form-item :label="t('pages.storagePage.tempRegion')">
 										<el-input v-model="form.oss.temp_region" />
 									</el-form-item>
 								</div>
-								<el-form-item label="Use Temp Bucket">
+								<el-form-item :label="t('pages.storagePage.useTempBucket')">
 									<el-switch v-model="form.oss.use_temp_bucket" />
 								</el-form-item>
 							</template>
@@ -205,25 +205,25 @@
 								:loading="testingProvider === selectedProvider"
 								@click="testProvider(selectedProvider)"
 							>
-								连通测试
+								{{ t('pages.storagePage.testConnection') }}
 							</el-button>
 							<el-button
 								@click="reloadSelectedProvider"
 								:loading="providerLoading === selectedProvider"
 							>
-								重新加载
+								{{ t('pages.storagePage.reload') }}
 							</el-button>
-							<el-button type="primary" :loading="saving" @click="saveConfig">保存配置</el-button>
+							<el-button type="primary" :loading="saving" @click="saveConfig">{{ t('pages.storagePage.saveConfig') }}</el-button>
 						</div>
 					</template>
-					<div v-else class="config-panel__empty">请先从左侧选择一个引擎。</div>
+					<div v-else class="config-panel__empty">{{ t('pages.storagePage.selectEngineFirst') }}</div>
 				</section>
 			</div>
 		</div>
 
 		<template #footer>
 			<div class="dialog-footer">
-				<el-button @click="visible = false">关闭</el-button>
+				<el-button @click="visible = false">{{ t('pages.storagePage.close') }}</el-button>
 			</div>
 		</template>
 	</el-dialog>
@@ -231,6 +231,7 @@
 
 <script setup lang="ts">
 import { reactive, ref } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import { ElMessage } from 'element-plus'
 import { storageProvider } from '@repo/api-service'
 
@@ -374,6 +375,8 @@ function createDefaultForm(): StorageFormState {
 
 const form = reactive<StorageFormState>(createDefaultForm())
 
+const { t } = useI18n()
+
 function formatProviderLabel(name: StorageProviderName) {
 	return name.toUpperCase()
 }
@@ -477,7 +480,7 @@ async function loadEngines() {
 			await selectProvider(engines.value[0]!.name)
 		}
 	} catch {
-		ElMessage.error('加载引擎失败')
+		ElMessage.error(t('pages.storagePage.loadEngineFailed'))
 	} finally {
 		engineLoading.value = false
 	}
@@ -488,13 +491,13 @@ async function handleInit() {
 	try {
 		const res = await storageProvider.postAiStorageProviderInitStorageProvider({})
 		if (res?.isSuccess) {
-			ElMessage.success('初始化成功')
+			ElMessage.success(t('pages.storagePage.initSuccess'))
 			await loadEngines()
 			return
 		}
-		ElMessage.error('初始化失败')
+		ElMessage.error(t('pages.storagePage.initFailed'))
 	} catch {
-		ElMessage.error('初始化失败')
+		ElMessage.error(t('pages.storagePage.initFailed'))
 	} finally {
 		initLoading.value = false
 	}
@@ -513,7 +516,7 @@ async function loadProviderConfig(provider: StorageProviderName) {
 			selectedProvider.value = provider
 		}
 	} catch {
-		ElMessage.error('加载配置失败')
+		ElMessage.error(t('pages.storagePage.loadConfigFailed'))
 	} finally {
 		providerLoading.value = ''
 	}
@@ -549,12 +552,12 @@ async function testProvider(provider: StorageProviderName) {
 		}
 		const res = await storageProvider.postAiStorageProviderCheck(buildCheckPayload(provider) as any)
 		if (res?.isSuccess) {
-			ElMessage.success(`${formatProviderLabel(provider)} 连通成功`)
+			ElMessage.success(`${formatProviderLabel(provider)} ${t('pages.storagePage.connectionSuccess')}`)
 			return
 		}
-		ElMessage.error(res?.error || `${formatProviderLabel(provider)} 连通失败`)
+		ElMessage.error(res?.error || `${formatProviderLabel(provider)} ${t('pages.storagePage.connectionFailed')}`)
 	} catch {
-		ElMessage.error(`${formatProviderLabel(provider)} 连通失败`)
+		ElMessage.error(`${formatProviderLabel(provider)} ${t('pages.storagePage.connectionFailed')}`)
 	} finally {
 		testingProvider.value = ''
 	}
@@ -574,20 +577,20 @@ function buildUpdatePayload() {
 
 async function saveConfig() {
 	if (!selectedProvider.value) {
-		ElMessage.warning('请先选择一个引擎')
+		ElMessage.warning(t('pages.storagePage.selectEngineFirstWarn'))
 		return
 	}
 	saving.value = true
 	try {
 		const res = await storageProvider.postAiStorageProviderUpdate(buildUpdatePayload() as any)
 		if (res?.isSuccess) {
-			ElMessage.success('配置已保存')
+			ElMessage.success(t('pages.storagePage.configSaved'))
 			await loadEngines()
 			return
 		}
-		ElMessage.error('保存失败')
+		ElMessage.error(t('pages.storagePage.saveFailed'))
 	} catch {
-		ElMessage.error('保存失败')
+		ElMessage.error(t('pages.storagePage.saveFailed'))
 	} finally {
 		saving.value = false
 	}

+ 10 - 7
apps/web/src/views/knowledge/index.vue

@@ -2,35 +2,36 @@
 	<div class="knowledge-management-page">
 		<div class="header">
 			<div class="flex items-center justify-between">
-				<h1>知识库</h1>
+				<h1>{{ t('pages.knowledge.title') }}</h1>
 			</div>
-			<p class="subtitle">统一管理知识库,并在库内处理知识内容和问答条目。</p>
+			<p class="subtitle">{{ t('pages.knowledge.subtitle') }}</p>
 		</div>
 		<div class="main-container">
 			<KnowledgeBaseSidebar :current-base-id="currentBase?.id!" @select-base="handleSelectBase" />
 			<div v-if="currentBase" class="content-container">
 				<el-tabs v-model="currentModule" type="border-card">
-					<el-tab-pane label="知识" name="document" v-if="currentBase.type === 'document'">
+					<el-tab-pane :label="t('pages.knowledge.knowledgeTab')" name="document" v-if="currentBase.type === 'document'">
 						<DocumentManage v-if="currentModule === 'document'" :current-base="currentBase" />
 					</el-tab-pane>
-					<el-tab-pane label="Wiki" name="wiki" v-if="currentBase.type === 'document'">
+					<el-tab-pane :label="t('pages.knowledge.wikiTab')" name="wiki" v-if="currentBase.type === 'document'">
 						<WikiManage v-if="currentModule === 'wiki'" :current-base="currentBase" />
 					</el-tab-pane>
-					<el-tab-pane label="知识图谱" name="graph" v-if="currentBase.type === 'document'">
+					<el-tab-pane :label="t('pages.knowledge.graphTab')" name="graph" v-if="currentBase.type === 'document'">
 						<WikiGraph v-if="currentModule === 'graph'" :current-base="currentBase" />
 					</el-tab-pane>
-					<el-tab-pane label="问答" name="qa" v-if="currentBase.type === 'faq'">
+					<el-tab-pane :label="t('pages.knowledge.qaTab')" name="qa" v-if="currentBase.type === 'faq'">
 						<QaManage v-if="currentModule === 'qa'" :current-base-id="currentBase?.id!" />
 					</el-tab-pane>
 				</el-tabs>
 			</div>
-			<el-empty v-else description="请先在左侧选择一个知识库" class="empty-container" />
+			<el-empty v-else :description="t('pages.knowledge.selectBase')" class="empty-container" />
 		</div>
 	</div>
 </template>
 
 <script setup lang="ts">
 import { ref } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import DocumentManage from './DocumentManage.vue'
 import KnowledgeBaseSidebar from './KnowledgeBaseSidebar.vue'
 import QaManage from './QaManage.vue'
@@ -38,6 +39,8 @@ import type { KnowledgeBaseItem } from './types'
 import WikiGraph from './WikiGraph.vue'
 import WikiManage from './WikiManage.vue'
 
+const { t } = useI18n()
+
 const currentBase = ref<KnowledgeBaseItem>()
 const currentModule = ref<'document' | 'qa' | 'wiki' | 'graph'>('document')
 

+ 75 - 72
apps/web/src/views/mcp/index.vue

@@ -2,8 +2,8 @@
 	<div class="management-page">
 		<div class="page-head">
 			<div>
-				<h1>MCP 服务</h1>
-				<p>维护 MCP 服务列表,并查看资源与工具详情。</p>
+				<h1>{{ t('pages.mcp.title') }}</h1>
+				<p>{{ t('pages.mcp.subtitle') }}</p>
 			</div>
 		</div>
 
@@ -13,7 +13,7 @@
 					<el-input
 						v-model="keyword"
 						clearable
-						placeholder="搜索 MCP 名称"
+						:placeholder="t('pages.mcp.searchPlaceholder')"
 						class="search-input"
 						@keyup.enter="loadList(1)"
 					>
@@ -26,7 +26,7 @@
 					<el-select
 						v-model="transportType"
 						clearable
-						placeholder="传输类型"
+						:placeholder="t('pages.mcp.transferType')"
 						style="width: 160px"
 						@change="loadList(1)"
 					>
@@ -38,11 +38,11 @@
 					<div class="toolbar-actions">
 						<el-button type="primary" @click="loadList(1)">
 							<el-icon><Search /></el-icon>
-							查询
+							{{ t('common.search') }}
 						</el-button>
 						<el-button @click="handleReset">
 							<el-icon><RefreshRight /></el-icon>
-							重置
+							{{ t('common.reset') }}
 						</el-button>
 					</div>
 				</div>
@@ -51,14 +51,14 @@
 						<el-icon>
 							<Plus />
 						</el-icon>
-						新建 MCP
+						{{ t('pages.mcp.createMcp') }}
 					</el-button>
 				</div>
 			</div>
 
 			<div class="toolbar-meta">
-				<span class="pill">共 {{ pagination.totalCount }} 个服务</span>
-				<span class="pill">已启用 {{ enabledCount }} 个</span>
+				<span class="pill">{{ t('pages.mcp.totalServices', { count: pagination.totalCount }) }}</span>
+				<span class="pill">{{ t('pages.mcp.enabledCount', { count: enabledCount }) }}</span>
 			</div>
 
 			<!-- <div class="summary-grid">
@@ -77,13 +77,13 @@
 		</div> -->
 
 			<div v-loading="loading" class="grid">
-				<el-empty class="empty" v-if="!list.length && !loading" description="暂无 MCP 服务" />
+				<el-empty class="empty" v-if="!list.length && !loading" :description="t('pages.mcp.noMcp')" />
 				<div v-for="row in list" :key="row.id" class="card">
 					<div class="card-head">
 						<div class="card-head__top">
 							<div class="title-block">
-								<div class="title">{{ row.name || '未命名 MCP' }}</div>
-								<div class="subtitle">{{ row.url || '未配置地址' }}</div>
+								<div class="title">{{ row.name || t('pages.mcp.unnamedMcp') }}</div>
+								<div class="subtitle">{{ row.url || t('pages.mcp.noAddress') }}</div>
 							</div>
 							<div class="actions">
 								<el-dropdown>
@@ -94,11 +94,11 @@
 									</span>
 									<template #dropdown>
 										<el-dropdown-menu>
-											<el-dropdown-item @click="checkItem(row.id!)">测试</el-dropdown-item>
-											<el-dropdown-item @click="openTools(row.id!)">工具</el-dropdown-item>
-											<el-dropdown-item @click="openEditById(row.id!)">编辑</el-dropdown-item>
+											<el-dropdown-item @click="checkItem(row.id!)">{{ t('pages.mcp.test') }}</el-dropdown-item>
+											<el-dropdown-item @click="openTools(row.id!)">{{ t('pages.mcp.tools') }}</el-dropdown-item>
+											<el-dropdown-item @click="openEditById(row.id!)">{{ t('common.edit') }}</el-dropdown-item>
 											<el-dropdown-item divided @click="removeItem(row.id!)">
-												<span class="danger-text">删除</span>
+												<span class="danger-text">{{ t('common.delete') }}</span>
 											</el-dropdown-item>
 										</el-dropdown-menu>
 									</template>
@@ -107,13 +107,13 @@
 						</div>
 						<div class="badge-row">
 							<el-tag :type="row.enabled ? 'success' : 'warining'" class="badge subtle">{{
-								row.enabled ? '启用中' : '已禁用'
+								row.enabled ? t('pages.mcp.enabled') : t('pages.mcp.disabled')
 							}}</el-tag>
-							<el-tag class="badge">{{ row.transport_type || '未设置传输类型' }}</el-tag>
+							<el-tag class="badge">{{ row.transport_type || t('pages.mcp.noTransferType') }}</el-tag>
 						</div>
 					</div>
 
-					<div class="desc">{{ row.description || '暂无描述' }}</div>
+					<div class="desc">{{ row.description || t('pages.mcp.noDescription') }}</div>
 				</div>
 			</div>
 
@@ -132,43 +132,43 @@
 
 		<el-drawer
 			v-model="drawerVisible"
-			:title="currentId ? '编辑 MCP' : '新建 MCP'"
+			:title="currentId ? t('pages.mcp.editMcp') : t('pages.mcp.createMcpTitle')"
 			direction="rtl"
 			size="760px"
 		>
 			<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
-				<el-form-item label="名称" prop="name">
+				<el-form-item :label="t('pages.mcp.name')" prop="name">
 					<el-input v-model="form.name" />
 				</el-form-item>
-				<el-form-item label="描述" prop="description">
+				<el-form-item :label="t('pages.mcp.description')" prop="description">
 					<el-input v-model="form.description" type="textarea" :rows="2" />
 				</el-form-item>
 				<div class="grid-2">
-					<el-form-item label="传输类型" prop="transport_type">
+					<el-form-item :label="t('pages.mcp.transferType')" prop="transport_type">
 						<el-select v-model="form.transport_type" style="width: 100%">
 							<el-option label="SSE(Server-Sent Events)" value="sse" />
 							<el-option label="HTTP Streamable" value="http-streamable" />
 						</el-select>
 					</el-form-item>
-					<el-form-item label="启用" prop="enabled">
+					<el-form-item :label="t('pages.mcp.enableLabel')" prop="enabled">
 						<el-switch v-model="form.enabled" />
 					</el-form-item>
 				</div>
-				<el-form-item label="地址" prop="url">
+				<el-form-item :label="t('pages.mcp.address')" prop="url">
 					<el-input v-model="form.url" />
 				</el-form-item>
-				<el-form-item label="超时(秒)" prop="advanced_config.timeout">
+				<el-form-item :label="t('pages.mcp.timeout')" prop="advanced_config.timeout">
 					<el-input-number v-model="form.advanced_config.timeout" :min="0" style="width: 100%" />
 				</el-form-item>
 				<div class="grid-2">
-					<el-form-item label="重试次数" prop="advanced_config.retry_count">
+					<el-form-item :label="t('pages.mcp.retryCount')" prop="advanced_config.retry_count">
 						<el-input-number
 							v-model="form.advanced_config.retry_count"
 							:min="0"
 							style="width: 100%"
 						/>
 					</el-form-item>
-					<el-form-item label="重试间隔(秒)" prop="advanced_config.retry_delay">
+					<el-form-item :label="t('pages.mcp.retryInterval')" prop="advanced_config.retry_delay">
 						<el-input-number
 							v-model="form.advanced_config.retry_delay"
 							:min="0"
@@ -179,9 +179,9 @@
 				<el-form-item label="Headers">
 					<div class="kv-config">
 						<div class="kv-config__top">
-							<span>请求头</span>
+							<span>{{ t('pages.mcp.requestHeaders') }}</span>
 							<el-button type="primary" link @click="addHeaderRow">
-								<el-icon> <Plus /> </el-icon>添加
+								<el-icon> <Plus /> </el-icon>{{ t('pages.mcp.add') }}
 							</el-button>
 						</div>
 						<div class="kv-config__rows">
@@ -190,15 +190,15 @@
 								:key="`header-${index}`"
 								class="kv-config__row"
 							>
-								<el-input v-model="item.key" placeholder="Header 名称" />
-								<el-input v-model="item.value" placeholder="Header 值" />
+								<el-input v-model="item.key" :placeholder="t('pages.mcp.headerName')" />
+								<el-input v-model="item.value" :placeholder="t('pages.mcp.headerValue')" />
 								<el-button
 									link
 									type="danger"
 									:disabled="headerList.length === 1"
 									@click="removeHeaderRow(index)"
 								>
-									删除
+									{{ t('common.delete') }}
 								</el-button>
 							</div>
 						</div>
@@ -207,22 +207,22 @@
 				<el-form-item label="Auth Config">
 					<div class="kv-config">
 						<div class="kv-config__top">
-							<span>鉴权配置</span>
+							<span>{{ t('pages.mcp.authConfig') }}</span>
 							<el-button type="primary" link @click="addAuthRow">
-								<el-icon> <Plus /> </el-icon>添加
+								<el-icon> <Plus /> </el-icon>{{ t('pages.mcp.add') }}
 							</el-button>
 						</div>
 						<div class="kv-config__rows">
 							<div v-for="(item, index) in authList" :key="`auth-${index}`" class="kv-config__row">
-								<el-input v-model="item.key" placeholder="配置项名称" />
-								<el-input v-model="item.value" placeholder="配置项值" />
+								<el-input v-model="item.key" :placeholder="t('pages.mcp.configName')" />
+								<el-input v-model="item.value" :placeholder="t('pages.mcp.configValue')" />
 								<el-button
 									link
 									type="danger"
 									:disabled="authList.length === 1"
 									@click="removeAuthRow(index)"
 								>
-									删除
+									{{ t('common.delete') }}
 								</el-button>
 							</div>
 						</div>
@@ -231,22 +231,22 @@
 				<el-form-item label="Env Vars">
 					<div class="kv-config">
 						<div class="kv-config__top">
-							<span>环境变量</span>
+							<span>{{ t('pages.mcp.envVariables') }}</span>
 							<el-button type="primary" link @click="addEnvRow">
-								<el-icon> <Plus /> </el-icon>添加
+								<el-icon> <Plus /> </el-icon>{{ t('pages.mcp.add') }}
 							</el-button>
 						</div>
 						<div class="kv-config__rows">
 							<div v-for="(item, index) in envList" :key="`env-${index}`" class="kv-config__row">
-								<el-input v-model="item.key" placeholder="变量名" />
-								<el-input v-model="item.value" placeholder="变量值" />
+								<el-input v-model="item.key" :placeholder="t('pages.mcp.envVarName')" />
+								<el-input v-model="item.value" :placeholder="t('pages.mcp.envVarValue')" />
 								<el-button
 									link
 									type="danger"
 									:disabled="envList.length === 1"
 									@click="removeEnvRow(index)"
 								>
-									删除
+									{{ t('common.delete') }}
 								</el-button>
 							</div>
 						</div>
@@ -255,7 +255,7 @@
 				<el-form-item>
 					<div class="check-box">
 						<el-button :loading="checkLoading" :disabled="!currentId" @click="checkItemByForm">
-							测试连接
+							{{ t('pages.mcp.testConnection') }}
 						</el-button>
 						<el-alert
 							v-if="checkMessage"
@@ -269,38 +269,38 @@
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="drawerVisible = false">取消</el-button>
-					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
+					<el-button @click="drawerVisible = false">{{ t('common.cancel') }}</el-button>
+					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">{{ t('common.save') }}</el-button>
 				</div>
 			</template>
 		</el-drawer>
 
-		<el-dialog v-model="detailVisible" title="MCP 详情" width="720px">
+		<el-dialog v-model="detailVisible" :title="t('pages.mcp.mcpDetail')" width="720px">
 			<el-descriptions v-if="detailItem" :column="1" border>
-				<el-descriptions-item label="名称">{{ detailItem.name }}</el-descriptions-item>
-				<el-descriptions-item label="传输类型">{{
+				<el-descriptions-item :label="t('pages.mcp.name')">{{ detailItem.name }}</el-descriptions-item>
+				<el-descriptions-item :label="t('pages.mcp.transferType')">{{
 					detailItem.transport_type
 				}}</el-descriptions-item>
-				<el-descriptions-item label="地址">{{ detailItem.url || '-' }}</el-descriptions-item>
-				<el-descriptions-item label="描述">{{
+				<el-descriptions-item :label="t('pages.mcp.address')">{{ detailItem.url || '-' }}</el-descriptions-item>
+				<el-descriptions-item :label="t('pages.mcp.description')">{{
 					detailItem.description || '-'
 				}}</el-descriptions-item>
 			</el-descriptions>
 		</el-dialog>
 
-		<el-dialog v-model="resourcesVisible" title="MCP 资源" width="720px">
-			<el-empty v-if="!resourceList.length" description="暂无资源" />
+		<el-dialog v-model="resourcesVisible" :title="t('pages.mcp.mcpResources')" width="720px">
+			<el-empty v-if="!resourceList.length" :description="t('pages.mcp.noResources')" />
 			<el-space v-else wrap>
 				<el-tag v-for="item in resourceList" :key="item">{{ item }}</el-tag>
 			</el-space>
 		</el-dialog>
 
-		<el-dialog v-model="toolsVisible" title="MCP 工具" width="720px" class="tool-modal">
+		<el-dialog v-model="toolsVisible" :title="t('pages.mcp.mcpTools')" width="720px" class="tool-modal">
 			<div class="tool-dialog">
 				<el-input
 					v-model="toolKeyword"
 					clearable
-					placeholder="搜索工具名称或描述"
+					:placeholder="t('pages.mcp.searchToolPlaceholder')"
 					class="tool-search-input"
 				>
 					<template #prefix>
@@ -309,7 +309,7 @@
 						</el-icon>
 					</template>
 				</el-input>
-				<el-empty v-if="!filteredToolDetailList.length" description="暂无工具" />
+				<el-empty v-if="!filteredToolDetailList.length" :description="t('pages.mcp.noTools')" />
 				<el-scrollbar v-else max-height="420px">
 					<el-collapse>
 						<el-collapse-item v-for="item in filteredToolDetailList" :key="item.name">
@@ -321,7 +321,7 @@
 							</template>
 							<el-card>
 								<el-descriptions direction="vertical" :column="1">
-									<el-descriptions-item label="参数结构">
+									<el-descriptions-item :label="t('pages.mcp.paramSchema')">
 										<CodeEditor
 											:model-value="JSON.stringify(item.inputSchema, null, 2)"
 											language="json"
@@ -346,6 +346,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search, MoreFilled, RefreshRight } from '@element-plus/icons-vue'
 import { resource } from '@repo/api-service'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
+import { useI18n } from '@/composables/useI18n'
 
 type McpPageResponse = Awaited<ReturnType<typeof resource.postMcpPageList>>
 type McpItem = NonNullable<McpPageResponse['result']>['model'][number]
@@ -355,6 +356,8 @@ type McpCheckResponse = Awaited<ReturnType<typeof resource.postMcpCheck>>
 type McpToolItem = NonNullable<McpCheckResponse['result']>['tools'][number]
 type KvRow = { key: string; value: string }
 
+const { t } = useI18n()
+
 const keyword = ref('')
 const transportType = ref('')
 const loading = ref(false)
@@ -401,9 +404,9 @@ const form = reactive({
 })
 
 const rules = {
-	name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
-	transport_type: [{ required: true, message: '请选择传输类型', trigger: 'change' }],
-	url: [{ required: true, message: '请输入地址', trigger: 'blur' }]
+	name: [{ required: true, message: t('pages.mcp.pleaseInputName'), trigger: 'blur' }],
+	transport_type: [{ required: true, message: t('pages.mcp.pleaseSelectTransferType'), trigger: 'change' }],
+	url: [{ required: true, message: t('pages.mcp.pleaseInputAddress'), trigger: 'blur' }]
 }
 
 const enabledCount = computed(() => list.value.filter((item) => item.enabled).length)
@@ -536,7 +539,7 @@ function openCreate() {
 async function openEditById(id: string) {
 	const res = await resource.postMcpInfo({ id })
 	if (!res.isSuccess || !res.result) {
-		ElMessage.error('获取详情失败')
+		ElMessage.error(t('pages.mcp.fetchDetailFailed'))
 		return
 	}
 	resetForm()
@@ -562,19 +565,19 @@ async function checkItem(id: string) {
 	try {
 		const res = await resource.postMcpCheck({ id })
 		if (res.isSuccess) {
-			ElMessage.success(res.result?.message || '连通测试成功')
+			ElMessage.success(res.result?.message || t('pages.mcp.connectSuccess'))
 		} else {
-			ElMessage.error('连通测试失败')
+			ElMessage.error(t('pages.mcp.connectFailed'))
 		}
 	} catch {
-		ElMessage.error('连通测试失败')
+		ElMessage.error(t('pages.mcp.connectFailed'))
 	}
 }
 
 async function checkItemByForm() {
 	if (!currentId.value) {
 		checkSuccess.value = false
-		checkMessage.value = '请先保存 MCP 服务,再执行连通测试'
+		checkMessage.value = t('pages.mcp.pleaseSaveFirst')
 		return
 	}
 	checkLoading.value = true
@@ -582,10 +585,10 @@ async function checkItemByForm() {
 	try {
 		const res = await resource.postMcpCheck({ id: currentId.value })
 		checkSuccess.value = !!res.isSuccess
-		checkMessage.value = res.isSuccess ? res.result?.message || '连通测试成功' : '连通测试失败'
+		checkMessage.value = res.isSuccess ? res.result?.message || t('pages.mcp.connectSuccess') : t('pages.mcp.connectFailed')
 	} catch {
 		checkSuccess.value = false
-		checkMessage.value = '连通测试失败'
+		checkMessage.value = t('pages.mcp.connectFailed')
 	} finally {
 		checkLoading.value = false
 	}
@@ -613,15 +616,15 @@ async function handleSubmit() {
 			}
 			if (currentId.value) {
 				await resource.postMcpUpdate({ id: currentId.value, ...(payload as any) })
-				ElMessage.success('更新成功')
+				ElMessage.success(t('pages.mcp.updateSuccess'))
 			} else {
 				await resource.postMcpCreate(payload as any)
-				ElMessage.success('创建成功')
+				ElMessage.success(t('pages.mcp.createSuccess'))
 			}
 			drawerVisible.value = false
 			loadList(pagination.pageIndex)
 		} catch {
-			ElMessage.error('保存失败')
+			ElMessage.error(t('pages.mcp.saveFailed'))
 		} finally {
 			submitLoading.value = false
 		}
@@ -630,9 +633,9 @@ async function handleSubmit() {
 
 async function removeItem(id: string) {
 	try {
-		await ElMessageBox.confirm('确定删除该 MCP 服务吗?', '提示', { type: 'warning' })
+		await ElMessageBox.confirm(t('pages.mcp.confirmDelete'), t('pages.mcp.tip'), { type: 'warning' })
 		await resource.postMcpOpenApiDelete({ id })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('pages.mcp.deleteSuccess'))
 		loadList(pagination.pageIndex)
 	} catch {
 		// ignore

+ 43 - 19
apps/web/src/views/model/components/ModelDetailDialog.vue

@@ -1,22 +1,37 @@
 <template>
-	<el-dialog v-model="visible" title="模型详情" width="700px">
+	<el-dialog v-model="visible" :title="t('pages.model.detailTitle')" width="700px">
 		<el-descriptions v-if="model" :column="1" border>
-			<el-descriptions-item label="模型标识">{{ model.name }}</el-descriptions-item>
-			<el-descriptions-item label="显示名称">{{ model.title }}</el-descriptions-item>
-			<el-descriptions-item label="模型类型">{{ modelTypeName }}</el-descriptions-item>
-			<el-descriptions-item label="模型来源">{{ modelSourceName }}</el-descriptions-item>
-			<el-descriptions-item label="服务商">{{ model.provider }}</el-descriptions-item>
-			<el-descriptions-item label="描述">{{ model.description || '无' }}</el-descriptions-item>
-			<el-descriptions-item v-if="model.source === 'remote'" label="API地址">
-				{{ model.parameters.base_url || '无' }}
+			<el-descriptions-item :label="t('pages.model.fields.name')">{{
+				model.name
+			}}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.title')">{{
+				model.title
+			}}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.type')">{{
+				modelTypeName
+			}}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.source')">{{
+				modelSourceName
+			}}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.provider')">{{
+				model.provider
+			}}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.description')">{{
+				model.description || t('pages.model.none')
+			}}</el-descriptions-item>
+			<el-descriptions-item
+				v-if="model.source === 'remote'"
+				:label="t('pages.model.fields.baseUrl')"
+			>
+				{{ model.parameters.base_url || t('pages.model.none') }}
 			</el-descriptions-item>
-			<el-descriptions-item v-if="model.source === 'remote'" label="API Key">
-				{{ maskApiKey(model.parameters.api_key) }}
-			</el-descriptions-item>
-			<el-descriptions-item v-if="model.source === 'remote'" label="连接测试">
+			<el-descriptions-item
+				v-if="model.source === 'remote'"
+				:label="t('pages.model.fields.connectionTest')"
+			>
 				<div class="model-check-box">
 					<el-button type="primary" plain :loading="checkLoading" @click="$emit('check')">
-						测试连接
+						{{ t('pages.model.testConnection') }}
 					</el-button>
 					<el-alert
 						v-if="checkResult.message"
@@ -27,19 +42,26 @@
 					/>
 				</div>
 			</el-descriptions-item>
-			<el-descriptions-item label="创建时间">{{ model.creationTime }}</el-descriptions-item>
-			<el-descriptions-item label="更新时间">{{ model.updateTime }}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.createdAt')">{{
+				model.creationTime
+			}}</el-descriptions-item>
+			<el-descriptions-item :label="t('pages.model.fields.updatedAt')">{{
+				model.updateTime
+			}}</el-descriptions-item>
 		</el-descriptions>
 		<template #footer>
-			<el-button @click="visible = false">关闭</el-button>
+			<el-button @click="visible = false">{{ t('common.close') }}</el-button>
 		</template>
 	</el-dialog>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import { useI18n } from '@/composables/useI18n'
 import type { ModelDetail } from '../types'
 
+const { t } = useI18n()
+
 const props = defineProps<{
 	modelValue: boolean
 	model: ModelDetail | null
@@ -63,11 +85,13 @@ const visible = computed({
 
 const modelTypeName = computed(() => props.getModelTypeName(props.model?.type))
 const modelSourceName = computed(() =>
-	props.model?.source === 'local' ? '本地Ollama' : '远程API'
+	props.model?.source === 'local'
+		? t('pages.model.sources.localOllama')
+		: t('pages.model.sources.remoteApi')
 )
 
 function maskApiKey(key: string) {
-	if (!key) return '无'
+	if (!key) return t('pages.model.none')
 	if (key.length <= 8) return '****'
 	return `${key.slice(0, 4)}${'*'.repeat(key.length - 8)}${key.slice(-4)}`
 }

+ 44 - 42
apps/web/src/views/model/components/ModelEditDrawer.vue

@@ -2,7 +2,7 @@
 	<el-drawer
 		v-model="visible"
 		direction="rtl"
-		:title="currentModelId ? '编辑模型' : '新建模型'"
+		:title="currentModelId ? t('pages.model.editTitle') : t('pages.model.createTitle')"
 		width="700px"
 	>
 		<el-form
@@ -12,28 +12,28 @@
 			label-width="120px"
 			label-position="top"
 		>
-			<el-form-item label="模型来源" prop="source">
+			<el-form-item :label="t('pages.model.fields.source')" prop="source">
 				<el-radio-group v-model="modelForm.source" @change="$emit('source-change')">
-					<el-radio label="local">本地Ollama</el-radio>
-					<el-radio label="remote">服务商</el-radio>
+					<el-radio label="local">{{ t('pages.model.sources.localOllama') }}</el-radio>
+					<el-radio label="remote">{{ t('pages.model.sources.remote') }}</el-radio>
 				</el-radio-group>
 			</el-form-item>
 
-			<el-form-item v-if="modelForm.source === 'remote'" label="模型类型" prop="type">
+			<el-form-item v-if="modelForm.source === 'remote'" :label="t('pages.model.fields.type')" prop="type">
 				<el-select
 					v-model="modelForm.type"
-					placeholder="请选择模型类型"
+					:placeholder="t('pages.model.placeholders.type')"
 					@change="$emit('type-change')"
 				>
-					<el-option label="对话模型" value="KnowledgeQA" />
-					<el-option label="Embedding模型" value="Embedding" />
-					<el-option label="Rerank模型" value="Rerank" />
-					<el-option label="视觉模型" value="VLLM" />
+					<el-option :label="t('pages.model.types.KnowledgeQA')" value="KnowledgeQA" />
+					<el-option :label="t('pages.model.types.Embedding')" value="Embedding" />
+					<el-option :label="t('pages.model.types.Rerank')" value="Rerank" />
+					<el-option :label="t('pages.model.types.VLLM')" value="VLLM" />
 				</el-select>
 			</el-form-item>
 
-			<el-form-item v-if="modelForm.source === 'local'" label="本地模型" prop="name">
-				<el-select v-model="modelForm.name" placeholder="请选择" style="width: 100%">
+			<el-form-item v-if="modelForm.source === 'local'" :label="t('pages.model.fields.localModel')" prop="name">
+				<el-select v-model="modelForm.name" :placeholder="t('pages.model.placeholders.select')" style="width: 100%">
 					<el-option
 						v-for="model in availableLocalModels"
 						:key="model.name"
@@ -43,10 +43,10 @@
 				</el-select>
 			</el-form-item>
 
-			<el-form-item v-if="modelForm.source === 'remote'" label="服务商" prop="provider">
+			<el-form-item v-if="modelForm.source === 'remote'" :label="t('pages.model.fields.provider')" prop="provider">
 				<el-select
 					v-model="modelForm.provider"
-					placeholder="请选择服务商"
+					:placeholder="t('pages.model.placeholders.provider')"
 					style="width: 100%"
 					@change="$emit('provider-change')"
 				>
@@ -54,47 +54,47 @@
 				</el-select>
 			</el-form-item>
 
-			<el-form-item label="模型名称" prop="name">
+			<el-form-item :label="t('pages.model.fields.name')" prop="name">
 				<el-input
 					v-model="modelForm.name"
 					:readonly="modelForm.source === 'local'"
-					placeholder="如:llama3.1"
+					:placeholder="t('pages.model.placeholders.name')"
 				/>
 			</el-form-item>
 
-			<el-form-item label="显示名称" prop="title">
-				<el-input v-model="modelForm.title" placeholder="请输入显示名称" />
+			<el-form-item :label="t('pages.model.fields.title')" prop="title">
+				<el-input v-model="modelForm.title" :placeholder="t('pages.model.placeholders.title')" />
 			</el-form-item>
 
-			<el-form-item label="描述" prop="description">
+			<el-form-item :label="t('pages.model.fields.description')" prop="description">
 				<el-input
 					v-model="modelForm.description"
 					type="textarea"
 					rows="2"
-					placeholder="模型描述..."
+					:placeholder="t('pages.model.placeholders.description')"
 				/>
 			</el-form-item>
 
-			<el-form-item v-if="modelForm.source === 'remote'" label="API地址" prop="base_url">
+			<el-form-item v-if="modelForm.source === 'remote'" :label="t('pages.model.fields.baseUrl')" prop="base_url">
 				<el-autocomplete
 					v-model="modelForm.base_url"
 					:fetch-suggestions="queryBaseUrlSuggestions"
 					clearable
-					placeholder="可从默认地址选择,或手动输入"
+					:placeholder="t('pages.model.placeholders.baseUrl')"
 					style="width: 100%"
 				/>
 			</el-form-item>
 
 			<el-form-item
 				v-if="modelForm.source === 'remote' && !currentModelId"
-				label="API Key"
+				:label="t('pages.model.fields.apiKey')"
 				prop="api_key"
 			>
-				<el-input v-model="modelForm.api_key" type="password" placeholder="输入你的API密钥" />
+				<el-input v-model="modelForm.api_key" type="password" :placeholder="t('pages.model.placeholders.apiKey')" />
 			</el-form-item>
 			<el-form-item
 				v-if="modelForm.source === 'remote' && modelForm.type === 'Embedding'"
-				label="维度"
+				:label="t('pages.model.fields.dimension')"
 				prop="dimension"
 			>
 				<el-input-number
@@ -103,13 +103,13 @@
 					:step="1"
 					controls-position="right"
 					style="width: 100%"
-					placeholder="请输入向量维度"
+					:placeholder="t('pages.model.placeholders.dimension')"
 				/>
 			</el-form-item>
 
 			<el-form-item
 				v-if="modelForm.source === 'remote' && modelForm.type === 'Embedding'"
-				label="截断Tokens"
+				:label="t('pages.model.fields.truncateTokens')"
 				prop="truncate_prompt_tokens"
 			>
 				<el-input-number
@@ -118,13 +118,13 @@
 					:step="1"
 					controls-position="right"
 					style="width: 100%"
-					placeholder="请输入截断 token 数"
+					:placeholder="t('pages.model.placeholders.truncateTokens')"
 				/>
 			</el-form-item>
 
 			<el-form-item
 				v-if="modelForm.source === 'remote' && modelForm.type === 'KnowledgeQA'"
-				label="支持视觉"
+				:label="t('pages.model.fields.supportsVision')"
 				prop="supports_vision"
 			>
 				<el-switch v-model="modelForm.supports_vision" />
@@ -132,12 +132,12 @@
 
 			<el-form-item
 				v-if="modelForm.source === 'remote' && modelForm.type === 'KnowledgeQA'"
-				label="思考模式参数格式"
+				:label="t('pages.model.fields.thinkingControl')"
 				prop="thinking_control"
 			>
 				<el-select
 					v-model="modelForm.thinking_control"
-					placeholder="请选择思考模式参数格式"
+					:placeholder="t('pages.model.placeholders.thinkingControl')"
 					popper-class="thinking-control-select"
 					style="width: 100%"
 				>
@@ -154,36 +154,35 @@
 					</el-option>
 				</el-select>
 				<div class="field-tip">
-					决定智能体「思考模式」开/关时如何写入 API。已尝试按厂商/模型预选,若与实际情况不符请按
-					API 文档手动修改;选「不写入」时,智能体「思考模式」开关不生效。
+					{{ t('pages.model.thinking.tip') }}
 				</div>
 			</el-form-item>
 
 			<el-form-item prop="custom_headers">
 				<div class="header-config">
 					<div class="header-config__top">
-						<span>自定义请求头(可选)</span>
+						<span>{{ t('pages.model.fields.customHeaders') }}</span>
 						<el-button type="primary" link @click="$emit('add-custom-header')">
 							<el-icon>
 								<Plus />
 							</el-icon>
-							添加请求头
+							{{ t('pages.model.addHeader') }}
 						</el-button>
 					</div>
 					<div class="header-config__desc">
-						调用远程模型API时附加的HTTP请求头,常用于鉴权、链路追踪等场景
+						{{ t('pages.model.customHeadersDesc') }}
 					</div>
 					<div class="header-config__rows">
 						<div v-for="(item, index) in customHeaderList" :key="index" class="header-config__row">
-							<el-input v-model="item.key" placeholder="Header名称" />
-							<el-input v-model="item.value" placeholder="Header值" />
+							<el-input v-model="item.key" :placeholder="t('pages.model.placeholders.headerName')" />
+							<el-input v-model="item.value" :placeholder="t('pages.model.placeholders.headerValue')" />
 							<el-button
 								link
 								type="danger"
 								:disabled="customHeaderList.length === 1"
 								@click="$emit('remove-custom-header', index)"
 							>
-								删除
+								{{ t('common.delete') }}
 							</el-button>
 						</div>
 					</div>
@@ -192,7 +191,7 @@
 			<el-form-item>
 				<div v-if="modelForm.source === 'remote'" class="model-check-box">
 					<el-button type="primary" plain :loading="formCheckLoading" @click="$emit('check')">
-						测试连接
+						{{ t('pages.model.testConnection') }}
 					</el-button>
 					<el-alert
 						v-if="formCheckResult.message"
@@ -205,8 +204,8 @@
 			</el-form-item>
 		</el-form>
 		<template #footer>
-			<el-button @click="visible = false">取消</el-button>
-			<el-button type="primary" :loading="submitLoading" @click="$emit('submit')">提交</el-button>
+			<el-button @click="visible = false">{{ t('common.cancel') }}</el-button>
+			<el-button type="primary" :loading="submitLoading" @click="$emit('submit')">{{ t('pages.model.submit') }}</el-button>
 		</template>
 	</el-drawer>
 </template>
@@ -214,6 +213,7 @@
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 import { Plus } from '@element-plus/icons-vue'
+import { useI18n } from '@/composables/useI18n'
 import type {
 	ModelCreateForm,
 	ModelProvider,
@@ -221,6 +221,8 @@ import type {
 	ThinkingControlType
 } from '../types'
 
+const { t } = useI18n()
+
 const props = defineProps<{
 	modelValue: boolean
 	currentModelId: string | null

+ 106 - 80
apps/web/src/views/model/index.vue

@@ -2,8 +2,8 @@
 	<div class="management-page">
 		<div class="page-head">
 			<div>
-				<h1>模型管理</h1>
-				<p>统一管理远程模型与本地导入到系统的模型配置。</p>
+				<h1>{{ t('pages.model.title') }}</h1>
+				<p>{{ t('pages.model.subtitle') }}</p>
 			</div>
 		</div>
 
@@ -14,7 +14,7 @@
 						<el-input
 							v-model="searchForm.keyword"
 							clearable
-							placeholder="关键词搜索模型名称/标识"
+							:placeholder="t('pages.model.searchPlaceholder')"
 							class="search-input"
 							@keyup.enter="handleSearch"
 						>
@@ -25,44 +25,44 @@
 						<el-select
 							v-model="searchForm.type"
 							clearable
-							placeholder="模型类型"
+							:placeholder="t('pages.model.typePlaceholder')"
 							style="width: 160px"
 							@change="handleSearch"
 						>
-							<el-option label="对话模型" value="KnowledgeQA" />
-							<el-option label="Embedding模型" value="Embedding" />
-							<el-option label="Rerank模型" value="Rerank" />
-							<el-option label="视觉模型" value="VLLM" />
+							<el-option :label="t('pages.model.types.KnowledgeQA')" value="KnowledgeQA" />
+							<el-option :label="t('pages.model.types.Embedding')" value="Embedding" />
+							<el-option :label="t('pages.model.types.Rerank')" value="Rerank" />
+							<el-option :label="t('pages.model.types.VLLM')" value="VLLM" />
 						</el-select>
 						<el-select
 							v-model="searchForm.source"
 							clearable
-							placeholder="来源"
+							:placeholder="t('pages.model.sourcePlaceholder')"
 							style="width: 140px"
 							@change="handleSearch"
 						>
-							<el-option label="本地" value="local" />
-							<el-option label="服务商" value="remote" />
+							<el-option :label="t('pages.model.sources.local')" value="local" />
+							<el-option :label="t('pages.model.sources.remote')" value="remote" />
 						</el-select>
 						<div class="toolbar-actions">
 							<el-button type="primary" @click="handleSearch">
 								<el-icon><Search /></el-icon>
-								查询
+								{{ t('common.search') }}
 							</el-button>
 							<el-button @click="handleResetSearch">
 								<el-icon><RefreshRight /></el-icon>
-								重置
+								{{ t('common.reset') }}
 							</el-button>
 						</div>
 					</div>
 					<div class="action-bar__right">
 						<el-button v-permission="'add'" type="primary" @click="openCreateModel">
 							<el-icon><Plus /></el-icon>
-							新建模型
+							{{ t('pages.model.create') }}
 						</el-button>
 						<el-button @click="getAllModelList()">
 							<el-icon><Refresh /></el-icon>
-							刷新
+							{{ t('common.refresh') }}
 						</el-button>
 					</div>
 				</div>
@@ -74,7 +74,7 @@
 				>
 					<el-empty
 						v-if="!allModels.length && !modelLoading"
-						description="暂无配置的模型"
+						:description="t('pages.model.empty')"
 						class="empty"
 					/>
 
@@ -83,8 +83,10 @@
 							<div class="card-head__content">
 								<div class="card-head__top">
 									<div class="title-block">
-										<div class="title">{{ row.title || row.name || '未命名模型' }}</div>
-										<div class="subtitle">{{ row.name || '未设置模型标识' }}</div>
+										<div class="title">
+											{{ row.title || row.name || t('pages.model.unnamedModel') }}
+										</div>
+										<div class="subtitle">{{ row.name || t('pages.model.unsetModelId') }}</div>
 									</div>
 									<div class="actions" @click.stop>
 										<el-dropdown>
@@ -95,30 +97,34 @@
 											</span>
 											<template #dropdown>
 												<el-dropdown-menu>
-													<el-dropdown-item @click="openDetailModel(row.id)">详情</el-dropdown-item>
-													<el-dropdown-item v-permission="'edit'" @click="openEditModel(row.id)"
-														>编辑</el-dropdown-item
-													>
+													<el-dropdown-item @click="openDetailModel(row.id)">{{
+														t('pages.model.detail')
+													}}</el-dropdown-item>
+													<el-dropdown-item v-permission="'edit'" @click="openEditModel(row.id)">{{
+														t('pages.model.edit')
+													}}</el-dropdown-item>
 													<el-dropdown-item
 														v-if="row.source === 'remote'"
 														v-permission="'edit'"
 														@click="updateModelCredentials(row.id)"
 													>
-														更新凭证
+														{{ t('pages.model.updateCredentials') }}
 													</el-dropdown-item>
 													<el-dropdown-item
 														v-if="row.source === 'remote'"
 														v-permission="'edit'"
 														@click="deleteModelCredentials(row.id)"
 													>
-														<span class="danger-text">删除凭证</span>
+														<span class="danger-text">{{
+															t('pages.model.deleteCredentials')
+														}}</span>
 													</el-dropdown-item>
 													<el-dropdown-item
 														v-permission="'del'"
 														@click="deleteModelConfirm(row.id)"
 														divided
 													>
-														<span class="danger-text">删除</span>
+														<span class="danger-text">{{ t('common.delete') }}</span>
 													</el-dropdown-item>
 												</el-dropdown-menu>
 											</template>
@@ -133,10 +139,12 @@
 							</div>
 						</div>
 
-						<div class="desc">{{ row.description || '暂无描述' }}</div>
+						<div class="desc">{{ row.description || t('pages.model.noDescription') }}</div>
 
 						<div class="tags">
-							<el-tag v-if="row.is_default" type="success" effect="light">默认</el-tag>
+							<el-tag v-if="row.is_default" type="success" effect="light">{{
+								t('pages.model.defaultTag')
+							}}</el-tag>
 							<el-tag :type="getModelStatusType(row.status)" effect="light">
 								{{ formatModelStatus(row.status) }}
 							</el-tag>
@@ -200,6 +208,7 @@ import { computed, ref, reactive, onMounted, watch } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Refresh, MoreFilled, Search, RefreshRight } from '@element-plus/icons-vue'
 import { aiModel, ollama } from '@repo/api-service'
+import { useI18n } from '@/composables/useI18n'
 import ModelDetailDialog from './components/ModelDetailDialog.vue'
 import ModelEditDrawer from './components/ModelEditDrawer.vue'
 import type {
@@ -212,6 +221,8 @@ import type {
 	ThinkingControlType
 } from './types'
 
+const { t } = useI18n()
+
 const props = withDefaults(
 	defineProps<{
 		localOllamaModels?: OllamaModel[]
@@ -232,33 +243,34 @@ const currentDetailModel = ref<ModelDetail | null>(null)
 const modelFormRef = ref()
 const customHeaderList = ref<Array<{ key: string; value: string }>>([{ key: '', value: '' }])
 const localModelOptions = ref<OllamaModel[]>([])
-const thinkingControlOptions: Array<{
-	title: string
-	description: string
-	value: ThinkingControlType
-}> = [
+const thinkingControlOptions = computed<
+	Array<{
+		title: string
+		description: string
+		value: ThinkingControlType
+	}>
+>(() => [
 	{
-		title: '不写入思考参数',
-		description: '智能体「思考模式」开关不生效,不会在请求中写入思考相关参数',
+		title: t('pages.model.thinking.noneTitle'),
+		description: t('pages.model.thinking.noneDescription'),
 		value: 'none'
 	},
 	{
 		title: 'chat_template_kwargs',
-		description: '自定义 OpenAI 兼容、NVIDIA NIM、vLLM / 本地 Qwen 部署',
+		description: t('pages.model.thinking.chatTemplateDescription'),
 		value: 'chat_template_kwargs'
 	},
 	{
 		title: 'enable_thinking',
-		description: '阿里云 DashScope:qwen3、qwen-plus、qwen-max、qwen-turbo',
+		description: t('pages.model.thinking.enableThinkingDescription'),
 		value: 'enable_thinking'
 	},
 	{
 		title: 'thinking.type',
-		description:
-			'火山引擎 Ark;腾讯云 LKEAP(DeepSeek V3 等,选 LKEAP 时默认此项;R1 请改「不写入」)',
+		description: t('pages.model.thinking.thinkingTypeDescription'),
 		value: 'thinking_type'
 	}
-]
+])
 const detailCheckLoading = ref(false)
 const detailCheckResult = reactive<{ success: boolean; message: string }>({
 	success: false,
@@ -300,11 +312,13 @@ const modelForm = reactive<ModelCreateForm>({
 	thinking_control: 'none'
 })
 
-const modelRules = {
-	name: [{ required: true, message: '请输入模型名称', trigger: 'change' }],
-	title: [{ required: true, message: '请输入显示名称', trigger: 'blur' }],
-	type: [{ required: true, message: '请选择模型类型', trigger: 'change' }],
-	source: [{ required: true, message: '请选择模型来源', trigger: 'change' }],
+const modelRules = computed(() => ({
+	name: [{ required: true, message: t('pages.model.messages.nameRequired'), trigger: 'change' }],
+	title: [{ required: true, message: t('pages.model.messages.titleRequired'), trigger: 'blur' }],
+	type: [{ required: true, message: t('pages.model.messages.typeRequired'), trigger: 'change' }],
+	source: [
+		{ required: true, message: t('pages.model.messages.sourceRequired'), trigger: 'change' }
+	],
 	dimension: [
 		{
 			validator: (_: any, value: number, callback: (error?: Error) => void) => {
@@ -313,7 +327,7 @@ const modelRules = {
 					modelForm.type === 'Embedding' &&
 					(!value || value <= 0)
 				) {
-					callback(new Error('Embedding 模型请填写有效的 dimension'))
+					callback(new Error(t('pages.model.messages.dimensionRequired')))
 					return
 				}
 				callback()
@@ -321,14 +335,14 @@ const modelRules = {
 			trigger: 'blur'
 		}
 	]
-}
+}))
 
-const typeLabelMap: Record<string, string> = {
-	KnowledgeQA: '对话模型',
-	Embedding: 'Embedding模型',
-	Rerank: 'Rerank模型',
-	VLLM: '视觉模型'
-}
+const typeLabelMap = computed<Record<string, string>>(() => ({
+	KnowledgeQA: t('pages.model.types.KnowledgeQA'),
+	Embedding: t('pages.model.types.Embedding'),
+	Rerank: t('pages.model.types.Rerank'),
+	VLLM: t('pages.model.types.VLLM')
+}))
 
 // const filteredProviders = computed(() => {
 // 	if (!modelForm.type) return providers.value
@@ -360,16 +374,16 @@ const typeLabelMap: Record<string, string> = {
 
 function getModelTypeName(type?: string) {
 	if (!type) return '-'
-	return typeLabelMap[type] || type
+	return typeLabelMap.value[type] || type
 }
 
 function formatModelSource(source?: string) {
-	return source === 'local' ? '本地模型' : '服务商'
+	return source === 'local' ? t('pages.model.sources.localModel') : t('pages.model.sources.remote')
 }
 
 function formatModelStatus(status?: string) {
-	if (!status) return '未知状态'
-	if (status === 'active') return '可用'
+	if (!status) return t('pages.model.status.unknown')
+	if (status === 'active') return t('pages.model.status.active')
 	return status
 }
 
@@ -398,15 +412,15 @@ async function runModelCheck(
 		const res = await aiModel.postModelCheck(payload as any)
 		if (res?.isSuccess) {
 			resultState.success = true
-			resultState.message = res?.result?.message ?? '连接测试成功'
+			resultState.message = res?.result?.message ?? t('pages.model.messages.checkSuccess')
 			return true
 		}
 		resultState.success = false
-		resultState.message = res?.result?.message || '连接测试失败,请检查配置'
+		resultState.message = res?.result?.message || t('pages.model.messages.checkFailed')
 		return false
 	} catch {
 		resultState.success = false
-		resultState.message = '连接测试失败,请检查配置'
+		resultState.message = t('pages.model.messages.checkFailed')
 		return false
 	}
 }
@@ -673,6 +687,11 @@ async function checkCurrentDetailModel() {
 	try {
 		const detail = currentDetailModel.value
 		const payload: Record<string, any> = {
+			name: detail.name,
+			type: detail.type,
+			source: detail.source,
+			provider: detail.provider,
+			api_key: '',
 			id: detail.id
 		}
 		if (detail.type === 'Embedding') {
@@ -694,7 +713,7 @@ async function checkModelFormConnection() {
 	if (!valid) return
 	if (!modelForm.provider || !modelForm.api_key) {
 		formCheckResult.success = false
-		formCheckResult.message = '请先完善服务商和 API Key'
+		formCheckResult.message = t('pages.model.messages.providerAndKeyRequired')
 		return
 	}
 	formCheckLoading.value = true
@@ -730,7 +749,7 @@ async function submitModelForm() {
 			if (modelForm.source === 'remote') {
 				const checkSuccess = await runModelCheck(buildCheckParams(), formCheckResult)
 				if (!checkSuccess) {
-					ElMessage.error(formCheckResult.message || '模型不可用,请检查数据')
+					ElMessage.error(formCheckResult.message || t('pages.model.messages.modelUnavailable'))
 					return
 				}
 			}
@@ -763,15 +782,15 @@ async function submitModelForm() {
 			}
 			if (currentModelId.value) {
 				await aiModel.postModelUpdate({ id: currentModelId.value, ...params } as any)
-				ElMessage.success('更新成功')
+				ElMessage.success(t('pages.model.messages.updateSuccess'))
 			} else {
 				await aiModel.postModelCreate(params as any)
-				ElMessage.success('创建成功')
+				ElMessage.success(t('pages.model.messages.createSuccess'))
 			}
 			showModelDialog.value = false
 			getAllModelList()
 		} catch {
-			ElMessage.error('提交失败')
+			ElMessage.error(t('pages.model.messages.submitFailed'))
 		} finally {
 			submitLoading.value = false
 		}
@@ -779,43 +798,50 @@ async function submitModelForm() {
 }
 
 async function deleteModelConfirm(id: string) {
-	ElMessageBox.confirm('确定要删除该模型吗?删除后不可恢复').then(async () => {
+	ElMessageBox.confirm(t('pages.model.messages.deleteConfirm')).then(async () => {
 		await aiModel.postModelOpenApiDelete({ id })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('pages.model.messages.deleteSuccess'))
 		getAllModelList()
 	})
 }
 
 async function updateModelCredentials(id: string) {
 	try {
-		const { value } = await ElMessageBox.prompt('请输入新的 API Key', '更新模型凭证', {
-			confirmButtonText: '确定',
-			cancelButtonText: '取消',
-			inputType: 'password',
-			inputPlaceholder: '请输入 API Key',
-			inputValidator: (value) => !!value?.trim() || '请输入 API Key'
-		})
+		const { value } = await ElMessageBox.prompt(
+			t('pages.model.messages.updateCredentialPrompt'),
+			t('pages.model.messages.updateCredentialTitle'),
+			{
+				confirmButtonText: t('common.confirm'),
+				cancelButtonText: t('common.cancel'),
+				inputType: 'password',
+				inputValidator: (value) => !!value?.trim() || t('pages.model.messages.apiKeyRequired')
+			}
+		)
 		await aiModel.postModelCredentials({ id, api_key: value.trim() })
-		ElMessage.success('凭证更新成功')
+		ElMessage.success(t('pages.model.messages.credentialUpdateSuccess'))
 		getAllModelList()
 	} catch (error) {
 		if (error !== 'cancel' && error !== 'close') {
-			ElMessage.error('凭证更新失败')
+			ElMessage.error(t('pages.model.messages.credentialUpdateFailed'))
 		}
 	}
 }
 
 async function deleteModelCredentials(id: string) {
 	try {
-		await ElMessageBox.confirm('确定要删除该模型凭证吗?删除后该模型可能无法调用。', '提示', {
-			type: 'warning'
-		})
+		await ElMessageBox.confirm(
+			t('pages.model.messages.deleteCredentialConfirm'),
+			t('common.dialog.tip'),
+			{
+				type: 'warning'
+			}
+		)
 		await aiModel.postModelDeleteCredentials({ id })
-		ElMessage.success('凭证删除成功')
+		ElMessage.success(t('pages.model.messages.credentialDeleteSuccess'))
 		getAllModelList()
 	} catch (error) {
 		if (error !== 'cancel' && error !== 'close') {
-			ElMessage.error('凭证删除失败')
+			ElMessage.error(t('pages.model.messages.credentialDeleteFailed'))
 		}
 	}
 }

+ 37 - 38
apps/web/src/views/ollama/index.vue

@@ -3,7 +3,7 @@
 		<div class="page-head">
 			<div>
 				<h1>Ollama</h1>
-				<p>查看 Ollama 连接状态、本地模型列表,以及下载任务进度。</p>
+				<p>{{ t('pages.ollama.subtitle') }}</p>
 			</div>
 		</div>
 
@@ -18,10 +18,10 @@
 								</el-icon>
 							</div>
 							<div class="stat-info">
-								<div class="stat-label">Ollama连接状态</div>
-								<div class="stat-value">{{ ollamaStatus.isSuccess ? '已连接' : '未连接' }}</div>
+								<div class="stat-label">{{ t('pages.ollama.connectionStatus') }}</div>
+								<div class="stat-value">{{ ollamaStatus.isSuccess ? t('pages.ollama.connected') : t('pages.ollama.disconnected') }}</div>
 							</div>
-							<el-button type="primary" plain @click="checkOllamaStatus">刷新</el-button>
+							<el-button type="primary" plain @click="checkOllamaStatus">{{ t('pages.ollama.refresh') }}</el-button>
 						</div>
 					</el-card>
 				</el-col>
@@ -34,7 +34,7 @@
 								</el-icon>
 							</div>
 							<div class="stat-info">
-								<div class="stat-label">已下载模型</div>
+								<div class="stat-label">{{ t('pages.ollama.downloadedModels') }}</div>
 								<div class="stat-value">{{ localModels.length }}</div>
 							</div>
 						</div>
@@ -49,7 +49,7 @@
 								</el-icon>
 							</div>
 							<div class="stat-info">
-								<div class="stat-label">进行中任务</div>
+								<div class="stat-label">{{ t('pages.ollama.pendingTasks') }}</div>
 								<div class="stat-value">{{ downloadingTasks }}</div>
 							</div>
 						</div>
@@ -63,13 +63,13 @@
 						<el-icon>
 							<Plus />
 						</el-icon>
-						下载新模型
+						{{ t('pages.ollama.downloadNewModel') }}
 					</el-button>
 					<el-button @click="handleRefresh()">
 						<el-icon>
 							<Refresh />
 						</el-icon>
-						刷新
+						{{ t('pages.ollama.refresh') }}
 					</el-button>
 				</div>
 			</div>
@@ -81,30 +81,30 @@
 					v-loading="localLoading"
 					border
 				>
-					<el-table-column prop="name" label="模型名称" />
-					<el-table-column prop="size" label="大小" :formatter="formatSize" />
+					<el-table-column prop="name" :label="t('pages.ollama.modelName')" />
+					<el-table-column prop="size" :label="t('pages.ollama.size')" :formatter="formatSize" />
 					<el-table-column prop="digest" label="Digest" />
-					<el-table-column prop="modified_at" label="更新时间" />
-					<template #empty>暂无已下载的本地模型</template>
+					<el-table-column prop="modified_at" :label="t('pages.ollama.updateTime')" />
+					<template #empty>{{ t('pages.ollama.noModels') }}</template>
 				</el-table>
-				<el-empty v-else description="暂无已下载的本地模型" class="page-empty" />
+				<el-empty v-else :description="t('pages.ollama.noModels')" class="page-empty" />
 			</el-card>
 
-			<div class="task-title">下载任务</div>
+			<div class="task-title">{{ t('pages.ollama.downloadTasks') }}</div>
 			<el-card class="list-card mt-4">
 				<el-table
 					v-if="sortedDownloadTasks.length"
 					:data="sortedDownloadTasks"
 					border
-					no-data-text="暂无下载任务"
+				:no-data-text="t('pages.ollama.noTasks')"
 				>
-					<el-table-column prop="modelName" label="模型名称" width="200" />
-					<el-table-column prop="status" label="状态" width="120">
+					<el-table-column prop="modelName" :label="t('pages.ollama.modelName')" width="200" />
+					<el-table-column prop="status" :label="t('pages.ollama.status')" width="120">
 						<template #default="{ row }">
 							<el-tag :type="getStatusType(row.status)">{{ formatStatus(row.status) }}</el-tag>
 						</template>
 					</el-table-column>
-					<el-table-column prop="progress" label="进度" width="200">
+					<el-table-column prop="progress" :label="t('pages.ollama.progress')" width="200">
 						<template #default="{ row }">
 							<el-progress
 								:percentage="getPercentage(row.progress)"
@@ -113,31 +113,27 @@
 							/>
 						</template>
 					</el-table-column>
-					<el-table-column prop="startTime" label="开始时间" />
-					<el-table-column prop="message" label="消息" />
-					<template #empty>暂无下载任务</template>
+					<el-table-column prop="startTime" :label="t('pages.ollama.startTime')" />
+					<el-table-column prop="message" :label="t('pages.ollama.message')" />
+					<template #empty>{{ t('pages.ollama.noTasks') }}</template>
 				</el-table>
-				<el-empty v-else description="暂无下载任务" class="page-empty page-empty--compact" />
+				<el-empty v-else :description="t('pages.ollama.noTasks')" class="page-empty page-empty--compact" />
 			</el-card>
 
-			<el-dialog v-model="showDownloadDialog" title="下载新模型" width="500px">
+			<el-dialog v-model="showDownloadDialog" :title="t('pages.ollama.downloadTitle')" width="500px">
 				<el-form :model="downloadForm" label-width="80px">
-					<el-form-item label="模型名称" required>
-						<el-input v-model="downloadForm.modelName" placeholder="例如: llama3.1" />
+					<el-form-item :label="t('pages.ollama.modelName')" required>
+						<el-input v-model="downloadForm.modelName" :placeholder="t('pages.ollama.modelPlaceholder')" />
 					</el-form-item>
 					<el-form-item>
 						<div>
-							你可访问
-							<a href="https://ollama.ai/library" target="_blank">https://ollama.ai/library</a>
-							查询可用模型
+							{{ t('pages.ollama.modelRegistryTip', { url: 'https://ollama.ai/library' }) }}
 						</div>
 					</el-form-item>
 				</el-form>
 				<template #footer>
-					<el-button @click="showDownloadDialog = false">取消</el-button>
-					<el-button v-permission="'download'" type="primary" @click="startDownloadModel" :loading="downloadLoading"
-						>开始下载</el-button
-					>
+					<el-button @click="showDownloadDialog = false">{{ t('common.cancel') }}</el-button>
+					<el-button v-permission="'download'" type="primary" @click="startDownloadModel" :loading="downloadLoading">{{ t('pages.ollama.startDownload') }}</el-button>
 				</template>
 			</el-dialog>
 		</div>
@@ -146,6 +142,9 @@
 
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed, onUnmounted } from 'vue'
+import { useI18n } from '@/composables/useI18n'
+
+const { t } = useI18n()
 import { ElMessage, dayjs } from 'element-plus'
 import { Document, List, Plus, Refresh, CircleCheck, CircleClose } from '@element-plus/icons-vue'
 import { ollama } from '@repo/api-service'
@@ -183,9 +182,9 @@ function getPercentage(progress = 0) {
 }
 function formatStatus(status: string) {
 	const statusMap: Record<string, string> = {
-		downloading: '下载中',
-		completed: '已完成',
-		failed: '失败'
+		downloading: t('pages.ollama.downloading'),
+		completed: t('pages.ollama.completed'),
+		failed: t('pages.ollama.failed')
 	}
 	return statusMap[status] || status
 }
@@ -255,20 +254,20 @@ async function getDownloadTasks() {
 
 async function startDownloadModel() {
 	if (!downloadForm.modelName.trim()) {
-		ElMessage.warning('请输入模型名称')
+		ElMessage.warning(t('pages.ollama.pleaseInputModel'))
 		return
 	}
 	downloadLoading.value = true
 	try {
 		const res = await ollama.postOllamaDownloadModel({ model: downloadForm.modelName.trim() })
 		if (res.isSuccess) {
-			ElMessage.success('下载任务已创建')
+			ElMessage.success(t('pages.ollama.downloadCreated'))
 			showDownloadDialog.value = false
 			downloadForm.modelName = ''
 			await getDownloadTasks()
 			startProgressPoll()
 		} else {
-			ElMessage.error('创建下载任务失败')
+			ElMessage.error(t('pages.ollama.downloadCreateFailed'))
 		}
 	} finally {
 		downloadLoading.value = false

+ 53 - 50
apps/web/src/views/prompt/index.vue

@@ -2,8 +2,8 @@
 	<div class="management-page">
 		<div class="page-head">
 			<div>
-				<h1>提示词</h1>
-				<p>管理系统提示词、上下文模板、改写与兜底等资源配置。</p>
+				<h1>{{ t('pages.prompt.title') }}</h1>
+				<p>{{ t('pages.prompt.subtitle') }}</p>
 			</div>
 		</div>
 
@@ -13,7 +13,7 @@
 					<el-input
 						v-model="keyword"
 						clearable
-						placeholder="搜索提示词名称 / 描述"
+						:placeholder="t('pages.prompt.searchPlaceholder')"
 						class="search-input"
 						@keyup.enter="loadList(1)"
 					>
@@ -26,7 +26,7 @@
 					<el-select
 						v-model="type"
 						clearable
-						placeholder="类型"
+						:placeholder="t('pages.prompt.type')"
 						style="width: 160px"
 						:options="typeList"
 						@change="loadList(1)"
@@ -34,11 +34,11 @@
 					<div class="toolbar-actions">
 						<el-button type="primary" @click="loadList(1)">
 							<el-icon><Search /></el-icon>
-							查询
+							{{ t('common.search') }}
 						</el-button>
 						<el-button @click="handleReset">
 							<el-icon><RefreshRight /></el-icon>
-							重置
+							{{ t('common.reset') }}
 						</el-button>
 					</div>
 				</div>
@@ -47,22 +47,22 @@
 						<el-icon>
 							<Plus />
 						</el-icon>
-						新建提示词
+						{{ t('pages.prompt.createPrompt') }}
 					</el-button>
 				</div>
 			</div>
 
 			<div v-loading="loading" class="card-grid">
-				<el-empty v-if="!visibleList.length && !loading" description="暂无提示词" class="empty" />
+				<el-empty v-if="!visibleList.length && !loading" :description="t('pages.prompt.noPrompt')" class="empty" />
 				<div v-for="item in visibleList" :key="item.id" class="resource-card">
 					<div class="resource-card__top">
 						<div>
 							<div class="resource-card__title flex items-center gap-4px">
-								<el-tag v-if="item.is_builtin" type="success" effect="light">内置</el-tag
+								<el-tag v-if="item.is_builtin" type="success" effect="light">{{ t('pages.prompt.builtIn') }}</el-tag
 								>{{ item.name }}
 							</div>
 
-							<div class="resource-card__desc">{{ item.description || '暂无描述' }}</div>
+							<div class="resource-card__desc">{{ item.description || t('pages.prompt.noDescription') }}</div>
 						</div>
 						<el-dropdown :hide-on-click="false">
 							<span class="cursor-pointer">
@@ -73,20 +73,20 @@
 							<template #dropdown>
 								<el-dropdown-menu>
 									<el-dropdown-item>
-										<el-button link type="primary" @click="openDetail(item)">详情</el-button>
+										<el-button link type="primary" @click="openDetail(item)">{{ t('pages.prompt.detail') }}</el-button>
 									</el-dropdown-item>
 									<el-dropdown-item>
-										<el-button link type="primary" @click="openEdit(item)">编辑</el-button>
+										<el-button link type="primary" @click="openEdit(item)">{{ t('common.edit') }}</el-button>
 									</el-dropdown-item>
 									<el-dropdown-item>
-										<el-button link type="danger" @click="removeItem(item.id)">删除</el-button>
+										<el-button link type="danger" @click="removeItem(item.id)">{{ t('common.delete') }}</el-button>
 									</el-dropdown-item>
 								</el-dropdown-menu>
 							</template>
 						</el-dropdown>
 					</div>
 					<div class="resource-card__meta">
-						<span>类型:{{ formatType(item.type) }}</span>
+						<span>{{ t('pages.prompt.typeLabel') }}{{ formatType(item.type) }}</span>
 					</div>
 					<div class="resource-card__content">{{ shortText(item.content) }}</div>
 				</div>
@@ -107,25 +107,25 @@
 
 		<el-drawer
 			v-model="drawerVisible"
-			:title="currentId ? '编辑提示词' : '新建提示词'"
+			:title="currentId ? t('pages.prompt.editPrompt') : t('pages.prompt.createPromptTitle')"
 			direction="rtl"
 			size="760px"
 		>
 			<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
-				<el-form-item label="名称" prop="name">
+				<el-form-item :label="t('pages.prompt.name')" prop="name">
 					<el-input v-model="form.name" />
 				</el-form-item>
-				<el-form-item label="描述" prop="description">
+				<el-form-item :label="t('pages.prompt.description')" prop="description">
 					<el-input v-model="form.description" type="textarea" :rows="2" />
 				</el-form-item>
-				<el-form-item label="内容" prop="content">
+				<el-form-item :label="t('pages.prompt.content')" prop="content">
 					<el-input v-model="form.content" type="textarea" :rows="8" />
 				</el-form-item>
-				<el-form-item label="用户提示词" prop="user">
+				<el-form-item :label="t('pages.prompt.userPrompt')" prop="user">
 					<el-input v-model="form.user" type="textarea" :rows="4" />
 				</el-form-item>
 				<div class="grid-2">
-					<el-form-item label="类型" prop="type">
+					<el-form-item :label="t('pages.prompt.type')" prop="type">
 						<el-select v-model="form.type" style="width: 100%">
 							<el-option label="系统提示词" value="system-prompt" />
 							<el-option label="agent 系统提示词" value="agent-system-prompt" />
@@ -134,35 +134,35 @@
 							<el-option label="上下文模板" value="context-template" />
 						</el-select>
 					</el-form-item>
-					<el-form-item label="是否默认" prop="is_default">
+					<el-form-item :label="t('pages.prompt.isDefault')" prop="is_default">
 						<el-switch v-model="form.is_default" />
 					</el-form-item>
 				</div>
 				<div class="grid-2">
-					<el-form-item label="包含知识库" prop="has_knowledge_base">
+					<el-form-item :label="t('pages.prompt.includeKnowledge')" prop="has_knowledge_base">
 						<el-switch v-model="form.has_knowledge_base" />
 					</el-form-item>
-					<el-form-item label="包含网络搜索" prop="has_web_search">
+					<el-form-item :label="t('pages.prompt.includeWebSearch')" prop="has_web_search">
 						<el-switch v-model="form.has_web_search" />
 					</el-form-item>
 				</div>
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="drawerVisible = false">取消</el-button>
-					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
+					<el-button @click="drawerVisible = false">{{ t('common.cancel') }}</el-button>
+					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">{{ t('common.save') }}</el-button>
 				</div>
 			</template>
 		</el-drawer>
 
-		<el-drawer v-model="detailVisible" title="提示词详情" width="760px">
+		<el-drawer v-model="detailVisible" :title="t('pages.prompt.promptDetail')" width="760px">
 			<el-descriptions v-if="detailItem" :column="1" direction="vertical">
-				<el-descriptions-item label="名称">{{ detailItem.name }}</el-descriptions-item>
-				<el-descriptions-item label="类型">{{ formatType(detailItem.type) }}</el-descriptions-item>
-				<el-descriptions-item label="描述">{{
+				<el-descriptions-item :label="t('pages.prompt.name')">{{ detailItem.name }}</el-descriptions-item>
+				<el-descriptions-item :label="t('pages.prompt.type')">{{ formatType(detailItem.type) }}</el-descriptions-item>
+				<el-descriptions-item :label="t('pages.prompt.description')">{{
 					detailItem.description || '-'
 				}}</el-descriptions-item>
-				<el-descriptions-item label="内容">
+				<el-descriptions-item :label="t('pages.prompt.content')">
 					<div class="w-full p-12px box-border border-1px rounded-md border-solid border-gray-200">
 						<SMarkdown :markdown="detailItem.content" />
 					</div>
@@ -178,11 +178,14 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search, MoreFilled, RefreshRight } from '@element-plus/icons-vue'
 import { resource } from '@repo/api-service'
 import SMarkdown from '@/components/Chat/SMarkdown.vue'
+import { useI18n } from '@/composables/useI18n'
 
 type PromptPageResponse = Awaited<ReturnType<typeof resource.postPromptTemplatePageList>>
 type PromptPageResult = NonNullable<PromptPageResponse['result']>
 type PromptItem = PromptPageResult['model'][number]
 
+const { t } = useI18n()
+
 const keyword = ref('')
 const type = ref('')
 const loading = ref(false)
@@ -214,28 +217,28 @@ const form = reactive({
 })
 
 const rules = {
-	name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
-	content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
-	type: [{ required: true, message: '请选择类型', trigger: 'change' }]
+	name: [{ required: true, message: t('pages.prompt.pleaseInputName'), trigger: 'blur' }],
+	content: [{ required: true, message: t('pages.prompt.pleaseInputContent'), trigger: 'blur' }],
+	type: [{ required: true, message: t('pages.prompt.pleaseSelectType'), trigger: 'change' }]
 }
 
 const visibleList = computed(() => list.value)
 
-const typeMap: Record<string, string> = {
-	'system-prompt': '系统提示词',
-	'agent-system-prompt': 'Agent 系统提示词',
-	rewrite: '改写提示词',
-	'fall-back': '兜底提示词',
-	'context-template': '上下文模板',
-	'generate-session-title': '生成会话标题',
-	'generate-summary': '生成概要',
-	'keywords-extraction': '关键词提取'
-}
+const typeMap = computed<Record<string, string>>(() => ({
+	'system': t('pages.prompt.typeMap.system'),
+	'agent_system': t('pages.prompt.typeMap.agentSystem'),
+	'rewrite': t('pages.prompt.typeMap.rewrite'),
+	'fallback': t('pages.prompt.typeMap.fallback'),
+	'context_template': t('pages.prompt.typeMap.contextTemplate'),
+	'generate_title': t('pages.prompt.typeMap.generateTitle'),
+	'generate_summary': t('pages.prompt.typeMap.generateSummary'),
+	'keyword_extraction': t('pages.prompt.typeMap.keywordExtraction')
+}))
 
-const typeList = Object.keys(typeMap).map((type) => ({ label: typeMap[type], value: type }))
+const typeList = computed(() => Object.keys(typeMap.value).map((type) => ({ label: typeMap.value[type], value: type })))
 
 function formatType(type?: string) {
-	return typeMap[type || ''] || type || '-'
+	return typeMap.value[type || ''] || type || '-'
 }
 
 function shortText(text?: string) {
@@ -331,15 +334,15 @@ async function handleSubmit() {
 			}
 			if (currentId.value) {
 				await resource.postPromptTemplateUpdate({ id: currentId.value, ...(payload as any) })
-				ElMessage.success('更新成功')
+				ElMessage.success(t('pages.prompt.updateSuccess'))
 			} else {
 				await resource.postPromptTemplateCreate(payload as any)
-				ElMessage.success('创建成功')
+				ElMessage.success(t('pages.prompt.createSuccess'))
 			}
 			drawerVisible.value = false
 			loadList(pagination.pageIndex)
 		} catch {
-			ElMessage.error('保存失败')
+			ElMessage.error(t('pages.prompt.saveFailed'))
 		} finally {
 			submitLoading.value = false
 		}
@@ -348,9 +351,9 @@ async function handleSubmit() {
 
 async function removeItem(id: string) {
 	try {
-		await ElMessageBox.confirm('确定删除该提示词吗?', '提示', { type: 'warning' })
+		await ElMessageBox.confirm(t('pages.prompt.confirmDelete'), t('pages.prompt.tip'), { type: 'warning' })
 		await resource.postPromptTemplateOpenApiDelete({ id })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('pages.prompt.deleteSuccess'))
 		loadList(pagination.pageIndex)
 	} catch {
 		// ignore

+ 9 - 6
apps/web/src/views/skills/index.vue

@@ -2,8 +2,8 @@
 	<div class="management-page">
 		<div class="page-head">
 			<div>
-				<h1>Skills 技能</h1>
-				<p>查看当前系统可用的 Skills 列表,供智能体配置时选择。</p>
+				<h1>{{ t('pages.skills.title') }}</h1>
+				<p>{{ t('pages.skills.subtitle') }}</p>
 			</div>
 		</div>
 
@@ -13,7 +13,7 @@
 					<el-input
 						v-model="keyword"
 						clearable
-						placeholder="搜索技能名称或描述"
+						:placeholder="t('pages.skills.searchPlaceholder')"
 						class="search-input"
 					>
 						<template #prefix>
@@ -23,20 +23,20 @@
 					<div class="toolbar-actions">
 						<el-button @click="loadSkills" :loading="loading">
 							<el-icon><Refresh /></el-icon>
-							刷新
+							{{ t('pages.skills.refresh') }}
 						</el-button>
 					</div>
 				</div>
 			</div>
 
 			<div v-loading="loading" class="card-grid">
-				<el-empty v-if="!visibleList.length && !loading" description="暂无 Skills" class="empty" />
+				<el-empty v-if="!visibleList.length && !loading" :description="t('pages.skills.noSkills')" class="empty" />
 				<div v-for="item in visibleList" :key="item.name" class="skill-card">
 					<div class="skill-card__top">
 						<div class="skill-card__title">{{ item.name }}</div>
 						<el-tag effect="plain" type="info">Skill</el-tag>
 					</div>
-					<div class="skill-card__desc">{{ item.description || '暂无描述' }}</div>
+					<div class="skill-card__desc">{{ item.description || t('pages.skills.noDescription') }}</div>
 				</div>
 			</div>
 		</el-card>
@@ -45,6 +45,9 @@
 
 <script setup lang="ts">
 import { computed, onMounted, ref } from 'vue'
+import { useI18n } from '@/composables/useI18n'
+
+const { t } = useI18n()
 import { Search, Refresh } from '@element-plus/icons-vue'
 import { resource } from '@repo/api-service'
 

+ 36 - 33
apps/web/src/views/storage/index.vue

@@ -2,8 +2,8 @@
 	<div class="management-page">
 		<div class="page-head">
 			<div>
-				<h1>存储引擎</h1>
-				<p>管理对象存储引擎配置,支持保存与连通测试。</p>
+				<h1>{{ t('pages.storagePage.title') }}</h1>
+				<p>{{ t('pages.storagePage.subtitle') }}</p>
 			</div>
 		</div>
 
@@ -11,11 +11,11 @@
 			<div class="storage-actions">
 				<el-button v-permission="'init'" type="primary" :loading="initLoading" @click="handleInit">
 					<el-icon><SetUp /></el-icon>
-					初始化存储
+					{{ t('pages.storagePage.initStorage') }}
 				</el-button>
 				<el-button :loading="engineLoading" @click="loadEngines">
 					<el-icon><Refresh /></el-icon>
-					刷新列表
+					{{ t('pages.storagePage.refreshList') }}
 				</el-button>
 			</div>
 
@@ -25,7 +25,7 @@
 						<div class="card-head__top">
 							<div class="title-block">
 								<div class="title">{{ formatProviderLabel(item.name) }}</div>
-								<div class="subtitle">{{ item.description || '支持对象存储配置与连通测试' }}</div>
+								<div class="subtitle">{{ item.description || t('pages.storagePage.engineDescription') }}</div>
 							</div>
 							<div class="actions">
 								<el-dropdown>
@@ -40,9 +40,9 @@
 												:disabled="currentDefaultProvider === item.name"
 												@click="updateDefaultProvider(item.name)"
 											>
-												{{ currentDefaultProvider === item.name ? '当前默认' : '设为默认' }}
+												{{ currentDefaultProvider === item.name ? t('pages.storagePage.currentDefault') : t('pages.storagePage.setDefault') }}
 											</el-dropdown-item>
-											<el-dropdown-item v-permission="'edit'" @click="openEditProvider(item.name)">编辑</el-dropdown-item>
+											<el-dropdown-item v-permission="'edit'" @click="openEditProvider(item.name)">{{ t('common.edit') }}</el-dropdown-item>
 										</el-dropdown-menu>
 									</template>
 								</el-dropdown>
@@ -56,23 +56,23 @@
 							class="default-provider-tag"
 							effect="light"
 						>
-							默认
+							{{ t('pages.storagePage.defaultLabel') }}
 						</el-tag>
 						<el-tag :type="item.allowed ? 'success' : 'info'" effect="light">
-							{{ item.allowed ? '允许' : '不允许' }}
+							{{ item.allowed ? t('pages.storagePage.allowed') : t('pages.storagePage.notAllowed') }}
 						</el-tag>
 						<el-tag :type="item.available ? 'primary' : 'warning'" effect="light">
-							{{ item.available ? '可用' : '不可用' }}
+							{{ item.available ? t('pages.storagePage.available') : t('pages.storagePage.notAvailable') }}
 						</el-tag>
 					</div>
 				</div>
-				<el-empty v-if="!engines.length" class="empty" description="暂无引擎" />
+				<el-empty v-if="!engines.length" class="empty" :description="t('pages.storagePage.noEngine')" />
 			</div>
 		</el-card>
 
 		<el-drawer
 			v-model="drawerVisible"
-			:title="selectedProvider ? `${formatProviderLabel(selectedProvider)} 配置` : '编辑存储引擎'"
+			:title="selectedProvider ? `${formatProviderLabel(selectedProvider)} ${t('pages.storagePage.editStorageEngine')}` : t('pages.storagePage.editStorageEngine')"
 			direction="rtl"
 			size="760px"
 		>
@@ -80,13 +80,13 @@
 				<div class="drawer-intro">
 					<div class="drawer-intro__title">{{ formatProviderLabel(selectedProvider) }}</div>
 					<div class="drawer-intro__desc">
-						修改当前存储引擎配置后保存,可直接在此执行重新加载与连通测试。
+						{{ t('pages.storagePage.editStorageDesc') }}
 					</div>
 				</div>
 
 				<el-form :model="form" label-position="top">
 					<template v-if="selectedProvider === 'local'">
-						<el-form-item label="存储前缀">
+						<el-form-item :label="t('pages.storagePage.storagePrefix')">
 							<el-input v-model="form.local.path_prefix" placeholder="例如 knowledge/files" />
 						</el-form-item>
 					</template>
@@ -241,7 +241,7 @@
 						</el-form-item>
 					</template>
 
-					<el-form-item label="设为默认">
+					<el-form-item :label="t('pages.storagePage.setDefault')">
 						<el-switch v-model="form.is_default" />
 					</el-form-item>
 				</el-form>
@@ -254,7 +254,7 @@
 						@click="reloadSelectedProvider"
 						:loading="providerLoading === selectedProvider"
 					>
-						重新加载
+						{{ t('pages.storagePage.reload') }}
 					</el-button>
 					<el-button
 						type="primary"
@@ -263,9 +263,9 @@
 						:loading="selectedProvider ? testingProvider === selectedProvider : false"
 						@click="selectedProvider && testProvider(selectedProvider)"
 					>
-						测试连接
+						{{ t('pages.storagePage.testConnection') }}
 					</el-button>
-					<el-button v-permission="'edit'" type="primary" :loading="saving" @click="saveConfig">保存配置</el-button>
+					<el-button v-permission="'edit'" type="primary" :loading="saving" @click="saveConfig">{{ t('pages.storagePage.saveConfig') }}</el-button>
 				</div>
 			</template>
 		</el-drawer>
@@ -277,6 +277,9 @@ import { computed, onMounted, reactive, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { MoreFilled, Refresh, SetUp } from '@element-plus/icons-vue'
 import { storageProvider } from '@repo/api-service'
+import { useI18n } from '@/composables/useI18n'
+
+const { t } = useI18n()
 
 type StorageProviderName = 'local' | 'minio' | 'cos' | 'tos' | 's3' | 'oss' | 'ks3'
 
@@ -553,7 +556,7 @@ async function loadEngines() {
 				}))
 		}
 	} catch {
-		ElMessage.error('加载引擎失败')
+		ElMessage.error(t('pages.storagePage.loadEngineFailed'))
 	} finally {
 		engineLoading.value = false
 	}
@@ -569,7 +572,7 @@ async function loadDefaultProvider() {
 		}
 		currentDefaultProvider.value = ''
 	} catch {
-		ElMessage.error('获取默认存储厂商失败')
+		ElMessage.error(t('pages.storagePage.getDefaultProviderFailed'))
 	}
 }
 
@@ -578,13 +581,13 @@ async function handleInit() {
 	try {
 		const res = await storageProvider.postAiStorageProviderInitStorageProvider({})
 		if (res?.isSuccess) {
-			ElMessage.success('初始化成功')
+			ElMessage.success(t('pages.storagePage.initSuccess'))
 			await Promise.all([loadEngines(), loadDefaultProvider()])
 			return
 		}
-		ElMessage.error('初始化失败')
+		ElMessage.error(t('pages.storagePage.initFailed'))
 	} catch {
-		ElMessage.error('初始化失败')
+		ElMessage.error(t('pages.storagePage.initFailed'))
 	} finally {
 		initLoading.value = false
 	}
@@ -600,7 +603,7 @@ async function loadProviderConfig(provider: StorageProviderName) {
 			selectedProvider.value = provider
 		}
 	} catch {
-		ElMessage.error('加载配置失败')
+		ElMessage.error(t('pages.storagePage.loadConfigFailed'))
 	} finally {
 		providerLoading.value = ''
 	}
@@ -643,11 +646,11 @@ async function testProvider(provider: StorageProviderName) {
 		}
 		const res = await storageProvider.postAiStorageProviderCheck(buildCheckPayload(provider) as any)
 		if (res?.isSuccess) {
-			ElMessage.success(`${formatProviderLabel(provider)} 连通成功`)
+			ElMessage.success(`${formatProviderLabel(provider)} ${t('pages.storagePage.connectSuccess')}`)
 			return
 		}
 	} catch {
-		ElMessage.error(`${formatProviderLabel(provider)} 连通失败`)
+		ElMessage.error(`${formatProviderLabel(provider)} ${t('pages.storagePage.connectFailed')}`)
 	} finally {
 		testingProvider.value = ''
 	}
@@ -660,12 +663,12 @@ async function updateDefaultProvider(provider: StorageProviderName) {
 		if (res?.isSuccess) {
 			currentDefaultProvider.value = provider
 			form.default_provider = provider
-			ElMessage.success('默认存储厂商已更新')
+			ElMessage.success(t('pages.storagePage.defaultProviderUpdated'))
 			return
 		}
-		ElMessage.error('默认存储厂商更新失败')
+		ElMessage.error(t('pages.storagePage.defaultProviderUpdateFailed'))
 	} catch {
-		ElMessage.error('默认存储厂商更新失败')
+		ElMessage.error(t('pages.storagePage.defaultProviderUpdateFailed'))
 	} finally {
 		settingDefaultProvider.value = ''
 	}
@@ -681,21 +684,21 @@ function buildUpdatePayload() {
 
 async function saveConfig() {
 	if (!selectedProvider.value) {
-		ElMessage.warning('请先选择一个引擎')
+		ElMessage.warning(t('pages.storagePage.pleaseSelectEngine'))
 		return
 	}
 	saving.value = true
 	try {
 		const res = await storageProvider.postAiStorageProviderUpdate(buildUpdatePayload() as any)
 		if (res?.isSuccess) {
-			ElMessage.success('配置已保存')
+			ElMessage.success(t('pages.storagePage.configSaved'))
 			await Promise.all([loadEngines(), loadDefaultProvider()])
 			drawerVisible.value = false
 			return
 		}
-		ElMessage.error('保存失败')
+		ElMessage.error(t('pages.storagePage.saveFailed'))
 	} catch {
-		ElMessage.error('保存失败')
+		ElMessage.error(t('pages.storagePage.saveFailed'))
 	} finally {
 		saving.value = false
 	}

+ 1 - 1
apps/web/src/views/vector/index.vue

@@ -84,7 +84,7 @@
 							<span class="info-value">{{ item.connection_config?.addr || '-' }}</span>
 						</div>
 						<div class="info-row">
-							<span class="info-label">创建时间</span>
+							<span class="info-label">{{ t('pages.vectorStore.creationTime') }}</span>
 							<span class="info-value">{{ item.creationTime || '-' }}</span>
 						</div>
 					</div>

+ 95 - 66
apps/web/src/views/web-search/index.vue

@@ -2,18 +2,18 @@
 	<div class="management-page">
 		<div class="page-head">
 			<div>
-				<h1>网络搜索</h1>
-				<p>管理搜索引擎接入、参数配置与连通测试。</p>
+				<h1>{{ t('pages.webSearch.title') }}</h1>
+				<p>{{ t('pages.webSearch.subtitle') }}</p>
 			</div>
 		</div>
 
 		<el-card class="list-card">
-			<div class="toolbar">
+			<div class="toolbar mb-16px">
 				<div class="toolbar-left">
 					<el-input
 						v-model="keyword"
 						clearable
-						placeholder="搜索提供者名称"
+						:placeholder="t('pages.webSearch.searchPlaceholder')"
 						class="search-input"
 						@keyup.enter="loadList(1)"
 					>
@@ -26,7 +26,7 @@
 					<el-select
 						v-model="provider"
 						clearable
-						placeholder="服务商"
+						:placeholder="t('pages.webSearch.provider')"
 						style="width: 180px"
 						@change="loadList(1)"
 					>
@@ -40,11 +40,11 @@
 					<div class="toolbar-actions">
 						<el-button type="primary" @click="loadList(1)">
 							<el-icon><Search /></el-icon>
-							查询
+							{{ t('pages.webSearch.query') }}
 						</el-button>
 						<el-button @click="handleReset">
 							<el-icon><RefreshRight /></el-icon>
-							重置
+							{{ t('common.reset') }}
 						</el-button>
 					</div>
 				</div>
@@ -53,17 +53,21 @@
 						<el-icon>
 							<Plus />
 						</el-icon>
-						新建网络搜索
+						{{ t('pages.webSearch.createWebSearch') }}
 					</el-button>
 				</div>
 			</div>
 
 			<div v-loading="loading" class="grid">
-				<el-empty v-if="!list.length && !loading" class="empty" description="暂无网络搜索配置" />
+				<el-empty
+					v-if="!list.length && !loading"
+					class="empty"
+					:description="t('pages.webSearch.noWebSearch')"
+				/>
 				<div v-for="row in list" :key="row.id" class="card">
 					<div class="card-head">
 						<div class="flex items-center justify-between">
-							<div class="title">{{ row.name || '未命名网络搜索' }}</div>
+							<div class="title">{{ row.name || t('pages.webSearch.unnamedWebSearch') }}</div>
 							<div class="actions" @click.stop>
 								<el-dropdown>
 									<span class="actions-trigger">
@@ -73,23 +77,17 @@
 									</span>
 									<template #dropdown>
 										<el-dropdown-menu>
-											<el-dropdown-item v-permission="'edit'" @click="openEditById(row.id)"
-												>编辑</el-dropdown-item
-											>
-											<el-dropdown-item
-												v-permission="'edit'"
-												@click="updateCredentials(row.id)"
-											>
-												更新凭证
+											<el-dropdown-item v-permission="'edit'" @click="openEditById(row.id)">{{
+												t('common.edit')
+											}}</el-dropdown-item>
+											<el-dropdown-item v-permission="'edit'" @click="updateCredentials(row.id)">
+												{{ t('pages.webSearch.updateCredential') }}
 											</el-dropdown-item>
-											<el-dropdown-item
-												v-permission="'edit'"
-												@click="deleteCredentials(row.id)"
-											>
-												<span class="danger-text">删除凭证</span>
+											<el-dropdown-item v-permission="'edit'" @click="deleteCredentials(row.id)">
+												<span class="danger-text">{{ t('pages.webSearch.deleteCredential') }}</span>
 											</el-dropdown-item>
 											<el-dropdown-item v-permission="'del'" @click="removeItem(row.id)" divided>
-												<span class="danger-text">删除</span>
+												<span class="danger-text">{{ t('common.delete') }}</span>
 											</el-dropdown-item>
 										</el-dropdown-menu>
 									</template>
@@ -100,12 +98,18 @@
 						<div class="subtitle">{{ getEngineName(row.provider) }}</div>
 						<div class="badge-row">
 							<span class="badge">{{ getEngineName(row.provider) }}</span>
-							<span class="badge subtle">{{ row.is_default ? '默认配置' : '普通配置' }}</span>
+							<span class="badge subtle">{{
+								row.is_default
+									? t('pages.webSearch.defaultConfig')
+									: t('pages.webSearch.normalConfig')
+							}}</span>
 						</div>
 					</div>
 
 					<div class="card-footer">
-						<el-button link type="primary" @click="checkItem(row.id)">连接测试</el-button>
+						<el-button link type="primary" @click="checkItem(row.id)">{{
+							t('pages.webSearch.testConnection')
+						}}</el-button>
 					</div>
 				</div>
 			</div>
@@ -125,12 +129,14 @@
 
 		<el-drawer
 			v-model="drawerVisible"
-			:title="currentId ? '编辑网络搜索' : '新建网络搜索'"
+			:title="
+				currentId ? t('pages.webSearch.editWebSearch') : t('pages.webSearch.createWebSearchTitle')
+			"
 			direction="rtl"
 			size="700px"
 		>
 			<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
-				<el-form-item label="服务商" prop="provider">
+				<el-form-item :label="t('pages.webSearch.provider')" prop="provider">
 					<el-select v-model="form.provider" style="width: 100%">
 						<el-option
 							v-for="item in engineOptions"
@@ -140,10 +146,10 @@
 						/>
 					</el-select>
 				</el-form-item>
-				<el-form-item label="名称" prop="name">
+				<el-form-item :label="t('pages.webSearch.name')" prop="name">
 					<el-input v-model="form.name" />
 				</el-form-item>
-				<el-form-item label="描述" prop="description">
+				<el-form-item :label="t('pages.webSearch.description')" prop="description">
 					<el-input v-model="form.description" type="textarea" :rows="2" />
 				</el-form-item>
 				<!-- 编辑不需要编辑api_key -->
@@ -151,19 +157,21 @@
 					<el-input v-model="form.parameters.api_key" type="password" show-password />
 				</el-form-item>
 				<div class="grid-2">
-					<el-form-item label="代理地址" prop="parameters.proxy_url">
+					<el-form-item :label="t('pages.webSearch.proxyUrl')" prop="parameters.proxy_url">
 						<el-input v-model="form.parameters.proxy_url" />
 					</el-form-item>
-					<el-form-item label="引擎 ID" prop="parameters.engine_id">
+					<el-form-item :label="t('pages.webSearch.engineId')" prop="parameters.engine_id">
 						<el-input v-model="form.parameters.engine_id" />
 					</el-form-item>
 				</div>
-				<el-form-item label="设为默认" prop="is_default">
+				<el-form-item :label="t('pages.webSearch.setDefault')" prop="is_default">
 					<el-switch v-model="form.is_default" />
 				</el-form-item>
 				<el-form-item>
 					<div class="check-box">
-						<el-button :loading="checkLoading" @click="checkWithParameters">测试连接</el-button>
+						<el-button :loading="checkLoading" @click="checkWithParameters">{{
+							t('pages.webSearch.testConnection')
+						}}</el-button>
 						<el-alert
 							v-if="checkMessage"
 							:title="checkMessage"
@@ -176,8 +184,10 @@
 			</el-form>
 			<template #footer>
 				<div class="drawer-footer">
-					<el-button @click="drawerVisible = false">取消</el-button>
-					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
+					<el-button @click="drawerVisible = false">{{ t('common.cancel') }}</el-button>
+					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">{{
+						t('common.save')
+					}}</el-button>
 				</div>
 			</template>
 		</el-drawer>
@@ -189,6 +199,9 @@ import { onMounted, reactive, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search, MoreFilled, RefreshRight } from '@element-plus/icons-vue'
 import { resource } from '@repo/api-service'
+import { useI18n } from '@/composables/useI18n'
+
+const { t } = useI18n()
 
 type EngineResponse = Awaited<ReturnType<typeof resource.postWebSearchEngines>>
 type EngineItem = NonNullable<EngineResponse['result']>[number]
@@ -226,8 +239,10 @@ const form = reactive({
 })
 
 const rules = {
-	provider: [{ required: true, message: '请选择服务商', trigger: 'change' }],
-	name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
+	provider: [
+		{ required: true, message: t('pages.webSearch.pleaseSelectProvider'), trigger: 'change' }
+	],
+	name: [{ required: true, message: t('pages.webSearch.pleaseInputName'), trigger: 'blur' }]
 }
 
 function resetForm() {
@@ -244,7 +259,11 @@ function resetForm() {
 }
 
 function getEngineName(id?: string) {
-	return engineOptions.value.find((item) => item.id === id)?.name || id || '未设置服务商'
+	return (
+		engineOptions.value.find((item) => item.id === id)?.name ||
+		id ||
+		t('pages.webSearch.noProvider')
+	)
 }
 
 async function loadEngines() {
@@ -298,7 +317,7 @@ function openCreate() {
 async function openEditById(id: string) {
 	const res = await resource.postWebSearchInfo({ id })
 	if (!res.isSuccess || !res.result) {
-		ElMessage.error('获取详情失败')
+		ElMessage.error(t('pages.webSearch.fetchDetailFailed'))
 		return
 	}
 	resetForm()
@@ -315,12 +334,10 @@ async function checkItem(id: string) {
 	try {
 		const res = await resource.postWebSearchCheckById({ id })
 		if (res.isSuccess) {
-			ElMessage.success('连通测试成功')
-		} else {
-			ElMessage.error(res?.error || '连通测试失败')
+			ElMessage.success(t('pages.webSearch.connectSuccess'))
 		}
 	} catch {
-		ElMessage.error('连通测试失败')
+		ElMessage.error(t('pages.webSearch.connectFailed'))
 	}
 }
 
@@ -331,7 +348,9 @@ async function checkWithParameters() {
 		if (currentId.value) {
 			const res = await resource.postWebSearchCheckById({ id: currentId.value })
 			checkSuccess.value = !!res.isSuccess
-			checkMessage.value = res.isSuccess ? '连通测试成功' : '连通测试失败'
+			checkMessage.value = res.isSuccess
+				? t('pages.webSearch.connectSuccess')
+				: t('pages.webSearch.connectFailed')
 		} else {
 			const res = await resource.postWebSearchCheckWithParameters({
 				provider: form.provider,
@@ -342,11 +361,12 @@ async function checkWithParameters() {
 				}
 			})
 			checkSuccess.value = !!res.isSuccess
-			checkMessage.value = res.isSuccess ? '连通测试成功' : '连通测试失败'
+			checkMessage.value = res.isSuccess
+				? t('pages.webSearch.connectSuccess')
+				: t('pages.webSearch.connectFailed')
 		}
 	} catch {
 		checkSuccess.value = false
-		checkMessage.value = '连通测试失败'
 	} finally {
 		checkLoading.value = false
 	}
@@ -370,15 +390,15 @@ async function handleSubmit() {
 			}
 			if (currentId.value) {
 				await resource.postWebSearchUpdate({ id: currentId.value, ...(payload as any) })
-				ElMessage.success('更新成功')
+				ElMessage.success(t('pages.webSearch.updateSuccess'))
 			} else {
 				await resource.postWebSearchCreate(payload as any)
-				ElMessage.success('创建成功')
+				ElMessage.success(t('pages.webSearch.createSuccess'))
 			}
 			drawerVisible.value = false
 			loadList(pagination.pageIndex)
 		} catch {
-			ElMessage.error('保存失败')
+			ElMessage.error(t('pages.webSearch.saveFailed'))
 		} finally {
 			submitLoading.value = false
 		}
@@ -387,9 +407,11 @@ async function handleSubmit() {
 
 async function removeItem(id: string) {
 	try {
-		await ElMessageBox.confirm('确定删除该网络搜索配置吗?', '提示', { type: 'warning' })
+		await ElMessageBox.confirm(t('pages.webSearch.confirmDelete'), t('pages.webSearch.tip'), {
+			type: 'warning'
+		})
 		await resource.postWebSearchOpenApiDelete({ id })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('pages.webSearch.deleteSuccess'))
 		loadList(pagination.pageIndex)
 	} catch {
 		// ignore
@@ -398,34 +420,41 @@ async function removeItem(id: string) {
 
 async function updateCredentials(id: string) {
 	try {
-		const { value } = await ElMessageBox.prompt('请输入新的 API Key', '更新网络搜索凭证', {
-			confirmButtonText: '确定',
-			cancelButtonText: '取消',
-			inputType: 'password',
-			inputPlaceholder: '请输入 API Key',
-			inputValidator: (value) => !!value?.trim() || '请输入 API Key'
-		})
+		const { value } = await ElMessageBox.prompt(
+			t('pages.webSearch.pleaseInputNewApiKey'),
+			t('pages.webSearch.updateCredentialTitle'),
+			{
+				confirmButtonText: t('common.confirm'),
+				cancelButtonText: t('common.cancel'),
+				inputType: 'password',
+				inputValidator: (value) => !!value?.trim() || t('pages.webSearch.pleaseInputApiKey')
+			}
+		)
 		await resource.postWebSearchCredentials({ id, api_key: value.trim() })
-		ElMessage.success('凭证更新成功')
+		ElMessage.success(t('pages.webSearch.credentialUpdateSuccess'))
 		loadList(pagination.pageIndex)
 	} catch (error) {
 		if (error !== 'cancel' && error !== 'close') {
-			ElMessage.error('凭证更新失败')
+			ElMessage.error(t('pages.webSearch.credentialUpdateFailed'))
 		}
 	}
 }
 
 async function deleteCredentials(id: string) {
 	try {
-		await ElMessageBox.confirm('确定要删除该网络搜索凭证吗?删除后该配置可能无法调用。', '提示', {
-			type: 'warning'
-		})
+		await ElMessageBox.confirm(
+			t('pages.webSearch.confirmDeleteCredential'),
+			t('pages.webSearch.tip'),
+			{
+				type: 'warning'
+			}
+		)
 		await resource.postWebSearchDeleteCredentials({ id })
-		ElMessage.success('凭证删除成功')
+		ElMessage.success(t('pages.webSearch.credentialDeleteSuccess'))
 		loadList(pagination.pageIndex)
 	} catch (error) {
 		if (error !== 'cancel' && error !== 'close') {
-			ElMessage.error('凭证删除失败')
+			ElMessage.error(t('pages.webSearch.credentialDeleteFailed'))
 		}
 	}
 }