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

@@ -142,7 +142,7 @@ function App() {
</Button>
<Button onClick={() => setIsAddOpen(true)}>
<Plus className="h-4 w-4" />
{t("header.addProvider", { defaultValue: "添加供应商" })}
{t("header.addProvider")}
</Button>
</div>
</div>
@@ -198,12 +198,11 @@ function App() {
<ConfirmDialog
isOpen={Boolean(confirmDelete)}
title={t("confirm.deleteProvider", { defaultValue: "删除供应商" })}
title={t("confirm.deleteProvider")}
message={
confirmDelete
? t("confirm.deleteProviderMessage", {
name: confirmDelete.name,
defaultValue: `确定删除 ${confirmDelete.name} 吗?`,
})
: ""
}

View File

@@ -1,5 +1,6 @@
import React from "react";
import { RefreshCw, AlertCircle } from "lucide-react";
import { useTranslation } from "react-i18next";
import { type AppType } from "@/lib/api";
import { useUsageQuery } from "@/lib/query/queries";
import { UsageData } from "../types";
@@ -15,6 +16,7 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
appType,
usageEnabled,
}) => {
const { t } = useTranslation();
const {
data: usage,
isLoading: loading,
@@ -31,7 +33,7 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2 text-red-500 dark:text-red-400">
<AlertCircle size={14} />
<span>{usage.error || "查询失败"}</span>
<span>{usage.error || t("usage.queryFailed")}</span>
</div>
{/* 刷新按钮 */}
@@ -39,7 +41,7 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
onClick={() => refetch()}
disabled={loading}
className="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors disabled:opacity-50 flex-shrink-0"
title="刷新用量"
title={t("usage.refreshUsage")}
>
<RefreshCw size={12} className={loading ? "animate-spin" : ""} />
</button>
@@ -58,13 +60,13 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
{/* 标题行:包含刷新按钮 */}
<div className="flex items-center justify-between mb-2">
<span className="text-xs text-gray-500 dark:text-gray-400 font-medium">
{t("usage.planUsage")}
</span>
<button
onClick={() => refetch()}
disabled={loading}
className="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors disabled:opacity-50"
title="刷新用量"
title={t("usage.refreshUsage")}
>
<RefreshCw size={12} className={loading ? "animate-spin" : ""} />
</button>
@@ -82,6 +84,7 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
// 单个套餐数据展示组件
const UsagePlanItem: React.FC<{ data: UsageData }> = ({ data }) => {
const { t } = useTranslation();
const {
planName,
extra,
@@ -130,7 +133,7 @@ const UsagePlanItem: React.FC<{ data: UsageData }> = ({ data }) => {
)}
{isExpired && (
<span className="text-red-500 dark:text-red-400 font-medium text-[10px] px-1.5 py-0.5 bg-red-50 dark:bg-red-900/20 rounded flex-shrink-0">
{invalidMessage || "已失效"}
{invalidMessage || t("usage.invalid")}
</span>
)}
</div>
@@ -143,7 +146,7 @@ const UsagePlanItem: React.FC<{ data: UsageData }> = ({ data }) => {
{/* 总额度 */}
{total !== undefined && (
<>
<span className="text-gray-500 dark:text-gray-400"></span>
<span className="text-gray-500 dark:text-gray-400">{t("usage.total")}</span>
<span className="tabular-nums text-gray-600 dark:text-gray-400">
{total === -1 ? "∞" : total.toFixed(2)}
</span>
@@ -154,7 +157,7 @@ const UsagePlanItem: React.FC<{ data: UsageData }> = ({ data }) => {
{/* 已用额度 */}
{used !== undefined && (
<>
<span className="text-gray-500 dark:text-gray-400">使</span>
<span className="text-gray-500 dark:text-gray-400">{t("usage.used")}</span>
<span className="tabular-nums text-gray-600 dark:text-gray-400">
{used.toFixed(2)}
</span>
@@ -165,7 +168,7 @@ const UsagePlanItem: React.FC<{ data: UsageData }> = ({ data }) => {
{/* 剩余额度 - 突出显示 */}
{remaining !== undefined && (
<>
<span className="text-gray-500 dark:text-gray-400"></span>
<span className="text-gray-500 dark:text-gray-400">{t("usage.remaining")}</span>
<span
className={`font-semibold tabular-nums ${
isExpired

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>

View File

@@ -135,8 +135,8 @@ export function AddProviderDialog({
const submitLabel =
appType === "claude"
? t("provider.addClaudeProvider", { defaultValue: "添加 Claude 供应商" })
: t("provider.addCodexProvider", { defaultValue: "添加 Codex 供应商" });
? t("provider.addClaudeProvider")
: t("provider.addCodexProvider");
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -144,16 +144,14 @@ export function AddProviderDialog({
<DialogHeader>
<DialogTitle>{submitLabel}</DialogTitle>
<DialogDescription>
{t("provider.addDescription", {
defaultValue: "填写信息后即可在列表中快速切换供应商。",
})}
{t("provider.addProviderHint")}
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6 py-4">
<ProviderForm
appType={appType}
submitLabel={t("common.add", { defaultValue: "添加" })}
submitLabel={t("common.add")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
showButtons={false}
@@ -162,11 +160,11 @@ export function AddProviderDialog({
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
{t("common.cancel", { defaultValue: "取消" })}
{t("common.cancel")}
</Button>
<Button type="submit" form="provider-form">
<Plus className="h-4 w-4" />
{t("common.add", { defaultValue: "添加" })}
{t("common.add")}
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -66,19 +66,17 @@ export function EditProviderDialog({
<DialogContent className="max-w-2xl max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle>
{t("provider.editProvider", { defaultValue: "编辑供应商" })}
{t("provider.editProvider")}
</DialogTitle>
<DialogDescription>
{t("provider.editDescription", {
defaultValue: "更新配置后将立即应用到当前供应商。",
})}
{t("provider.editProviderHint")}
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6 py-4">
<ProviderForm
appType={appType}
submitLabel={t("common.save", { defaultValue: "保存" })}
submitLabel={t("common.save")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={{
@@ -92,11 +90,11 @@ export function EditProviderDialog({
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
{t("common.cancel", { defaultValue: "取消" })}
{t("common.cancel")}
</Button>
<Button type="submit" form="provider-form">
<Save className="h-4 w-4" />
{t("common.save", { defaultValue: "保存" })}
{t("common.save")}
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -35,25 +35,25 @@ export function ProviderActions({
{isCurrent ? (
<>
<Check className="h-4 w-4" />
{t("provider.inUse", { defaultValue: "已启用" })}
{t("provider.inUse")}
</>
) : (
<>
<Play className="h-4 w-4" />
{t("provider.enable", { defaultValue: "启用" })}
{t("provider.enable")}
</>
)}
</Button>
<Button size="sm" variant="outline" onClick={onEdit}>
{t("common.edit", { defaultValue: "编辑" })}
{t("common.edit")}
</Button>
<Button
size="sm"
variant="outline"
onClick={onConfigureUsage}
title={t("provider.configureUsage", { defaultValue: "配置用量查询" })}
title={t("provider.configureUsage")}
>
<BarChart3 className="h-4 w-4" />
</Button>

View File

@@ -104,7 +104,7 @@ export function ProviderCard({
"mt-1 flex h-8 w-8 items-center justify-center rounded-md border border-transparent text-muted-foreground transition-colors hover:border-muted hover:text-foreground",
dragHandleProps?.isDragging && "border-primary text-primary",
)}
aria-label={t("provider.dragHandle", { defaultValue: "拖拽排序" })}
aria-label={t("provider.dragHandle")}
{...(dragHandleProps?.attributes ?? {})}
{...(dragHandleProps?.listeners ?? {})}
>
@@ -118,7 +118,7 @@ export function ProviderCard({
</h3>
{isCurrent && (
<span className="rounded-full bg-green-500/10 px-2 py-0.5 text-xs font-medium text-green-500 dark:text-green-400">
{t("provider.currentlyUsing", { defaultValue: "当前使用" })}
{t("provider.currentlyUsing")}
</span>
)}
</div>

View File

@@ -15,16 +15,14 @@ export function ProviderEmptyState({ onCreate }: ProviderEmptyStateProps) {
<Users className="h-7 w-7 text-muted-foreground" />
</div>
<h3 className="text-lg font-semibold">
{t("provider.noProviders", { defaultValue: "暂无供应商" })}
{t("provider.noProviders")}
</h3>
<p className="mt-2 max-w-sm text-sm text-muted-foreground">
{t("provider.noProvidersDescription", {
defaultValue: "开始添加一个供应商以快速完成切换。",
})}
{t("provider.noProvidersDescription")}
</p>
{onCreate && (
<Button className="mt-6" onClick={onCreate}>
{t("provider.addProvider", { defaultValue: "添加供应商" })}
{t("provider.addProvider")}
</Button>
)}
</div>

View File

@@ -25,14 +25,12 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
render={({ field }) => (
<FormItem>
<FormLabel>
{t("provider.name", { defaultValue: "供应商名称" })}
{t("provider.name")}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder={t("provider.namePlaceholder", {
defaultValue: "例如Claude 官方",
})}
placeholder={t("provider.namePlaceholder")}
/>
</FormControl>
<FormMessage />
@@ -46,7 +44,7 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
render={({ field }) => (
<FormItem>
<FormLabel>
{t("provider.websiteUrl", { defaultValue: "官网链接" })}
{t("provider.websiteUrl")}
</FormLabel>
<FormControl>
<Input {...field} placeholder="https://" />

View File

@@ -137,15 +137,11 @@ export function ClaudeFormFields({
{shouldShowSpeedTest && (
<EndpointField
id="baseUrl"
label={t("providerForm.apiEndpoint", { defaultValue: "API 端点" })}
label={t("providerForm.apiEndpoint")}
value={baseUrl}
onChange={onBaseUrlChange}
placeholder={t("providerForm.apiEndpointPlaceholder", {
defaultValue: "https://api.example.com",
})}
hint={t("providerForm.apiHint", {
defaultValue: "API 端点地址用于连接服务器",
})}
placeholder={t("providerForm.apiEndpointPlaceholder")}
hint={t("providerForm.apiHint")}
onManageClick={() => onEndpointModalToggle(true)}
/>
)}

View File

@@ -68,15 +68,11 @@ export function CodexFormFields({
{shouldShowSpeedTest && (
<EndpointField
id="codexBaseUrl"
label={t("codexConfig.apiUrlLabel", { defaultValue: "API 端点" })}
label={t("codexConfig.apiUrlLabel")}
value={codexBaseUrl}
onChange={onBaseUrlChange}
placeholder={t("providerForm.codexApiEndpointPlaceholder", {
defaultValue: "https://api.example.com/v1",
})}
hint={t("providerForm.codexApiHint", {
defaultValue: "Codex API 端点地址",
})}
placeholder={t("providerForm.codexApiEndpointPlaceholder")}
hint={t("providerForm.codexApiHint")}
onManageClick={() => onEndpointModalToggle(true)}
/>
)}

View File

@@ -51,7 +51,7 @@ export function CommonConfigEditor({
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="settingsConfig">
{t("provider.configJson", { defaultValue: "配置 JSON" })}
{t("provider.configJson")}
</Label>
<div className="flex items-center gap-2">
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">

View File

@@ -554,7 +554,7 @@ export function ProviderForm({
{showButtons && (
<div className="flex justify-end gap-2">
<Button variant="outline" type="button" onClick={onCancel}>
{t("common.cancel", { defaultValue: "取消" })}
{t("common.cancel")}
</Button>
<Button type="submit">{submitLabel}</Button>
</div>

View File

@@ -28,7 +28,7 @@ export function ProviderPresetSelector({
return (
<div className="space-y-3">
<FormLabel>
{t("providerPreset.label", { defaultValue: "预设供应商" })}
{t("providerPreset.label")}
</FormLabel>
<div className="flex flex-wrap gap-2">
{/* 自定义按钮 */}
@@ -41,7 +41,7 @@ export function ProviderPresetSelector({
: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700"
}`}
>
{t("providerPreset.custom", { defaultValue: "自定义配置" })}
{t("providerPreset.custom")}
</button>
{/* 预设按钮 */}

View File

@@ -73,13 +73,15 @@
"sortUpdateFailed": "Failed to update sort order",
"configureUsage": "Configure usage query",
"name": "Provider Name",
"namePlaceholder": "e.g., Claude Official",
"websiteUrl": "Website URL",
"configJson": "Config JSON",
"writeCommonConfig": "Write common config",
"editCommonConfigButton": "Edit common config",
"configJsonHint": "Please fill in complete Claude Code configuration",
"editCommonConfigTitle": "Edit common config snippet",
"editCommonConfigHint": "Common config snippet will be merged into all providers that enable it"
"editCommonConfigHint": "Common config snippet will be merged into all providers that enable it",
"addProvider": "Add Provider"
},
"notifications": {
"providerSaved": "Provider configuration saved",
@@ -297,6 +299,48 @@
"other": "Other",
"hint": "You can continue to adjust the fields below after selecting a preset."
},
"usage": {
"queryFailed": "Query failed",
"refreshUsage": "Refresh usage",
"planUsage": "Plan usage",
"invalid": "Expired",
"total": "Total:",
"used": "Used:",
"remaining": "Remaining:"
},
"usageScript": {
"title": "Configure Usage Query",
"enableUsageQuery": "Enable usage query",
"presetTemplate": "Preset template",
"queryScript": "Query script (JavaScript)",
"timeoutSeconds": "Timeout (seconds)",
"scriptHelp": "Script writing instructions:",
"configFormat": "Configuration format:",
"extractorFormat": "Extractor return format (all fields optional):",
"tips": "💡 Tips:",
"testing": "Testing...",
"testScript": "Test script",
"format": "Format",
"saveConfig": "Save config",
"scriptEmpty": "Script configuration cannot be empty",
"mustHaveReturn": "Script must contain return statement",
"testSuccess": "Test successful!",
"testFailed": "Test failed",
"formatSuccess": "Format successful",
"formatFailed": "Format failed",
"variablesHint": "Supported variables: {{apiKey}}, {{baseUrl}} | extractor function receives API response JSON object",
"fieldIsValid": "• isValid: Boolean, whether plan is valid",
"fieldInvalidMessage": "• invalidMessage: String, reason for expiration (shown when isValid is false)",
"fieldRemaining": "• remaining: Number, remaining quota",
"fieldUnit": "• unit: String, unit (e.g., \"USD\")",
"fieldPlanName": "• planName: String, plan name",
"fieldTotal": "• total: Number, total quota",
"fieldUsed": "• used: Number, used quota",
"fieldExtra": "• extra: String, custom display text",
"tip1": "• Variables {{apiKey}} and {{baseUrl}} are automatically replaced",
"tip2": "• Extractor function runs in sandbox environment, supports ES2020+ syntax",
"tip3": "• Entire config must be wrapped in () to form object literal expression"
},
"kimiSelector": {
"modelConfig": "Model Configuration",
"mainModel": "Main Model",

View File

@@ -73,13 +73,15 @@
"sortUpdateFailed": "排序更新失败",
"configureUsage": "配置用量查询",
"name": "供应商名称",
"namePlaceholder": "例如Claude 官方",
"websiteUrl": "官网链接",
"configJson": "配置 JSON",
"writeCommonConfig": "写入通用配置",
"editCommonConfigButton": "编辑通用配置",
"configJsonHint": "请填写完整的 Claude Code 配置",
"editCommonConfigTitle": "编辑通用配置片段",
"editCommonConfigHint": "通用配置片段将合并到所有启用它的供应商配置中"
"editCommonConfigHint": "通用配置片段将合并到所有启用它的供应商配置中",
"addProvider": "添加供应商"
},
"notifications": {
"providerSaved": "供应商配置已保存",
@@ -297,6 +299,48 @@
"other": "其他",
"hint": "选择预设后可继续调整下方字段。"
},
"usage": {
"queryFailed": "查询失败",
"refreshUsage": "刷新用量",
"planUsage": "套餐用量",
"invalid": "已失效",
"total": "总:",
"used": "使用:",
"remaining": "剩余:"
},
"usageScript": {
"title": "配置用量查询",
"enableUsageQuery": "启用用量查询",
"presetTemplate": "预设模板",
"queryScript": "查询脚本JavaScript",
"timeoutSeconds": "超时时间(秒)",
"scriptHelp": "脚本编写说明:",
"configFormat": "配置格式:",
"extractorFormat": "extractor 返回格式(所有字段均为可选):",
"tips": "💡 提示:",
"testing": "测试中...",
"testScript": "测试脚本",
"format": "格式化",
"saveConfig": "保存配置",
"scriptEmpty": "脚本配置不能为空",
"mustHaveReturn": "脚本必须包含 return 语句",
"testSuccess": "测试成功!",
"testFailed": "测试失败",
"formatSuccess": "格式化成功",
"formatFailed": "格式化失败",
"variablesHint": "支持变量: {{apiKey}}, {{baseUrl}} | extractor 函数接收 API 响应的 JSON 对象",
"fieldIsValid": "• isValid: 布尔值,套餐是否有效",
"fieldInvalidMessage": "• invalidMessage: 字符串,失效原因说明(当 isValid 为 false 时显示)",
"fieldRemaining": "• remaining: 数字,剩余额度",
"fieldUnit": "• unit: 字符串,单位(如 \"USD\"",
"fieldPlanName": "• planName: 字符串,套餐名称",
"fieldTotal": "• total: 数字,总额度",
"fieldUsed": "• used: 数字,已用额度",
"fieldExtra": "• extra: 字符串,扩展字段,可自由补充需要展示的文本",
"tip1": "• 变量 {{apiKey}} 和 {{baseUrl}} 会自动替换",
"tip2": "• extractor 函数在沙箱环境中执行,支持 ES2020+ 语法",
"tip3": "• 整个配置必须用 () 包裹,形成对象字面量表达式"
},
"kimiSelector": {
"modelConfig": "模型配置",
"mainModel": "主模型",