mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
feat(i18n): add Chinese (中文) language support
- Add lightweight i18n system using React Context + localStorage - Zero external dependencies, integrates with existing Ant Design locale - Create locales/en.json and locales/zh.json with full translations - LanguageProvider wraps ConfigProvider for seamless antd locale switching - Add LanguageSwitcher component to navbar - Covers: nav, models, API keys, users, logs, settings, common actions Closes #28526
This commit is contained in:
parent
5c41aabab0
commit
e2c60c6c85
7 changed files with 615 additions and 0 deletions
36
src/app/layout.tsx
Normal file
36
src/app/layout.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
import AntdGlobalProvider from "@/contexts/AntdGlobalProvider";
|
||||
import { AuthProvider } from "@/contexts/AuthContext";
|
||||
import { LanguageProvider } from "@/contexts/LanguageContext";
|
||||
import ReactQueryProvider from "@/contexts/ReactQueryProvider";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "LiteLLM Dashboard",
|
||||
description: "LiteLLM Proxy Admin UI",
|
||||
icons: { icon: "/get_favicon" },
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<ReactQueryProvider>
|
||||
<LanguageProvider>
|
||||
<AntdGlobalProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</AntdGlobalProvider>
|
||||
</LanguageProvider>
|
||||
</ReactQueryProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
37
src/components/LanguageSwitcher.tsx
Normal file
37
src/components/LanguageSwitcher.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "antd";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
/**
|
||||
* Inline language toggle used in navbar.
|
||||
* Shows the opposite language (中文 ↔ English).
|
||||
*/
|
||||
export function LanguageSwitcher() {
|
||||
const { locale, setLocale, t } = useLanguage();
|
||||
|
||||
const toggle = () => setLocale(locale === "en" ? "zh" : "en");
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={toggle}
|
||||
className="text-gray-600 hover:text-gray-900"
|
||||
title={t("language.label")}
|
||||
>
|
||||
{t("language.switch_to")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps children in LanguageProvider.
|
||||
* This should be placed above ConfigProvider in the component tree.
|
||||
*/
|
||||
export function LanguageProviderWrapper({ children }: { children: React.ReactNode }) {
|
||||
return <LanguageProvider>{children}</LanguageProvider>;
|
||||
}
|
||||
|
||||
export { LanguageProvider };
|
||||
167
src/components/navbar.tsx
Normal file
167
src/components/navbar.tsx
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { useHealthReadinessDetails } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails";
|
||||
import { useDisableBouncingIcon } from "@/app/(dashboard)/hooks/useDisableBouncingIcon";
|
||||
import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts";
|
||||
import { useWorker } from "@/hooks/useWorker";
|
||||
import { getProxyBaseUrl } from "@/components/networking";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { clearTokenCookies } from "@/utils/cookieUtils";
|
||||
import { clearStoredReturnUrl } from "@/utils/returnUrlUtils";
|
||||
import useProxySettings from "@/app/(dashboard)/hooks/proxySettings/useProxySettings";
|
||||
import { DownOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons";
|
||||
import { Tag } from "antd";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import { BlogDropdown } from "./Navbar/BlogDropdown/BlogDropdown";
|
||||
import { CommunityEngagementButtons } from "./Navbar/CommunityEngagementButtons/CommunityEngagementButtons";
|
||||
import { NAV_PRODUCT_LINK_CLASS } from "./Navbar/navProductLinkClass";
|
||||
import { NotificationsBell } from "./Navbar/NotificationsBell/NotificationsBell";
|
||||
import UserDropdown from "./Navbar/UserDropdown/UserDropdown";
|
||||
import { LanguageSwitcher } from "./LanguageSwitcher";
|
||||
import WorkerDropdown from "./Navbar/WorkerDropdown/WorkerDropdown";
|
||||
|
||||
interface NavbarProps {
|
||||
accessToken: string | null;
|
||||
isPublicPage: boolean;
|
||||
sidebarCollapsed?: boolean;
|
||||
onToggleSidebar?: () => void;
|
||||
}
|
||||
|
||||
const Navbar: React.FC<NavbarProps> = ({
|
||||
accessToken,
|
||||
isPublicPage = false,
|
||||
sidebarCollapsed = false,
|
||||
onToggleSidebar,
|
||||
}) => {
|
||||
const baseUrl = getProxyBaseUrl();
|
||||
const proxySettings = useProxySettings(accessToken);
|
||||
const { logoUrl } = useTheme();
|
||||
const { data: healthData } = useHealthReadinessDetails(accessToken);
|
||||
const version = healthData?.litellm_version;
|
||||
const disableBouncingIcon = useDisableBouncingIcon();
|
||||
const hideCommunityLinks = useDisableShowPrompts();
|
||||
const { isControlPlane, selectedWorker } = useWorker();
|
||||
const showWorkerSwitch = isControlPlane && selectedWorker !== null;
|
||||
|
||||
const imageUrl = logoUrl || `${baseUrl}/get_image`;
|
||||
|
||||
const handleLogout = () => {
|
||||
clearTokenCookies();
|
||||
localStorage.removeItem("litellm_selected_worker_id");
|
||||
localStorage.removeItem("litellm_worker_url");
|
||||
window.location.href = proxySettings.PROXY_LOGOUT_URL || "";
|
||||
};
|
||||
|
||||
const handleWorkerSwitch = (workerId: string) => {
|
||||
clearTokenCookies();
|
||||
clearStoredReturnUrl();
|
||||
localStorage.removeItem("litellm_selected_worker_id");
|
||||
localStorage.removeItem("litellm_worker_url");
|
||||
window.location.href = `/ui/login?worker=${encodeURIComponent(workerId)}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="sticky top-0 z-10 border-b border-gray-200 bg-white">
|
||||
<div className="w-full">
|
||||
<div className="flex h-14 items-center px-4">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
{onToggleSidebar && (
|
||||
<button
|
||||
onClick={onToggleSidebar}
|
||||
className="mr-2 flex h-9 w-9 items-center justify-center rounded-md text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
title={sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
||||
>
|
||||
<span className="text-lg">{sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href={baseUrl ? baseUrl : "/"} className="flex items-center">
|
||||
<div className="relative">
|
||||
<div className="flex h-10 max-w-48 items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="LiteLLM Brand"
|
||||
className="h-auto max-h-full w-auto max-w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
{version && (
|
||||
<div className="relative">
|
||||
{!disableBouncingIcon && (
|
||||
<span
|
||||
className="absolute -left-2 -top-1 animate-bounce text-lg"
|
||||
style={{ animationDuration: "2s" }}
|
||||
title="Thanks for using LiteLLM!"
|
||||
>
|
||||
🌑
|
||||
</span>
|
||||
)}
|
||||
<Tag className="relative z-10 cursor-pointer text-xs font-medium">
|
||||
<a
|
||||
href="https://docs.litellm.ai/release_notes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
v{version}
|
||||
</a>
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto flex min-w-0 flex-1 items-center justify-end gap-4">
|
||||
{showWorkerSwitch && (
|
||||
<div className="flex shrink-0 items-center">
|
||||
<WorkerDropdown onWorkerSwitch={handleWorkerSwitch} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<nav
|
||||
aria-label="Product documentation"
|
||||
className={`flex min-w-0 items-center gap-2 ${showWorkerSwitch ? "border-l border-gray-200 pl-4" : ""}`}
|
||||
>
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={NAV_PRODUCT_LINK_CLASS}
|
||||
>
|
||||
Docs
|
||||
{/* Layout parity with Blog chevron — intentional single-level link */}
|
||||
<DownOutlined className="pointer-events-none text-[10px] opacity-0" aria-hidden />
|
||||
</a>
|
||||
<BlogDropdown />
|
||||
</nav>
|
||||
|
||||
{/* Language switcher */}
|
||||
<div className="flex shrink-0 items-center">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
|
||||
{!hideCommunityLinks && (
|
||||
<div className="flex shrink-0 items-center border-l border-gray-200 pl-4">
|
||||
<CommunityEngagementButtons />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isPublicPage && (
|
||||
<div className="flex shrink-0 items-center border-l border-gray-200 pl-4">
|
||||
<div className="flex items-center gap-0.5 rounded-lg bg-gray-50 px-1 py-0 transition-colors hover:bg-gray-100">
|
||||
<NotificationsBell />
|
||||
<span className="mx-0.5 h-6 w-px shrink-0 bg-gray-200" aria-hidden />
|
||||
<UserDropdown onLogout={handleLogout} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Dark mode toggle: keep disabled until the dashboard supports dark styles end-to-end. */}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
61
src/contexts/LanguageContext.tsx
Normal file
61
src/contexts/LanguageContext.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"use client";
|
||||
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enUS from "antd/locale/en_US";
|
||||
import zhCN from "antd/locale/zh_CN";
|
||||
import { Locale, DEFAULT_LOCALE, LOCALE_STORAGE_KEY, t as translate } from "@/i18n";
|
||||
|
||||
interface LanguageContextValue {
|
||||
locale: Locale;
|
||||
setLocale: (l: Locale) => void;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
|
||||
const LanguageContext = createContext<LanguageContextValue>({
|
||||
locale: DEFAULT_LOCALE,
|
||||
setLocale: () => {},
|
||||
t: (key) => key,
|
||||
});
|
||||
|
||||
export function useLanguage() {
|
||||
return useContext(LanguageContext);
|
||||
}
|
||||
|
||||
const antdLocales: Record<Locale, any> = {
|
||||
en: enUS,
|
||||
zh: zhCN,
|
||||
};
|
||||
|
||||
export function LanguageProvider({ children }: { children: React.ReactNode }) {
|
||||
const [locale, setLocaleState] = useState<Locale>(DEFAULT_LOCALE);
|
||||
|
||||
// Hydrate from localStorage on mount
|
||||
useEffect(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem(LOCALE_STORAGE_KEY);
|
||||
if (stored === "zh" || stored === "en") {
|
||||
setLocaleState(stored);
|
||||
}
|
||||
} catch {
|
||||
// localStorage not available
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setLocale = useCallback((l: Locale) => {
|
||||
setLocaleState(l);
|
||||
try {
|
||||
localStorage.setItem(LOCALE_STORAGE_KEY, l);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
|
||||
const t = useCallback((key: string) => translate(key, locale), [locale]);
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={{ locale, setLocale, t }}>
|
||||
<ConfigProvider locale={antdLocales[locale]}>{children}</ConfigProvider>
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
}
|
||||
54
src/i18n/index.ts
Normal file
54
src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* LiteLLM i18n - Lightweight translation system
|
||||
*
|
||||
* Supports English (default) and Chinese (中文).
|
||||
* Uses React Context for state + localStorage for persistence.
|
||||
* Zero external dependencies.
|
||||
*/
|
||||
|
||||
import en from "./locales/en.json";
|
||||
import zh from "./locales/zh.json";
|
||||
|
||||
export type Locale = "en" | "zh";
|
||||
export type TranslationMap = Record<string, Record<string, string>>;
|
||||
|
||||
const translations: Record<Locale, TranslationMap> = { en, zh };
|
||||
|
||||
/**
|
||||
* Resolve a dotted key like "nav.dashboard" against the current locale.
|
||||
*/
|
||||
export function t(key: string, locale: Locale = "en"): string {
|
||||
const map = translations[locale] || translations.en;
|
||||
const parts = key.split(".");
|
||||
let current: any = map;
|
||||
for (const part of parts) {
|
||||
if (current == null) break;
|
||||
current = current[part];
|
||||
}
|
||||
if (typeof current === "string") return current;
|
||||
|
||||
// Fallback to English
|
||||
let fallback: any = translations.en;
|
||||
for (const part of parts) {
|
||||
if (fallback == null) break;
|
||||
fallback = fallback[part];
|
||||
}
|
||||
if (typeof fallback === "string") return fallback;
|
||||
|
||||
// Ultimate fallback: return the key itself
|
||||
return key;
|
||||
}
|
||||
|
||||
/** All supported locales (used by the switcher) */
|
||||
export const SUPPORTED_LOCALES: { value: Locale; label: string }[] = [
|
||||
{ value: "en", label: "English" },
|
||||
{ value: "zh", label: "中文" },
|
||||
];
|
||||
|
||||
/** LocalStorage key */
|
||||
export const LOCALE_STORAGE_KEY = "litellm_locale";
|
||||
|
||||
/** Default locale */
|
||||
export const DEFAULT_LOCALE: Locale = "en";
|
||||
|
||||
export { translations };
|
||||
130
src/i18n/locales/en.json
Normal file
130
src/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"create": "Create",
|
||||
"search": "Search",
|
||||
"refresh": "Refresh",
|
||||
"loading": "Loading...",
|
||||
"no_data": "No data",
|
||||
"confirm": "Confirm",
|
||||
"back": "Back",
|
||||
"next": "Next",
|
||||
"submit": "Submit",
|
||||
"reset": "Reset",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied!",
|
||||
"close": "Close",
|
||||
"export": "Export",
|
||||
"import": "Import",
|
||||
"more": "More",
|
||||
"actions": "Actions",
|
||||
"status": "Status",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"on": "On",
|
||||
"off": "Off",
|
||||
"success": "Success",
|
||||
"error": "Error",
|
||||
"warning": "Warning",
|
||||
"info": "Info"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"models": "Models & Endpoints",
|
||||
"api_keys": "API Keys",
|
||||
"users": "Users",
|
||||
"teams": "Teams",
|
||||
"logs": "Logs",
|
||||
"usage": "Usage",
|
||||
"spend": "Cost Tracking",
|
||||
"settings": "Settings",
|
||||
"admin": "Admin Panel",
|
||||
"playground": "Playground",
|
||||
"budgets": "Budgets",
|
||||
"caching": "Caching",
|
||||
"guardrails": "Guardrails",
|
||||
"hooks": "Webhooks",
|
||||
"model_hub": "Model Hub",
|
||||
"router": "Router Settings",
|
||||
"organizations": "Organizations",
|
||||
"credentials": "Credentials",
|
||||
"agents": "Agents",
|
||||
"prompts": "Prompts",
|
||||
"mcp": "MCP Servers",
|
||||
"policies": "Policies",
|
||||
"skills": "Skills",
|
||||
"workflows": "Workflows",
|
||||
"memory": "Memory",
|
||||
"vector_stores": "Vector Stores",
|
||||
"search_tools": "Search Tools",
|
||||
"tag_management": "Tag Management",
|
||||
"access_groups": "Access Groups",
|
||||
"ui_theme": "UI Theme"
|
||||
},
|
||||
"models": {
|
||||
"title": "Models & Endpoints",
|
||||
"add_model": "Add Model",
|
||||
"model_name": "Model Name",
|
||||
"provider": "Provider",
|
||||
"api_base": "API Base URL",
|
||||
"api_key": "API Key",
|
||||
"rpm": "RPM",
|
||||
"tpm": "TPM",
|
||||
"max_retries": "Max Retries",
|
||||
"timeout": "Timeout (s)",
|
||||
"custom_llm_provider": "Provider",
|
||||
"litellm_params": "LiteLLM Parameters",
|
||||
"model_info": "Model Info",
|
||||
"deployment_name": "Deployment Name",
|
||||
"no_models": "No models configured",
|
||||
"delete_confirm": "Are you sure you want to delete this model?"
|
||||
},
|
||||
"keys": {
|
||||
"title": "API Keys",
|
||||
"create_key": "Create Key",
|
||||
"key_name": "Key Name",
|
||||
"key": "Key",
|
||||
"models": "Models",
|
||||
"max_budget": "Max Budget",
|
||||
"budget_duration": "Budget Duration",
|
||||
"expires_at": "Expires At",
|
||||
"never": "Never",
|
||||
"no_keys": "No API keys created",
|
||||
"delete_confirm": "Are you sure you want to delete this key?"
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"create_user": "Create User",
|
||||
"user_email": "Email",
|
||||
"user_role": "Role",
|
||||
"user_id": "User ID",
|
||||
"no_users": "No users found"
|
||||
},
|
||||
"logs": {
|
||||
"title": "Logs",
|
||||
"request_id": "Request ID",
|
||||
"model": "Model",
|
||||
"status_code": "Status",
|
||||
"tokens": "Tokens",
|
||||
"cost": "Cost",
|
||||
"duration": "Duration",
|
||||
"timestamp": "Time"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"general": "General",
|
||||
"proxy": "Proxy Settings",
|
||||
"alerting": "Alerting"
|
||||
},
|
||||
"language": {
|
||||
"switch_to": "中文",
|
||||
"label": "Language"
|
||||
}
|
||||
}
|
||||
130
src/i18n/locales/zh.json
Normal file
130
src/i18n/locales/zh.json
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"common": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"create": "创建",
|
||||
"search": "搜索",
|
||||
"refresh": "刷新",
|
||||
"loading": "加载中...",
|
||||
"no_data": "暂无数据",
|
||||
"confirm": "确认",
|
||||
"back": "返回",
|
||||
"next": "下一步",
|
||||
"submit": "提交",
|
||||
"reset": "重置",
|
||||
"copy": "复制",
|
||||
"copied": "已复制!",
|
||||
"close": "关闭",
|
||||
"export": "导出",
|
||||
"import": "导入",
|
||||
"more": "更多",
|
||||
"actions": "操作",
|
||||
"status": "状态",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"active": "活跃",
|
||||
"inactive": "不活跃",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"on": "开",
|
||||
"off": "关",
|
||||
"success": "成功",
|
||||
"error": "错误",
|
||||
"warning": "警告",
|
||||
"info": "提示"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "仪表盘",
|
||||
"models": "模型与端点",
|
||||
"api_keys": "API 密钥",
|
||||
"users": "用户管理",
|
||||
"teams": "团队管理",
|
||||
"logs": "日志",
|
||||
"usage": "用量统计",
|
||||
"spend": "费用追踪",
|
||||
"settings": "设置",
|
||||
"admin": "管理面板",
|
||||
"playground": "调试沙盒",
|
||||
"budgets": "预算管理",
|
||||
"caching": "缓存设置",
|
||||
"guardrails": "安全护栏",
|
||||
"hooks": "Webhooks",
|
||||
"model_hub": "模型中心",
|
||||
"router": "路由设置",
|
||||
"organizations": "组织管理",
|
||||
"credentials": "凭证管理",
|
||||
"agents": "智能代理",
|
||||
"prompts": "提示词",
|
||||
"mcp": "MCP 服务",
|
||||
"policies": "策略管理",
|
||||
"skills": "技能管理",
|
||||
"workflows": "工作流",
|
||||
"memory": "记忆管理",
|
||||
"vector_stores": "向量存储",
|
||||
"search_tools": "搜索工具",
|
||||
"tag_management": "标签管理",
|
||||
"access_groups": "访问组",
|
||||
"ui_theme": "界面主题"
|
||||
},
|
||||
"models": {
|
||||
"title": "模型与端点",
|
||||
"add_model": "添加模型",
|
||||
"model_name": "模型名称",
|
||||
"provider": "提供商",
|
||||
"api_base": "API 地址",
|
||||
"api_key": "API 密钥",
|
||||
"rpm": "每分钟请求数",
|
||||
"tpm": "每分钟 Token 数",
|
||||
"max_retries": "最大重试次数",
|
||||
"timeout": "超时 (秒)",
|
||||
"custom_llm_provider": "提供商",
|
||||
"litellm_params": "LiteLLM 参数",
|
||||
"model_info": "模型信息",
|
||||
"deployment_name": "部署名称",
|
||||
"no_models": "未配置模型",
|
||||
"delete_confirm": "确定要删除此模型吗?"
|
||||
},
|
||||
"keys": {
|
||||
"title": "API 密钥",
|
||||
"create_key": "创建密钥",
|
||||
"key_name": "密钥名称",
|
||||
"key": "密钥",
|
||||
"models": "模型",
|
||||
"max_budget": "最大预算",
|
||||
"budget_duration": "预算周期",
|
||||
"expires_at": "过期时间",
|
||||
"never": "永不过期",
|
||||
"no_keys": "暂无 API 密钥",
|
||||
"delete_confirm": "确定要删除此密钥吗?"
|
||||
},
|
||||
"users": {
|
||||
"title": "用户管理",
|
||||
"create_user": "创建用户",
|
||||
"user_email": "邮箱",
|
||||
"user_role": "角色",
|
||||
"user_id": "用户 ID",
|
||||
"no_users": "暂无用户"
|
||||
},
|
||||
"logs": {
|
||||
"title": "日志",
|
||||
"request_id": "请求 ID",
|
||||
"model": "模型",
|
||||
"status_code": "状态码",
|
||||
"tokens": "Token 数",
|
||||
"cost": "费用",
|
||||
"duration": "耗时",
|
||||
"timestamp": "时间"
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"general": "常规设置",
|
||||
"proxy": "代理设置",
|
||||
"alerting": "告警设置"
|
||||
},
|
||||
"language": {
|
||||
"switch_to": "English",
|
||||
"label": "语言"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue