refactor: optimize backend module

This commit is contained in:
shiyu
2025-12-08 17:46:45 +08:00
parent cf8d10f71c
commit 8f515aaaf4
124 changed files with 6884 additions and 6390 deletions

68
web/src/api/audit.ts Normal file
View File

@@ -0,0 +1,68 @@
import request from './client';
export interface AuditLogItem {
id: number;
created_at: string;
action: string;
description?: string | null;
user_id?: number | null;
username?: string | null;
client_ip?: string | null;
method: string;
path: string;
status_code: number;
duration_ms?: number | null;
success: boolean;
request_params?: Record<string, any> | null;
request_body?: Record<string, any> | null;
error?: string | null;
}
export interface PaginatedAuditLogs {
items: AuditLogItem[];
total: number;
page: number;
page_size: number;
pages: number;
}
export interface GetAuditLogsParams {
page?: number;
page_size?: number;
action?: string;
success?: boolean;
username?: string;
path?: string;
start_time?: string;
end_time?: string;
}
export interface ClearAuditLogsParams {
start_time?: string;
end_time?: string;
}
export const auditApi = {
list: (params: GetAuditLogsParams = {}) => {
const query = new URLSearchParams();
if (params.page) query.append('page', params.page.toString());
if (params.page_size) query.append('page_size', params.page_size.toString());
if (params.action) query.append('action', params.action);
if (params.success !== undefined && params.success !== null) query.append('success', String(params.success));
if (params.username) query.append('username', params.username);
if (params.path) query.append('path', params.path);
if (params.start_time) query.append('start_time', params.start_time);
if (params.end_time) query.append('end_time', params.end_time);
const qs = query.toString();
return request<PaginatedAuditLogs>(`/audit/logs${qs ? `?${qs}` : ''}`);
},
clear: (params: ClearAuditLogsParams = {}) => {
const query = new URLSearchParams();
if (params.start_time) query.append('start_time', params.start_time);
if (params.end_time) query.append('end_time', params.end_time);
const qs = query.toString();
return request<{ deleted_count: number }>(`/audit/logs${qs ? `?${qs}` : ''}`, {
method: 'DELETE',
});
},
};

View File

@@ -1,54 +0,0 @@
import request from './client';
export interface LogItem {
id: number;
timestamp: string;
level: string;
source: string;
message: string;
details: Record<string, any>;
user_id?: number;
}
export interface PaginatedLogs {
items: LogItem[];
total: number;
page: number;
page_size: number;
pages: number;
}
export interface GetLogsParams {
page?: number;
page_size?: number;
level?: string;
source?: string;
start_time?: string;
end_time?: string;
}
export interface ClearLogsParams {
start_time?: string;
end_time?: string;
}
export const logsApi = {
list: (params: GetLogsParams = {}) => {
const query = new URLSearchParams();
if (params.page) query.append('page', params.page.toString());
if (params.page_size) query.append('page_size', params.page_size.toString());
if (params.level) query.append('level', params.level);
if (params.source) query.append('source', params.source);
if (params.start_time) query.append('start_time', params.start_time);
if (params.end_time) query.append('end_time', params.end_time);
return request<PaginatedLogs>(`/logs?${query.toString()}`);
},
clear: (params: ClearLogsParams = {}) => {
const query = new URLSearchParams();
if (params.start_time) query.append('start_time', params.start_time);
if (params.end_time) query.append('end_time', params.end_time);
return request<{ deleted_count: number }>(`/logs?${query.toString()}`, {
method: 'DELETE',
});
},
};

View File

@@ -139,6 +139,6 @@ export const vfsApi = {
});
if (page !== undefined) params.set('page', String(page));
if (page_size !== undefined) params.set('page_size', String(page_size));
return request<SearchResponse>(`/search?${params.toString()}`);
return request<SearchResponse>(`/fs/search?${params.toString()}`);
},
};

View File

