Skip to content

Commit c7750f4

Browse files
committed
feat(i18n): 添加多语言支持和翻译文本
1 parent 255a0d6 commit c7750f4

10 files changed

Lines changed: 128 additions & 58 deletions

File tree

src/components/chat/ChatView.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ function ChatD2BlockNode({
490490
isDark?: boolean;
491491
}) {
492492
const { token } = theme.useToken();
493+
const { t } = useTranslation();
493494
const containerRef = useRef<HTMLDivElement | null>(null);
494495
const [showSource, setShowSource] = useState(false);
495496
const [copied, setCopied] = useState(false);
@@ -696,10 +697,10 @@ function ChatD2BlockNode({
696697
<div className="flex items-center gap-x-2">
697698
<div className="flex items-center gap-x-1 rounded-md p-0.5" style={toggleStyle}>
698699
<button type="button" className={`mode-btn px-2 py-1 text-xs rounded ${!showSource ? 'is-active' : ''}`} onClick={() => setShowSource(false)}>
699-
预览
700+
{t('common.preview', '预览')}
700701
</button>
701702
<button type="button" className={`mode-btn px-2 py-1 text-xs rounded ${showSource ? 'is-active' : ''}`} onClick={() => setShowSource(true)}>
702-
源码
703+
{t('common.source', '源码')}
703704
</button>
704705
</div>
705706
<button type="button" className="d2-action-btn p-2 text-xs rounded-md transition-colors hover:bg-[var(--vscode-editor-selectionBackground)]" aria-label={copied ? 'Copied' : 'Copy'} onClick={() => void handleCopy()}>
@@ -726,7 +727,7 @@ function ChatD2BlockNode({
726727
) : (
727728
<div className="flex items-center justify-center px-4 py-10" style={{ color: token.colorTextSecondary, gap: 8 }}>
728729
<SyncOutlined spin />
729-
<span className="text-sm">{canRenderPreview ? '正在渲染图表…' : '图表即将渲染…'}</span>
730+
<span className="text-sm">{canRenderPreview ? t('chat.renderingChart', '正在渲染图表…') : t('chat.chartAboutToRender', '图表即将渲染…')}</span>
730731
</div>
731732
)}
732733
{error ? <p className="d2-error px-4 pb-3 text-xs">{error}</p> : null}
@@ -762,6 +763,7 @@ const AssistantMarkdown = React.memo(function AssistantMarkdown({
762763
codeFontFamily?: string;
763764
}) {
764765
const { token } = theme.useToken();
766+
const { t } = useTranslation();
765767
const containerRef = useRef<HTMLDivElement | null>(null);
766768
const codeBlockProps = useMemo(
767769
() => getChatCodeBlockProps(codeBlockDarkTheme),
@@ -849,7 +851,7 @@ const AssistantMarkdown = React.memo(function AssistantMarkdown({
849851
style={{ color: token.colorTextSecondary, gap: 8 }}
850852
>
851853
<SyncOutlined spin />
852-
<span className="text-sm">正在加载渲染内容…</span>
854+
<span className="text-sm">{t('chat.loadingRenderContent', '正在加载渲染内容…')}</span>
853855
</div>
854856
</div>
855857
);

src/components/chat/ConversationSettingsModal.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
22
import { Modal, Input, Slider, Button, Tooltip, Card, theme } from 'antd';
33
import { ModelIcon } from '@lobehub/icons';
44
import { Info, Undo2, Bot } from 'lucide-react';
5+
import { useTranslation } from 'react-i18next';
56
import { useConversationStore, useSettingsStore } from '@/stores';
67
import { CONV_ICON_KEY, type ConvIconType, type ConvIcon } from '@/lib/convIcon';
78
import { IconEditor } from '@/components/shared/IconEditor';
@@ -17,6 +18,7 @@ const CONTEXT_LIMIT_KEY = (id: string) => `aqbot_context_limit_${id}`;
1718

1819
export function ConversationSettingsModal({ open, onClose }: ConversationSettingsModalProps) {
1920
const { token } = theme.useToken();
21+
const { t } = useTranslation();
2022

2123
const conversations = useConversationStore((s) => s.conversations);
2224
const activeConversationId = useConversationStore((s) => s.activeConversationId);
@@ -108,7 +110,7 @@ export function ConversationSettingsModal({ open, onClose }: ConversationSetting
108110
{
109111
key: 'use_model',
110112
icon: <Bot size={14} />,
111-
label: '使用模型图标',
113+
label: t('settings.useModelIcon', '使用模型图标'),
112114
onClick: () => { setIconType('model'); setIconValue(''); },
113115
},
114116
];
@@ -130,17 +132,17 @@ export function ConversationSettingsModal({ open, onClose }: ConversationSetting
130132

131133
return (
132134
<Modal
133-
title="对话设置"
135+
title={t('settings.conversationSettings', '对话设置')}
134136
open={open}
135137
mask={{ enabled: true, blur: true }}
136138
onCancel={onClose}
137139
width={520}
138140
destroyOnHidden
139141
footer={
140142
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
141-
<Button onClick={onClose}>取消</Button>
143+
<Button onClick={onClose}>{t('common.cancel', '取消')}</Button>
142144
<Button type="primary" onClick={handleSave} loading={saving}>
143-
保存
145+
{t('common.save', '保存')}
144146
</Button>
145147
</div>
146148
}
@@ -169,24 +171,24 @@ export function ConversationSettingsModal({ open, onClose }: ConversationSetting
169171

170172
{/* Name */}
171173
<div style={{ marginBottom: 16 }}>
172-
<div style={labelStyle}>名称</div>
174+
<div style={labelStyle}>{t('common.name', '名称')}</div>
173175
<Input value={title} onChange={(e) => setTitle(e.target.value)} />
174176
</div>
175177

176178
{/* System Prompt */}
177179
<div style={{ marginBottom: 16 }}>
178-
<div style={labelStyle}>系统提示(角色设定)</div>
180+
<div style={labelStyle}>{t('settings.systemPromptLabel', '系统提示(角色设定)')}</div>
179181
<Input.TextArea
180182
value={systemPrompt}
181183
onChange={(e) => setSystemPrompt(e.target.value)}
182184
rows={3}
183-
placeholder="输入系统提示词..."
185+
placeholder={t('settings.systemPromptPlaceholder', '输入系统提示词...')}
184186
/>
185187
</div>
186188

187189
{/* Model Settings Card */}
188190
<Card
189-
title="模型设置"
191+
title={t('settings.modelSettings', '模型设置')}
190192
size="small"
191193
extra={
192194
<Button
@@ -195,20 +197,20 @@ export function ConversationSettingsModal({ open, onClose }: ConversationSetting
195197
icon={<Undo2 size={14} />}
196198
onClick={handleReset}
197199
>
198-
重置
200+
{t('common.reset', '重置')}
199201
</Button>
200202
}
201203
>
202204

203205
{/* Context Message Limit */}
204206
<div style={{ marginBottom: 20 }}>
205207
<div style={labelStyle}>
206-
上下文的消息数量上限
207-
<Tooltip title="限制发送给模型的历史消息数量。设为 50 表示不限制。">
208+
{t('settings.contextMessageLimit', '上下文的消息数量上限')}
209+
<Tooltip title={t('settings.contextMessageLimitTooltip', '限制发送给模型的历史消息数量。设为 50 表示不限制。')}>
208210
<Info size={14} style={{ color: token.colorTextSecondary, cursor: 'help' }} />
209211
</Tooltip>
210212
<span style={{ marginLeft: 'auto', color: token.colorTextSecondary, fontSize: 12 }}>
211-
{contextLimit >= 50 ? '不限制' : contextLimit}
213+
{contextLimit >= 50 ? t('common.unlimited', '不限制') : contextLimit}
212214
</span>
213215
</div>
214216
<div style={sliderRowStyle}>

src/components/gateway/GatewayTemplates.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,31 @@ const CONNECT_ITEMS: QuickConnectItem[] = [
2424
key: 'claude_code',
2525
name: 'Claude Code',
2626
avatar: (size) => <ClaudeCode.Avatar size={size} />,
27-
description: 'Anthropic 官方 CLI 编程助手,在终端中直接与 Claude 协作编码',
27+
description: 'gateway.templateDescClaude',
2828
},
2929
{
3030
key: 'codex',
3131
name: 'Codex',
3232
avatar: (size) => <Codex.Avatar size={size} />,
33-
description: 'OpenAI 轻量级编程代理,支持在沙盒中自主完成编码任务',
33+
description: 'gateway.templateDescCodex',
3434
},
3535
{
3636
key: 'opencode',
3737
name: 'OpenCode',
3838
avatar: (size) => <OpenCode.Avatar size={size} />,
39-
description: '开源终端 AI 编程助手,支持多模型后端的灵活代码工具',
39+
description: 'gateway.templateDescOpencode',
4040
},
4141
{
4242
key: 'gemini',
4343
name: 'Gemini CLI',
4444
avatar: (size) => <Gemini.Avatar size={size} />,
45-
description: 'Google Gemini CLI 编程代理,直接在终端中使用 Gemini 模型',
45+
description: 'gateway.templateDescGemini',
4646
},
4747
{
4848
key: 'cursor',
4949
name: 'Cursor',
5050
avatar: (size) => <Cursor.Avatar size={size} />,
51-
description: '基于 VS Code 的 AI 原生编辑器,深度集成代码补全与对话',
51+
description: 'gateway.templateDescCursor',
5252
},
5353
];
5454

@@ -190,7 +190,7 @@ function ToolCard({
190190
style={{ fontSize: 13, margin: 0, marginTop: 4 }}
191191
ellipsis={{ rows: 1 }}
192192
>
193-
{item.description}
193+
{t(item.description)}
194194
</Paragraph>
195195
{toolInfo?.configPath && (
196196
<Text type="secondary" style={{ fontSize: 11 }}>

src/components/settings/DefaultModelSettings.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const DEFAULT_COMPRESSION_PROMPT = '你是一个对话摘要助手。请将以
1515

1616
// ── Context count slider ───────────────────────────────────
1717

18-
const CONTEXT_MARKS: Record<number, string> = { 0: '0', 5: '5', 10: '10', 15: '15', 50: '不限' };
19-
2018
function ContextCountParam({
2119
label,
2220
tooltip,
@@ -29,7 +27,9 @@ function ContextCountParam({
2927
onChange: (v: number | null) => void;
3028
}) {
3129
const { token } = theme.useToken();
30+
const { t } = useTranslation();
3231
const effectiveValue = value ?? 5;
32+
const contextMarks: Record<number, string> = { 0: '0', 5: '5', 10: '10', 15: '15', 50: t('common.unlimited') };
3333

3434
return (
3535
<>
@@ -47,7 +47,7 @@ function ContextCountParam({
4747
<Slider
4848
style={{ flex: 1 }}
4949
min={0} max={50} step={1}
50-
marks={CONTEXT_MARKS}
50+
marks={contextMarks}
5151
value={effectiveValue}
5252
onChange={(v) => onChange(v)}
5353
/>

src/components/settings/IconPickerModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ export default function IconPickerModal({ open, onClose, onSelect, defaultTab =
6464
onChange={setActiveTab}
6565
size="small"
6666
items={[
67-
{ key: 'model', label: `模型 (${toc.filter((i) => i.group === 'model').length})` },
68-
{ key: 'provider', label: `厂商 (${toc.filter((i) => i.group === 'provider').length})` },
67+
{ key: 'model', label: `${t('settings.iconGroupModel')} (${toc.filter((i) => i.group === 'model').length})` },
68+
{ key: 'provider', label: `${t('settings.iconGroupProvider')} (${toc.filter((i) => i.group === 'provider').length})` },
6969
]}
7070
/>
7171

src/components/settings/KnowledgeSettings.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ import {
4141
} from '@dnd-kit/sortable';
4242
import { CSS } from '@dnd-kit/utilities';
4343

44-
const INDEX_STATUS_CONFIG: Record<string, { color: string; label: string }> = {
45-
pending: { color: 'default', label: '待索引' },
46-
indexing: { color: 'processing', label: '索引中' },
47-
ready: { color: 'success', label: '已索引' },
48-
failed: { color: 'error', label: '索引失败' },
44+
const INDEX_STATUS_CONFIG: Record<string, { color: string; labelKey: string }> = {
45+
pending: { color: 'default', labelKey: 'settings.indexStatus.pending' },
46+
indexing: { color: 'processing', labelKey: 'settings.indexStatus.indexing' },
47+
ready: { color: 'success', labelKey: 'settings.indexStatus.indexed' },
48+
failed: { color: 'error', labelKey: 'settings.indexStatus.failed' },
4949
};
5050

5151
// ── Sortable Knowledge Base Item ─────────────────────────
@@ -452,7 +452,7 @@ function KnowledgeBaseDetail({
452452
const tag = (
453453
<Tag color={cfg.color} style={{ fontSize: 11, cursor: status === 'failed' && record.indexError ? 'pointer' : undefined }}>
454454
{status === 'indexing' && <Spin size="small" style={{ marginRight: 4 }} />}
455-
{t(`settings.knowledge.indexStatus${status.charAt(0).toUpperCase() + status.slice(1)}`, cfg.label)}
455+
{t(cfg.labelKey)}
456456
</Tag>
457457
);
458458
if (status === 'failed' && record.indexError) {

src/components/settings/MemorySettings.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ const SOURCE_TAG_COLOR: Record<MemorySource, string> = {
5555
auto_extract: 'green',
5656
};
5757

58-
const INDEX_STATUS_CONFIG: Record<string, { color: string; label: string }> = {
59-
pending: { color: 'default', label: '待索引' },
60-
indexing: { color: 'processing', label: '索引中' },
61-
ready: { color: 'success', label: '已索引' },
62-
failed: { color: 'error', label: '索引失败' },
63-
skipped: { color: 'warning', label: '未配置' },
58+
const INDEX_STATUS_CONFIG: Record<string, { color: string; labelKey: string }> = {
59+
pending: { color: 'default', labelKey: 'settings.indexStatus.pending' },
60+
indexing: { color: 'processing', labelKey: 'settings.indexStatus.indexing' },
61+
ready: { color: 'success', labelKey: 'settings.indexStatus.indexed' },
62+
failed: { color: 'error', labelKey: 'settings.indexStatus.failed' },
63+
skipped: { color: 'warning', labelKey: 'settings.indexStatus.notConfigured' },
6464
};
6565

6666
// ── Sortable Namespace Item ──────────────────────────────
@@ -356,7 +356,7 @@ function MemoryItemsPanel({
356356
const tag = (
357357
<Tag color={cfg.color} style={{ fontSize: 11 }}>
358358
{status === 'indexing' && <Spin size="small" style={{ marginRight: 4 }} />}
359-
{t(`settings.memory.indexStatus.${status}`, cfg.label)}
359+
{t(cfg.labelKey)}
360360
</Tag>
361361
);
362362
if (status === 'failed' && record.indexError) {

src/components/settings/ProviderDetail.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ import { ModelParamSliders } from '@/components/common/ModelParamSliders';
3939
const { Text, Title } = Typography;
4040

4141
const CAPABILITY_LABEL_KEYS: Record<ModelCapability, string> = {
42-
TextChat: '文本对话',
43-
Vision: '视觉',
44-
FunctionCalling: '函数调用',
45-
Reasoning: '推理',
46-
RealtimeVoice: '实时语音',
42+
TextChat: 'settings.capability.TextChat',
43+
Vision: 'settings.capability.Vision',
44+
FunctionCalling: 'settings.capability.FunctionCalling',
45+
Reasoning: 'settings.capability.Reasoning',
46+
RealtimeVoice: 'settings.capability.RealtimeVoice',
4747
};
4848

4949
const CAPABILITY_COLORS: Record<ModelCapability, string> = {
@@ -62,10 +62,16 @@ const CAPABILITY_ICONS: Record<ModelCapability, React.ReactNode> = {
6262
RealtimeVoice: <Mic size={14} />,
6363
};
6464

65-
const MODEL_TYPE_CONFIG: Record<ModelType, { label: string; color: string; icon: React.ReactNode }> = {
66-
Chat: { label: '对话', color: 'blue', icon: <MessageSquare size={12} /> },
67-
Voice: { label: '语音', color: 'red', icon: <Mic size={12} /> },
68-
Embedding: { label: '向量', color: 'cyan', icon: <Database size={12} /> },
65+
const MODEL_TYPE_LABEL_KEYS: Record<ModelType, string> = {
66+
Chat: 'settings.modelType.Chat',
67+
Voice: 'settings.modelType.Voice',
68+
Embedding: 'settings.modelType.Embedding',
69+
};
70+
71+
const MODEL_TYPE_CONFIG: Record<ModelType, { color: string; icon: React.ReactNode }> = {
72+
Chat: { color: 'blue', icon: <MessageSquare size={12} /> },
73+
Voice: { color: 'red', icon: <Mic size={12} /> },
74+
Embedding: { color: 'cyan', icon: <Database size={12} /> },
6975
};
7076

7177
const DEFAULT_PATHS: Record<ProviderType, string> = {
@@ -1034,7 +1040,7 @@ export function ProviderDetail({ providerId }: ProviderDetailProps) {
10341040
style={{ fontSize: 10, lineHeight: '16px', padding: '0 4px', margin: 0 }}
10351041
>
10361042
{MODEL_TYPE_CONFIG[model.model_type || 'Chat'].icon}
1037-
<span style={{ marginLeft: 2 }}>{t(`settings.modelType.${model.model_type || 'Chat'}`, MODEL_TYPE_CONFIG[model.model_type || 'Chat'].label)}</span>
1043+
<span style={{ marginLeft: 2 }}>{t(`settings.modelType.${model.model_type || 'Chat'}`, MODEL_TYPE_LABEL_KEYS[model.model_type || 'Chat'])}</span>
10381044
</Tag>
10391045
{getVisibleModelCapabilities(model).map((cap) => (
10401046
<Tooltip key={cap} title={t(`settings.capability.${cap}`, CAPABILITY_LABEL_KEYS[cap])}>
@@ -1264,7 +1270,7 @@ export function ProviderDetail({ providerId }: ProviderDetailProps) {
12641270
onChange={(value) => setAddModelType(value as ModelType)}
12651271
options={(Object.keys(MODEL_TYPE_CONFIG) as ModelType[]).map((type_) => ({
12661272
value: type_,
1267-
label: t(`settings.modelType.${type_}`, MODEL_TYPE_CONFIG[type_].label),
1273+
label: t(`settings.modelType.${type_}`, MODEL_TYPE_LABEL_KEYS[type_]),
12681274
}))}
12691275
/>
12701276
</Form.Item>
@@ -1349,7 +1355,7 @@ export function ProviderDetail({ providerId }: ProviderDetailProps) {
13491355
}}
13501356
>
13511357
{MODEL_TYPE_CONFIG[type_].icon}
1352-
<span style={{ marginLeft: 4 }}>{t(`settings.modelType.${type_}`, MODEL_TYPE_CONFIG[type_].label)}</span>
1358+
<span style={{ marginLeft: 4 }}>{t(`settings.modelType.${type_}`, MODEL_TYPE_LABEL_KEYS[type_])}</span>
13531359
</Tag>
13541360
))}
13551361
</div>

0 commit comments

Comments
 (0)