feat(i18n): complete internationalization for usage query feature
Fully internationalized the usage query feature to support both Chinese and English. Frontend changes: - Refactored UsageScriptModal to use i18n translation keys - Replaced hardcoded Chinese template names with constants - Implemented dynamic template generation with i18n support - Internationalized all labels, placeholders, and code comments - Added template name mapping for translation Backend changes: - Replaced all hardcoded Chinese error messages in usage_script.rs - Converted 33 error instances from AppError::Message to AppError::localized - Added bilingual error messages for runtime, parsing, HTTP, and validation errors Translation updates: - Added 11 new translation key pairs to zh.json and en.json - Covered template names, field labels, placeholders, and code comments Impact: - 100% i18n coverage for usage query functionality - All user-facing text and error messages now support language switching - Better user experience for English-speaking users
This commit is contained in:
@@ -31,28 +31,28 @@ pub async fn execute_usage_script(
|
|||||||
// 2. 在独立作用域中提取 request 配置(确保 Runtime/Context 在 await 前释放)
|
// 2. 在独立作用域中提取 request 配置(确保 Runtime/Context 在 await 前释放)
|
||||||
let request_config = {
|
let request_config = {
|
||||||
let runtime =
|
let runtime =
|
||||||
Runtime::new().map_err(|e| AppError::Message(format!("创建 JS 运行时失败: {}", e)))?;
|
Runtime::new().map_err(|e| AppError::localized("usage_script.runtime_create_failed", format!("创建 JS 运行时失败: {}", e), format!("Failed to create JS runtime: {}", e)))?;
|
||||||
let context = Context::full(&runtime)
|
let context = Context::full(&runtime)
|
||||||
.map_err(|e| AppError::Message(format!("创建 JS 上下文失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.context_create_failed", format!("创建 JS 上下文失败: {}", e), format!("Failed to create JS context: {}", e)))?;
|
||||||
|
|
||||||
context.with(|ctx| {
|
context.with(|ctx| {
|
||||||
// 执行用户代码,获取配置对象
|
// 执行用户代码,获取配置对象
|
||||||
let config: rquickjs::Object = ctx
|
let config: rquickjs::Object = ctx
|
||||||
.eval(replaced.clone())
|
.eval(replaced.clone())
|
||||||
.map_err(|e| AppError::Message(format!("解析配置失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.config_parse_failed", format!("解析配置失败: {}", e), format!("Failed to parse config: {}", e)))?;
|
||||||
|
|
||||||
// 提取 request 配置
|
// 提取 request 配置
|
||||||
let request: rquickjs::Object = config
|
let request: rquickjs::Object = config
|
||||||
.get("request")
|
.get("request")
|
||||||
.map_err(|e| AppError::Message(format!("缺少 request 配置: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.request_missing", format!("缺少 request 配置: {}", e), format!("Missing request config: {}", e)))?;
|
||||||
|
|
||||||
// 将 request 转换为 JSON 字符串
|
// 将 request 转换为 JSON 字符串
|
||||||
let request_json: String = ctx
|
let request_json: String = ctx
|
||||||
.json_stringify(request)
|
.json_stringify(request)
|
||||||
.map_err(|e| AppError::Message(format!("序列化 request 失败: {}", e)))?
|
.map_err(|e| AppError::localized("usage_script.request_serialize_failed", format!("序列化 request 失败: {}", e), format!("Failed to serialize request: {}", e)))?
|
||||||
.ok_or_else(|| AppError::Message("序列化返回 None".into()))?
|
.ok_or_else(|| AppError::localized("usage_script.serialize_none", "序列化返回 None", "Serialization returned None"))?
|
||||||
.get()
|
.get()
|
||||||
.map_err(|e| AppError::Message(format!("获取字符串失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.get_string_failed", format!("获取字符串失败: {}", e), format!("Failed to get string: {}", e)))?;
|
||||||
|
|
||||||
Ok::<_, AppError>(request_json)
|
Ok::<_, AppError>(request_json)
|
||||||
})?
|
})?
|
||||||
@@ -60,7 +60,7 @@ pub async fn execute_usage_script(
|
|||||||
|
|
||||||
// 3. 解析 request 配置
|
// 3. 解析 request 配置
|
||||||
let request: RequestConfig = serde_json::from_str(&request_config)
|
let request: RequestConfig = serde_json::from_str(&request_config)
|
||||||
.map_err(|e| AppError::Message(format!("request 配置格式错误: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.request_format_invalid", format!("request 配置格式错误: {}", e), format!("Invalid request config format: {}", e)))?;
|
||||||
|
|
||||||
// 4. 发送 HTTP 请求
|
// 4. 发送 HTTP 请求
|
||||||
let response_data = send_http_request(&request, timeout_secs).await?;
|
let response_data = send_http_request(&request, timeout_secs).await?;
|
||||||
@@ -68,42 +68,42 @@ pub async fn execute_usage_script(
|
|||||||
// 5. 在独立作用域中执行 extractor(确保 Runtime/Context 在函数结束前释放)
|
// 5. 在独立作用域中执行 extractor(确保 Runtime/Context 在函数结束前释放)
|
||||||
let result: Value = {
|
let result: Value = {
|
||||||
let runtime =
|
let runtime =
|
||||||
Runtime::new().map_err(|e| AppError::Message(format!("创建 JS 运行时失败: {}", e)))?;
|
Runtime::new().map_err(|e| AppError::localized("usage_script.runtime_create_failed", format!("创建 JS 运行时失败: {}", e), format!("Failed to create JS runtime: {}", e)))?;
|
||||||
let context = Context::full(&runtime)
|
let context = Context::full(&runtime)
|
||||||
.map_err(|e| AppError::Message(format!("创建 JS 上下文失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.context_create_failed", format!("创建 JS 上下文失败: {}", e), format!("Failed to create JS context: {}", e)))?;
|
||||||
|
|
||||||
context.with(|ctx| {
|
context.with(|ctx| {
|
||||||
// 重新 eval 获取配置对象
|
// 重新 eval 获取配置对象
|
||||||
let config: rquickjs::Object = ctx
|
let config: rquickjs::Object = ctx
|
||||||
.eval(replaced.clone())
|
.eval(replaced.clone())
|
||||||
.map_err(|e| AppError::Message(format!("重新解析配置失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.config_reparse_failed", format!("重新解析配置失败: {}", e), format!("Failed to re-parse config: {}", e)))?;
|
||||||
|
|
||||||
// 提取 extractor 函数
|
// 提取 extractor 函数
|
||||||
let extractor: Function = config
|
let extractor: Function = config
|
||||||
.get("extractor")
|
.get("extractor")
|
||||||
.map_err(|e| AppError::Message(format!("缺少 extractor 函数: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.extractor_missing", format!("缺少 extractor 函数: {}", e), format!("Missing extractor function: {}", e)))?;
|
||||||
|
|
||||||
// 将响应数据转换为 JS 值
|
// 将响应数据转换为 JS 值
|
||||||
let response_js: rquickjs::Value = ctx
|
let response_js: rquickjs::Value = ctx
|
||||||
.json_parse(response_data.as_str())
|
.json_parse(response_data.as_str())
|
||||||
.map_err(|e| AppError::Message(format!("解析响应 JSON 失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.response_parse_failed", format!("解析响应 JSON 失败: {}", e), format!("Failed to parse response JSON: {}", e)))?;
|
||||||
|
|
||||||
// 调用 extractor(response)
|
// 调用 extractor(response)
|
||||||
let result_js: rquickjs::Value = extractor
|
let result_js: rquickjs::Value = extractor
|
||||||
.call((response_js,))
|
.call((response_js,))
|
||||||
.map_err(|e| AppError::Message(format!("执行 extractor 失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.extractor_exec_failed", format!("执行 extractor 失败: {}", e), format!("Failed to execute extractor: {}", e)))?;
|
||||||
|
|
||||||
// 转换为 JSON 字符串
|
// 转换为 JSON 字符串
|
||||||
let result_json: String = ctx
|
let result_json: String = ctx
|
||||||
.json_stringify(result_js)
|
.json_stringify(result_js)
|
||||||
.map_err(|e| AppError::Message(format!("序列化结果失败: {}", e)))?
|
.map_err(|e| AppError::localized("usage_script.result_serialize_failed", format!("序列化结果失败: {}", e), format!("Failed to serialize result: {}", e)))?
|
||||||
.ok_or_else(|| AppError::Message("序列化返回 None".into()))?
|
.ok_or_else(|| AppError::localized("usage_script.serialize_none", "序列化返回 None", "Serialization returned None"))?
|
||||||
.get()
|
.get()
|
||||||
.map_err(|e| AppError::Message(format!("获取字符串失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.get_string_failed", format!("获取字符串失败: {}", e), format!("Failed to get string: {}", e)))?;
|
||||||
|
|
||||||
// 解析为 serde_json::Value
|
// 解析为 serde_json::Value
|
||||||
serde_json::from_str(&result_json)
|
serde_json::from_str(&result_json)
|
||||||
.map_err(|e| AppError::Message(format!("JSON 解析失败: {}", e)))
|
.map_err(|e| AppError::localized("usage_script.json_parse_failed", format!("JSON 解析失败: {}", e), format!("JSON parse failed: {}", e)))
|
||||||
})?
|
})?
|
||||||
}; // Runtime 和 Context 在这里被 drop
|
}; // Runtime 和 Context 在这里被 drop
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ async fn send_http_request(config: &RequestConfig, timeout_secs: u64) -> Result<
|
|||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(Duration::from_secs(timeout))
|
.timeout(Duration::from_secs(timeout))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| AppError::Message(format!("创建客户端失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.client_create_failed", format!("创建客户端失败: {}", e), format!("Failed to create client: {}", e)))?;
|
||||||
|
|
||||||
// 严格校验 HTTP 方法,非法值不回退为 GET
|
// 严格校验 HTTP 方法,非法值不回退为 GET
|
||||||
let method: reqwest::Method = config
|
let method: reqwest::Method = config
|
||||||
@@ -155,13 +155,13 @@ async fn send_http_request(config: &RequestConfig, timeout_secs: u64) -> Result<
|
|||||||
let resp = req
|
let resp = req
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::Message(format!("请求失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.request_failed", format!("请求失败: {}", e), format!("Request failed: {}", e)))?;
|
||||||
|
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
let text = resp
|
let text = resp
|
||||||
.text()
|
.text()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::Message(format!("读取响应失败: {}", e)))?;
|
.map_err(|e| AppError::localized("usage_script.read_response_failed", format!("读取响应失败: {}", e), format!("Failed to read response: {}", e)))?;
|
||||||
|
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
let preview = if text.len() > 200 {
|
let preview = if text.len() > 200 {
|
||||||
@@ -169,7 +169,7 @@ async fn send_http_request(config: &RequestConfig, timeout_secs: u64) -> Result<
|
|||||||
} else {
|
} else {
|
||||||
text.clone()
|
text.clone()
|
||||||
};
|
};
|
||||||
return Err(AppError::Message(format!("HTTP {} : {}", status, preview)));
|
return Err(AppError::localized("usage_script.http_error", format!("HTTP {} : {}", status, preview), format!("HTTP {} : {}", status, preview)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(text)
|
Ok(text)
|
||||||
@@ -180,11 +180,11 @@ fn validate_result(result: &Value) -> Result<(), AppError> {
|
|||||||
// 如果是数组,验证每个元素
|
// 如果是数组,验证每个元素
|
||||||
if let Some(arr) = result.as_array() {
|
if let Some(arr) = result.as_array() {
|
||||||
if arr.is_empty() {
|
if arr.is_empty() {
|
||||||
return Err(AppError::InvalidInput("脚本返回的数组不能为空".into()));
|
return Err(AppError::localized("usage_script.empty_array", "脚本返回的数组不能为空", "Script returned empty array"));
|
||||||
}
|
}
|
||||||
for (idx, item) in arr.iter().enumerate() {
|
for (idx, item) in arr.iter().enumerate() {
|
||||||
validate_single_usage(item)
|
validate_single_usage(item)
|
||||||
.map_err(|e| AppError::InvalidInput(format!("数组索引[{}]验证失败: {}", idx, e)))?;
|
.map_err(|e| AppError::localized("usage_script.array_validation_failed", format!("数组索引[{}]验证失败: {}", idx, e), format!("Validation failed at index [{}]: {}", idx, e)))?;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -197,48 +197,44 @@ fn validate_result(result: &Value) -> Result<(), AppError> {
|
|||||||
fn validate_single_usage(result: &Value) -> Result<(), AppError> {
|
fn validate_single_usage(result: &Value) -> Result<(), AppError> {
|
||||||
let obj = result
|
let obj = result
|
||||||
.as_object()
|
.as_object()
|
||||||
.ok_or_else(|| AppError::InvalidInput("脚本必须返回对象或对象数组".into()))?;
|
.ok_or_else(|| AppError::localized("usage_script.must_return_object", "脚本必须返回对象或对象数组", "Script must return object or array of objects"))?;
|
||||||
|
|
||||||
// 所有字段均为可选,只进行类型检查
|
// 所有字段均为可选,只进行类型检查
|
||||||
if obj.contains_key("isValid")
|
if obj.contains_key("isValid")
|
||||||
&& !result["isValid"].is_null()
|
&& !result["isValid"].is_null()
|
||||||
&& !result["isValid"].is_boolean()
|
&& !result["isValid"].is_boolean()
|
||||||
{
|
{
|
||||||
return Err(AppError::InvalidInput("isValid 必须是布尔值或 null".into()));
|
return Err(AppError::localized("usage_script.isvalid_type_error", "isValid 必须是布尔值或 null", "isValid must be boolean or null"));
|
||||||
}
|
}
|
||||||
if obj.contains_key("invalidMessage")
|
if obj.contains_key("invalidMessage")
|
||||||
&& !result["invalidMessage"].is_null()
|
&& !result["invalidMessage"].is_null()
|
||||||
&& !result["invalidMessage"].is_string()
|
&& !result["invalidMessage"].is_string()
|
||||||
{
|
{
|
||||||
return Err(AppError::InvalidInput(
|
return Err(AppError::localized("usage_script.invalidmessage_type_error", "invalidMessage 必须是字符串或 null", "invalidMessage must be string or null"));
|
||||||
"invalidMessage 必须是字符串或 null".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if obj.contains_key("remaining")
|
if obj.contains_key("remaining")
|
||||||
&& !result["remaining"].is_null()
|
&& !result["remaining"].is_null()
|
||||||
&& !result["remaining"].is_number()
|
&& !result["remaining"].is_number()
|
||||||
{
|
{
|
||||||
return Err(AppError::InvalidInput("remaining 必须是数字或 null".into()));
|
return Err(AppError::localized("usage_script.remaining_type_error", "remaining 必须是数字或 null", "remaining must be number or null"));
|
||||||
}
|
}
|
||||||
if obj.contains_key("unit") && !result["unit"].is_null() && !result["unit"].is_string() {
|
if obj.contains_key("unit") && !result["unit"].is_null() && !result["unit"].is_string() {
|
||||||
return Err(AppError::InvalidInput("unit 必须是字符串或 null".into()));
|
return Err(AppError::localized("usage_script.unit_type_error", "unit 必须是字符串或 null", "unit must be string or null"));
|
||||||
}
|
}
|
||||||
if obj.contains_key("total") && !result["total"].is_null() && !result["total"].is_number() {
|
if obj.contains_key("total") && !result["total"].is_null() && !result["total"].is_number() {
|
||||||
return Err(AppError::InvalidInput("total 必须是数字或 null".into()));
|
return Err(AppError::localized("usage_script.total_type_error", "total 必须是数字或 null", "total must be number or null"));
|
||||||
}
|
}
|
||||||
if obj.contains_key("used") && !result["used"].is_null() && !result["used"].is_number() {
|
if obj.contains_key("used") && !result["used"].is_null() && !result["used"].is_number() {
|
||||||
return Err(AppError::InvalidInput("used 必须是数字或 null".into()));
|
return Err(AppError::localized("usage_script.used_type_error", "used 必须是数字或 null", "used must be number or null"));
|
||||||
}
|
}
|
||||||
if obj.contains_key("planName")
|
if obj.contains_key("planName")
|
||||||
&& !result["planName"].is_null()
|
&& !result["planName"].is_null()
|
||||||
&& !result["planName"].is_string()
|
&& !result["planName"].is_string()
|
||||||
{
|
{
|
||||||
return Err(AppError::InvalidInput(
|
return Err(AppError::localized("usage_script.planname_type_error", "planName 必须是字符串或 null", "planName must be string or null"));
|
||||||
"planName 必须是字符串或 null".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if obj.contains_key("extra") && !result["extra"].is_null() && !result["extra"].is_string() {
|
if obj.contains_key("extra") && !result["extra"].is_null() && !result["extra"].is_string() {
|
||||||
return Err(AppError::InvalidInput("extra 必须是字符串或 null".into()));
|
return Err(AppError::localized("usage_script.extra_type_error", "extra 必须是字符串或 null", "extra must be string or null"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -25,9 +25,16 @@ interface UsageScriptModalProps {
|
|||||||
onSave: (script: UsageScript) => void;
|
onSave: (script: UsageScript) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预设模板(JS 对象字面量格式)
|
// 预设模板键名(用于国际化)
|
||||||
const PRESET_TEMPLATES: Record<string, string> = {
|
const TEMPLATE_KEYS = {
|
||||||
自定义: `({
|
CUSTOM: "custom",
|
||||||
|
GENERAL: "general",
|
||||||
|
NEW_API: "newapi",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// 生成预设模板的函数(支持国际化)
|
||||||
|
const generatePresetTemplates = (t: (key: string) => string): Record<string, string> => ({
|
||||||
|
[TEMPLATE_KEYS.CUSTOM]: `({
|
||||||
request: {
|
request: {
|
||||||
url: "",
|
url: "",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -41,7 +48,7 @@ const PRESET_TEMPLATES: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
})`,
|
})`,
|
||||||
|
|
||||||
通用模板: `({
|
[TEMPLATE_KEYS.GENERAL]: `({
|
||||||
request: {
|
request: {
|
||||||
url: "{{baseUrl}}/user/balance",
|
url: "{{baseUrl}}/user/balance",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -59,7 +66,7 @@ const PRESET_TEMPLATES: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
})`,
|
})`,
|
||||||
|
|
||||||
NewAPI: `({
|
[TEMPLATE_KEYS.NEW_API]: `({
|
||||||
request: {
|
request: {
|
||||||
url: "{{baseUrl}}/api/user/self",
|
url: "{{baseUrl}}/api/user/self",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -72,7 +79,7 @@ const PRESET_TEMPLATES: Record<string, string> = {
|
|||||||
extractor: function (response) {
|
extractor: function (response) {
|
||||||
if (response.success && response.data) {
|
if (response.success && response.data) {
|
||||||
return {
|
return {
|
||||||
planName: response.data.group || "默认套餐",
|
planName: response.data.group || "${t("usageScript.defaultPlan")}",
|
||||||
remaining: response.data.quota / 500000,
|
remaining: response.data.quota / 500000,
|
||||||
used: response.data.used_quota / 500000,
|
used: response.data.used_quota / 500000,
|
||||||
total: (response.data.quota + response.data.used_quota) / 500000,
|
total: (response.data.quota + response.data.used_quota) / 500000,
|
||||||
@@ -81,10 +88,17 @@ const PRESET_TEMPLATES: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
invalidMessage: response.message || "查询失败"
|
invalidMessage: response.message || "${t("usageScript.queryFailedMessage")}"
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
})`,
|
})`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模板名称国际化键映射
|
||||||
|
const TEMPLATE_NAME_KEYS: Record<string, string> = {
|
||||||
|
[TEMPLATE_KEYS.CUSTOM]: "usageScript.templateCustom",
|
||||||
|
[TEMPLATE_KEYS.GENERAL]: "usageScript.templateGeneral",
|
||||||
|
[TEMPLATE_KEYS.NEW_API]: "usageScript.templateNewAPI",
|
||||||
};
|
};
|
||||||
|
|
||||||
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||||
@@ -95,16 +109,16 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
onSave,
|
onSave,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// 生成带国际化的预设模板
|
||||||
|
const PRESET_TEMPLATES = generatePresetTemplates(t);
|
||||||
|
|
||||||
const [script, setScript] = useState<UsageScript>(() => {
|
const [script, setScript] = useState<UsageScript>(() => {
|
||||||
return (
|
return (
|
||||||
provider.meta?.usage_script || {
|
provider.meta?.usage_script || {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
language: "javascript",
|
language: "javascript",
|
||||||
code: PRESET_TEMPLATES[
|
code: PRESET_TEMPLATES[TEMPLATE_KEYS.GENERAL],
|
||||||
t("usageScript.presetTemplate") === "预设模板"
|
|
||||||
? "通用模板"
|
|
||||||
: "General"
|
|
||||||
],
|
|
||||||
timeout: 10,
|
timeout: 10,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -118,7 +132,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
() => {
|
() => {
|
||||||
const existingScript = provider.meta?.usage_script;
|
const existingScript = provider.meta?.usage_script;
|
||||||
if (existingScript?.accessToken || existingScript?.userId) {
|
if (existingScript?.accessToken || existingScript?.userId) {
|
||||||
return "NewAPI";
|
return TEMPLATE_KEYS.NEW_API;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -210,7 +224,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
const preset = PRESET_TEMPLATES[presetName];
|
const preset = PRESET_TEMPLATES[presetName];
|
||||||
if (preset) {
|
if (preset) {
|
||||||
// 如果选择的不是 NewAPI 模板,清空高级配置字段
|
// 如果选择的不是 NewAPI 模板,清空高级配置字段
|
||||||
if (presetName !== "NewAPI") {
|
if (presetName !== TEMPLATE_KEYS.NEW_API) {
|
||||||
setScript({
|
setScript({
|
||||||
...script,
|
...script,
|
||||||
code: preset,
|
code: preset,
|
||||||
@@ -225,7 +239,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 判断是否应该显示高级配置(仅 NewAPI 模板需要)
|
// 判断是否应该显示高级配置(仅 NewAPI 模板需要)
|
||||||
const shouldShowAdvancedConfig = selectedTemplate === "NewAPI";
|
const shouldShowAdvancedConfig = selectedTemplate === TEMPLATE_KEYS.NEW_API;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||||
@@ -273,7 +287,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700"
|
: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{name}
|
{t(TEMPLATE_NAME_KEYS[name])}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -285,7 +299,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
<div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
|
<div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
访问令牌
|
{t("usageScript.accessToken")}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -293,14 +307,14 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setScript({ ...script, accessToken: e.target.value })
|
setScript({ ...script, accessToken: e.target.value })
|
||||||
}
|
}
|
||||||
placeholder="在“安全设置”里生成"
|
placeholder={t("usageScript.accessTokenPlaceholder")}
|
||||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
用户 ID
|
{t("usageScript.userId")}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -308,7 +322,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setScript({ ...script, userId: e.target.value })
|
setScript({ ...script, userId: e.target.value })
|
||||||
}
|
}
|
||||||
placeholder="例如:114514"
|
placeholder={t("usageScript.userIdPlaceholder")}
|
||||||
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
className="mt-1 w-full px-3 py-2 border border-border-default dark:border-border-default rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -373,10 +387,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
"Authorization": "Bearer {{apiKey}}",
|
"Authorization": "Bearer {{apiKey}}",
|
||||||
"User-Agent": "cc-switch/1.0"
|
"User-Agent": "cc-switch/1.0"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ key: "value" }) // 可选
|
body: JSON.stringify({ key: "value" }) // ${t("usageScript.commentOptional")}
|
||||||
},
|
},
|
||||||
extractor: function(response) {
|
extractor: function(response) {
|
||||||
// response 是 API 返回的 JSON 数据
|
// ${t("usageScript.commentResponseIsJson")}
|
||||||
return {
|
return {
|
||||||
isValid: !response.error,
|
isValid: !response.error,
|
||||||
remaining: response.balance,
|
remaining: response.balance,
|
||||||
|
|||||||
@@ -344,10 +344,21 @@
|
|||||||
"title": "Configure Usage Query",
|
"title": "Configure Usage Query",
|
||||||
"enableUsageQuery": "Enable usage query",
|
"enableUsageQuery": "Enable usage query",
|
||||||
"presetTemplate": "Preset template",
|
"presetTemplate": "Preset template",
|
||||||
|
"templateCustom": "Custom",
|
||||||
|
"templateGeneral": "General",
|
||||||
|
"templateNewAPI": "NewAPI",
|
||||||
|
"accessToken": "Access Token",
|
||||||
|
"accessTokenPlaceholder": "Generate in 'Security Settings'",
|
||||||
|
"userId": "User ID",
|
||||||
|
"userIdPlaceholder": "e.g., 114514",
|
||||||
|
"defaultPlan": "Default Plan",
|
||||||
|
"queryFailedMessage": "Query failed",
|
||||||
"queryScript": "Query script (JavaScript)",
|
"queryScript": "Query script (JavaScript)",
|
||||||
"timeoutSeconds": "Timeout (seconds)",
|
"timeoutSeconds": "Timeout (seconds)",
|
||||||
"scriptHelp": "Script writing instructions:",
|
"scriptHelp": "Script writing instructions:",
|
||||||
"configFormat": "Configuration format:",
|
"configFormat": "Configuration format:",
|
||||||
|
"commentOptional": "optional",
|
||||||
|
"commentResponseIsJson": "response is the JSON data returned by the API",
|
||||||
"extractorFormat": "Extractor return format (all fields optional):",
|
"extractorFormat": "Extractor return format (all fields optional):",
|
||||||
"tips": "💡 Tips:",
|
"tips": "💡 Tips:",
|
||||||
"testing": "Testing...",
|
"testing": "Testing...",
|
||||||
|
|||||||
@@ -344,10 +344,21 @@
|
|||||||
"title": "配置用量查询",
|
"title": "配置用量查询",
|
||||||
"enableUsageQuery": "启用用量查询",
|
"enableUsageQuery": "启用用量查询",
|
||||||
"presetTemplate": "预设模板",
|
"presetTemplate": "预设模板",
|
||||||
|
"templateCustom": "自定义",
|
||||||
|
"templateGeneral": "通用模板",
|
||||||
|
"templateNewAPI": "NewAPI",
|
||||||
|
"accessToken": "访问令牌",
|
||||||
|
"accessTokenPlaceholder": "在'安全设置'里生成",
|
||||||
|
"userId": "用户 ID",
|
||||||
|
"userIdPlaceholder": "例如:114514",
|
||||||
|
"defaultPlan": "默认套餐",
|
||||||
|
"queryFailedMessage": "查询失败",
|
||||||
"queryScript": "查询脚本(JavaScript)",
|
"queryScript": "查询脚本(JavaScript)",
|
||||||
"timeoutSeconds": "超时时间(秒)",
|
"timeoutSeconds": "超时时间(秒)",
|
||||||
"scriptHelp": "脚本编写说明:",
|
"scriptHelp": "脚本编写说明:",
|
||||||
"configFormat": "配置格式:",
|
"configFormat": "配置格式:",
|
||||||
|
"commentOptional": "可选",
|
||||||
|
"commentResponseIsJson": "response 是 API 返回的 JSON 数据",
|
||||||
"extractorFormat": "extractor 返回格式(所有字段均为可选):",
|
"extractorFormat": "extractor 返回格式(所有字段均为可选):",
|
||||||
"tips": "💡 提示:",
|
"tips": "💡 提示:",
|
||||||
"testing": "测试中...",
|
"testing": "测试中...",
|
||||||
|
|||||||
Reference in New Issue
Block a user