@@ -1,14 +1,14 @@
import { createContext, useContext, useMemo, useState, useEffect } from 'react';
import type { PropsWithChildren } from 'react';
import { zh } from './locales/zh';
import { en } from './locales/en';
import en from './locales/en.json';
import zhOverrides from './locales/zh.json';
type Lang = 'zh' | 'en';
type Dict = Record<string, string>;
const dicts: Record<Lang, Dict> = {
zh,
en,
zh: { ...en, ...zhOverrides },
};
export interface I18nContextValue {

View File

@@ -0,0 +1,662 @@
{
"All Files": "All Files",
"Manage": "Manage",
"Follow System": "System",
"Automation": "Automation",
"My Shares": "My Shares",
"Offline Downloads": "Offline Downloads",
"Adapters": "Adapters",
"Plugins": "App Center",
"System Settings": "System Settings",
"Backup & Restore": "Backup & Restore",
"System Logs": "System Logs",
"Audit Logs": "Audit Logs",
"Audit Log Details": "Audit Log Details",
"Search files / tags / types": "Search files / tags / types",
"Log Out": "Log Out",
"Admin": "Admin",
"Profile": "Profile",
"Account Settings": "Account Settings",
"Language": "Language",
"Chinese": "中文",
"English": "English",
"Full Name": "Full Name",
"Email": "Email",
"Change Password": "Change Password",
"Old Password": "Old Password",
"New Password": "New Password",
"Please fill both old and new password": "Please fill both old and new password",
"Welcome Back": "Welcome Back",
"Sign in to your Foxel account": "Sign in to your Foxel account",
"Username / Email": "Username / Email",
"Password": "Password",
"Sign In": "Sign In",
"Please enter username and password": "Please enter username and password",
"Login failed": "Login failed",
"Your next-generation file manager": "Your next-generation file manager",
"Cross-platform sync, access anywhere": "Cross-platform sync, access anywhere",
"AI-powered search for quick find": "AI-powered search for quick find",
"Flexible sharing and collaboration": "Flexible sharing and collaboration",
"Powerful automation to simplify tasks": "Powerful automation to simplify tasks",
"Join our community:": "Join our community:",
"Refresh": "Refresh",
"Copy": "Copy",
"Copied link": "Link copied",
"Share canceled": "Share canceled",
"Cancel failed": "Cancel failed",
"Load failed": "Load failed",
"Are you sure to cancel share?": "Are you sure to cancel share?",
"Clear expired shares": "Clear expired shares",
"Confirm clear expired shares?": "Confirm clear expired shares?",
"Cleared {count} expired shares": "Cleared {count} expired shares",
"Please select time range": "Please select time range",
"Share Name": "Share Name",
"Share Content": "Share Content",
"Created At": "Created At",
"Expires At": "Expires At",
"Forever": "Forever",
"Access": "Access",
"Public": "Public",
"By Password": "By Password",
"Password Required": "Password Required",
"Please enter password": "Please enter password",
"Confirm": "Confirm",
"Unable to load share info": "Unable to load share info",
"Share load failed": "Failed to load share",
"Wrong password": "Wrong password",
"Root": "All Files",
"Created on {date}": "Created on {date}",
"Expires on {date}": "Expires on {date}",
"Download File": "Download File",
"Preview not supported for this file type": "Preview not supported for this file type",
"Back": "Back",
"Download": "Download",
"No offline download tasks": "No offline download tasks",
"Create Offline Download": "Create Offline Download",
"Offline Download Tasks": "Offline Download Tasks",
"URL": "URL",
"Please input URL": "Please input URL",
"Destination Folder": "Destination Folder",
"Select destination": "Select destination",
"Filename": "Filename",
"Please input filename": "Please input filename",
"Start Download": "Start Download",
"Stage": "Stage",
"Progress": "Progress",
"Bytes": "Bytes",
"Save Path": "Save Path",
"Queued": "Queued",
"Downloading": "Downloading",
"Transferring": "Transferring",
"Completed": "Completed",
"Pending": "Pending",
"Running": "Running",
"Success": "Success",
"Failed": "Failed",
"Failure": "Failure",
"Home": "Home",
"File Manager": "File Manager",
"New Folder": "New Folder",
"Upload": "Upload",
"Name": "Name",
"Size": "Size",
"Modified Time": "Modified Time",
"Grid": "Grid",
"List": "List",
"Mount Point": "Mount Point",
"Move": "Move",
"Move to": "Move to",
"Copy to": "Copy to",
"Destination path": "Destination path",
"Move task queued": "Move task queued",
"Move completed": "Move completed",
"Copy task queued": "Copy task queued",
"Copy completed": "Copy completed",
"Please input destination path": "Please input destination path",
"Upload File": "Upload File",
"Upload Files": "Upload Files",
"Upload Folder": "Upload Folder",
"Open": "Open",
"Open With": "Open With",
"Default": "Default",
"Processor": "Processor",
"Share": "Share",
"Rename": "Rename",
"Delete": "Delete",
"Details": "Details",
"Get Direct Link": "Get Direct Link",
"User": "User",
"Status Code": "Status Code",
"Duration (ms)": "Duration (ms)",
"Client IP": "Client IP",
"Result": "Result",
"Request Params": "Request Params",
"Request Body": "Request Body",
"Total progress": "Total progress",
"Upload bytes summary": "{uploaded} / {total}",
"Upload task summary": "Tasks: {completed} / {total} completed, {pending} pending, {failures} failed",
"Overwrite confirmation required": "Overwrite confirmation required",
"Target already exists: {path}": "Target already exists: {path}",
"Overwrite": "Overwrite",
"Skip": "Skip",
"Overwrite All": "Overwrite All",
"Skip All": "Skip All",
"Directory": "Directory",
"Creating directory...": "Creating directory...",
"Directory ready": "Directory ready",
"Create directory failed": "Create directory failed",
"Waiting to create": "Waiting to create",
"Waiting for overwrite decision": "Waiting for overwrite decision",
"Waiting to upload": "Waiting to upload",
"Skipped": "Skipped",
"Upload succeeded": "Upload succeeded",
"Upload failed": "Upload failed",
"No items selected for upload": "No items selected for upload",
"No uploadable files or directories found": "No uploadable files or directories found",
"Missing file content": "Missing file content",
"Directory conflicts with existing file": "A file with the same name already exists at the target location",
"Join Community": "Join Community",
"Scan to join WeChat group": "Scan to join WeChat group",
"If QR expires, add drizzle2001 to join": "If QR expires, add drizzle2001 to join",
"Version Info": "Version Info",
"Current Version": "Current Version",
"Latest Version": "Latest Version",
"New version found: {version}": "New version found: {version}",
"Please update to the latest for features and fixes": "Please update to the latest for features and fixes",
"Open Releases": "Open Releases",
"Changelog": "Changelog",
"Fetching latest version...": "Fetching latest version...",
"Update available": "Update available",
"You are on the latest: {version}": "You are on the latest: {version}",
"Up to date": "Up to date",
"Share {count} items": "Share {count} items",
"Share link created": "Share link created",
"Create failed": "Create failed",
"Copied to clipboard": "Copied to clipboard",
"Expiration (days)": "Expiration (days)",
"Set 0 or negative for forever": "Set 0 or negative for forever",
"Share link created successfully!": "Share link created successfully!",
"Share Link": "Share Link",
"Share created": "Share created",
"Create Share": "Create Share",
"Done": "Done",
"Create": "Create",
"Failed to generate link": "Failed to generate link",
"Markdown copied to clipboard": "Markdown copied to clipboard",
"Generate a direct link for {name}": "Generate a direct link for {name}",
"1 hour": "1 hour",
"1 day": "1 day",
"7 days": "7 days",
"Generating link...": "Generating link...",
"Link will appear here": "Link will appear here",
"Copy Markdown": "Copy Markdown",
"Close": "Close",
"Task Queue": "Task Queue",
"Last updated at {time}": "Last updated at {time}",
"Total Tasks": "Total Tasks",
"Running Tasks": "Running Tasks",
"Waiting Tasks": "Waiting Tasks",
"Failed Tasks": "Failed Tasks",
"Active Workers": "Active Workers",
"Task Type": "Task Type",
"Search by name or ID": "Search by name or ID",
"Filter by status": "Filter by status",
"Queue Concurrency": "Queue Concurrency",
"Settings saved": "Settings saved",
"Expand": "Expand",
"Adjust worker concurrency immediately": "Adjust worker concurrency immediately",
"Auto": "Auto",
"Manual": "Manual",
"Camera Make": "Camera Make",
"Camera Model": "Camera Model",
"Capture Time": "Capture Time",
"X Resolution": "X Resolution",
"Y Resolution": "Y Resolution",
"Exposure Time": "Exposure Time",
"Aperture": "Aperture",
"Focal Length": "Focal Length",
"Width": "Width",
"Height": "Height",
"No common EXIF info": "No common EXIF info",
"File Properties": "File Properties",
"Loading file info...": "Loading file info...",
"Basic Info": "Basic Info",
"Type": "Type",
"Folder": "Folder",
"File": "File",
"Path": "Path",
"Path copied to clipboard": "Path copied to clipboard",
"Copy failed": "Copy failed",
"Permissions": "Permissions",
"EXIF Info": "EXIF Info",
"Index Info": "Index Info",
"Indexed Items": "Indexed Items",
"Indexed Types": "Indexed Types",
"No index data": "No index data",
"Indexed Chunks": "Indexed Chunks",
"More Indexed Chunks": "More Indexed Chunks",
"Chunk ID": "Chunk ID",
"Offset Range": "Offset Range",
"Vector ID": "Vector ID",
"Preview": "Preview",
"Showing first {count} entries": "Showing first {count} entries",
"Smart Search": "Smart Search",
"Name Search": "Name Search",
"Search Results": "Search Results",
"No files found": "No files found",
"Relevance": "Relevance",
"Saved successfully": "Saved successfully",
"Save failed": "Save failed",
"Loading...": "Loading...",
"Appearance Settings": "Appearance Settings",
"Theme": "Theme",
"Theme Mode": "Theme Mode",
"Light": "Light",
"Dark": "Dark",
"Primary Color": "Primary Color",
"Border Radius": "Border Radius",
"Advanced": "Advanced",
"Override AntD Tokens (JSON)": "Override AntD Tokens (JSON)",
"e.g. {\"colorText\": \"#222\"}": "e.g. {\"colorText\": \"#222\"}",
"Custom CSS": "Custom CSS",
"Save": "Save",
"App Settings": "App Settings",
"Email Settings": "Email Settings",
"AI Settings": "AI Settings",
"Protocol Mappings": "Protocol Mappings",
"S3 Mapping": "S3 Mapping",
"S3 Endpoint": "S3 Endpoint",
"Bucket Name": "Bucket Name",
"Bucket API Path": "Bucket API Path",
"Region": "Region",
"Base Path": "Base Path",
"Access Key": "Access Key",
"Secret Key": "Secret Key",
"Vision Model": "Vision Model",
"Embedding Model": "Embedding Model",
"Embedding Dimension": "Embedding Dimension",
"Vector Database": "Vector Database",
"Vector Database Settings": "Vector Database Settings",
"Current Statistics": "Current Statistics",
"Collections": "Collections",
"Vectors": "Vectors",
"Database Size": "Database Size",
"Estimated Memory": "Estimated Memory",
"No collections": "No collections",
"Dimension": "Dimension",
"Non-vector collection": "Non-vector collection",
"Estimated memory": "Estimated memory",
"Indexes": "Indexes",
"Unnamed index": "Unnamed index",
"Indexed rows": "Indexed rows",
"Pending rows": "Pending rows",
"Estimated memory is calculated as vectors x dimension x 4 bytes (float32).": "Estimated memory is calculated as vectors x dimension x 4 bytes (float32).",
"Database Provider": "Database Provider",
"Please select a provider": "Please select a provider",
"Coming soon": "Coming soon",
"This provider is not available yet": "This provider is not available yet",
"Database file path": "Database file path",
"Server URI": "Server URI",
"Token": "Token",
"Server URL": "Server URL",
"API Key": "API Key",
"Embedded Milvus Lite (local file storage).": "Embedded Milvus Lite (local file storage).",
"Remote Milvus instance accessed via URI.": "Remote Milvus instance accessed via URI.",
"Qdrant vector database (HTTP API).": "Qdrant vector database (HTTP API).",
"Database Type": "Database Type",
"Confirm embedding dimension change": "Confirm embedding dimension change",
"Changing the embedding dimension will clear the vector database automatically. You will need to rebuild indexes afterwards. Continue?": "Changing the embedding dimension will clear the vector database automatically. You will need to rebuild indexes afterwards. Continue?",
"Confirm clear vector database?": "Confirm clear vector database?",
"This will delete all collections irreversibly.": "This will delete all collections irreversibly.",
"Confirm Clear": "Confirm Clear",
"Vector database cleared": "Vector database cleared",
"Clear failed": "Clear failed",
"Clear Vector DB": "Clear Vector DB",
"App Name": "App Name",
"Logo URL": "Logo URL",
"Favicon URL": "Favicon URL",
"App Domain": "App Domain",
"File Domain": "File Domain",
"Configure Access Key and Secret to enable S3 mapping.": "Configure Access Key and Secret to enable S3 mapping.",
"Mount point inside the virtual file system (e.g. / or /workspace).": "Mount point inside the virtual file system (e.g. / or /workspace).",
"Please input bucket name": "Please input bucket name",
"Please input region": "Please input region",
"Please input access key": "Please input access key",
"Please input secret key": "Please input secret key",
"Save S3 Settings": "Save S3 Settings",
"Example CLI command": "Example CLI command",
"WebDAV Mapping": "WebDAV Mapping",
"WebDAV Endpoint": "WebDAV Endpoint",
"Basic (system account password)": "Basic (system account password)",
"Root Path": "Root Path",
"Client Compatibility": "Client Compatibility",
"Supports Finder, Windows network drive, rclone, and other WebDAV clients.": "Supports Finder, Windows network drive, rclone, and other WebDAV clients.",
"Toggle the switch to expose the virtual file system via WebDAV.": "Toggle the switch to expose the virtual file system via WebDAV.",
"SMTP Settings": "SMTP Settings",
"SMTP Host": "SMTP Host",
"Please input SMTP host": "Please input SMTP host",
"SMTP Port": "SMTP Port",
"Please input SMTP port": "Please input SMTP port",
"Security": "Security",
"None": "None",
"SSL": "SSL",
"STARTTLS": "STARTTLS",
"Timeout (seconds)": "Timeout (seconds)",
"Sender": "Sender",
"Sender Name": "Sender Name",
"Sender Email": "Sender Email",
"Please input sender email": "Please input sender email",
"Authentication": "Authentication",
"SMTP Username": "SMTP Username",
"SMTP Password": "SMTP Password",
"Test Email": "Test Email",
"Current Configuration": "Current Configuration",
"Available variables": "Available variables",
"Not set": "Not set",
"Password Reset Template": "Password Reset Template",
"Live Preview": "Live Preview",
"Foxel Mail Test": "Foxel Mail Test",
"Recipient Address": "Recipient Address",
"Please input recipient email": "Please input recipient email",
"Test Subject": "Test Subject",
"Test User Name": "Test User Name",
"Optional": "Optional",
"Send Test Email": "Send Test Email",
"Please complete all required fields": "Please complete all required fields",
"SMTP port must be a positive number": "SMTP port must be a positive number",
"Test email queued (task {{taskId}})": "Test email queued (task {{taskId}})",
"Test email failed": "Test email failed",
"Forgot Password?": "Forgot password?",
"Reset Your Password": "Reset Your Password",
"Enter the email linked to your account and we will send a reset link.": "Enter the email linked to your account and we will send a reset link.",
"If the email exists, a reset link has been sent.": "If the email exists, a reset link has been sent.",
"Send Reset Link": "Send Reset Link",
"Resend Link": "Resend Link",
"Back to login": "Back to login",
"Request failed": "Request failed",
"Reset link is invalid": "Reset link is invalid",
"Reset link is invalid or expired": "Reset link is invalid or expired",
"Reset failed": "Reset failed",
"Try again": "Try again",
"Set a new password": "Set a new password",
"Please enter new password": "Please enter new password",
"Confirm Password": "Confirm Password",
"Please confirm new password": "Please confirm new password",
"Update Password": "Update Password",
"Passwords do not match": "Passwords do not match",
"Password updated, please login again.": "Password updated, please login again.",
"Failed to reset password": "Failed to reset password",
"Vision API URL": "Vision API URL",
"Vision API Key": "Vision API Key",
"Embedding API URL": "Embedding API URL",
"Embedding API Key": "Embedding API Key",
"AI Providers & Models": "AI Providers & Models",
"Manage AI providers, synchronize compatible models, and configure default capabilities across the system.": "Manage AI providers, synchronize compatible models, and configure default capabilities across the system.",
"Add Provider": "Add Provider",
"Edit Provider": "Edit Provider",
"Pull Models": "Pull Models",
"Manual Add": "Manual Add",
"Clear Remote List": "Clear Remote List",
"Select models from the list to add them automatically": "Select models from the list to add them automatically",
"No remote models": "No remote models",
"No remote models found": "No remote models found",
"No remote models match search": "No remote models match search",
"Search fetched models": "Search fetched models",
"Already Added": "Already Added",
"Add Selected Models": "Add Selected Models",
"Fetch failed": "Fetch failed",
"Select models to add": "Select models to add",
"Added {count} models": "Added {count} models",
"Choose Template": "Choose Template",
"Configure Provider": "Configure Provider",
"Back to Templates": "Back to Templates",
"View Docs": "View Docs",
"Custom Provider": "Custom Provider",
"Custom Provider Description": "Bring your own endpoint compatible with OpenAI or Gemini formats.",
"OpenAI Provider": "OpenAI",
"OpenAI Provider Description": "Access GPT-4o, GPT-4.1, GPT-3.5 and more models from OpenAI.",
"Azure OpenAI Provider": "Azure OpenAI",
"Azure OpenAI Provider Description": "Use OpenAI models deployed on Microsoft Azure.",
"Google AI Provider": "Google AI",
"Google AI Provider Description": "Gemini series models served via the Google AI platform.",
"SiliconFlow Provider": "SiliconFlow",
"SiliconFlow Provider Description": "High-performance inference platform with OpenAI-compatible APIs.",
"OpenRouter Provider": "OpenRouter",
"OpenRouter Provider Description": "Connect to multiple AI providers through a single OpenAI-style endpoint.",
"Anthropic Provider": "Anthropic",
"Anthropic Provider Description": "Claude 3 family models exposed through the Claude API.",
"DeepSeek Provider": "DeepSeek",
"DeepSeek Provider Description": "DeepSeek language models via OpenAI-compatible API.",
"Grok Provider": "Grok (xAI)",
"Grok Provider Description": "Grok models powered by xAI with OpenAI-style routes.",
"Ollama Provider": "Ollama",
"Ollama Provider Description": "Self-host and run models locally with Ollama's OpenAI bridge.",
"Voyage Provider": "Voyage AI",
"Voyage Provider Description": "High-quality embeddings and rerankers from Voyage AI.",
"Delete provider?": "Delete provider?",
"Deleting this provider will also remove all associated models. Continue?": "Deleting this provider will also remove all associated models. Continue?",
"Deleted successfully": "Deleted successfully",
"Sync Models": "Sync Models",
"Sync completed: {created} created, {updated} updated": "Sync completed: {created} created, {updated} updated",
"Sync failed": "Sync failed",
"Add Model": "Add Model",
"Edit Model": "Edit Model",
"Delete model?": "Delete model?",
"This operation cannot be undone. Continue?": "This operation cannot be undone. Continue?",
"No models yet": "No models yet",
"Add your first AI provider to get started": "Add your first AI provider to get started",
"Default Models Configuration": "Default Models Configuration",
"Main Chat Model": "Main Chat Model",
"Primary assistant for conversations, reasoning, and tool calls.": "Primary assistant for conversations, reasoning, and tool calls.",
"Handles multimodal perception such as image understanding.": "Handles multimodal perception such as image understanding.",
"Transforms content into dense vectors for search and retrieval.": "Transforms content into dense vectors for search and retrieval.",
"Optimises ranking quality for search candidates.": "Optimises ranking quality for search candidates.",
"Covers text-to-speech and speech understanding scenarios.": "Covers text-to-speech and speech understanding scenarios.",
"Supports function calling, orchestration, and automation.": "Supports function calling, orchestration, and automation.",
"Select a model": "Select a model",
"Template": "Template",
"Select a template": "Select a template",
"Display Name": "Display Name",
"Enter name": "Enter name",
"Identifier": "Identifier",
"Enter identifier": "Enter identifier",
"Only lowercase letters, numbers, dash, dot and underscore are allowed": "Only lowercase letters, numbers, dash, dot and underscore are allowed",
"API Format": "API Format",
"Base URL": "Base URL",
"Enter base url": "Enter base URL",
"Optional, can also be provided per request": "Optional, can also be provided per request",
"Model Identifier": "Model Identifier",
"Enter model identifier": "Enter model identifier",
"Description": "Description",
"Capabilities": "Capabilities",
"Context Window": "Context Window",
"Embedding Dimensions": "Embedding Dimensions",
"Price /1K input tokens": "Price /1K input tokens",
"Price /1K output tokens": "Price /1K output tokens",
"Missing required config:": "Missing required config:",
"Updated successfully": "Updated successfully",
"Created successfully": "Created successfully",
"Operation failed": "Operation failed",
"Deleted": "Deleted",
"Delete failed": "Delete failed",
"Status updated": "Status updated",
"Update failed": "Update failed",
"Mount Path": "Mount Path",
"Sub Path": "Sub Path",
"Sub Path (optional)": "Sub Path (optional)",
"Sub directory inside adapter": "Sub directory inside adapter",
"Enabled": "Enabled",
"Actions": "Actions",
"Edit": "Edit",
"Confirm delete?": "Confirm delete?",
"No config fields": "No config fields",
"Please input {label}": "Please input {label}",
"Storage Adapters": "Storage Adapters",
"Create Adapter": "Create Adapter",
"Unique name": "Unique name",
"Select adapter type": "Select adapter type",
"/ or /drive": "/ or /drive",
"Adapter Config": "Adapter Config",
"adapter.type.local": "Local Filesystem",
"adapter.type.webdav": "WebDAV",
"adapter.type.googledrive": "Google Drive",
"adapter.type.onedrive": "OneDrive",
"adapter.type.s3": "Amazon S3",
"adapter.type.ftp": "FTP",
"adapter.type.sftp": "SFTP",
"adapter.type.telegram": "Telegram",
"adapter.type.quark": "Quark Drive",
"Automation Tasks": "Automation Tasks",
"Create Task": "Create Task",
"Edit Task": "Edit Task",
"Create Automation Task": "Create Automation Task",
"Task Name": "Task Name",
"Trigger Event": "Trigger Event",
"File Written": "File Written",
"File Deleted": "File Deleted",
"Matching Rules": "Matching Rules",
"Path Prefix (optional)": "Path Prefix (optional)",
"Filename Regex (optional)": "Filename Regex (optional)",
"Action": "Action",
"Current Task Queue": "Current Task Queue",
"Params": "Params",
"Status": "Status",
"Confirm clear logs?": "Confirm clear logs?",
"This will delete logs in selected range irreversibly.": "This will delete logs in selected range irreversibly.",
"Cleared {count} logs": "Cleared {count} logs",
"Time": "Time",
"Level": "Level",
"Source": "Source",
"Message": "Message",
"User ID": "User ID",
"Search source": "Search source",
"Clear": "Clear",
"Log Details": "Log Details",
"Raw Log": "Raw Log",
"Export started, check your downloads.": "Export started, check your downloads.",
"Export failed": "Export failed",
"Confirm import backup?": "Confirm import backup?",
"Are you sure to import from this file?": "Are you sure to import from this file?",
"Warning: This will overwrite all data including users (with passwords), settings, storages and tasks. Irreversible!": "Warning: This will overwrite all data including users (with passwords), settings, storages and tasks. Irreversible!",
"Confirm Import": "Confirm Import",
"Import succeeded! The page will refresh.": "Import succeeded! The page will refresh.",
"Import failed": "Import failed",
"Export": "Export",
"Import": "Import",
"Export all data (adapters, users, tasks, shares) into a JSON file.": "Export all data (adapters, users, tasks, shares) into a JSON file.",
"Keep your backup file safe.": "Keep your backup file safe.",
"Export Backup": "Export Backup",
"Restore data from a previously exported JSON file.": "Restore data from a previously exported JSON file.",
"Warning: This will clear and overwrite existing data.": "Warning: This will clear and overwrite existing data.",
"Choose File and Restore": "Choose File and Restore",
"No files yet here": "No files yet here",
"This folder is empty": "This folder is empty",
"Start uploading files or create folders to organize your content": "Start uploading files or create folders to organize your content",
"You can create folders or upload files here": "You can create folders or upload files here",
"Please input name": "Please input name",
"Confirm delete {name}?": "Confirm delete {name}?",
"items": "items",
"Downloading folders is not supported": "Downloading folders is not supported",
"Download failed": "Download failed",
"Please select files or folders to share": "Please select files or folders to share",
"Direct links for folders are not supported": "Direct links for folders are not supported",
"Processing finished": "Processing finished",
"Processing failed": "Processing failed",
"Processors": "Processors",
"Processor List": "Processor List",
"Reload": "Reload",
"Run Processor": "Run Processor",
"Target Path": "Target Path",
"Please select a path": "Please select a path",
"Select Directory": "Select Directory",
"Overwrite original": "Overwrite original",
"Save To": "Save To",
"Optional output path": "Optional output path",
"Run": "Run",
"Select a processor": "Select a processor",
"No module path": "No module path",
"Source saved": "Source saved",
"Processors reloaded": "Processors reloaded",
"Unsaved changes": "Unsaved changes",
"Switching processor will discard unsaved changes. Continue?": "Switching processor will discard unsaved changes. Continue?",
"Task submitted": "Task submitted",
"Supported Extensions": "Supported Extensions",
"All": "All",
"Produces File": "Produces File",
"Yes": "Yes",
"No": "No",
"Please select a processor": "Please select a processor",
"Select a path": "Select a path",
"Source Editor": "Source Editor",
"Module Path": "Module Path",
"Directory processing always overwrites original files": "Directory processing always overwrites original files",
"No data": "No data",
"Select File": "Select File",
"Select Path": "Select Path",
"Select Folder": "Select Folder",
"Select": "Select",
"Current": "Current",
"Up": "Up",
"Select Current Folder": "Select Current Folder",
"Please select a file": "Please select a file",
"Installed successfully": "Installed successfully",
"Plugin": "Plugin",
"Open Link": "Open Link",
"Link copied": "Link copied",
"Copy Link": "Copy Link",
"Confirm delete this plugin?": "Confirm delete this plugin?",
"Author": "Author",
"Website": "Website",
"Install App": "Install App",
"Search name/author/url/extension": "Search name/author/url/extension",
"No plugins": "No plugins",
"Install": "Install",
"App URL": "App URL",
"Please input a valid URL": "Please input a valid URL",
"Installed": "Installed",
"Discover": "Discover",
"Search apps": "Search apps",
"Sort by": "Sort by",
"Downloads": "Downloads",
"Created (newest)": "Created (newest)",
"Installed already": "Installed",
"No results": "No results",
"Initialization succeeded! Logging you in...": "Initialization succeeded! Logging you in...",
"Initialization failed, please try later": "Initialization failed, please try later",
"Database Setup": "Database Setup",
"Choose database driver": "Choose database driver",
"Select database and vector database for system data": "Select database and vector database for system data",
"Database Driver": "Database Driver",
"Vector DB Driver": "Vector DB Driver",
"Initialize Mount": "Initialize Mount",
"Configure initial storage": "Configure initial storage",
"Create the first storage mount for your files": "Create the first storage mount for your files",
"Mount Name": "Mount Name",
"Local Storage": "Local Storage",
"Please input mount name!": "Please input mount name!",
"Storage Type": "Storage Type",
"Please input mount path!": "Please input mount path!",
"Root Directory": "Root Directory",
"Please input root directory!": "Please input root directory!",
"e.g., data/ or /var/foxel/data": "e.g., data/ or /var/foxel/data",
"Optional, used for external links. Leave empty to use the current site.": "Optional, used for external links. Leave empty to use the current site.",
"Create Admin": "Create Admin",
"Create admin account": "Create admin account",
"This is the first account with full permissions": "This is the first account with full permissions",
"Username": "Username",
"Please input a valid email!": "Please input a valid email!",
"Please confirm your password!": "Please confirm your password!",
"Passwords do not match!": "Passwords do not match!",
"System Initialization": "System Initialization",
"Previous": "Previous",
"Next": "Next",
"Finish Initialization": "Finish Initialization",
"Plugin run failed": "Plugin run failed",
"Plugin Error": "Plugin Error",
"Cannot open file: no available app": "Cannot open file: no available app",
"Error": "Error",
"App \"{key}\" not found.": "App \"{key}\" not found.",
"Open with {app}": "Open with {app}",
"Set as default for .{ext}": "Set as default for .{ext}",
"Advanced tokens must be valid JSON": "Advanced tokens must be valid JSON"
}

View File

@@ -1,712 +0,0 @@
export const en = {
// General
'All Files': 'All Files',
'Manage': 'Manage',
// 'System' defined above for navigation
'Follow System': 'System',
'Automation': 'Automation',
'My Shares': 'My Shares',
'Offline Downloads': 'Offline Downloads',
'Adapters': 'Adapters',
'Plugins': 'App Center',
'System Settings': 'System Settings',
'Backup & Restore': 'Backup & Restore',
'System Logs': 'System Logs',
// Top header
'Search files / tags / types': 'Search files / tags / types',
'Log Out': 'Log Out',
'Admin': 'Admin',
'Profile': 'Profile',
'Account Settings': 'Account Settings',
'Language': 'Language',
'Chinese': '中文',
'English': 'English',
'Full Name': 'Full Name',
'Email': 'Email',
'Change Password': 'Change Password',
'Old Password': 'Old Password',
'New Password': 'New Password',
'Please fill both old and new password': 'Please fill both old and new password',
// Auth / Login
'Welcome Back': 'Welcome Back',
'Sign in to your Foxel account': 'Sign in to your Foxel account',
'Username / Email': 'Username / Email',
'Password': 'Password',
'Sign In': 'Sign In',
'Please enter username and password': 'Please enter username and password',
'Login failed': 'Login failed',
'Your next-generation file manager': 'Your next-generation file manager',
'Cross-platform sync, access anywhere': 'Cross-platform sync, access anywhere',
'AI-powered search for quick find': 'AI-powered search for quick find',
'Flexible sharing and collaboration': 'Flexible sharing and collaboration',
'Powerful automation to simplify tasks': 'Powerful automation to simplify tasks',
'Join our community:': 'Join our community:',
// Share page
'Refresh': 'Refresh',
'Copy': 'Copy',
// 'Cancel' already defined above
'Copied link': 'Link copied',
'Share canceled': 'Share canceled',
'Cancel failed': 'Cancel failed',
'Load failed': 'Load failed',
'Are you sure to cancel share?': 'Are you sure to cancel share?',
'Clear expired shares': 'Clear expired shares',
'Confirm clear expired shares?': 'Confirm clear expired shares?',
'Cleared {count} expired shares': 'Cleared {count} expired shares',
'Share Name': 'Share Name',
'Share Content': 'Share Content',
'Created At': 'Created At',
'Expires At': 'Expires At',
'Forever': 'Forever',
'Access': 'Access',
'Public': 'Public',
'By Password': 'By Password',
// Public share page
'Password Required': 'Password Required',
'Please enter password': 'Please enter password',
'Confirm': 'Confirm',
'Unable to load share info': 'Unable to load share info',
'Share load failed': 'Failed to load share',
'Wrong password': 'Wrong password',
'Root': 'All Files',
'Created on {date}': 'Created on {date}',
'Expires on {date}': 'Expires on {date}',
'Download File': 'Download File',
'Preview not supported for this file type': 'Preview not supported for this file type',
'Back': 'Back',
'Download': 'Download',
// Offline download
'No offline download tasks': 'No offline download tasks',
'Create Offline Download': 'Create Offline Download',
'Offline Download Tasks': 'Offline Download Tasks',
'URL': 'URL',
'Please input URL': 'Please input URL',
'Destination Folder': 'Destination Folder',
'Select destination': 'Select destination',
'Filename': 'Filename',
'Please input filename': 'Please input filename',
'Start Download': 'Start Download',
'Stage': 'Stage',
'Progress': 'Progress',
'Bytes': 'Bytes',
'Save Path': 'Save Path',
'Queued': 'Queued',
'Downloading': 'Downloading',
'Transferring': 'Transferring',
'Completed': 'Completed',
'Pending': 'Pending',
'Running': 'Running',
'Success': 'Success',
'Failed': 'Failed',
// Header/File Explorer
'Home': 'Home',
'File Manager': 'File Manager',
'New Folder': 'New Folder',
'Upload': 'Upload',
'Name': 'Name',
'Size': 'Size',
'Modified Time': 'Modified Time',
'Grid': 'Grid',
'List': 'List',
'Mount Point': 'Mount Point',
'Move': 'Move',
'Move to': 'Move to',
'Copy to': 'Copy to',
'Destination path': 'Destination path',
'Move task queued': 'Move task queued',
'Move completed': 'Move completed',
'Copy task queued': 'Copy task queued',
'Copy completed': 'Copy completed',
'Please input destination path': 'Please input destination path',
// Context menu
'Upload File': 'Upload File',
'Upload Files': 'Upload Files',
'Upload Folder': 'Upload Folder',
'Open': 'Open',
'Open With': 'Open With',
'Default': 'Default',
'Processor': 'Processor',
'Share': 'Share',
'Rename': 'Rename',
'Delete': 'Delete',
'Details': 'Details',
'Get Direct Link': 'Get Direct Link',
// Upload modal
'Total progress': 'Total progress',
'Upload bytes summary': '{uploaded} / {total}',
'Upload task summary': 'Tasks: {completed} / {total} completed, {pending} pending, {failures} failed',
'Overwrite confirmation required': 'Overwrite confirmation required',
'Target already exists: {path}': 'Target already exists: {path}',
'Overwrite': 'Overwrite',
'Skip': 'Skip',
'Overwrite All': 'Overwrite All',
'Skip All': 'Skip All',
'Directory': 'Directory',
'Creating directory...': 'Creating directory...',
'Directory ready': 'Directory ready',
'Create directory failed': 'Create directory failed',
'Waiting to create': 'Waiting to create',
'Waiting for overwrite decision': 'Waiting for overwrite decision',
'Waiting to upload': 'Waiting to upload',
'Skipped': 'Skipped',
'Upload succeeded': 'Upload succeeded',
'Upload failed': 'Upload failed',
'No items selected for upload': 'No items selected for upload',
'No uploadable files or directories found': 'No uploadable files or directories found',
'Missing file content': 'Missing file content',
'Directory conflicts with existing file': 'A file with the same name already exists at the target location',
// Side nav modals
'Join Community': 'Join Community',
'Scan to join WeChat group': 'Scan to join WeChat group',
'If QR expires, add drizzle2001 to join': 'If QR expires, add drizzle2001 to join',
'Version Info': 'Version Info',
'Current Version': 'Current Version',
'Latest Version': 'Latest Version',
'New version found: {version}': 'New version found: {version}',
'Please update to the latest for features and fixes': 'Please update to the latest for features and fixes',
'Open Releases': 'Open Releases',
'Changelog': 'Changelog',
'Fetching latest version...': 'Fetching latest version...',
'Update available': 'Update available',
'You are on the latest: {version}': 'You are on the latest: {version}',
'Up to date': 'Up to date',
// Share modal
'Share {count} items': 'Share {count} items',
'Share link created': 'Share link created',
'Create failed': 'Create failed',
'Copied to clipboard': 'Copied to clipboard',
'Expiration (days)': 'Expiration (days)',
'Set 0 or negative for forever': 'Set 0 or negative for forever',
'Share link created successfully!': 'Share link created successfully!',
'Share Link': 'Share Link',
'Share created': 'Share created',
'Create Share': 'Create Share',
'Done': 'Done',
'Create': 'Create',
// Direct link modal
'Failed to generate link': 'Failed to generate link',
'Markdown copied to clipboard': 'Markdown copied to clipboard',
'Generate a direct link for {name}': 'Generate a direct link for {name}',
'1 hour': '1 hour',
'1 day': '1 day',
'7 days': '7 days',
'Generating link...': 'Generating link...',
'Link will appear here': 'Link will appear here',
'Copy Markdown': 'Copy Markdown',
'Close': 'Close',
// Task queue
'Task Queue': 'Task Queue',
'Last updated at {time}': 'Last updated at {time}',
'Total Tasks': 'Total Tasks',
'Running Tasks': 'Running Tasks',
'Waiting Tasks': 'Waiting Tasks',
'Failed Tasks': 'Failed Tasks',
'Active Workers': 'Active Workers',
'Task Type': 'Task Type',
'Search by name or ID': 'Search by name or ID',
'Filter by status': 'Filter by status',
'Queue Concurrency': 'Queue Concurrency',
'Settings saved': 'Settings saved',
'Expand': 'Expand',
'Adjust worker concurrency immediately': 'Adjust worker concurrency immediately',
'Auto': 'Auto',
'Manual': 'Manual',
// File detail
'Camera Make': 'Camera Make',
'Camera Model': 'Camera Model',
'Capture Time': 'Capture Time',
'X Resolution': 'X Resolution',
'Y Resolution': 'Y Resolution',
'Exposure Time': 'Exposure Time',
'Aperture': 'Aperture',
'Focal Length': 'Focal Length',
'Width': 'Width',
'Height': 'Height',
'No common EXIF info': 'No common EXIF info',
'File Properties': 'File Properties',
'Loading file info...': 'Loading file info...',
'Basic Info': 'Basic Info',
'Type': 'Type',
'Folder': 'Folder',
'File': 'File',
'Path': 'Path',
'Path copied to clipboard': 'Path copied to clipboard',
'Copy failed': 'Copy failed',
'Permissions': 'Permissions',
'EXIF Info': 'EXIF Info',
'Index Info': 'Index Info',
'Indexed Items': 'Indexed Items',
'Indexed Types': 'Indexed Types',
'No index data': 'No index data',
'Indexed Chunks': 'Indexed Chunks',
'More Indexed Chunks': 'More Indexed Chunks',
'Chunk ID': 'Chunk ID',
'Offset Range': 'Offset Range',
'Vector ID': 'Vector ID',
'Preview': 'Preview',
'Showing first {count} entries': 'Showing first {count} entries',
// Search dialog
'Smart Search': 'Smart Search',
'Name Search': 'Name Search',
'Search Results': 'Search Results',
'No files found': 'No files found',
'Relevance': 'Relevance',
// System settings
'Saved successfully': 'Saved successfully',
'Save failed': 'Save failed',
'Loading...': 'Loading...',
'Appearance Settings': 'Appearance Settings',
'Theme': 'Theme',
'Theme Mode': 'Theme Mode',
'Light': 'Light',
'Dark': 'Dark',
// 'Follow System' used for theme mode
'Primary Color': 'Primary Color',
'Border Radius': 'Border Radius',
'Advanced': 'Advanced',
'Override AntD Tokens (JSON)': 'Override AntD Tokens (JSON)',
'e.g. {"colorText": "#222"}': 'e.g. {"colorText": "#222"}',
'Custom CSS': 'Custom CSS',
'Save': 'Save',
'App Settings': 'App Settings',
'Email Settings': 'Email Settings',
'AI Settings': 'AI Settings',
'Protocol Mappings': 'Protocol Mappings',
'S3 Mapping': 'S3 Mapping',
'S3 Endpoint': 'S3 Endpoint',
'Bucket Name': 'Bucket Name',
'Bucket API Path': 'Bucket API Path',
'Region': 'Region',
'Base Path': 'Base Path',
'Access Key': 'Access Key',
'Secret Key': 'Secret Key',
'Vision Model': 'Vision Model',
'Embedding Model': 'Embedding Model',
'Embedding Dimension': 'Embedding Dimension',
'Vector Database': 'Vector Database',
'Vector Database Settings': 'Vector Database Settings',
'Current Statistics': 'Current Statistics',
'Collections': 'Collections',
'Vectors': 'Vectors',
'Database Size': 'Database Size',
'Estimated Memory': 'Estimated Memory',
'No collections': 'No collections',
'Dimension': 'Dimension',
'Non-vector collection': 'Non-vector collection',
'Estimated memory': 'Estimated memory',
'Indexes': 'Indexes',
'Unnamed index': 'Unnamed index',
'Indexed rows': 'Indexed rows',
'Pending rows': 'Pending rows',
'Estimated memory is calculated as vectors x dimension x 4 bytes (float32).': 'Estimated memory is calculated as vectors x dimension x 4 bytes (float32).',
'Database Provider': 'Database Provider',
'Please select a provider': 'Please select a provider',
'Coming soon': 'Coming soon',
'This provider is not available yet': 'This provider is not available yet',
'Database file path': 'Database file path',
'Server URI': 'Server URI',
'Token': 'Token',
'Server URL': 'Server URL',
'API Key': 'API Key',
'Embedded Milvus Lite (local file storage).': 'Embedded Milvus Lite (local file storage).',
'Remote Milvus instance accessed via URI.': 'Remote Milvus instance accessed via URI.',
'Qdrant vector database (HTTP API).': 'Qdrant vector database (HTTP API).',
'Database Type': 'Database Type',
'Confirm embedding dimension change': 'Confirm embedding dimension change',
'Changing the embedding dimension will clear the vector database automatically. You will need to rebuild indexes afterwards. Continue?': 'Changing the embedding dimension will clear the vector database automatically. You will need to rebuild indexes afterwards. Continue?',
'Confirm clear vector database?': 'Confirm clear vector database?',
'This will delete all collections irreversibly.': 'This will delete all collections irreversibly.',
'Confirm Clear': 'Confirm Clear',
// 'Cancel' defined above
'Vector database cleared': 'Vector database cleared',
'Clear failed': 'Clear failed',
'Clear Vector DB': 'Clear Vector DB',
'App Name': 'App Name',
'Logo URL': 'Logo URL',
'Favicon URL': 'Favicon URL',
'App Domain': 'App Domain',
'File Domain': 'File Domain',
'Configure Access Key and Secret to enable S3 mapping.': 'Configure Access Key and Secret to enable S3 mapping.',
'Mount point inside the virtual file system (e.g. / or /workspace).': 'Mount point inside the virtual file system (e.g. / or /workspace).',
'Please input bucket name': 'Please input bucket name',
'Please input region': 'Please input region',
'Please input access key': 'Please input access key',
'Please input secret key': 'Please input secret key',
'Save S3 Settings': 'Save S3 Settings',
'Example CLI command': 'Example CLI command',
'WebDAV Mapping': 'WebDAV Mapping',
'WebDAV Endpoint': 'WebDAV Endpoint',
'Basic (system account password)': 'Basic (system account password)',
'Root Path': 'Root Path',
'Client Compatibility': 'Client Compatibility',
'Supports Finder, Windows network drive, rclone, and other WebDAV clients.': 'Supports Finder, Windows network drive, rclone, and other WebDAV clients.',
'Toggle the switch to expose the virtual file system via WebDAV.': 'Toggle the switch to expose the virtual file system via WebDAV.',
'SMTP Settings': 'SMTP Settings',
'SMTP Host': 'SMTP Host',
'Please input SMTP host': 'Please input SMTP host',
'SMTP Port': 'SMTP Port',
'Please input SMTP port': 'Please input SMTP port',
'Security': 'Security',
'None': 'None',
'SSL': 'SSL',
'STARTTLS': 'STARTTLS',
'Timeout (seconds)': 'Timeout (seconds)',
'Sender': 'Sender',
'Sender Name': 'Sender Name',
'Sender Email': 'Sender Email',
'Please input sender email': 'Please input sender email',
'Authentication': 'Authentication',
'SMTP Username': 'SMTP Username',
'SMTP Password': 'SMTP Password',
'Test Email': 'Test Email',
'Current Configuration': 'Current Configuration',
'Available variables': 'Available variables',
'Not set': 'Not set',
'Password Reset Template': 'Password Reset Template',
'Live Preview': 'Live Preview',
'Foxel Mail Test': 'Foxel Mail Test',
'Recipient Address': 'Recipient Address',
'Please input recipient email': 'Please input recipient email',
'Test Subject': 'Test Subject',
'Test User Name': 'Test User Name',
'Optional': 'Optional',
'Send Test Email': 'Send Test Email',
'Please complete all required fields': 'Please complete all required fields',
'SMTP port must be a positive number': 'SMTP port must be a positive number',
'Test email queued (task {{taskId}})': 'Test email queued (task {{taskId}})',
'Test email failed': 'Test email failed',
// Auth reset
'Forgot Password?': 'Forgot password?',
'Reset Your Password': 'Reset Your Password',
'Enter the email linked to your account and we will send a reset link.': 'Enter the email linked to your account and we will send a reset link.',
'If the email exists, a reset link has been sent.': 'If the email exists, a reset link has been sent.',
'Send Reset Link': 'Send Reset Link',
'Resend Link': 'Resend Link',
'Back to login': 'Back to login',
'Request failed': 'Request failed',
'Reset link is invalid': 'Reset link is invalid',
'Reset link is invalid or expired': 'Reset link is invalid or expired',
'Reset failed': 'Reset failed',
'Try again': 'Try again',
'Set a new password': 'Set a new password',
'Please enter new password': 'Please enter new password',
'Confirm Password': 'Confirm Password',
'Please confirm new password': 'Please confirm new password',
'Update Password': 'Update Password',
'Passwords do not match': 'Passwords do not match',
'Password updated, please login again.': 'Password updated, please login again.',
'Failed to reset password': 'Failed to reset password',
'Vision API URL': 'Vision API URL',
'Vision API Key': 'Vision API Key',
'Embedding API URL': 'Embedding API URL',
'Embedding API Key': 'Embedding API Key',
'AI Providers & Models': 'AI Providers & Models',
'Manage AI providers, synchronize compatible models, and configure default capabilities across the system.': 'Manage AI providers, synchronize compatible models, and configure default capabilities across the system.',
'Add Provider': 'Add Provider',
'Edit Provider': 'Edit Provider',
'Pull Models': 'Pull Models',
'Manual Add': 'Manual Add',
'Clear Remote List': 'Clear Remote List',
'Select models from the list to add them automatically': 'Select models from the list to add them automatically',
'No remote models': 'No remote models',
'No remote models found': 'No remote models found',
'No remote models match search': 'No remote models match search',
'Search fetched models': 'Search fetched models',
'Already Added': 'Already Added',
'Add Selected Models': 'Add Selected Models',
'Fetch failed': 'Fetch failed',
'Select models to add': 'Select models to add',
'Added {count} models': 'Added {count} models',
'Choose Template': 'Choose Template',
'Configure Provider': 'Configure Provider',
'Back to Templates': 'Back to Templates',
'View Docs': 'View Docs',
'Custom Provider': 'Custom Provider',
'Custom Provider Description': 'Bring your own endpoint compatible with OpenAI or Gemini formats.',
'OpenAI Provider': 'OpenAI',
'OpenAI Provider Description': 'Access GPT-4o, GPT-4.1, GPT-3.5 and more models from OpenAI.',
'Azure OpenAI Provider': 'Azure OpenAI',
'Azure OpenAI Provider Description': 'Use OpenAI models deployed on Microsoft Azure.',
'Google AI Provider': 'Google AI',
'Google AI Provider Description': 'Gemini series models served via the Google AI platform.',
'SiliconFlow Provider': 'SiliconFlow',
'SiliconFlow Provider Description': 'High-performance inference platform with OpenAI-compatible APIs.',
'OpenRouter Provider': 'OpenRouter',
'OpenRouter Provider Description': 'Connect to multiple AI providers through a single OpenAI-style endpoint.',
'Anthropic Provider': 'Anthropic',
'Anthropic Provider Description': 'Claude 3 family models exposed through the Claude API.',
'DeepSeek Provider': 'DeepSeek',
'DeepSeek Provider Description': 'DeepSeek language models via OpenAI-compatible API.',
'Grok Provider': 'Grok (xAI)',
'Grok Provider Description': 'Grok models powered by xAI with OpenAI-style routes.',
'Ollama Provider': 'Ollama',
'Ollama Provider Description': 'Self-host and run models locally with Ollama\'s OpenAI bridge.',
'Voyage Provider': 'Voyage AI',
'Voyage Provider Description': 'High-quality embeddings and rerankers from Voyage AI.',
'Delete provider?': 'Delete provider?',
'Deleting this provider will also remove all associated models. Continue?': 'Deleting this provider will also remove all associated models. Continue?',
'Deleted successfully': 'Deleted successfully',
'Sync Models': 'Sync Models',
'Sync completed: {created} created, {updated} updated': 'Sync completed: {created} created, {updated} updated',
'Sync failed': 'Sync failed',
'Add Model': 'Add Model',
'Edit Model': 'Edit Model',
'Delete model?': 'Delete model?',
'This operation cannot be undone. Continue?': 'This operation cannot be undone. Continue?',
'No models yet': 'No models yet',
'Add your first AI provider to get started': 'Add your first AI provider to get started',
'Default Models Configuration': 'Default Models Configuration',
'Main Chat Model': 'Main Chat Model',
'Primary assistant for conversations, reasoning, and tool calls.': 'Primary assistant for conversations, reasoning, and tool calls.',
'Handles multimodal perception such as image understanding.': 'Handles multimodal perception such as image understanding.',
'Transforms content into dense vectors for search and retrieval.': 'Transforms content into dense vectors for search and retrieval.',
'Optimises ranking quality for search candidates.': 'Optimises ranking quality for search candidates.',
'Covers text-to-speech and speech understanding scenarios.': 'Covers text-to-speech and speech understanding scenarios.',
'Supports function calling, orchestration, and automation.': 'Supports function calling, orchestration, and automation.',
'Select a model': 'Select a model',
'Template': 'Template',
'Select a template': 'Select a template',
'Display Name': 'Display Name',
'Enter name': 'Enter name',
'Identifier': 'Identifier',
'Enter identifier': 'Enter identifier',
'Only lowercase letters, numbers, dash, dot and underscore are allowed': 'Only lowercase letters, numbers, dash, dot and underscore are allowed',
'API Format': 'API Format',
'Base URL': 'Base URL',
'Enter base url': 'Enter base URL',
'Optional, can also be provided per request': 'Optional, can also be provided per request',
'Model Identifier': 'Model Identifier',
'Enter model identifier': 'Enter model identifier',
'Description': 'Description',
'Capabilities': 'Capabilities',
'Context Window': 'Context Window',
'Embedding Dimensions': 'Embedding Dimensions',
'Price /1K input tokens': 'Price /1K input tokens',
'Price /1K output tokens': 'Price /1K output tokens',
// Adapters
'Missing required config:': 'Missing required config:',
'Updated successfully': 'Updated successfully',
'Created successfully': 'Created successfully',
'Operation failed': 'Operation failed',
'Deleted': 'Deleted',
'Delete failed': 'Delete failed',
'Status updated': 'Status updated',
'Update failed': 'Update failed',
'Mount Path': 'Mount Path',
'Sub Path': 'Sub Path',
'Sub Path (optional)': 'Sub Path (optional)',
'Sub directory inside adapter': 'Sub directory inside adapter',
'Enabled': 'Enabled',
'Actions': 'Actions',
'Edit': 'Edit',
'Confirm delete?': 'Confirm delete?',
'No config fields': 'No config fields',
'Please input {label}': 'Please input {label}',
'Storage Adapters': 'Storage Adapters',
'Create Adapter': 'Create Adapter',
'Unique name': 'Unique name',
'Select adapter type': 'Select adapter type',
'/ or /drive': '/ or /drive',
'Adapter Config': 'Adapter Config',
'adapter.type.local': 'Local Filesystem',
'adapter.type.webdav': 'WebDAV',
'adapter.type.googledrive': 'Google Drive',
'adapter.type.onedrive': 'OneDrive',
'adapter.type.s3': 'Amazon S3',
'adapter.type.ftp': 'FTP',
'adapter.type.sftp': 'SFTP',
'adapter.type.telegram': 'Telegram',
'adapter.type.quark': 'Quark Drive',
// Tasks
'Automation Tasks': 'Automation Tasks',
'Create Task': 'Create Task',
'Edit Task': 'Edit Task',
'Create Automation Task': 'Create Automation Task',
'Task Name': 'Task Name',
'Trigger Event': 'Trigger Event',
'File Written': 'File Written',
'File Deleted': 'File Deleted',
'Matching Rules': 'Matching Rules',
'Path Prefix (optional)': 'Path Prefix (optional)',
'Filename Regex (optional)': 'Filename Regex (optional)',
'Action': 'Action',
'Current Task Queue': 'Current Task Queue',
'Params': 'Params',
'Status': 'Status',
// Logs
'Confirm clear logs?': 'Confirm clear logs?',
'This will delete logs in selected range irreversibly.': 'This will delete logs in selected range irreversibly.',
'Cleared {count} logs': 'Cleared {count} logs',
'Time': 'Time',
'Level': 'Level',
'Source': 'Source',
'Message': 'Message',
'User ID': 'User ID',
'Search source': 'Search source',
'Clear': 'Clear',
'Log Details': 'Log Details',
'Raw Log': 'Raw Log',
// Backup
'Export started, check your downloads.': 'Export started, check your downloads.',
'Export failed': 'Export failed',
'Confirm import backup?': 'Confirm import backup?',
'Are you sure to import from this file?': 'Are you sure to import from this file?',
'Warning: This will overwrite all data including users (with passwords), settings, storages and tasks. Irreversible!': 'Warning: This will overwrite all data including users (with passwords), settings, storages and tasks. Irreversible!',
'Confirm Import': 'Confirm Import',
'Import succeeded! The page will refresh.': 'Import succeeded! The page will refresh.',
'Import failed': 'Import failed',
'Export': 'Export',
'Import': 'Import',
'Export all data (adapters, users, tasks, shares) into a JSON file.': 'Export all data (adapters, users, tasks, shares) into a JSON file.',
'Keep your backup file safe.': 'Keep your backup file safe.',
'Export Backup': 'Export Backup',
'Restore data from a previously exported JSON file.': 'Restore data from a previously exported JSON file.',
'Warning: This will clear and overwrite existing data.': 'Warning: This will clear and overwrite existing data.',
'Choose File and Restore': 'Choose File and Restore',
// Empty state
'No files yet here': 'No files yet here',
'This folder is empty': 'This folder is empty',
'Start uploading files or create folders to organize your content': 'Start uploading files or create folders to organize your content',
'You can create folders or upload files here': 'You can create folders or upload files here',
// File actions
'Please input name': 'Please input name',
'Confirm delete {name}?': 'Confirm delete {name}?',
'items': 'items',
'Downloading folders is not supported': 'Downloading folders is not supported',
'Download failed': 'Download failed',
'Please select files or folders to share': 'Please select files or folders to share',
'Direct links for folders are not supported': 'Direct links for folders are not supported',
// Processor flow
'Processing finished': 'Processing finished',
'Processing failed': 'Processing failed',
'Processors': 'Processors',
'Processor List': 'Processor List',
'Reload': 'Reload',
'Run Processor': 'Run Processor',
'Target Path': 'Target Path',
'Please select a path': 'Please select a path',
'Select Directory': 'Select Directory',
'Overwrite original': 'Overwrite original',
'Save To': 'Save To',
'Optional output path': 'Optional output path',
'Run': 'Run',
'Select a processor': 'Select a processor',
'No module path': 'No module path',
'Source saved': 'Source saved',
'Processors reloaded': 'Processors reloaded',
'Unsaved changes': 'Unsaved changes',
'Switching processor will discard unsaved changes. Continue?': 'Switching processor will discard unsaved changes. Continue?',
'Task submitted': 'Task submitted',
'Supported Extensions': 'Supported Extensions',
'All': 'All',
'Produces File': 'Produces File',
'Yes': 'Yes',
'No': 'No',
'Please select a processor': 'Please select a processor',
'Select a path': 'Select a path',
'Source Editor': 'Source Editor',
'Module Path': 'Module Path',
'Directory processing always overwrites original files': 'Directory processing always overwrites original files',
'No data': 'No data',
// Path selector
'Select File': 'Select File',
'Select Path': 'Select Path',
'Select Folder': 'Select Folder',
'Select': 'Select',
'Current': 'Current',
'Up': 'Up',
'Select Current Folder': 'Select Current Folder',
'Please select a file': 'Please select a file',
// Plugins page
'Installed successfully': 'Installed successfully',
'Plugin': 'Plugin',
'Open Link': 'Open Link',
'Link copied': 'Link copied',
'Copy Link': 'Copy Link',
'Confirm delete this plugin?': 'Confirm delete this plugin?',
'Author': 'Author',
'Website': 'Website',
'Install App': 'Install App',
'Search name/author/url/extension': 'Search name/author/url/extension',
'No plugins': 'No plugins',
'Install': 'Install',
'App URL': 'App URL',
'Please input a valid URL': 'Please input a valid URL',
'Installed': 'Installed',
'Discover': 'Discover',
'Search apps': 'Search apps',
'Sort by': 'Sort by',
'Downloads': 'Downloads',
'Created (newest)': 'Created (newest)',
'Installed already': 'Installed',
'No results': 'No results',
// Setup page
'Initialization succeeded! Logging you in...': 'Initialization succeeded! Logging you in...',
'Initialization failed, please try later': 'Initialization failed, please try later',
'Database Setup': 'Database Setup',
'Choose database driver': 'Choose database driver',
'Select database and vector database for system data': 'Select database and vector database for system data',
'Database Driver': 'Database Driver',
'Vector DB Driver': 'Vector DB Driver',
'Initialize Mount': 'Initialize Mount',
'Configure initial storage': 'Configure initial storage',
'Create the first storage mount for your files': 'Create the first storage mount for your files',
'Mount Name': 'Mount Name',
'Local Storage': 'Local Storage',
'Please input mount name!': 'Please input mount name!',
'Storage Type': 'Storage Type',
'Please input mount path!': 'Please input mount path!',
'Root Directory': 'Root Directory',
'Please input root directory!': 'Please input root directory!',
'e.g., data/ or /var/foxel/data': 'e.g., data/ or /var/foxel/data',
'Optional, used for external links. Leave empty to use the current site.': 'Optional, used for external links. Leave empty to use the current site.',
'Create Admin': 'Create Admin',
'Create admin account': 'Create admin account',
'This is the first account with full permissions': 'This is the first account with full permissions',
'Username': 'Username',
'Please input a valid email!': 'Please input a valid email!',
'Please confirm your password!': 'Please confirm your password!',
'Passwords do not match!': 'Passwords do not match!',
'System Initialization': 'System Initialization',
'Previous': 'Previous',
'Next': 'Next',
'Finish Initialization': 'Finish Initialization',
// Plugin host
'Plugin run failed': 'Plugin run failed',
'Plugin Error': 'Plugin Error',
'Cannot open file: no available app': 'Cannot open file: no available app',
'Error': 'Error',
'App "{key}" not found.': 'App "{key}" not found.',
'Open with {app}': 'Open with {app}',
'Set as default for .{ext}': 'Set as default for .{ext}',
'Advanced tokens must be valid JSON': 'Advanced tokens must be valid JSON',
} as const;
export type EnKeys = keyof typeof en;

View File

@@ -0,0 +1,655 @@
{
"All Files": "全部文件",
"Manage": "管理",
"System": "系统",
"Automation": "自动任务",
"My Shares": "我的分享",
"Offline Downloads": "离线下载",
"No offline download tasks": "暂无离线下载任务",
"Create Offline Download": "创建离线下载任务",
"Offline Download Tasks": "离线下载任务列表",
"URL": "下载地址",
"Please input URL": "请输入下载地址",
"Destination Folder": "保存目录",
"Select destination": "请选择保存目录",
"Filename": "文件名",
"Please input filename": "请输入文件名",
"Start Download": "开始下载",
"Stage": "阶段",
"Progress": "进度",
"Bytes": "已传输",
"Save Path": "保存路径",
"Queued": "排队中",
"Downloading": "下载中",
"Transferring": "转存中",
"Completed": "已完成",
"Pending": "等待",
"Running": "进行中",
"Success": "成功",
"Failed": "失败",
"Failure": "失败",
"Adapters": "存储挂载",
"Plugins": "应用中心",
"System Settings": "系统设置",
"Backup & Restore": "备份恢复",
"System Logs": "系统日志",
"Audit Logs": "审计日志",
"Audit Log Details": "审计日志详情",
"Search files / tags / types": "搜索文件 / 标签 / 类型",
"Log Out": "退出登录",
"Admin": "管理员",
"Profile": "个人资料",
"Account Settings": "账户设置",
"Language": "语言",
"Full Name": "昵称",
"Email": "邮箱",
"Change Password": "修改密码",
"Old Password": "原密码",
"New Password": "新密码",
"Please fill both old and new password": "请同时填写原密码和新密码",
"Welcome Back": "欢迎回来",
"Sign in to your Foxel account": "登录到您的 Foxel 账户",
"Username / Email": "用户名/邮箱",
"Password": "密码",
"Sign In": "登录",
"Please enter username and password": "请输入用户名与密码",
"Login failed": "登录失败",
"Forgot Password?": "忘记密码?",
"Your next-generation file manager": "您的下一代文件管理系统",
"Cross-platform sync, access anywhere": "跨平台同步,随时随地访问",
"AI-powered search for quick find": "AI 驱动的智能搜索,快速定位文件",
"Flexible sharing and collaboration": "灵活的分享与协作,提升团队效率",
"Powerful automation to simplify tasks": "强大的自动化工作流,简化繁琐任务",
"Join our community:": "加入我们的社区:",
"Reset Your Password": "重置你的密码",
"Enter the email linked to your account and we will send a reset link.": "请输入你账户绑定的邮箱,我们会发送重置链接。",
"If the email exists, a reset link has been sent.": "如果邮箱存在,我们已发送重置链接。",
"Send Reset Link": "发送重置链接",
"Resend Link": "重新发送链接",
"Back to login": "返回登录",
"Request failed": "请求失败",
"Reset link is invalid": "重置链接无效",
"Reset link is invalid or expired": "重置链接无效或已过期",
"Reset failed": "重置失败",
"Try again": "重试",
"Set a new password": "设置新密码",
"Please enter new password": "请输入新密码",
"Confirm Password": "确认新密码",
"Please confirm new password": "请确认新密码",
"Update Password": "更新密码",
"Passwords do not match": "两次输入的密码不一致",
"Password updated, please login again.": "密码已更新,请重新登录。",
"Failed to reset password": "密码重置失败",
"Refresh": "刷新",
"Copy": "复制",
"Cancel": "取消",
"Copied link": "链接已复制",
"Share canceled": "分享已取消",
"Cancel failed": "取消失败",
"Load failed": "加载失败",
"Are you sure to cancel share?": "确认取消分享?",
"Clear expired shares": "清空过期分享",
"Confirm clear expired shares?": "确认清空过期分享?",
"Cleared {count} expired shares": "已清理 {count} 个过期分享",
"Please select time range": "请选择时间范围",
"Share Name": "分享名称",
"Share Content": "分享内容",
"Created At": "创建时间",
"Expires At": "过期时间",
"Forever": "永久有效",
"Access": "访问",
"Public": "公开",
"By Password": "密码",
"Password Required": "需要密码",
"Please enter password": "请输入密码",
"Confirm": "确认",
"Unable to load share info": "无法加载分享信息",
"Share load failed": "加载分享失败",
"Wrong password": "密码错误",
"Root": "全部文件",
"Created on {date}": "创建于 {date}",
"Expires on {date}": "将于 {date} 过期",
"Download File": "下载文件",
"Preview not supported for this file type": "暂不支持在线预览此类型文件",
"Back": "返回",
"Download": "下载",
"Home": "主页",
"File Manager": "文件管理",
"New Folder": "新建目录",
"Upload": "上传",
"Name": "名称",
"Size": "大小",
"Modified Time": "修改时间",
"Grid": "网格",
"List": "列表",
"Mount Point": "挂载点",
"Move": "移动",
"Move to": "移动到",
"Copy to": "复制到",
"Destination path": "目标路径",
"Move task queued": "移动任务已排队",
"Move completed": "移动完成",
"Copy task queued": "复制任务已排队",
"Copy completed": "复制完成",
"Please input destination path": "请输入目标路径",
"Upload File": "上传文件",
"Upload Files": "上传文件",
"Upload Folder": "上传文件夹",
"Open": "打开",
"Open With": "打开方式",
"Default": "默认",
"Processor": "处理器",
"Share": "分享",
"Rename": "重命名",
"Delete": "删除",
"Details": "详情",
"Get Direct Link": "获取直链",
"User": "用户",
"Status Code": "状态码",
"Duration (ms)": "耗时 (ms)",
"Client IP": "客户端 IP",
"Result": "结果",
"Request Params": "请求参数",
"Request Body": "请求体",
"Total progress": "总体进度",
"Upload task summary": "任务:已完成 {completed} / {total},待处理 {pending},失败 {failures}",
"Overwrite confirmation required": "需要确认是否覆盖",
"Target already exists: {path}": "目标已存在:{path}",
"Overwrite": "覆盖",
"Skip": "跳过",
"Overwrite All": "全部覆盖",
"Skip All": "全部跳过",
"Directory": "目录",
"Creating directory...": "正在创建目录...",
"Directory ready": "目录已就绪",
"Create directory failed": "创建目录失败",
"Waiting to create": "等待创建",
"Waiting for overwrite decision": "等待覆盖处理",
"Waiting to upload": "等待上传",
"Skipped": "已跳过",
"Upload succeeded": "上传成功",
"Upload failed": "上传失败",
"No items selected for upload": "未选择任何可上传项",
"No uploadable files or directories found": "未找到可上传的文件或目录",
"Missing file content": "缺少文件内容",
"Directory conflicts with existing file": "目标存在同名文件,无法创建目录",
"Join Community": "加入社区",
"Scan to join WeChat group": "微信扫码加入交流群",
"If QR expires, add drizzle2001 to join": "如二维码失效,请添加 drizzle2001 拉群",
"Version Info": "版本信息",
"Current Version": "当前版本",
"Latest Version": "最新版本",
"New version found: {version}": "发现新版本: {version}",
"Please update to the latest for features and fixes": "建议尽快更新到最新版本,以获得新功能和安全修复。",
"Open Releases": "前往发布页面",
"Changelog": "更新日志",
"Fetching latest version...": "正在获取最新版本信息...",
"Update available": "有更新",
"You are on the latest: {version}": "当前为最新版: {version}",
"Up to date": "已是最新版",
"Share {count} items": "分享 {count} 个项目",
"Share link created": "分享链接已创建",
"Create failed": "创建失败",
"Copied to clipboard": "已复制到剪贴板",
"Expiration (days)": "有效期 (天)",
"Set 0 or negative for forever": "设置为 0 或负数表示永久有效",
"Share link created successfully!": "分享链接已成功创建!",
"Share Link": "分享链接",
"Share created": "分享创建成功",
"Create Share": "创建分享",
"Done": "完成",
"Create": "创建",
"Failed to generate link": "生成链接失败",
"Markdown copied to clipboard": "Markdown 格式已复制到剪贴板",
"Generate a direct link for {name}": "为 {name} 生成一个直接访问链接。",
"1 hour": "1 小时",
"1 day": "1 天",
"7 days": "7 天",
"Generating link...": "正在生成链接...",
"Link will appear here": "链接将显示在这里",
"Copy Markdown": "复制 Markdown",
"Close": "关闭",
"Task Queue": "任务队列",
"Last updated at {time}": "上次刷新时间 {time}",
"Total Tasks": "任务总数",
"Waiting Tasks": "等待中的任务",
"Failed Tasks": "失败的任务",
"Active Workers": "活跃 Worker 数",
"Task Type": "任务类型",
"Search by name or ID": "按名称或 ID 搜索",
"Filter by status": "按状态筛选",
"Queue Concurrency": "队列并发数",
"Settings saved": "设置已保存",
"Expand": "展开",
"Adjust worker concurrency immediately": "立即调整任务并发数",
"Auto": "自动",
"Manual": "手动",
"Camera Make": "设备品牌",
"Camera Model": "设备型号",
"Capture Time": "拍摄时间",
"X Resolution": "水平分辨率",
"Y Resolution": "垂直分辨率",
"Exposure Time": "曝光时间",
"Aperture": "光圈值",
"Focal Length": "焦距",
"Width": "宽度",
"Height": "高度",
"No common EXIF info": "无常见EXIF信息",
"File Properties": "文件属性",
"Loading file info...": "加载文件信息...",
"Basic Info": "基本信息",
"Type": "类型",
"Folder": "文件夹",
"File": "文件",
"Path": "路径",
"Path copied to clipboard": "路径已复制到剪贴板",
"Copy failed": "复制失败",
"Permissions": "权限",
"EXIF Info": "EXIF信息",
"Index Info": "索引信息",
"Indexed Items": "索引条目数",
"Indexed Types": "索引类型统计",
"No index data": "暂无索引数据",
"Indexed Chunks": "索引条目",
"More Indexed Chunks": "更多索引条目",
"Chunk ID": "分片ID",
"Offset Range": "偏移范围",
"Vector ID": "向量ID",
"Preview": "内容预览",
"Showing first {count} entries": "仅展示前 {count} 条",
"Smart Search": "智能搜索",
"Name Search": "名称搜索",
"Search Results": "搜索结果",
"No files found": "未找到相关文件",
"Relevance": "相关度",
"Saved successfully": "保存成功",
"Save failed": "保存失败",
"Loading...": "加载中...",
"Appearance Settings": "外观设置",
"Theme": "主题",
"Theme Mode": "主题模式",
"Light": "亮色",
"Dark": "暗色",
"Follow System": "跟随系统",
"Primary Color": "主色",
"Border Radius": "圆角",
"Advanced": "高级",
"Override AntD Tokens (JSON)": "覆盖 AntD TokenJSON",
"e.g. {\"colorText\": \"#222\"}": "例如:{\"colorText\": \"#222\"}",
"Custom CSS": "自定义 CSS",
"Save": "保存",
"App Settings": "应用设置",
"Email Settings": "邮箱设置",
"AI Settings": "AI设置",
"Protocol Mappings": "映射协议",
"S3 Mapping": "S3 映射",
"S3 Endpoint": "S3 访问地址",
"Bucket Name": "Bucket 名称",
"Bucket API Path": "Bucket API 路径",
"Region": "区域",
"Base Path": "基础路径",
"Choose Template": "选择模板",
"Configure Provider": "配置提供商",
"Back to Templates": "返回选择",
"View Docs": "查看文档",
"Vision Model": "视觉模型",
"Embedding Model": "嵌入模型",
"Embedding Dimension": "向量维度",
"Vector Database": "向量数据库",
"Vector Database Settings": "向量数据库设置",
"Current Statistics": "当前统计",
"Collections": "集合",
"Vectors": "向量",
"Database Size": "数据库大小",
"Estimated Memory": "估算内存",
"No collections": "暂无集合",
"Dimension": "维度",
"Non-vector collection": "非向量集合",
"Estimated memory": "估算内存",
"Indexes": "索引",
"Unnamed index": "未命名索引",
"Indexed rows": "已索引行数",
"Pending rows": "待索引行数",
"Estimated memory is calculated as vectors x dimension x 4 bytes (float32).": "估算内存 = 向量数量 x 维度 x 4 字节float32。",
"Database Provider": "数据库提供者",
"Please select a provider": "请选择提供者",
"Coming soon": "敬请期待",
"This provider is not available yet": "该提供者暂不可用",
"Database file path": "数据库文件路径",
"Server URI": "服务器 URI",
"Token": "令牌",
"Server URL": "服务器地址",
"Embedded Milvus Lite (local file storage).": "嵌入式 Milvus Lite本地文件存储。",
"Remote Milvus instance accessed via URI.": "通过 URI 访问的远程 Milvus 实例。",
"Qdrant vector database (HTTP API).": "Qdrant 向量数据库HTTP API。",
"Database Type": "数据库类型",
"Confirm embedding dimension change": "确认修改向量维度",
"Changing the embedding dimension will clear the vector database automatically. You will need to rebuild indexes afterwards. Continue?": "修改向量维度会自动清空向量数据库,之后需要重建索引,是否继续?",
"Confirm clear vector database?": "确认清空向量数据库?",
"This will delete all collections irreversibly.": "此操作将删除所有集合中的所有数据,且不可逆。",
"Confirm Clear": "确认清空",
"Vector database cleared": "向量数据库已清空",
"Clear failed": "清空失败",
"Clear Vector DB": "清空向量库",
"App Name": "应用名称",
"Logo URL": "LOGO地址",
"Favicon URL": "Favicon 地址",
"App Domain": "应用域名",
"File Domain": "文件域名",
"Configure Access Key and Secret to enable S3 mapping.": "配置 Access Key 与 Secret 后才能启用 S3 映射。",
"Mount point inside the virtual file system (e.g. / or /workspace).": "虚拟文件系统中的挂载路径,例如 / 或 /workspace。",
"Please input bucket name": "请输入 Bucket 名",
"Please input region": "请输入 Region",
"Please input access key": "请输入 Access Key",
"Please input secret key": "请输入 Secret Key",
"Save S3 Settings": "保存 S3 配置",
"Example CLI command": "示例 CLI 命令",
"WebDAV Mapping": "WebDAV 映射",
"WebDAV Endpoint": "WebDAV 访问地址",
"Basic (system account password)": "Basic系统账号密码",
"Root Path": "根路径",
"Client Compatibility": "客户端兼容性",
"Supports Finder, Windows network drive, rclone, and other WebDAV clients.": "兼容 Finder、Windows 网络驱动器、rclone 等 WebDAV 客户端。",
"Toggle the switch to expose the virtual file system via WebDAV.": "通过开关控制是否对外暴露虚拟文件系统的 WebDAV 协议。",
"SMTP Settings": "SMTP 配置",
"SMTP Host": "SMTP 服务器",
"Please input SMTP host": "请输入 SMTP 服务器",
"SMTP Port": "SMTP 端口",
"Please input SMTP port": "请输入 SMTP 端口",
"Security": "安全协议",
"None": "无",
"Timeout (seconds)": "超时时间(秒)",
"Sender": "发件人",
"Sender Name": "发件人名称",
"Sender Email": "发件人邮箱",
"Please input sender email": "请输入发件人邮箱",
"Authentication": "身份认证",
"SMTP Username": "SMTP 用户名",
"SMTP Password": "SMTP 密码",
"Test Email": "测试发信",
"Current Configuration": "当前配置摘要",
"Available variables": "可用变量",
"Not set": "未设置",
"Password Reset Template": "密码重置模板",
"Live Preview": "实时预览",
"Template saved": "模板已保存",
"Failed to save template": "模板保存失败",
"Failed to load template": "模板加载失败",
"Preview failed": "预览失败",
"Foxel Mail Test": "Foxel 邮件测试",
"Recipient Address": "收件人地址",
"Please input recipient email": "请输入收件人邮箱",
"Test Subject": "测试邮件标题",
"Test User Name": "测试用户名",
"Optional": "可选",
"Send Test Email": "发送测试邮件",
"Please complete all required fields": "请填写所有必填项",
"SMTP port must be a positive number": "SMTP 端口必须为正数",
"Test email queued (task {{taskId}})": "测试邮件已入队(任务 {{taskId}}",
"Test email failed": "测试邮件发送失败",
"Vision API URL": "视觉模型 API 地址",
"Vision API Key": "视觉模型 API Key",
"Embedding API URL": "嵌入模型 API 地址",
"Embedding API Key": "嵌入模型 API Key",
"AI Providers & Models": "AI 提供商与模型",
"Manage AI providers, synchronize compatible models, and configure default capabilities across the system.": "管理所有 AI 提供商,批量同步兼容模型,并配置系统默认能力。",
"Add Provider": "添加提供商",
"Edit Provider": "编辑提供商",
"Pull Models": "拉取模型",
"Manual Add": "手动添加",
"Clear Remote List": "清空列表",
"Select models from the list to add them automatically": "选择模型后可一键添加到系统",
"No remote models": "暂无远程模型",
"No remote models found": "未获取到远程模型",
"No remote models match search": "没有匹配的远程模型",
"Search fetched models": "搜索已拉取模型",
"Already Added": "已添加",
"Add Selected Models": "添加所选模型",
"Fetch failed": "拉取失败",
"Select models to add": "请选择要添加的模型",
"Added {count} models": "已添加 {count} 个模型",
"Custom Provider": "自定义提供商",
"Custom Provider Description": "自定义兼容 OpenAI 或 Gemini 标准的 API 端点。",
"OpenAI Provider Description": "访问 OpenAI 的 GPT-4o、GPT-4.1、GPT-3.5 等模型。",
"Azure OpenAI Provider Description": "使用托管在微软 Azure 上的 OpenAI 模型。",
"Google AI Provider Description": "Google AI 平台提供的 Gemini 系列模型。",
"SiliconFlow Provider": "硅基流动",
"SiliconFlow Provider Description": "硅基流动高性能推理平台,兼容 OpenAI 接口。",
"OpenRouter Provider Description": "通过一个 OpenAI 风格入口接入多家 AI 提供商。",
"Anthropic Provider Description": "通过 Claude API 使用 Claude 3 系列模型。",
"DeepSeek Provider Description": "DeepSeek 语言模型,支持 OpenAI 兼容接口。",
"Grok Provider Description": "xAI 的 Grok 模型,提供 OpenAI 风格接口。",
"Ollama Provider Description": "使用 Ollama 在本地运行并管理大模型。",
"Voyage Provider Description": "Voyage AI 提供的高质量嵌入与重排序模型。",
"Delete provider?": "确认删除该提供商?",
"Deleting this provider will also remove all associated models. Continue?": "删除后将同时移除该提供商下的全部模型,是否继续?",
"Deleted successfully": "删除成功",
"Sync Models": "同步模型",
"Sync completed: {created} created, {updated} updated": "同步完成:新增 {created} 个,更新 {updated} 个",
"Sync failed": "同步失败",
"Add Model": "添加模型",
"Edit Model": "编辑模型",
"Delete model?": "确认删除该模型?",
"This operation cannot be undone. Continue?": "此操作不可撤销,是否继续?",
"No models yet": "暂无模型",
"Add your first AI provider to get started": "添加第一个 AI 提供商开始配置",
"Default Models Configuration": "默认模型配置",
"Main Chat Model": "主对话模型",
"Primary assistant for conversations, reasoning, and tool calls.": "用于对话、推理与工具调用的核心模型。",
"Handles multimodal perception such as image understanding.": "负责多模态感知与图像理解。",
"Transforms content into dense vectors for search and retrieval.": "将内容向量化以驱动搜索与检索。",
"Optimises ranking quality for search candidates.": "重新排序候选结果,提升检索相关性。",
"Covers text-to-speech and speech understanding scenarios.": "覆盖文本转语音与语音理解场景。",
"Supports function calling, orchestration, and automation.": "支持函数调用、编排与自动化。",
"Select a model": "选择模型",
"Template": "模板",
"Select a template": "选择模板",
"Display Name": "显示名称",
"Enter name": "请输入名称",
"Identifier": "标识符",
"Enter identifier": "请输入标识符",
"Only lowercase letters, numbers, dash, dot and underscore are allowed": "仅允许小写字母、数字、连字符、点和下划线",
"API Format": "API 格式",
"Base URL": "基础 URL",
"Enter base url": "请输入基础 URL",
"Optional, can also be provided per request": "可选,也可在请求时提供",
"Model Identifier": "模型标识",
"Enter model identifier": "请输入模型标识",
"Description": "描述",
"Capabilities": "能力标签",
"Context Window": "上下文窗口",
"Embedding Dimensions": "向量维度",
"Price /1K input tokens": "价格 /1K 输入 token",
"Price /1K output tokens": "价格 /1K 输出 token",
"Missing required config:": "缺少必填配置:",
"Updated successfully": "更新成功",
"Created successfully": "创建成功",
"Operation failed": "操作失败",
"Deleted": "已删除",
"Delete failed": "删除失败",
"Status updated": "状态已更新",
"Update failed": "更新失败",
"Mount Path": "挂载路径",
"Sub Path": "子路径",
"Sub Path (optional)": "子路径(可选)",
"Sub directory inside adapter": "适配器内部子目录",
"Enabled": "启用",
"Actions": "操作",
"Edit": "编辑",
"Confirm delete?": "确认删除?",
"No config fields": "无配置项",
"Please input {label}": "请输入{label}",
"Storage Adapters": "存储适配器",
"Create Adapter": "新建适配器",
"Unique name": "唯一名称",
"Select adapter type": "选择适配器类型",
"/ or /drive": "/或/drive",
"Adapter Config": "适配器配置",
"adapter.type.local": "本地文件系统",
"adapter.type.quark": "夸克网盘",
"Automation Tasks": "自动化任务",
"Running Tasks": "运行中的任务",
"Create Task": "新建任务",
"Edit Task": "编辑任务",
"Create Automation Task": "新建自动化任务",
"Task Name": "任务名称",
"Trigger Event": "触发事件",
"File Written": "文件写入",
"File Deleted": "文件删除",
"Matching Rules": "匹配规则",
"Path Prefix (optional)": "路径前缀 (可选)",
"Filename Regex (optional)": "文件名正则 (可选)",
"Action": "执行动作",
"Current Task Queue": "当前任务队列",
"Params": "参数",
"Status": "状态",
"Confirm clear logs?": "确认清理日志?",
"This will delete logs in selected range irreversibly.": "该操作将删除选定时间范围内的所有日志,且不可恢复。",
"Cleared {count} logs": "成功清理 {count} 条日志",
"Time": "时间",
"Level": "级别",
"Source": "来源",
"Message": "消息",
"User ID": "用户 ID",
"Search source": "搜索来源",
"Clear": "清理",
"Log Details": "日志详情",
"Raw Log": "原始日志",
"Export started, check your downloads.": "导出已开始,请检查您的下载。",
"Export failed": "导出失败",
"Confirm import backup?": "确认导入备份?",
"Are you sure to import from this file?": "您确定要从此文件导入数据吗?",
"Warning: This will overwrite all data including users (with passwords), settings, storages and tasks. Irreversible!": "警告:此操作将覆盖当前数据库中的所有现有数据,包括用户(含密码)、设置、存储和任务。此操作不可逆!",
"Confirm Import": "确认导入",
"Import succeeded! The page will refresh.": "导入成功!页面将刷新。",
"Import failed": "导入失败",
"Export": "导出",
"Import": "恢复",
"Export all data (adapters, users, tasks, shares) into a JSON file.": "点击按钮将所有数据(包括存储、用户、自动化任务和分享)导出为一个 JSON 文件。",
"Keep your backup file safe.": "请妥善保管您的备份文件。",
"Export Backup": "导出备份",
"Restore data from a previously exported JSON file.": "从之前导出的JSON文件恢复数据。",
"Warning: This will clear and overwrite existing data.": "警告:此操作将清除并覆盖现有数据。",
"Choose File and Restore": "选择文件并恢复",
"No files yet here": "这里还没有任何文件",
"This folder is empty": "此目录为空",
"Start uploading files or create folders to organize your content": "开始上传文件或创建新目录来组织您的内容",
"You can create folders or upload files here": "您可以在此目录中创建新的文件夹或上传文件",
"Please input name": "请输入名称",
"Confirm delete {name}?": "确认删除 {name} ?",
"items": "项",
"Downloading folders is not supported": "暂不支持下载目录",
"Download failed": "下载失败",
"Please select files or folders to share": "请选择要分享的文件或目录",
"Direct links for folders are not supported": "不支持获取目录的直链",
"Processing finished": "处理完成",
"Processing failed": "处理失败",
"Processors": "处理器",
"Processor List": "处理器列表",
"Reload": "重载",
"Run Processor": "运行处理器",
"Target Path": "目标路径",
"Please select a path": "请选择路径",
"Select Directory": "选择目录",
"Overwrite original": "覆盖原文件",
"Save To": "保存到",
"Optional output path": "可选输出路径",
"Run": "运行",
"Select a processor": "选择处理器",
"No module path": "未检测到模块路径",
"Source saved": "源码已保存",
"Processors reloaded": "处理器已重载",
"Unsaved changes": "存在未保存的修改",
"Switching processor will discard unsaved changes. Continue?": "切换处理器会丢失未保存的修改,确认继续?",
"Task submitted": "任务已提交",
"Supported Extensions": "支持的扩展名",
"All": "全部",
"Produces File": "生成文件",
"Yes": "是",
"No": "否",
"Please select a processor": "请选择处理器",
"Select a path": "请选择路径",
"Source Editor": "源码编辑",
"Module Path": "模块路径",
"Directory processing always overwrites original files": "选择目录时会强制覆盖原文件",
"Directory execution will enqueue one task per file": "目录模式会为每个文件单独创建任务",
"Directory scope": "目录范围",
"Current level only": "仅当前层级",
"Include subdirectories": "包含子目录",
"Max depth": "最大层级",
"Leave empty to traverse all subdirectories": "留空表示遍历所有子目录",
"Depth must be greater or equal to 0": "层级必须大于或等于 0",
"Output suffix": "输出后缀",
"Suffix will be inserted before the file extension, e.g. demo_processed.mp4": "后缀会插入到文件扩展名前,例如 demo_processed.mp4",
"Suffix such as _processed": "例如 _processed 的后缀",
"Suffix cannot be empty": "后缀不能为空",
"No data": "暂无数据",
"Select File": "选择文件",
"Select Path": "选择路径",
"Select Folder": "选择目录",
"Select": "选择",
"Current": "当前",
"Up": "上一级",
"Select Current Folder": "选择当前目录",
"Please select a file": "请选择一个文件",
"Installed successfully": "安装成功",
"Plugin": "插件",
"Open Link": "打开链接",
"Link copied": "已复制链接",
"Copy Link": "复制链接",
"Confirm delete this plugin?": "确认删除该插件?",
"Author": "作者",
"Website": "官网",
"Install App": "安装应用",
"Search name/author/url/extension": "搜索 名称/作者/链接/扩展名",
"No plugins": "暂无插件",
"Install": "安装",
"App URL": "应用链接",
"Please input a valid URL": "请输入合法的 URL",
"Installed": "已安装",
"Discover": "发现",
"Search apps": "搜索应用",
"Sort by": "排序",
"Downloads": "下载量",
"Created (newest)": "创建时间(最新)",
"Installed already": "已安装",
"No results": "暂无结果",
"Initialization succeeded! Logging you in...": "初始化成功!正在为您登录,请不要刷新。",
"Initialization failed, please try later": "初始化失败,请稍后重试",
"Database Setup": "数据库设置",
"Choose database driver": "选择数据库驱动",
"Select database and vector database for system data": "选择用于存储系统数据的数据库和向量数据库。",
"Database Driver": "数据库驱动",
"Vector DB Driver": "向量数据库驱动",
"Initialize Mount": "初始化挂载",
"Configure initial storage": "配置初始存储",
"Create the first storage mount for your files": "为您的文件创建第一个存储挂载点。",
"Mount Name": "挂载名称",
"Local Storage": "本地存储",
"Please input mount name!": "请输入挂载名称!",
"Storage Type": "存储类型",
"Please input mount path!": "请输入挂载路径!",
"Root Directory": "根目录",
"Please input root directory!": "请输入根目录!",
"e.g., data/ or /var/foxel/data": "例如: data/ 或 /var/foxel/data",
"Optional, used for external links. Leave empty to use the current site.": "可选,用于生成外部链接;留空则使用当前站点。",
"Create Admin": "创建管理员",
"Create admin account": "创建管理员账户",
"This is the first account with full permissions": "这是系统的第一个账户,将拥有最高权限。",
"Username": "用户名",
"Please input a valid email!": "请输入有效的邮箱地址!",
"Please confirm your password!": "请确认您的密码!",
"Passwords do not match!": "两次输入的密码不一致!",
"System Initialization": "系统初始化",
"Previous": "上一步",
"Next": "下一步",
"Finish Initialization": "完成初始化",
"Plugin run failed": "插件运行失败",
"Plugin Error": "插件错误",
"Cannot open file: no available app": "无法打开该文件:没有可用的应用",
"Error": "错误",
"App \"{key}\" not found.": "应用 \"{key}\" 不存在。",
"Open with {app}": "使用 {app} 打开",
"Set as default for .{ext}": "设为该类型(.{ext})默认应用",
"Advanced tokens must be valid JSON": "高级 Token 需为合法 JSON"
}

View File

@@ -1,727 +0,0 @@
import { en } from './en';
// Start from English defaults, then override with Chinese translations we have.
export const zh = {
...en,
// General
'All Files': '全部文件',
'Manage': '管理',
'System': '系统',
'Automation': '自动任务',
'My Shares': '我的分享',
'Offline Downloads': '离线下载',
'No offline download tasks': '暂无离线下载任务',
'Create Offline Download': '创建离线下载任务',
'Offline Download Tasks': '离线下载任务列表',
'URL': '下载地址',
'Please input URL': '请输入下载地址',
'Destination Folder': '保存目录',
'Select destination': '请选择保存目录',
'Filename': '文件名',
'Please input filename': '请输入文件名',
'Start Download': '开始下载',
'Stage': '阶段',
'Progress': '进度',
'Bytes': '已传输',
'Save Path': '保存路径',
'Queued': '排队中',
'Downloading': '下载中',
'Transferring': '转存中',
'Completed': '已完成',
'Pending': '等待',
'Running': '进行中',
'Success': '成功',
'Failed': '失败',
'Adapters': '存储挂载',
'Plugins': '应用中心',
'System Settings': '系统设置',
'Backup & Restore': '备份恢复',
'System Logs': '系统日志',
// Top header
'Search files / tags / types': '搜索文件 / 标签 / 类型',
'Log Out': '退出登录',
'Admin': '管理员',
'Profile': '个人资料',
'Account Settings': '账户设置',
'Language': '语言',
'Chinese': '中文',
'English': 'English',
'Full Name': '昵称',
'Email': '邮箱',
'Change Password': '修改密码',
'Old Password': '原密码',
'New Password': '新密码',
'Please fill both old and new password': '请同时填写原密码和新密码',
// Auth / Login
'Welcome Back': '欢迎回来',
'Sign in to your Foxel account': '登录到您的 Foxel 账户',
'Username / Email': '用户名/邮箱',
'Password': '密码',
'Sign In': '登录',
'Please enter username and password': '请输入用户名与密码',
'Login failed': '登录失败',
'Forgot Password?': '忘记密码?',
'Your next-generation file manager': '您的下一代文件管理系统',
'Cross-platform sync, access anywhere': '跨平台同步,随时随地访问',
'AI-powered search for quick find': 'AI 驱动的智能搜索,快速定位文件',
'Flexible sharing and collaboration': '灵活的分享与协作,提升团队效率',
'Powerful automation to simplify tasks': '强大的自动化工作流,简化繁琐任务',
'Join our community:': '加入我们的社区:',
'Reset Your Password': '重置你的密码',
'Enter the email linked to your account and we will send a reset link.': '请输入你账户绑定的邮箱,我们会发送重置链接。',
'If the email exists, a reset link has been sent.': '如果邮箱存在,我们已发送重置链接。',
'Send Reset Link': '发送重置链接',
'Resend Link': '重新发送链接',
'Back to login': '返回登录',
'Request failed': '请求失败',
'Reset link is invalid': '重置链接无效',
'Reset link is invalid or expired': '重置链接无效或已过期',
'Reset failed': '重置失败',
'Try again': '重试',
'Set a new password': '设置新密码',
'Please enter new password': '请输入新密码',
'Confirm Password': '确认新密码',
'Please confirm new password': '请确认新密码',
'Update Password': '更新密码',
'Passwords do not match': '两次输入的密码不一致',
'Password updated, please login again.': '密码已更新,请重新登录。',
'Failed to reset password': '密码重置失败',
// Share page
'Refresh': '刷新',
'Copy': '复制',
'Cancel': '取消',
'Copied link': '链接已复制',
'Share canceled': '分享已取消',
'Cancel failed': '取消失败',
'Load failed': '加载失败',
'Are you sure to cancel share?': '确认取消分享?',
'Clear expired shares': '清空过期分享',
'Confirm clear expired shares?': '确认清空过期分享?',
'Cleared {count} expired shares': '已清理 {count} 个过期分享',
'Share Name': '分享名称',
'Share Content': '分享内容',
'Created At': '创建时间',
'Expires At': '过期时间',
'Forever': '永久有效',
'Access': '访问',
'Public': '公开',
'By Password': '密码',
// Public share page
'Password Required': '需要密码',
'Please enter password': '请输入密码',
'Confirm': '确认',
'Unable to load share info': '无法加载分享信息',
'Share load failed': '加载分享失败',
'Wrong password': '密码错误',
'Root': '全部文件',
'Created on {date}': '创建于 {date}',
'Expires on {date}': '将于 {date} 过期',
'Download File': '下载文件',
'Preview not supported for this file type': '暂不支持在线预览此类型文件',
'Back': '返回',
'Download': '下载',
// Header/File Explorer
'Home': '主页',
'File Manager': '文件管理',
'New Folder': '新建目录',
'Upload': '上传',
'Name': '名称',
'Size': '大小',
'Modified Time': '修改时间',
'Grid': '网格',
'List': '列表',
'Mount Point': '挂载点',
'Move': '移动',
'Move to': '移动到',
'Copy to': '复制到',
'Destination path': '目标路径',
'Move task queued': '移动任务已排队',
'Move completed': '移动完成',
'Copy task queued': '复制任务已排队',
'Copy completed': '复制完成',
'Please input destination path': '请输入目标路径',
// Context menu
'Upload File': '上传文件',
'Upload Files': '上传文件',
'Upload Folder': '上传文件夹',
'Open': '打开',
'Open With': '打开方式',
'Default': '默认',
'Processor': '处理器',
'Share': '分享',
'Rename': '重命名',
'Delete': '删除',
'Details': '详情',
'Get Direct Link': '获取直链',
// Upload modal
'Total progress': '总体进度',
'Upload bytes summary': '{uploaded} / {total}',
'Upload task summary': '任务:已完成 {completed} / {total},待处理 {pending},失败 {failures}',
'Overwrite confirmation required': '需要确认是否覆盖',
'Target already exists: {path}': '目标已存在:{path}',
'Overwrite': '覆盖',
'Skip': '跳过',
'Overwrite All': '全部覆盖',
'Skip All': '全部跳过',
'Directory': '目录',
'Creating directory...': '正在创建目录...',
'Directory ready': '目录已就绪',
'Create directory failed': '创建目录失败',
'Waiting to create': '等待创建',
'Waiting for overwrite decision': '等待覆盖处理',
'Waiting to upload': '等待上传',
'Skipped': '已跳过',
'Upload succeeded': '上传成功',
'Upload failed': '上传失败',
'No items selected for upload': '未选择任何可上传项',
'No uploadable files or directories found': '未找到可上传的文件或目录',
'Missing file content': '缺少文件内容',
'Directory conflicts with existing file': '目标存在同名文件,无法创建目录',
// Side nav modals
'Join Community': '加入社区',
'Scan to join WeChat group': '微信扫码加入交流群',
'If QR expires, add drizzle2001 to join': '如二维码失效,请添加 drizzle2001 拉群',
'Version Info': '版本信息',
'Current Version': '当前版本',
'Latest Version': '最新版本',
'New version found: {version}': '发现新版本: {version}',
'Please update to the latest for features and fixes': '建议尽快更新到最新版本,以获得新功能和安全修复。',
'Open Releases': '前往发布页面',
'Changelog': '更新日志',
'Fetching latest version...': '正在获取最新版本信息...',
'Update available': '有更新',
'You are on the latest: {version}': '当前为最新版: {version}',
'Up to date': '已是最新版',
// Share modal
'Share {count} items': '分享 {count} 个项目',
'Share link created': '分享链接已创建',
'Create failed': '创建失败',
'Copied to clipboard': '已复制到剪贴板',
'Expiration (days)': '有效期 (天)',
'Set 0 or negative for forever': '设置为 0 或负数表示永久有效',
'Share link created successfully!': '分享链接已成功创建!',
'Share Link': '分享链接',
'Share created': '分享创建成功',
'Create Share': '创建分享',
'Done': '完成',
'Create': '创建',
// Direct link modal
'Failed to generate link': '生成链接失败',
'Markdown copied to clipboard': 'Markdown 格式已复制到剪贴板',
'Generate a direct link for {name}': '为 {name} 生成一个直接访问链接。',
'1 hour': '1 小时',
'1 day': '1 天',
'7 days': '7 天',
'Generating link...': '正在生成链接...',
'Link will appear here': '链接将显示在这里',
'Copy Markdown': '复制 Markdown',
'Close': '关闭',
// Task queue
'Task Queue': '任务队列',
'Last updated at {time}': '上次刷新时间 {time}',
'Total Tasks': '任务总数',
'Waiting Tasks': '等待中的任务',
'Failed Tasks': '失败的任务',
'Active Workers': '活跃 Worker 数',
'Task Type': '任务类型',
'Search by name or ID': '按名称或 ID 搜索',
'Filter by status': '按状态筛选',
'Queue Concurrency': '队列并发数',
'Settings saved': '设置已保存',
'Expand': '展开',
'Adjust worker concurrency immediately': '立即调整任务并发数',
'Auto': '自动',
'Manual': '手动',
// File detail
'Camera Make': '设备品牌',
'Camera Model': '设备型号',
'Capture Time': '拍摄时间',
'X Resolution': '水平分辨率',
'Y Resolution': '垂直分辨率',
'Exposure Time': '曝光时间',
'Aperture': '光圈值',
'Focal Length': '焦距',
'Width': '宽度',
'Height': '高度',
'No common EXIF info': '无常见EXIF信息',
'File Properties': '文件属性',
'Loading file info...': '加载文件信息...',
'Basic Info': '基本信息',
'Type': '类型',
'Folder': '文件夹',
'File': '文件',
'Path': '路径',
'Path copied to clipboard': '路径已复制到剪贴板',
'Copy failed': '复制失败',
'Permissions': '权限',
'EXIF Info': 'EXIF信息',
'Index Info': '索引信息',
'Indexed Items': '索引条目数',
'Indexed Types': '索引类型统计',
'No index data': '暂无索引数据',
'Indexed Chunks': '索引条目',
'More Indexed Chunks': '更多索引条目',
'Chunk ID': '分片ID',
'Offset Range': '偏移范围',
'Vector ID': '向量ID',
'Preview': '内容预览',
'Showing first {count} entries': '仅展示前 {count} 条',
// Search dialog
'Smart Search': '智能搜索',
'Name Search': '名称搜索',
'Search Results': '搜索结果',
'No files found': '未找到相关文件',
'Relevance': '相关度',
// System settings
'Saved successfully': '保存成功',
'Save failed': '保存失败',
'Loading...': '加载中...',
'Appearance Settings': '外观设置',
'Theme': '主题',
'Theme Mode': '主题模式',
'Light': '亮色',
'Dark': '暗色',
// 'Follow System' used for theme mode
'Follow System': '跟随系统',
'Primary Color': '主色',
'Border Radius': '圆角',
'Advanced': '高级',
'Override AntD Tokens (JSON)': '覆盖 AntD TokenJSON',
'e.g. {"colorText": "#222"}': '例如:{"colorText": "#222"}',
'Custom CSS': '自定义 CSS',
'Save': '保存',
'App Settings': '应用设置',
'Email Settings': '邮箱设置',
'AI Settings': 'AI设置',
'Protocol Mappings': '映射协议',
'S3 Mapping': 'S3 映射',
'S3 Endpoint': 'S3 访问地址',
'Bucket Name': 'Bucket 名称',
'Bucket API Path': 'Bucket API 路径',
'Region': '区域',
'Base Path': '基础路径',
'Access Key': 'Access Key',
'Secret Key': 'Secret Key',
'Choose Template': '选择模板',
'Configure Provider': '配置提供商',
'Back to Templates': '返回选择',
'View Docs': '查看文档',
'Vision Model': '视觉模型',
'Embedding Model': '嵌入模型',
'Embedding Dimension': '向量维度',
'Vector Database': '向量数据库',
'Vector Database Settings': '向量数据库设置',
'Current Statistics': '当前统计',
'Collections': '集合',
'Vectors': '向量',
'Database Size': '数据库大小',
'Estimated Memory': '估算内存',
'No collections': '暂无集合',
'Dimension': '维度',
'Non-vector collection': '非向量集合',
'Estimated memory': '估算内存',
'Indexes': '索引',
'Unnamed index': '未命名索引',
'Indexed rows': '已索引行数',
'Pending rows': '待索引行数',
'Estimated memory is calculated as vectors x dimension x 4 bytes (float32).': '估算内存 = 向量数量 x 维度 x 4 字节float32。',
'Database Provider': '数据库提供者',
'Please select a provider': '请选择提供者',
'Coming soon': '敬请期待',
'This provider is not available yet': '该提供者暂不可用',
'Database file path': '数据库文件路径',
'Server URI': '服务器 URI',
'Token': '令牌',
'Server URL': '服务器地址',
'API Key': 'API Key',
'Embedded Milvus Lite (local file storage).': '嵌入式 Milvus Lite本地文件存储。',
'Remote Milvus instance accessed via URI.': '通过 URI 访问的远程 Milvus 实例。',
'Qdrant vector database (HTTP API).': 'Qdrant 向量数据库HTTP API。',
'Database Type': '数据库类型',
'Confirm embedding dimension change': '确认修改向量维度',
'Changing the embedding dimension will clear the vector database automatically. You will need to rebuild indexes afterwards. Continue?': '修改向量维度会自动清空向量数据库,之后需要重建索引,是否继续?',
'Confirm clear vector database?': '确认清空向量数据库?',
'This will delete all collections irreversibly.': '此操作将删除所有集合中的所有数据,且不可逆。',
'Confirm Clear': '确认清空',
// 'Cancel' defined above
'Vector database cleared': '向量数据库已清空',
'Clear failed': '清空失败',
'Clear Vector DB': '清空向量库',
'App Name': '应用名称',
'Logo URL': 'LOGO地址',
'Favicon URL': 'Favicon 地址',
'App Domain': '应用域名',
'File Domain': '文件域名',
'Configure Access Key and Secret to enable S3 mapping.': '配置 Access Key 与 Secret 后才能启用 S3 映射。',
'Mount point inside the virtual file system (e.g. / or /workspace).': '虚拟文件系统中的挂载路径,例如 / 或 /workspace。',
'Please input bucket name': '请输入 Bucket 名',
'Please input region': '请输入 Region',
'Please input access key': '请输入 Access Key',
'Please input secret key': '请输入 Secret Key',
'Save S3 Settings': '保存 S3 配置',
'Example CLI command': '示例 CLI 命令',
'WebDAV Mapping': 'WebDAV 映射',
'WebDAV Endpoint': 'WebDAV 访问地址',
'Basic (system account password)': 'Basic系统账号密码',
'Root Path': '根路径',
'Client Compatibility': '客户端兼容性',
'Supports Finder, Windows network drive, rclone, and other WebDAV clients.': '兼容 Finder、Windows 网络驱动器、rclone 等 WebDAV 客户端。',
'Toggle the switch to expose the virtual file system via WebDAV.': '通过开关控制是否对外暴露虚拟文件系统的 WebDAV 协议。',
'SMTP Settings': 'SMTP 配置',
'SMTP Host': 'SMTP 服务器',
'Please input SMTP host': '请输入 SMTP 服务器',
'SMTP Port': 'SMTP 端口',
'Please input SMTP port': '请输入 SMTP 端口',
'Security': '安全协议',
'None': '无',
'SSL': 'SSL',
'STARTTLS': 'STARTTLS',
'Timeout (seconds)': '超时时间(秒)',
'Sender': '发件人',
'Sender Name': '发件人名称',
'Sender Email': '发件人邮箱',
'Please input sender email': '请输入发件人邮箱',
'Authentication': '身份认证',
'SMTP Username': 'SMTP 用户名',
'SMTP Password': 'SMTP 密码',
'Test Email': '测试发信',
'Current Configuration': '当前配置摘要',
'Available variables': '可用变量',
'Not set': '未设置',
'Password Reset Template': '密码重置模板',
'Live Preview': '实时预览',
'Template saved': '模板已保存',
'Failed to save template': '模板保存失败',
'Failed to load template': '模板加载失败',
'Preview failed': '预览失败',
'Foxel Mail Test': 'Foxel 邮件测试',
'Recipient Address': '收件人地址',
'Please input recipient email': '请输入收件人邮箱',
'Test Subject': '测试邮件标题',
'Test User Name': '测试用户名',
'Optional': '可选',
'Send Test Email': '发送测试邮件',
'Please complete all required fields': '请填写所有必填项',
'SMTP port must be a positive number': 'SMTP 端口必须为正数',
'Test email queued (task {{taskId}})': '测试邮件已入队(任务 {{taskId}}',
'Test email failed': '测试邮件发送失败',
'Vision API URL': '视觉模型 API 地址',
'Vision API Key': '视觉模型 API Key',
'Embedding API URL': '嵌入模型 API 地址',
'Embedding API Key': '嵌入模型 API Key',
'AI Providers & Models': 'AI 提供商与模型',
'Manage AI providers, synchronize compatible models, and configure default capabilities across the system.': '管理所有 AI 提供商,批量同步兼容模型,并配置系统默认能力。',
'Add Provider': '添加提供商',
'Edit Provider': '编辑提供商',
'Pull Models': '拉取模型',
'Manual Add': '手动添加',
'Clear Remote List': '清空列表',
'Select models from the list to add them automatically': '选择模型后可一键添加到系统',
'No remote models': '暂无远程模型',
'No remote models found': '未获取到远程模型',
'No remote models match search': '没有匹配的远程模型',
'Search fetched models': '搜索已拉取模型',
'Already Added': '已添加',
'Add Selected Models': '添加所选模型',
'Fetch failed': '拉取失败',
'Select models to add': '请选择要添加的模型',
'Added {count} models': '已添加 {count} 个模型',
'Custom Provider': '自定义提供商',
'Custom Provider Description': '自定义兼容 OpenAI 或 Gemini 标准的 API 端点。',
'OpenAI Provider': 'OpenAI',
'OpenAI Provider Description': '访问 OpenAI 的 GPT-4o、GPT-4.1、GPT-3.5 等模型。',
'Azure OpenAI Provider': 'Azure OpenAI',
'Azure OpenAI Provider Description': '使用托管在微软 Azure 上的 OpenAI 模型。',
'Google AI Provider': 'Google AI',
'Google AI Provider Description': 'Google AI 平台提供的 Gemini 系列模型。',
'SiliconFlow Provider': '硅基流动',
'SiliconFlow Provider Description': '硅基流动高性能推理平台,兼容 OpenAI 接口。',
'OpenRouter Provider': 'OpenRouter',
'OpenRouter Provider Description': '通过一个 OpenAI 风格入口接入多家 AI 提供商。',
'Anthropic Provider': 'Anthropic',
'Anthropic Provider Description': '通过 Claude API 使用 Claude 3 系列模型。',
'DeepSeek Provider': 'DeepSeek',
'DeepSeek Provider Description': 'DeepSeek 语言模型,支持 OpenAI 兼容接口。',
'Grok Provider': 'Grok (xAI)',
'Grok Provider Description': 'xAI 的 Grok 模型,提供 OpenAI 风格接口。',
'Ollama Provider': 'Ollama',
'Ollama Provider Description': '使用 Ollama 在本地运行并管理大模型。',
'Voyage Provider': 'Voyage AI',
'Voyage Provider Description': 'Voyage AI 提供的高质量嵌入与重排序模型。',
'Delete provider?': '确认删除该提供商?',
'Deleting this provider will also remove all associated models. Continue?': '删除后将同时移除该提供商下的全部模型,是否继续?',
'Deleted successfully': '删除成功',
'Sync Models': '同步模型',
'Sync completed: {created} created, {updated} updated': '同步完成:新增 {created} 个,更新 {updated} 个',
'Sync failed': '同步失败',
'Add Model': '添加模型',
'Edit Model': '编辑模型',
'Delete model?': '确认删除该模型?',
'This operation cannot be undone. Continue?': '此操作不可撤销,是否继续?',
'No models yet': '暂无模型',
'Add your first AI provider to get started': '添加第一个 AI 提供商开始配置',
'Default Models Configuration': '默认模型配置',
'Main Chat Model': '主对话模型',
'Primary assistant for conversations, reasoning, and tool calls.': '用于对话、推理与工具调用的核心模型。',
'Handles multimodal perception such as image understanding.': '负责多模态感知与图像理解。',
'Transforms content into dense vectors for search and retrieval.': '将内容向量化以驱动搜索与检索。',
'Optimises ranking quality for search candidates.': '重新排序候选结果,提升检索相关性。',
'Covers text-to-speech and speech understanding scenarios.': '覆盖文本转语音与语音理解场景。',
'Supports function calling, orchestration, and automation.': '支持函数调用、编排与自动化。',
'Select a model': '选择模型',
'Template': '模板',
'Select a template': '选择模板',
'Display Name': '显示名称',
'Enter name': '请输入名称',
'Identifier': '标识符',
'Enter identifier': '请输入标识符',
'Only lowercase letters, numbers, dash, dot and underscore are allowed': '仅允许小写字母、数字、连字符、点和下划线',
'API Format': 'API 格式',
'Base URL': '基础 URL',
'Enter base url': '请输入基础 URL',
'Optional, can also be provided per request': '可选,也可在请求时提供',
'Model Identifier': '模型标识',
'Enter model identifier': '请输入模型标识',
'Description': '描述',
'Capabilities': '能力标签',
'Context Window': '上下文窗口',
'Embedding Dimensions': '向量维度',
'Price /1K input tokens': '价格 /1K 输入 token',
'Price /1K output tokens': '价格 /1K 输出 token',
// Adapters
'Missing required config:': '缺少必填配置:',
'Updated successfully': '更新成功',
'Created successfully': '创建成功',
'Operation failed': '操作失败',
'Deleted': '已删除',
'Delete failed': '删除失败',
'Status updated': '状态已更新',
'Update failed': '更新失败',
'Mount Path': '挂载路径',
'Sub Path': '子路径',
'Sub Path (optional)': '子路径(可选)',
'Sub directory inside adapter': '适配器内部子目录',
'Enabled': '启用',
'Actions': '操作',
'Edit': '编辑',
'Confirm delete?': '确认删除?',
'No config fields': '无配置项',
'Please input {label}': '请输入{label}',
'Storage Adapters': '存储适配器',
'Create Adapter': '新建适配器',
'Unique name': '唯一名称',
'Select adapter type': '选择适配器类型',
'/ or /drive': '/或/drive',
'Adapter Config': '适配器配置',
'adapter.type.local': '本地文件系统',
'adapter.type.webdav': 'WebDAV',
'adapter.type.googledrive': 'Google Drive',
'adapter.type.onedrive': 'OneDrive',
'adapter.type.s3': 'Amazon S3',
'adapter.type.ftp': 'FTP',
'adapter.type.sftp': 'SFTP',
'adapter.type.telegram': 'Telegram',
'adapter.type.quark': '夸克网盘',
// Tasks
'Automation Tasks': '自动化任务',
'Running Tasks': '运行中的任务',
'Create Task': '新建任务',
'Edit Task': '编辑任务',
'Create Automation Task': '新建自动化任务',
'Task Name': '任务名称',
'Trigger Event': '触发事件',
'File Written': '文件写入',
'File Deleted': '文件删除',
'Matching Rules': '匹配规则',
'Path Prefix (optional)': '路径前缀 (可选)',
'Filename Regex (optional)': '文件名正则 (可选)',
'Action': '执行动作',
'Current Task Queue': '当前任务队列',
'Params': '参数',
'Status': '状态',
// Logs
'Confirm clear logs?': '确认清理日志?',
'This will delete logs in selected range irreversibly.': '该操作将删除选定时间范围内的所有日志,且不可恢复。',
'Cleared {count} logs': '成功清理 {count} 条日志',
'Time': '时间',
'Level': '级别',
'Source': '来源',
'Message': '消息',
'User ID': '用户 ID',
'Search source': '搜索来源',
'Clear': '清理',
'Log Details': '日志详情',
'Raw Log': '原始日志',
// Backup
'Export started, check your downloads.': '导出已开始,请检查您的下载。',
'Export failed': '导出失败',
'Confirm import backup?': '确认导入备份?',
'Are you sure to import from this file?': '您确定要从此文件导入数据吗?',
'Warning: This will overwrite all data including users (with passwords), settings, storages and tasks. Irreversible!': '警告:此操作将覆盖当前数据库中的所有现有数据,包括用户(含密码)、设置、存储和任务。此操作不可逆!',
'Confirm Import': '确认导入',
'Import succeeded! The page will refresh.': '导入成功!页面将刷新。',
'Import failed': '导入失败',
'Export': '导出',
'Import': '恢复',
'Export all data (adapters, users, tasks, shares) into a JSON file.': '点击按钮将所有数据(包括存储、用户、自动化任务和分享)导出为一个 JSON 文件。',
'Keep your backup file safe.': '请妥善保管您的备份文件。',
'Export Backup': '导出备份',
'Restore data from a previously exported JSON file.': '从之前导出的JSON文件恢复数据。',
'Warning: This will clear and overwrite existing data.': '警告:此操作将清除并覆盖现有数据。',
'Choose File and Restore': '选择文件并恢复',
// Empty state
'No files yet here': '这里还没有任何文件',
'This folder is empty': '此目录为空',
'Start uploading files or create folders to organize your content': '开始上传文件或创建新目录来组织您的内容',
'You can create folders or upload files here': '您可以在此目录中创建新的文件夹或上传文件',
// File actions
'Please input name': '请输入名称',
'Confirm delete {name}?': '确认删除 {name} ?',
'items': '项',
'Downloading folders is not supported': '暂不支持下载目录',
'Download failed': '下载失败',
'Please select files or folders to share': '请选择要分享的文件或目录',
'Direct links for folders are not supported': '不支持获取目录的直链',
// Processor flow
'Processing finished': '处理完成',
'Processing failed': '处理失败',
'Processors': '处理器',
'Processor List': '处理器列表',
'Reload': '重载',
'Run Processor': '运行处理器',
'Target Path': '目标路径',
'Please select a path': '请选择路径',
'Select Directory': '选择目录',
'Overwrite original': '覆盖原文件',
'Save To': '保存到',
'Optional output path': '可选输出路径',
'Run': '运行',
'Select a processor': '选择处理器',
'No module path': '未检测到模块路径',
'Source saved': '源码已保存',
'Processors reloaded': '处理器已重载',
'Unsaved changes': '存在未保存的修改',
'Switching processor will discard unsaved changes. Continue?': '切换处理器会丢失未保存的修改,确认继续?',
'Task submitted': '任务已提交',
'Supported Extensions': '支持的扩展名',
'All': '全部',
'Produces File': '生成文件',
'Yes': '是',
'No': '否',
'Please select a processor': '请选择处理器',
'Select a path': '请选择路径',
'Source Editor': '源码编辑',
'Module Path': '模块路径',
'Directory processing always overwrites original files': '选择目录时会强制覆盖原文件',
'Directory execution will enqueue one task per file': '目录模式会为每个文件单独创建任务',
'Directory scope': '目录范围',
'Current level only': '仅当前层级',
'Include subdirectories': '包含子目录',
'Max depth': '最大层级',
'Leave empty to traverse all subdirectories': '留空表示遍历所有子目录',
'Depth must be greater or equal to 0': '层级必须大于或等于 0',
'Output suffix': '输出后缀',
'Suffix will be inserted before the file extension, e.g. demo_processed.mp4': '后缀会插入到文件扩展名前,例如 demo_processed.mp4',
'Suffix such as _processed': '例如 _processed 的后缀',
'Suffix cannot be empty': '后缀不能为空',
'No data': '暂无数据',
// Path selector
'Select File': '选择文件',
'Select Path': '选择路径',
'Select Folder': '选择目录',
'Select': '选择',
'Current': '当前',
'Up': '上一级',
'Select Current Folder': '选择当前目录',
'Please select a file': '请选择一个文件',
// Plugins page
'Installed successfully': '安装成功',
'Plugin': '插件',
'Open Link': '打开链接',
'Link copied': '已复制链接',
'Copy Link': '复制链接',
'Confirm delete this plugin?': '确认删除该插件?',
'Author': '作者',
'Website': '官网',
'Install App': '安装应用',
'Search name/author/url/extension': '搜索 名称/作者/链接/扩展名',
'No plugins': '暂无插件',
'Install': '安装',
'App URL': '应用链接',
'Please input a valid URL': '请输入合法的 URL',
'Installed': '已安装',
'Discover': '发现',
'Search apps': '搜索应用',
'Sort by': '排序',
'Downloads': '下载量',
'Created (newest)': '创建时间(最新)',
'Installed already': '已安装',
'No results': '暂无结果',
// Setup page
'Initialization succeeded! Logging you in...': '初始化成功!正在为您登录,请不要刷新。',
'Initialization failed, please try later': '初始化失败,请稍后重试',
'Database Setup': '数据库设置',
'Choose database driver': '选择数据库驱动',
'Select database and vector database for system data': '选择用于存储系统数据的数据库和向量数据库。',
'Database Driver': '数据库驱动',
'Vector DB Driver': '向量数据库驱动',
'Initialize Mount': '初始化挂载',
'Configure initial storage': '配置初始存储',
'Create the first storage mount for your files': '为您的文件创建第一个存储挂载点。',
'Mount Name': '挂载名称',
'Local Storage': '本地存储',
'Please input mount name!': '请输入挂载名称!',
'Storage Type': '存储类型',
'Please input mount path!': '请输入挂载路径!',
'Root Directory': '根目录',
'Please input root directory!': '请输入根目录!',
'e.g., data/ or /var/foxel/data': '例如: data/ 或 /var/foxel/data',
'Optional, used for external links. Leave empty to use the current site.': '可选,用于生成外部链接;留空则使用当前站点。',
'Create Admin': '创建管理员',
'Create admin account': '创建管理员账户',
'This is the first account with full permissions': '这是系统的第一个账户,将拥有最高权限。',
'Username': '用户名',
'Please input a valid email!': '请输入有效的邮箱地址!',
'Please confirm your password!': '请确认您的密码!',
'Passwords do not match!': '两次输入的密码不一致!',
'System Initialization': '系统初始化',
'Previous': '上一步',
'Next': '下一步',
'Finish Initialization': '完成初始化',
// Plugin host
'Plugin run failed': '插件运行失败',
'Plugin Error': '插件错误',
'Cannot open file: no available app': '无法打开该文件:没有可用的应用',
'Error': '错误',
'App "{key}" not found.': '应用 "{key}" 不存在。',
'Open with {app}': '使用 {app} 打开',
'Set as default for .{ext}': '设为该类型(.{ext})默认应用',
'Advanced tokens must be valid JSON': '高级 Token 需为合法 JSON',
} as const;
export type ZhKeys = keyof typeof zh;

View File

@@ -44,7 +44,7 @@ export const navGroups: NavGroup[] = [
children: [
{ key: 'settings', icon: React.createElement(SettingOutlined), label: 'System Settings' },
{ key: 'backup', icon: React.createElement(DatabaseOutlined), label: 'Backup & Restore' },
{ key: 'logs', icon: React.createElement(BugOutlined), label: 'System Logs' }
{ key: 'audit', icon: React.createElement(BugOutlined), label: 'Audit Logs' }
]
}
];

