i18n: complete internationalization for provider and usage query panels

- Add 45+ new translation keys for usage query and usage script features
- Fix duplicate provider object in translation files that caused missing translations
- Remove all hardcoded Chinese text and defaultValue fallbacks from components
- Add proper translations for:
  * Usage footer (query status, plan usage display)
  * Usage script modal (script editor, validation, test controls)
  * Provider forms (basic fields, endpoints, model selectors)
  * Provider dialogs (add/edit hints and titles)

Modified 16 files:
- 2 translation files (zh.json, en.json)
- 14 component files (removed defaultValue, added t() calls)

All UI text now properly supports Chinese/English switching.
This commit is contained in:
Jason
2025-10-19 11:55:46 +08:00
parent bae6a1cf55
commit eb6948a562
16 changed files with 176 additions and 125 deletions

View File

@@ -1,6 +1,7 @@
import React, { useState } from "react";
import { Play, Wand2 } from "lucide-react";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import { Provider, UsageScript } from "../types";
import { usageApi, type AppType } from "@/lib/api";
import JsonEditor from "./JsonEditor";
@@ -88,12 +89,13 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
onClose,
onSave,
}) => {
const { t } = useTranslation();
const [script, setScript] = useState<UsageScript>(() => {
return (
provider.meta?.usage_script || {
enabled: false,
language: "javascript",
code: PRESET_TEMPLATES["通用模板"],
code: PRESET_TEMPLATES[t("usageScript.presetTemplate") === "预设模板" ? "通用模板" : "General"],
timeout: 10,
}
);
@@ -104,13 +106,13 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const handleSave = () => {
// 验证脚本格式
if (script.enabled && !script.code.trim()) {
toast.error("脚本配置不能为空");
toast.error(t("usageScript.scriptEmpty"));
return;
}
// 基本的 JS 语法检查(检查是否包含 return 语句)
if (script.enabled && !script.code.includes("return")) {
toast.error("脚本必须包含 return 语句", { duration: 5000 });
toast.error(t("usageScript.mustHaveReturn"), { duration: 5000 });
return;
}
@@ -127,17 +129,17 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const summary = result.data
.map((plan) => {
const planInfo = plan.planName ? `[${plan.planName}]` : "";
return `${planInfo} 剩余: ${plan.remaining} ${plan.unit}`;
return `${planInfo} ${t("usage.remaining")} ${plan.remaining} ${plan.unit}`;
})
.join(", ");
toast.success(`测试成功!${summary}`, { duration: 3000 });
toast.success(`${t("usageScript.testSuccess")}${summary}`, { duration: 3000 });
} else {
toast.error(`测试失败: ${result.error || "无数据返回"}`, {
toast.error(`${t("usageScript.testFailed")}: ${result.error || t("endpointTest.noResult")}`, {
duration: 5000,
});
}
} catch (error: any) {
toast.error(`测试失败: ${error?.message || "未知错误"}`, {
toast.error(`${t("usageScript.testFailed")}: ${error?.message || t("common.unknown")}`, {
duration: 5000,
});
} finally {
@@ -156,9 +158,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
printWidth: 80,
});
setScript({ ...script, code: formatted.trim() });
toast.success("格式化成功", { duration: 1000 });
toast.success(t("usageScript.formatSuccess"), { duration: 1000 });
} catch (error: any) {
toast.error(`格式化失败: ${error?.message || "语法错误"}`, {
toast.error(`${t("usageScript.formatFailed")}: ${error?.message || t("jsonEditor.invalidJson")}`, {
duration: 3000,
});
}
@@ -175,7 +177,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-3xl max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle> - {provider.name}</DialogTitle>
<DialogTitle>{t("usageScript.title")} - {provider.name}</DialogTitle>
</DialogHeader>
{/* Content - Scrollable */}
@@ -191,7 +193,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
className="w-4 h-4"
/>
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
{t("usageScript.enableUsageQuery")}
</span>
</label>
@@ -200,7 +202,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 预设模板选择 */}
<div>
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
{t("usageScript.presetTemplate")}
</label>
<div className="flex gap-2">
{Object.keys(PRESET_TEMPLATES).map((name) => (
@@ -218,7 +220,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 脚本编辑器 */}
<div>
<label className="block text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
JavaScript
{t("usageScript.queryScript")}
</label>
<JsonEditor
value={script.code}
@@ -227,9 +229,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
language="javascript"
/>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
: <code>{"{{apiKey}}"}</code>,{" "}
<code>{"{{baseUrl}}"}</code> | extractor API
JSON
{t("usageScript.variablesHint", {
apiKey: "{{apiKey}}",
baseUrl: "{{baseUrl}}"
})}
</p>
</div>
@@ -237,7 +240,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
<div className="grid grid-cols-2 gap-4">
<label className="block">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
{t("usageScript.timeoutSeconds")}
</span>
<input
type="number"
@@ -257,10 +260,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
{/* 脚本说明 */}
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg text-sm text-gray-700 dark:text-gray-300">
<h4 className="font-medium mb-2"></h4>
<h4 className="font-medium mb-2">{t("usageScript.scriptHelp")}</h4>
<div className="space-y-3 text-xs">
<div>
<strong></strong>
<strong>{t("usageScript.configFormat")}</strong>
<pre className="mt-1 p-2 bg-white/50 dark:bg-black/20 rounded text-[10px] overflow-x-auto">
{`({
request: {
@@ -285,51 +288,25 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
</div>
<div>
<strong>extractor </strong>
<strong>{t("usageScript.extractorFormat")}</strong>
<ul className="mt-1 space-y-0.5 ml-2">
<li>
<code>isValid</code>:
</li>
<li>
<code>invalidMessage</code>:
isValid false
</li>
<li>
<code>remaining</code>:
</li>
<li>
<code>unit</code>: "USD"
</li>
<li>
<code>planName</code>:
</li>
<li>
<code>total</code>:
</li>
<li>
<code>used</code>:
</li>
<li>
<code>extra</code>:
</li>
<li>{t("usageScript.fieldIsValid")}</li>
<li>{t("usageScript.fieldInvalidMessage")}</li>
<li>{t("usageScript.fieldRemaining")}</li>
<li>{t("usageScript.fieldUnit")}</li>
<li>{t("usageScript.fieldPlanName")}</li>
<li>{t("usageScript.fieldTotal")}</li>
<li>{t("usageScript.fieldUsed")}</li>
<li>{t("usageScript.fieldExtra")}</li>
</ul>
</div>
<div className="text-gray-600 dark:text-gray-400">
<strong>💡 </strong>
<strong>{t("usageScript.tips")}</strong>
<ul className="mt-1 space-y-0.5 ml-2">
<li>
<code>{"{{apiKey}}"}</code> {" "}
<code>{"{{baseUrl}}"}</code>
</li>
<li>
extractor ES2020+
</li>
<li>
<code>()</code>{" "}
</li>
<li>{t("usageScript.tip1", { apiKey: "{{apiKey}}", baseUrl: "{{baseUrl}}" })}</li>
<li>{t("usageScript.tip2")}</li>
<li>{t("usageScript.tip3")}</li>
</ul>
</div>
</div>
@@ -349,27 +326,27 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
disabled={!script.enabled || testing}
>
<Play size={14} />
{testing ? "测试中..." : "测试脚本"}
{testing ? t("usageScript.testing") : t("usageScript.testScript")}
</Button>
<Button
variant="outline"
size="sm"
onClick={handleFormat}
disabled={!script.enabled}
title="格式化代码 (Prettier)"
title={t("usageScript.format")}
>
<Wand2 size={14} />
{t("usageScript.format")}
</Button>
</div>
{/* Right side - Cancel and Save buttons */}
<div className="flex gap-2">
<Button variant="ghost" size="sm" onClick={onClose}>
{t("common.cancel")}
</Button>
<Button variant="default" size="sm" onClick={handleSave}>
{t("usageScript.saveConfig")}
</Button>
</div>
</DialogFooter>