feat: 全面支持中英文多语言切换
将翻译文件拆分为独立的 en.ts/zh.ts,为 t() 函数添加插值支持, 国际化 Dashboard 全部页面和组件(登录、注册、项目管理、设置、 MCP 集成等),修复 ThemeToggle 仅中文标签的 bug, 在 Dashboard header 中添加 LanguageToggle 组件。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
import { useI18n } from '../../lib/i18n';
|
||||
import { useLayoutContext } from '../Layout';
|
||||
|
||||
type Project = { id: string; name: string };
|
||||
@@ -8,6 +9,7 @@ type Project = { id: string; name: string };
|
||||
export default function McpIntegration({ project }: { project: Project }) {
|
||||
const [copied, setCopied] = useState<string | null>(null);
|
||||
const { onOpenSettings } = useLayoutContext();
|
||||
const { t } = useI18n();
|
||||
const mcpHost = window.location.hostname;
|
||||
const mcpUrl = `http://${mcpHost}:3001/mcp/${project.id}`;
|
||||
|
||||
@@ -37,15 +39,15 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
<div className="max-w-2xl space-y-8">
|
||||
{/* MCP URL */}
|
||||
<section>
|
||||
<p className="section-title">MCP Service URL</p>
|
||||
<p className="section-desc mb-3">Connect your LLM client to this endpoint.</p>
|
||||
<p className="section-title">{t('dashboard.mcp.urlTitle')}</p>
|
||||
<p className="section-desc mb-3">{t('dashboard.mcp.urlDesc')}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 px-3.5 py-2.5 rounded-lg bg-bg-tertiary border border-border-muted text-[13px] font-mono text-text-primary truncate">{mcpUrl}</code>
|
||||
<button onClick={() => copyText(mcpUrl, 'url')} className="btn-outline shrink-0">
|
||||
{copied === 'url' ? (
|
||||
<><svg className="w-3.5 h-3.5 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path d="M5 13l4 4L19 7" /></svg> Copied</>
|
||||
<><svg className="w-3.5 h-3.5 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path d="M5 13l4 4L19 7" /></svg> {t('common.copied')}</>
|
||||
) : (
|
||||
<><svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg> Copy</>
|
||||
<><svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg> {t('common.copy')}</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -53,12 +55,12 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
|
||||
{/* Config snippet */}
|
||||
<section>
|
||||
<p className="section-title">Configuration for Claude Code / Cursor</p>
|
||||
<p className="section-desc mb-3">Add this to your MCP client configuration.</p>
|
||||
<p className="section-title">{t('dashboard.mcp.configTitle')}</p>
|
||||
<p className="section-desc mb-3">{t('dashboard.mcp.configDesc')}</p>
|
||||
<div className="relative">
|
||||
<pre className="code-block text-xs">{configSnippet}</pre>
|
||||
<button onClick={() => copyText(configSnippet, 'config')} className="copy-btn absolute top-2.5 right-2.5">
|
||||
{copied === 'config' ? 'Copied!' : 'Copy'}
|
||||
{copied === 'config' ? `${t('common.copied')}!` : t('common.copy')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -69,17 +71,17 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
<div className="flex items-center gap-2 px-3.5 py-2.5 rounded-lg bg-bg-tertiary border border-border-muted">
|
||||
<svg className="w-4 h-4 text-success shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M5 13l4 4L19 7" /></svg>
|
||||
<p className="text-[13px] text-text-secondary">
|
||||
API key generated. Copy it from{' '}
|
||||
<button onClick={onOpenSettings} className="text-accent hover:underline font-medium">Settings</button>
|
||||
{' '}and replace <code className="text-xs font-mono bg-bg-inset px-1 py-0.5 rounded"><your-api-key></code> above.
|
||||
{t('dashboard.mcp.keyGenerated')}{' '}
|
||||
<button onClick={onOpenSettings} className="text-accent hover:underline font-medium">{t('common.settings')}</button>
|
||||
{' '}{t('dashboard.mcp.keyReplace')} <code className="text-xs font-mono bg-bg-inset px-1 py-0.5 rounded"><your-api-key></code> {t('dashboard.mcp.keyAbove')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-3 p-3.5 rounded-lg bg-warning-muted border border-warning/20">
|
||||
<svg className="w-4 h-4 text-warning shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" /></svg>
|
||||
<p className="text-[13px] text-text-secondary flex-1">You need to generate an API key before using MCP.</p>
|
||||
<p className="text-[13px] text-text-secondary flex-1">{t('dashboard.mcp.noKeyWarning')}</p>
|
||||
<button onClick={onOpenSettings} className="btn-primary shrink-0 text-[13px] py-1.5 px-3">
|
||||
Open Settings
|
||||
{t('dashboard.mcp.openSettings')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -89,21 +91,21 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
|
||||
{/* Available tools */}
|
||||
<section>
|
||||
<p className="section-title">Available MCP Tools</p>
|
||||
<p className="section-desc mb-3">5 tools for progressive drill-down, designed for minimal token usage.</p>
|
||||
<p className="section-title">{t('dashboard.mcp.toolsTitle')}</p>
|
||||
<p className="section-desc mb-3">{t('dashboard.mcp.toolsDesc')}</p>
|
||||
<div className="space-y-1.5 stagger-children">
|
||||
{[
|
||||
{ name: 'get_project_overview', desc: 'Get project name, version, base URL, and module summary. Call this first.', num: '1' },
|
||||
{ name: 'list_modules', desc: 'List all modules with descriptions and endpoint counts.', num: '2' },
|
||||
{ name: 'list_endpoints', desc: 'List endpoints in a module. Provide moduleId.', num: '3' },
|
||||
{ name: 'get_endpoint_detail', desc: 'Get full endpoint details: parameters, request body, responses.', num: '4' },
|
||||
{ name: 'search_endpoints', desc: 'Search by keyword across all endpoints. Optional moduleId filter.', num: '5' },
|
||||
].map((t) => (
|
||||
<div key={t.name} className="card px-4 py-3 flex items-start gap-3">
|
||||
<span className="w-5 h-5 rounded-full bg-accent-muted text-accent text-[10px] font-bold flex items-center justify-center shrink-0 mt-0.5">{t.num}</span>
|
||||
{ name: 'get_project_overview', desc: t('dashboard.mcp.tool1Desc'), num: '1' },
|
||||
{ name: 'list_modules', desc: t('dashboard.mcp.tool2Desc'), num: '2' },
|
||||
{ name: 'list_endpoints', desc: t('dashboard.mcp.tool3Desc'), num: '3' },
|
||||
{ name: 'get_endpoint_detail', desc: t('dashboard.mcp.tool4Desc'), num: '4' },
|
||||
{ name: 'search_endpoints', desc: t('dashboard.mcp.tool5Desc'), num: '5' },
|
||||
].map((tool) => (
|
||||
<div key={tool.name} className="card px-4 py-3 flex items-start gap-3">
|
||||
<span className="w-5 h-5 rounded-full bg-accent-muted text-accent text-[10px] font-bold flex items-center justify-center shrink-0 mt-0.5">{tool.num}</span>
|
||||
<div className="min-w-0">
|
||||
<code className="text-[13px] font-mono font-medium text-accent">{t.name}</code>
|
||||
<p className="text-xs text-text-muted mt-0.5 leading-relaxed">{t.desc}</p>
|
||||
<code className="text-[13px] font-mono font-medium text-accent">{tool.name}</code>
|
||||
<p className="text-xs text-text-muted mt-0.5 leading-relaxed">{tool.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user