View File

@@ -0,0 +1,298 @@
import { memo, useState, useEffect, useCallback } from 'react';
import { Table, message, Tag, Input, Select, Button, Space, Modal, DatePicker, Descriptions, Divider, Typography } from 'antd';
import PageCard from '../components/PageCard';
import { auditApi, type AuditLogItem, type PaginatedAuditLogs } from '../api/audit';
import { useI18n } from '../i18n';
import { format, formatISO } from 'date-fns';
const { RangePicker } = DatePicker;
const ACTION_OPTIONS = [
'login',
'logout',
'register',
'read',
'create',
'update',
'delete',
'reset_password',
'share',
'download',
'upload',
'other'
];
const AuditLogsPage = memo(function AuditLogsPage() {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<PaginatedAuditLogs | null>(null);
const [filters, setFilters] = useState<{
page: number;
page_size: number;
action: string;
success: '' | boolean;
username: string;
path: string;
start_time: string;
end_time: string;
}>({
page: 1,
page_size: 20,
action: '',
success: '',
username: '',
path: '',
start_time: '',
end_time: '',
});
const [selectedLog, setSelectedLog] = useState<AuditLogItem | null>(null);
const { t } = useI18n();
const buildParams = () => {
const params: any = { ...filters };
if (!params.action) delete params.action;
if (params.success === '') delete params.success;
if (!params.username) delete params.username;
if (!params.path) delete params.path;
if (!params.start_time) delete params.start_time;
if (!params.end_time) delete params.end_time;
return params;
};
const fetchList = useCallback(async () => {
setLoading(true);
try {
const res = await auditApi.list(buildParams());
setData(res);
} catch (e: any) {
message.error(e.message || t('Load failed'));
} finally {
setLoading(false);
}
}, [filters]);
useEffect(() => {
fetchList();
}, [fetchList]);
const handleClearLogs = () => {
if (!filters.start_time && !filters.end_time) {
message.warning(t('Please select time range'));
return;
}
Modal.confirm({
title: t('Confirm clear logs?'),
content: t('This will delete logs in selected range irreversibly.'),
onOk: async () => {
try {
const params: any = {};
if (filters.start_time) params.start_time = filters.start_time;
if (filters.end_time) params.end_time = filters.end_time;
const res = await auditApi.clear(params);
message.success(t('Cleared {count} logs', { count: String(res.deleted_count) }));
fetchList();
} catch (e: any) {
message.error(e.message || t('Clear failed'));
}
},
});
};
const columns = [
{
title: t('Time'),
dataIndex: 'created_at',
width: 180,
render: (ts: string) => format(new Date(ts), 'yyyy-MM-dd HH:mm:ss'),
},
{
title: t('Action'),
dataIndex: 'action',
width: 140,
render: (action: string) => <Tag color="blue">{action}</Tag>,
},
{
title: t('User'),
dataIndex: 'username',
width: 160,
render: (_: any, rec: AuditLogItem) => rec.username || rec.user_id || '-',
},
{
title: t('Path'),
dataIndex: 'path',
ellipsis: true,
render: (path: string, rec: AuditLogItem) => (
<Space size={4}>
<Tag bordered={false} color="default" style={{ margin: 0, paddingInline: 8 }}>{rec.method}</Tag>
<span style={{ maxWidth: 320, display: 'inline-block' }}>{path}</span>
</Space>
),
},
{
title: t('Status Code'),
dataIndex: 'status_code',
width: 100,
},
{
title: t('Duration (ms)'),
dataIndex: 'duration_ms',
width: 120,
render: (ms?: number | null) => (ms !== null && ms !== undefined ? ms : '-'),
},
{
title: t('Client IP'),
dataIndex: 'client_ip',
width: 140,
render: (ip?: string | null) => ip || '-',
},
{
title: t('Result'),
dataIndex: 'success',
width: 100,
render: (success: boolean) => (
<Tag color={success ? 'green' : 'red'}>
{success ? t('Success') : t('Failure')}
</Tag>
),
},
{
title: t('Actions'),
width: 100,
render: (_: any, rec: AuditLogItem) => (
<Button size="small" onClick={() => setSelectedLog(rec)}>{t('Details')}</Button>
),
},
];
return (
<PageCard
title={t('Audit Logs')}
extra={
<Space align="center" wrap size={[8, 8]}>
<RangePicker
showTime
size="small"
onChange={dates => {
setFilters(f => ({
...f,
start_time: dates?.[0] ? formatISO(dates[0].toDate()) : '',
end_time: dates?.[1] ? formatISO(dates[1].toDate()) : '',
page: 1,
}));
}}
/>
<Select
style={{ width: 120 }}
placeholder={t('Action')}
allowClear
size="small"
value={filters.action || undefined}
onChange={action => setFilters(f => ({ ...f, action: action || '', page: 1 }))}
options={ACTION_OPTIONS.map(a => ({ value: a, label: a }))}
/>
<Select
style={{ width: 120 }}
placeholder={t('Result')}
allowClear
size="small"
value={filters.success === '' ? undefined : filters.success}
onChange={value => setFilters(f => ({ ...f, success: (value as boolean) ?? '', page: 1 }))}
options={[
{ value: true, label: t('Success') },
{ value: false, label: t('Failure') },
]}
/>
<Input.Search
style={{ width: 120 }}
placeholder={t('Username')}
size="small"
allowClear
value={filters.username}
onChange={e => setFilters(f => ({ ...f, username: e.target.value }))}
onSearch={username => setFilters(f => ({ ...f, username, page: 1 }))}
/>
<Button onClick={fetchList} loading={loading}>{t('Refresh')}</Button>
<Button danger onClick={handleClearLogs}>{t('Clear')}</Button>
</Space>
}
>
<Table
rowKey="id"
dataSource={data?.items}
columns={columns}
loading={loading}
pagination={{
current: filters.page,
pageSize: filters.page_size,
total: data?.total,
showSizeChanger: true,
onChange: (page, pageSize) => setFilters(f => ({ ...f, page, page_size: pageSize })),
}}
scroll={{ x: 900 }}
/>
<Modal
title={t('Audit Log Details')}
open={!!selectedLog}
onCancel={() => setSelectedLog(null)}
footer={null}
width={900}
>
{selectedLog && (
<Space direction="vertical" size={16} style={{ width: '100%' }}>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label={t('Time')}>
{format(new Date(selectedLog.created_at), 'yyyy-MM-dd HH:mm:ss')}
</Descriptions.Item>
<Descriptions.Item label={t('Action')}>
<Tag color="blue">{selectedLog.action}</Tag>
</Descriptions.Item>
<Descriptions.Item label={t('User')}>
{selectedLog.username || selectedLog.user_id || '-'}
</Descriptions.Item>
<Descriptions.Item label={t('Client IP')}>
{selectedLog.client_ip || '-'}
</Descriptions.Item>
<Descriptions.Item label={t('Path')} span={2}>
<Space size={6}>
<Tag bordered={false} color="default" style={{ margin: 0, paddingInline: 8 }}>{selectedLog.method}</Tag>
<Typography.Text copyable>{selectedLog.path}</Typography.Text>
</Space>
</Descriptions.Item>
<Descriptions.Item label={t('Status Code')}>
{selectedLog.status_code}
</Descriptions.Item>
<Descriptions.Item label={t('Duration (ms)')}>
{selectedLog.duration_ms ?? '-'}
</Descriptions.Item>
<Descriptions.Item label={t('Result')}>
<Tag color={selectedLog.success ? 'green' : 'red'}>
{selectedLog.success ? t('Success') : t('Failure')}
</Tag>
</Descriptions.Item>
<Descriptions.Item label={t('Description')}>
{selectedLog.description || '-'}
</Descriptions.Item>
<Descriptions.Item label={t('Error')} span={2}>
{selectedLog.error || '-'}
</Descriptions.Item>
</Descriptions>
<Divider style={{ margin: '12px 0 0' }} />
<Typography.Title level={5} style={{ margin: 0 }}>
{t('Request Params')}
</Typography.Title>
<pre style={{ maxHeight: 200, overflow: 'auto', background: 'var(--ant-color-fill-tertiary, #f5f5f5)', padding: 12 }}>
{selectedLog.request_params ? JSON.stringify(selectedLog.request_params, null, 2) : '-'}
</pre>
<Typography.Title level={5} style={{ margin: 0 }}>
{t('Request Body')}
</Typography.Title>
<pre style={{ maxHeight: 200, overflow: 'auto', background: 'var(--ant-color-fill-tertiary, #f5f5f5)', padding: 12 }}>
{selectedLog.request_body ? JSON.stringify(selectedLog.request_body, null, 2) : '-'}
</pre>
</Space>
)}
</Modal>
</PageCard>
);
});
export default AuditLogsPage;

