- i18n: complete remaining internationalization across the UI

- Locales: add and align keys (common.enterValidValue, apiKeyInput.*, jsonEditor.*, claudeConfig.*); fix zh common.unknown mapping
- ProviderForm: localize labels/placeholders/hints/errors; unify JSON/auth validation to providerForm.*; add wizard CTA for Codex custom with i18n; cancel uses common.cancel
- CodexConfigEditor: i18n for quick wizard, labels/placeholders/hints, common config modal (title/help/buttons)
- ClaudeConfigEditor: i18n for main label, common-config toggle/button, modal title/help, footer buttons
- EndpointSpeedTest: localize failed/noEndpoints/done and aria labels
- ApiKeyInput: i18n for placeholder and show/hide aria
- JsonEditor: i18n linter messages
- PresetSelector: remove hardcoded defaults, use i18n keys
- UpdateBadge: i18n close aria
- Build/typecheck: pass; scan shows no visible hardcoded Chinese strings outside locales
This commit is contained in:
Jason
2025-10-07 23:31:00 +08:00
parent 420a4234de
commit 01da9a1eac
13 changed files with 425 additions and 154 deletions

View File

@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Zap, Loader2, Plus, X, AlertCircle } from "lucide-react";
import { isLinux } from "../../lib/platform";
@@ -74,6 +75,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
onClose,
onCustomEndpointsChange,
}) => {
const { t } = useTranslation();
const [entries, setEntries] = useState<EndpointEntry[]>(() =>
buildInitialEntries(initialEndpoints, value),
);
@@ -127,14 +129,14 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
return Array.from(map.values());
});
} catch (error) {
console.error("加载自定义端点失败:", error);
console.error(t("endpointTest.loadEndpointsFailed"), error);
}
};
if (visible) {
loadCustomEndpoints();
}
}, [appType, visible, providerId]);
}, [appType, visible, providerId, t]);
useEffect(() => {
setEntries((prev) => {
@@ -214,7 +216,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
let errorMsg: string | null = null;
if (!candidate) {
errorMsg = "请输入有效的 URL";
errorMsg = t("endpointTest.enterValidUrl");
}
let parsed: URL | null = null;
@@ -222,12 +224,12 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
try {
parsed = new URL(candidate);
} catch {
errorMsg = "URL 格式不正确";
errorMsg = t("endpointTest.invalidUrlFormat");
}
}
if (!errorMsg && parsed && !parsed.protocol.startsWith("http")) {
errorMsg = "仅支持 HTTP/HTTPS";
errorMsg = t("endpointTest.onlyHttps");
}
let sanitized = "";
@@ -236,7 +238,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
// 使用当前 entries 做去重校验,避免依赖可能过期的 addError
const isDuplicate = entries.some((entry) => entry.url === sanitized);
if (isDuplicate) {
errorMsg = "该地址已存在";
errorMsg = t("endpointTest.urlExists");
}
}
@@ -277,11 +279,11 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
setAddError(message || "保存失败,请重试");
console.error("添加自定义端点失败:", error);
setAddError(message || t("endpointTest.saveFailed"));
console.error(t("endpointTest.addEndpointFailed"), error);
}
},
[customUrl, entries, normalizedSelected, onChange, appType, providerId],
[customUrl, entries, normalizedSelected, onChange, appType, providerId, t],
);
const handleRemoveEndpoint = useCallback(
@@ -291,7 +293,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
try {
await window.api.removeCustomEndpoint(appType, providerId, entry.url);
} catch (error) {
console.error("删除自定义端点失败:", error);
console.error(t("endpointTest.removeEndpointFailed"), error);
return;
}
}
@@ -306,18 +308,18 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
return next;
});
},
[normalizedSelected, onChange, appType, providerId],
[normalizedSelected, onChange, appType, providerId, t],
);
const runSpeedTest = useCallback(async () => {
const urls = entries.map((entry) => entry.url);
if (urls.length === 0) {
setLastError("请先添加端点");
setLastError(t("endpointTest.pleaseAddEndpoint"));
return;
}
if (typeof window === "undefined" || !window.api?.testApiEndpoints) {
setLastError("测速功能不可用");
setLastError(t("endpointTest.testUnavailable"));
return;
}
@@ -350,7 +352,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
...entry,
latency: null,
status: undefined,
error: "未返回结果",
error: t("endpointTest.noResult"),
};
}
return {
@@ -374,12 +376,12 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
}
} catch (error) {
const message =
error instanceof Error ? error.message : `测速失败: ${String(error)}`;
error instanceof Error ? error.message : `${t("endpointTest.testFailed", { error: String(error) })}`;
setLastError(message);
} finally {
setIsTesting(false);
}
}, [entries, autoSelect, appType, normalizedSelected, onChange]);
}, [entries, autoSelect, appType, normalizedSelected, onChange, t]);
const handleSelect = useCallback(
async (url: string) => {
@@ -431,13 +433,13 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-800">
<h3 className="text-base font-medium text-gray-900 dark:text-gray-100">
{t("endpointTest.title")}
</h3>
<button
type="button"
onClick={onClose}
className="p-1 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
aria-label="关闭"
aria-label={t("common.close")}
>
<X size={16} />
</button>
@@ -448,7 +450,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
{/* 测速控制栏 */}
<div className="flex items-center justify-between">
<div className="text-sm text-gray-600 dark:text-gray-400">
{entries.length}
{entries.length} {t("endpointTest.endpoints")}
</div>
<div className="flex items-center gap-3">
<label className="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400">
@@ -458,7 +460,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
onChange={(event) => setAutoSelect(event.target.checked)}
className="h-3.5 w-3.5 rounded border-gray-300 dark:border-gray-600"
/>
{t("endpointTest.autoSelect")}
</label>
<button
type="button"
@@ -469,12 +471,12 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
{isTesting ? (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin" />
{t("endpointTest.testing")}
</>
) : (
<>
<Zap className="h-3.5 w-3.5" />
{t("endpointTest.testSpeed")}
</>
)}
</button>
@@ -487,7 +489,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
<input
type="url"
value={customUrl}
placeholder="https://api.example.com"
placeholder={t("endpointTest.addEndpointPlaceholder")}
onChange={(event) => setCustomUrl(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
@@ -567,7 +569,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
) : isTesting ? (
<Loader2 className="h-4 w-4 animate-spin text-gray-400" />
) : entry.error ? (
<div className="text-xs text-gray-400"></div>
<div className="text-xs text-gray-400">{t("endpointTest.failed")}</div>
) : (
<div className="text-xs text-gray-400"></div>
)}
@@ -589,7 +591,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
</div>
) : (
<div className="rounded-md border border-dashed border-gray-200 bg-gray-50 py-8 text-center text-xs text-gray-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400">
{t("endpointTest.noEndpoints")}
</div>
)}
@@ -609,7 +611,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
onClick={onClose}
className="px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-lg hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-sm font-medium"
>
{t("endpointTest.done")}
</button>
</div>
</div>