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:
@@ -3,6 +3,8 @@
|
||||
* Replaces raw JSON.stringify output with readable tables and schema trees.
|
||||
*/
|
||||
|
||||
import { useI18n } from '../lib/i18n';
|
||||
|
||||
/* ===== Helpers ===== */
|
||||
|
||||
type SchemaObj = {
|
||||
@@ -79,21 +81,22 @@ function InBadge({ location }: { location: string }) {
|
||||
/* ===== Parameters Table ===== */
|
||||
|
||||
export function ParametersView({ parameters }: { parameters: unknown }) {
|
||||
const { t } = useI18n();
|
||||
if (!Array.isArray(parameters) || parameters.length === 0) return null;
|
||||
const params = parameters as Parameter[];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="section-label mb-2">Parameters</p>
|
||||
<p className="section-label mb-2">{t('dashboard.schema.parameters')}</p>
|
||||
<div className="border border-border-default rounded-lg overflow-hidden">
|
||||
<table className="w-full text-[13px]">
|
||||
<thead>
|
||||
<tr className="bg-bg-tertiary/50 text-text-muted text-[11px] uppercase tracking-wider">
|
||||
<th className="text-left px-3 py-2 font-medium">Name</th>
|
||||
<th className="text-left px-3 py-2 font-medium">In</th>
|
||||
<th className="text-left px-3 py-2 font-medium">Type</th>
|
||||
<th className="text-left px-3 py-2 font-medium">Required</th>
|
||||
<th className="text-left px-3 py-2 font-medium">Description</th>
|
||||
<th className="text-left px-3 py-2 font-medium">{t('dashboard.schema.name')}</th>
|
||||
<th className="text-left px-3 py-2 font-medium">{t('dashboard.schema.in')}</th>
|
||||
<th className="text-left px-3 py-2 font-medium">{t('dashboard.schema.type')}</th>
|
||||
<th className="text-left px-3 py-2 font-medium">{t('dashboard.schema.required')}</th>
|
||||
<th className="text-left px-3 py-2 font-medium">{t('dashboard.schema.descriptionCol')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-muted">
|
||||
@@ -119,9 +122,9 @@ export function ParametersView({ parameters }: { parameters: unknown }) {
|
||||
</td>
|
||||
<td className="px-3 py-2.5">
|
||||
{p.required ? (
|
||||
<span className="text-[11px] font-medium text-danger">required</span>
|
||||
<span className="text-[11px] font-medium text-danger">{t('dashboard.schema.required')}</span>
|
||||
) : (
|
||||
<span className="text-[11px] text-text-muted">optional</span>
|
||||
<span className="text-[11px] text-text-muted">{t('dashboard.schema.optional')}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-2.5 text-text-secondary max-w-xs">
|
||||
@@ -129,7 +132,7 @@ export function ParametersView({ parameters }: { parameters: unknown }) {
|
||||
{p.description && <span>{p.description}</span>}
|
||||
{enumVals && enumVals.length > 0 && (
|
||||
<div className="mt-1 flex items-center gap-1 flex-wrap">
|
||||
<span className="text-[11px] text-text-muted">enum:</span>
|
||||
<span className="text-[11px] text-text-muted">{t('dashboard.schema.enum')}</span>
|
||||
{enumVals.map((v, j) => (
|
||||
<code key={j} className="text-[11px] font-mono bg-bg-tertiary px-1 py-0.5 rounded text-text-secondary">
|
||||
{String(v)}
|
||||
@@ -139,7 +142,7 @@ export function ParametersView({ parameters }: { parameters: unknown }) {
|
||||
)}
|
||||
{p.schema?.default !== undefined && (
|
||||
<div className="mt-0.5 text-[11px] text-text-muted">
|
||||
default: <code className="font-mono">{JSON.stringify(p.schema.default)}</code>
|
||||
{t('dashboard.schema.default')} <code className="font-mono">{JSON.stringify(p.schema.default)}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -157,6 +160,7 @@ export function ParametersView({ parameters }: { parameters: unknown }) {
|
||||
/* ===== Schema Properties Tree ===== */
|
||||
|
||||
function SchemaProperties({ schema, depth = 0 }: { schema: SchemaObj; depth?: number }) {
|
||||
const { t } = useI18n();
|
||||
const properties = schema.properties;
|
||||
const requiredSet = new Set(schema.required || []);
|
||||
|
||||
@@ -189,10 +193,10 @@ function SchemaProperties({ schema, depth = 0 }: { schema: SchemaObj; depth?: nu
|
||||
<span className="text-[11px] text-text-muted">({prop.format})</span>
|
||||
)}
|
||||
{requiredSet.has(name) && (
|
||||
<span className="text-[11px] font-medium text-danger">required</span>
|
||||
<span className="text-[11px] font-medium text-danger">{t('dashboard.schema.required')}</span>
|
||||
)}
|
||||
{prop.nullable && (
|
||||
<span className="text-[11px] text-text-muted">nullable</span>
|
||||
<span className="text-[11px] text-text-muted">{t('dashboard.schema.nullable')}</span>
|
||||
)}
|
||||
{prop.description && (
|
||||
<span className="text-text-secondary text-[12px] leading-snug">{prop.description}</span>
|
||||
@@ -200,7 +204,7 @@ function SchemaProperties({ schema, depth = 0 }: { schema: SchemaObj; depth?: nu
|
||||
</div>
|
||||
{prop.enum && prop.enum.length > 0 && (
|
||||
<div className="ml-0 mt-0.5 flex items-center gap-1 flex-wrap">
|
||||
<span className="text-[11px] text-text-muted">enum:</span>
|
||||
<span className="text-[11px] text-text-muted">{t('dashboard.schema.enum')}</span>
|
||||
{prop.enum.map((v, j) => (
|
||||
<code key={j} className="text-[11px] font-mono bg-bg-tertiary px-1 py-0.5 rounded text-text-secondary">
|
||||
{String(v)}
|
||||
@@ -210,7 +214,7 @@ function SchemaProperties({ schema, depth = 0 }: { schema: SchemaObj; depth?: nu
|
||||
)}
|
||||
{prop.default !== undefined && (
|
||||
<div className="text-[11px] text-text-muted mt-0.5">
|
||||
default: <code className="font-mono">{JSON.stringify(prop.default)}</code>
|
||||
{t('dashboard.schema.default')} <code className="font-mono">{JSON.stringify(prop.default)}</code>
|
||||
</div>
|
||||
)}
|
||||
{hasChildren && <SchemaProperties schema={prop} depth={depth + 1} />}
|
||||
@@ -225,6 +229,7 @@ function SchemaProperties({ schema, depth = 0 }: { schema: SchemaObj; depth?: nu
|
||||
/* ===== Request Body ===== */
|
||||
|
||||
export function RequestBodyView({ requestBody }: { requestBody: unknown }) {
|
||||
const { t } = useI18n();
|
||||
if (!requestBody || typeof requestBody !== 'object') return null;
|
||||
const body = requestBody as {
|
||||
required?: boolean;
|
||||
@@ -238,8 +243,8 @@ export function RequestBodyView({ requestBody }: { requestBody: unknown }) {
|
||||
return (
|
||||
<div>
|
||||
<p className="section-label mb-2">
|
||||
Request Body
|
||||
{body.required && <span className="text-danger ml-2 normal-case tracking-normal text-[11px]">required</span>}
|
||||
{t('dashboard.schema.requestBody')}
|
||||
{body.required && <span className="text-danger ml-2 normal-case tracking-normal text-[11px]">{t('dashboard.schema.required')}</span>}
|
||||
</p>
|
||||
<div className="border border-border-default rounded-lg p-3">
|
||||
<SchemaProperties schema={body.schema} />
|
||||
@@ -255,8 +260,8 @@ export function RequestBodyView({ requestBody }: { requestBody: unknown }) {
|
||||
return (
|
||||
<div>
|
||||
<p className="section-label mb-2">
|
||||
Request Body
|
||||
{body.required && <span className="text-danger ml-2 normal-case tracking-normal text-[11px]">required</span>}
|
||||
{t('dashboard.schema.requestBody')}
|
||||
{body.required && <span className="text-danger ml-2 normal-case tracking-normal text-[11px]">{t('dashboard.schema.required')}</span>}
|
||||
</p>
|
||||
{body.description && (
|
||||
<p className="text-[13px] text-text-secondary mb-2">{body.description}</p>
|
||||
@@ -277,7 +282,7 @@ export function RequestBodyView({ requestBody }: { requestBody: unknown }) {
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<span className="text-[13px] text-text-muted">No schema</span>
|
||||
<span className="text-[13px] text-text-muted">{t('dashboard.schema.noSchema')}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -303,13 +308,14 @@ function StatusBadge({ code }: { code: string }) {
|
||||
}
|
||||
|
||||
export function ResponsesView({ responses }: { responses: unknown }) {
|
||||
const { t } = useI18n();
|
||||
if (!responses || typeof responses !== 'object') return null;
|
||||
const entries = Object.entries(responses as Record<string, unknown>);
|
||||
if (entries.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="section-label mb-2">Responses</p>
|
||||
<p className="section-label mb-2">{t('dashboard.schema.responses')}</p>
|
||||
<div className="space-y-2">
|
||||
{entries.map(([code, resp]) => {
|
||||
const response = resp as {
|
||||
@@ -349,7 +355,7 @@ export function ResponsesView({ responses }: { responses: unknown }) {
|
||||
) : schema.type === 'array' && schema.items?.properties ? (
|
||||
<div>
|
||||
<div className="text-[11px] text-text-muted mb-1">
|
||||
<TypeBadge type="array" /> of objects:
|
||||
<TypeBadge type="array" /> {t('dashboard.schema.ofObjects')}
|
||||
</div>
|
||||
<SchemaProperties schema={schema.items} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user