View File

@@ -1,184 +0,0 @@
import { memo, useState, useEffect, useCallback } from 'react';
import { Table, message, Tag, Input, Select, Button, Space, Modal, DatePicker, Descriptions, Divider, Typography } from 'antd';
import PageCard from '../components/PageCard';
import { logsApi, type LogItem, type PaginatedLogs } from '../api/logs';
import { useI18n } from '../i18n';
import { format, formatISO } from 'date-fns';
const { RangePicker } = DatePicker;
const LOG_LEVELS = ['API', 'INFO', 'WARNING', 'ERROR'];
const LEVEL_COLOR_MAP: Record<string, string> = { API: 'blue', INFO: 'green', WARNING: 'orange', ERROR: 'red' };
const LogsPage = memo(function LogsPage() {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<PaginatedLogs | null>(null);
const [filters, setFilters] = useState({
page: 1,
page_size: 20,
level: '',
source: '',
start_time: '',
end_time: '',
});
const [selectedLog, setSelectedLog] = useState<LogItem | null>(null);
const { t } = useI18n();
const fetchList = useCallback(async () => {
setLoading(true);
try {
const params = { ...filters };
if (!params.start_time) delete (params as any).start_time;
if (!params.end_time) delete (params as any).end_time;
const res = await logsApi.list(params);
setData(res);
} catch (e: any) {
message.error(e.message || t('Load failed'));
} finally {
setLoading(false);
}
}, [filters]);
useEffect(() => {
fetchList();
}, [fetchList]);
const handleClearLogs = () => {
Modal.confirm({
title: t('Confirm clear logs?'),
content: t('This will delete logs in selected range irreversibly.'),
onOk: async () => {
try {
const params = { start_time: filters.start_time, end_time: filters.end_time };
if (!params.start_time) delete (params as any).start_time;
if (!params.end_time) delete (params as any).end_time;
const res = await logsApi.clear(params);
message.success(t('Cleared {count} logs', { count: String(res.deleted_count) }));
fetchList();
} catch (e: any) {
message.error(e.message || t('Clear failed'));
}
},
});
};
const columns = [
{
title: t('Time'),
dataIndex: 'timestamp',
width: 180,
render: (ts: string) => format(new Date(ts), 'yyyy-MM-dd HH:mm:ss'),
},
{
title: t('Level'),
dataIndex: 'level',
width: 100,
render: (level: string) => {
const color = LEVEL_COLOR_MAP[level] || 'default';
return <Tag color={color}>{level}</Tag>;
},
},
{ title: t('Source'), dataIndex: 'source', width: 180 },
{ title: t('Message'), dataIndex: 'message', ellipsis: true },
{
title: t('Actions'),
width: 100,
render: (_: any, rec: LogItem) => (
<Button size="small" onClick={() => setSelectedLog(rec)}>{t('Details')}</Button>
),
},
];
return (
<PageCard
title={t('System Logs')}
extra={
<Space align="center">
<RangePicker
showTime
size="small"
onChange={dates => {
setFilters(f => ({
...f,
start_time: dates?.[0] ? formatISO(dates[0].toDate()) : '',
end_time: dates?.[1] ? formatISO(dates[1].toDate()) : '',
page: 1,
}));
}}
/>
<Select
style={{ width: 120 }}
placeholder={t('Level')}
allowClear
size="small"
value={filters.level || undefined}
onChange={level => setFilters(f => ({ ...f, level: level || '', page: 1 }))}
options={LOG_LEVELS.map(l => ({ value: l, label: l }))}
/>
<Input.Search
style={{ width: 240 }}
placeholder={t('Search source')}
size="small"
onSearch={source => setFilters(f => ({ ...f, source, page: 1 }))}
allowClear
/>
<Button onClick={fetchList} loading={loading}>{t('Refresh')}</Button>
<Button danger onClick={handleClearLogs}>{t('Clear')}</Button>
</Space>
}
>
<Table
rowKey="id"
dataSource={data?.items}
columns={columns}
loading={loading}
pagination={{
current: filters.page,
pageSize: filters.page_size,
total: data?.total,
showSizeChanger: true,
onChange: (page, pageSize) => setFilters(f => ({ ...f, page, page_size: pageSize })),
}}
/>
<Modal
title={t('Log Details')}
open={!!selectedLog}
onCancel={() => setSelectedLog(null)}
footer={null}
width={800}
>
{selectedLog && (
<Space direction="vertical" size={16} style={{ width: '100%' }}>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label={t('Time')}>
{format(new Date(selectedLog.timestamp), 'yyyy-MM-dd HH:mm:ss')}
</Descriptions.Item>
<Descriptions.Item label={t('Level')}>
<Tag color={LEVEL_COLOR_MAP[selectedLog.level] || 'default'}>{selectedLog.level}</Tag>
</Descriptions.Item>
<Descriptions.Item label={t('Source')}>
{selectedLog.source}
</Descriptions.Item>
<Descriptions.Item label={t('Message')}>
<Typography.Text style={{ whiteSpace: 'pre-wrap' }}>{selectedLog.message}</Typography.Text>
</Descriptions.Item>
<Descriptions.Item label={t('User ID')}>
{selectedLog.user_id ?? '-'}
</Descriptions.Item>
</Descriptions>
<Divider style={{ margin: '12px 0 0' }} />
<Typography.Title level={5} style={{ margin: 0 }}>
{t('Raw Log')}
</Typography.Title>
<pre style={{ maxHeight: '60vh', overflow: 'auto', background: 'var(--ant-color-fill-tertiary, #f5f5f5)', padding: 12 }}>
{JSON.stringify(selectedLog, null, 2)}
</pre>
</Space>
)}
</Modal>
</PageCard>
);
});
export default LogsPage;

View File

@@ -11,7 +11,7 @@ import TaskQueuePage from '../pages/TaskQueuePage.tsx';
import ProcessorsPage from '../pages/ProcessorsPage.tsx';
import OfflineDownloadPage from '../pages/OfflineDownloadPage.tsx';
import SystemSettingsPage from '../pages/SystemSettingsPage/SystemSettingsPage.tsx';
import LogsPage from '../pages/LogsPage.tsx';
import AuditLogsPage from '../pages/AuditLogsPage.tsx';
import BackupPage from '../pages/SystemSettingsPage/BackupPage.tsx';
import PluginsPage from '../pages/PluginsPage.tsx';
import { AppWindowsProvider, useAppWindows } from '../contexts/AppWindowsContext';
@@ -58,7 +58,7 @@ const ShellBody = memo(function ShellBody() {
onTabNavigate={(key, options) => navigate(`/settings/${key}`, options)}
/>
)}
{navKey === 'logs' && <LogsPage />}
{navKey === 'audit' && <AuditLogsPage />}
{navKey === 'backup' && <BackupPage />}
</Flex>
</div>

View File

@@ -12,6 +12,7 @@
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"resolveJsonModule": true,
"noEmit": true,
"jsx": "react-jsx",