From e6e62e2992dd4814a4cec350049e4a39d2bcbaaf Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Fri, 6 Feb 2026 03:34:38 +0800 Subject: [PATCH] feat(i18n): add internationalization support for visual config editor --- src/components/config/VisualConfigEditor.tsx | 230 ++++++++++--------- src/i18n/locales/en.json | 138 ++++++++++- src/i18n/locales/zh-CN.json | 138 ++++++++++- 3 files changed, 390 insertions(+), 116 deletions(-) diff --git a/src/components/config/VisualConfigEditor.tsx b/src/components/config/VisualConfigEditor.tsx index a4e76d5..7541b29 100644 --- a/src/components/config/VisualConfigEditor.tsx +++ b/src/components/config/VisualConfigEditor.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; @@ -198,6 +199,7 @@ function ApiKeysCardEditor({ disabled?: boolean; onChange: (nextValue: string) => void; }) { + const { t } = useTranslation(); const apiKeys = useMemo( () => value @@ -244,11 +246,11 @@ function ApiKeysCardEditor({ const handleSave = () => { const trimmed = inputValue.trim(); if (!trimmed) { - setFormError('请输入 API 密钥'); + setFormError(t('config_management.visual.api_keys.error_empty')); return; } if (!isValidApiKeyCharset(trimmed)) { - setFormError('API 密钥包含无效字符'); + setFormError(t('config_management.visual.api_keys.error_invalid')); return; } @@ -263,9 +265,9 @@ function ApiKeysCardEditor({ return (
- +
@@ -279,7 +281,7 @@ function ApiKeysCardEditor({ textAlign: 'center', }} > - 暂无 API 密钥 + {t('config_management.visual.api_keys.empty')}
) : (
@@ -292,10 +294,10 @@ function ApiKeysCardEditor({
@@ -303,31 +305,31 @@ function ApiKeysCardEditor({ )} -
每个条目代表一个 API 密钥(与 “API 密钥管理” 页面样式一致)
+
{t('config_management.visual.api_keys.hint')}
} > setInputValue(e.target.value)} disabled={disabled} error={formError || undefined} - hint="此处仅修改本地配置文件内容,不会自动同步到 API 密钥管理接口" + hint={t('config_management.visual.api_keys.input_hint')} /> @@ -345,6 +347,7 @@ function StringListEditor({ placeholder?: string; onChange: (next: string[]) => void; }) { + const { t } = useTranslation(); const items = value.length ? value : []; const updateItem = (index: number, nextValue: string) => @@ -365,13 +368,13 @@ function StringListEditor({ style={{ flex: 1 }} /> ))}
@@ -389,6 +392,7 @@ function PayloadRulesEditor({ protocolFirst?: boolean; onChange: (next: PayloadRule[]) => void; }) { + const { t } = useTranslation(); const rules = value.length ? value : []; const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]); @@ -441,15 +445,15 @@ function PayloadRulesEditor({ const getValuePlaceholder = (valueType: PayloadParamValueType) => { switch (valueType) { case 'string': - return '字符串值'; + return t('config_management.visual.payload_rules.value_string'); case 'number': - return '数字值 (如 0.7)'; + return t('config_management.visual.payload_rules.value_number'); case 'boolean': - return 'true 或 false'; + return t('config_management.visual.payload_rules.value_boolean'); case 'json': - return 'JSON 值'; + return t('config_management.visual.payload_rules.value_json'); default: - return '值'; + return t('config_management.visual.payload_rules.value_default'); } }; @@ -468,14 +472,14 @@ function PayloadRulesEditor({ }} >
-
规则 {ruleIndex + 1}
+
{t('config_management.visual.payload_rules.rule')} {ruleIndex + 1}
-
适用模型
+
{t('config_management.visual.payload_rules.models')}
{(rule.models.length ? rule.models : []).map((model, modelIndex) => (
updateModel(ruleIndex, modelIndex, { protocol: (nextValue || undefined) as PayloadModelEntry['protocol'], @@ -500,7 +504,7 @@ function PayloadRulesEditor({ /> updateModel(ruleIndex, modelIndex, { name: e.target.value })} disabled={disabled} @@ -510,7 +514,7 @@ function PayloadRulesEditor({ <> updateModel(ruleIndex, modelIndex, { name: e.target.value })} disabled={disabled} @@ -519,7 +523,7 @@ function PayloadRulesEditor({ value={model.protocol ?? ''} options={VISUAL_CONFIG_PROTOCOL_OPTIONS} disabled={disabled} - ariaLabel="供应商类型" + ariaLabel={t('config_management.visual.payload_rules.provider_type')} onChange={(nextValue) => updateModel(ruleIndex, modelIndex, { protocol: (nextValue || undefined) as PayloadModelEntry['protocol'], @@ -529,24 +533,24 @@ function PayloadRulesEditor({ )}
))}
-
参数设置
+
{t('config_management.visual.payload_rules.params')}
{(rule.params.length ? rule.params : []).map((param, paramIndex) => (
updateParam(ruleIndex, paramIndex, { path: e.target.value })} disabled={disabled} @@ -555,7 +559,7 @@ function PayloadRulesEditor({ value={param.valueType} options={VISUAL_CONFIG_PAYLOAD_VALUE_TYPE_OPTIONS} disabled={disabled} - ariaLabel="参数类型" + ariaLabel={t('config_management.visual.payload_rules.param_type')} onChange={(nextValue) => updateParam(ruleIndex, paramIndex, { valueType: nextValue as PayloadParamValueType }) } @@ -568,13 +572,13 @@ function PayloadRulesEditor({ disabled={disabled} />
))}
@@ -591,13 +595,13 @@ function PayloadRulesEditor({ textAlign: 'center', }} > - 暂无规则 + {t('config_management.visual.payload_rules.no_rules')} )}
@@ -613,6 +617,7 @@ function PayloadFilterRulesEditor({ disabled?: boolean; onChange: (next: PayloadFilterRule[]) => void; }) { + const { t } = useTranslation(); const rules = value.length ? value : []; const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]); @@ -654,19 +659,19 @@ function PayloadFilterRulesEditor({ }} >
-
规则 {ruleIndex + 1}
+
{t('config_management.visual.payload_rules.rule')} {ruleIndex + 1}
-
适用模型
+
{t('config_management.visual.payload_rules.models')}
{rule.models.map((model, modelIndex) => (
updateModel(ruleIndex, modelIndex, { name: e.target.value })} disabled={disabled} @@ -675,7 +680,7 @@ function PayloadFilterRulesEditor({ value={model.protocol ?? ''} options={VISUAL_CONFIG_PROTOCOL_OPTIONS} disabled={disabled} - ariaLabel="供应商类型" + ariaLabel={t('config_management.visual.payload_rules.provider_type')} onChange={(nextValue) => updateModel(ruleIndex, modelIndex, { protocol: (nextValue || undefined) as PayloadModelEntry['protocol'], @@ -683,23 +688,23 @@ function PayloadFilterRulesEditor({ } />
))}
-
移除参数
+
{t('config_management.visual.payload_rules.remove_params')}
updateRule(ruleIndex, { params })} />
@@ -708,7 +713,7 @@ function PayloadFilterRulesEditor({
@@ -716,23 +721,24 @@ function PayloadFilterRulesEditor({ } export function VisualConfigEditor({ values, disabled = false, onChange }: VisualConfigEditorProps) { + const { t } = useTranslation(); const isKeepaliveDisabled = values.streaming.keepaliveSeconds === '' || values.streaming.keepaliveSeconds === '0'; const isNonstreamKeepaliveDisabled = values.streaming.nonstreamKeepaliveInterval === '' || values.streaming.nonstreamKeepaliveInterval === '0'; return (
- + onChange({ host: e.target.value })} disabled={disabled} /> - +
onChange({ tlsEnable })} @@ -756,14 +762,14 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua onChange({ tlsCert: e.target.value })} disabled={disabled} /> onChange({ tlsKey: e.target.value })} @@ -775,33 +781,33 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
- +
onChange({ rmAllowRemote })} /> onChange({ rmDisableControlPanel })} /> onChange({ rmSecretKey: e.target.value })} disabled={disabled} /> onChange({ rmPanelRepo: e.target.value })} @@ -811,15 +817,15 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
- +
onChange({ authDir: e.target.value })} disabled={disabled} - hint="存放认证文件的目录路径(支持 ~)" + hint={t('config_management.visual.sections.auth.auth_dir_hint')} /> - +
onChange({ debug })} /> onChange({ commercialMode })} /> onChange({ loggingToFile })} /> onChange({ usageStatisticsEnabled })} @@ -864,7 +870,7 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua onChange({ usageRecordsRetentionDays: e.target.value })} disabled={disabled} - hint="0 为无限制(不清理)" + hint={t('config_management.visual.sections.system.usage_retention_hint')} />
- +
onChange({ proxyUrl: e.target.value })} disabled={disabled} />
- + onChange({ routingStrategy: nextValue as VisualConfigValues['routingStrategy'] }) } /> -
选择凭据选择策略
+
{t('config_management.visual.sections.network.routing_strategy_hint')}
onChange({ forceModelPrefix })} /> onChange({ wsAuth })} @@ -945,18 +951,18 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
- +
onChange({ quotaSwitchProject })} /> onChange({ quotaSwitchPreviewModel })} @@ -964,11 +970,11 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua
- +
- +
- 已禁用 + {t('config_management.visual.sections.streaming.disabled')} )}
-
设置为 0 或留空表示禁用 keepalive
+
{t('config_management.visual.sections.streaming.keepalive_hint')}
onChange({ streaming: { ...values.streaming, bootstrapRetries: e.target.value } })} disabled={disabled} - hint="流式传输启动时(首包前)的重试次数" + hint={t('config_management.visual.sections.streaming.bootstrap_hint')} />
- +
- 已禁用 + {t('config_management.visual.sections.streaming.disabled')} )}
- 非流式响应时每隔 N 秒发送空行以防止空闲超时,设置为 0 或留空表示禁用 + {t('config_management.visual.sections.streaming.nonstream_keepalive_hint')}
- +
-
默认规则
+
{t('config_management.visual.sections.payload.default_rules')}
- 当请求中未指定参数时,使用这些默认值 + {t('config_management.visual.sections.payload.default_rules_desc')}
-
覆盖规则
+
{t('config_management.visual.sections.payload.override_rules')}
- 强制覆盖请求中的参数值 + {t('config_management.visual.sections.payload.override_rules_desc')}
-
过滤规则
+
{t('config_management.visual.sections.payload.filter_rules')}
- 通过 JSON Path 预过滤上游请求体,自动剔除不合规/冗余参数(Request Sanitization) + {t('config_management.visual.sections.payload.filter_rules_desc')}