From 7298777febc22d5a90329fea001169d7f3564514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B9=E9=9B=AA?= <2b@yorha.xyz> Date: Thu, 11 Jun 2026 03:41:30 +0900 Subject: [PATCH] feat(ui): i18n for projects pages and modals --- .../Projects/ProjectDetailsPage.tsx | 62 +++--- .../Projects/ProjectKeysSection.tsx | 8 +- .../components/Projects/ProjectKeysTable.tsx | 24 ++- .../ProjectModals/CreateProjectModal.tsx | 12 +- .../ProjectModals/EditProjectModal.tsx | 12 +- .../ProjectModals/ProjectBaseForm.tsx | 78 ++++---- .../src/components/Projects/ProjectsPage.tsx | 177 +++++++++--------- ui/litellm-dashboard/src/locales/en.json | 94 ++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 94 ++++++++++ 9 files changed, 402 insertions(+), 159 deletions(-) diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectDetailsPage.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectDetailsPage.tsx index 637771e2299..1cd9ce55252 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectDetailsPage.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectDetailsPage.tsx @@ -19,6 +19,7 @@ import { LoadingOutlined } from "@ant-design/icons"; import { BarChart } from "@tremor/react"; import { ArrowLeftIcon, DollarSignIcon, EditIcon, KeyIcon, UsersIcon } from "lucide-react"; import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; import { EditProjectModal } from "./ProjectModals/EditProjectModal"; @@ -41,6 +42,7 @@ interface ProjectDetailProps { } export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { + const { t } = useTranslation(); const { data: project, isLoading } = useProjectDetails(projectId); const { data: teamData } = useTeam(project?.team_id ?? undefined); // teamInfoCall returns { team_id, team_info: {...}, keys, team_memberships } @@ -86,7 +88,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { }} > {/* Project Details */} - - {project.description || "\u2014"} - + + {project.description || "\u2014"} + {new Date(project.created_at).toLocaleString()} {project.created_by && ( -  {"by"}  +  {t("projects.projectDetailsPage.by")}  )} - + {new Date(project.updated_at).toLocaleString()} {project.updated_by && ( -  {"by"}  +  {t("projects.projectDetailsPage.by")}  )} @@ -155,7 +159,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { title={ - Budget + {t("projects.projectDetailsPage.budgetTitle")} } style={{ height: "100%" }} @@ -166,13 +170,19 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { ${spend.toFixed(2)}
- {hasLimit ? `of $${maxBudget.toFixed(2)} budget` : "No budget limit"} + + {hasLimit + ? t("projects.projectDetailsPage.budgetOf", { budget: maxBudget.toFixed(2) }) + : t("projects.projectDetailsPage.noBudgetLimit")} + {hasLimit && (
- {(Math.round(spendPercent * 10) / 10).toFixed(1)}% utilized + {t("projects.projectDetailsPage.utilized", { + percent: (Math.round(spendPercent * 10) / 10).toFixed(1), + })}
)} @@ -180,7 +190,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) {
- + {modelSpendData.length > 0 ? ( ) : ( - + )} @@ -207,12 +217,15 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { title={ - Keys + {t("projects.projectDetailsPage.keysTitle")} } style={{ height: "100%" }} > - + @@ -220,7 +233,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { title={ - Team + {t("projects.projectDetailsPage.teamTitle")} } style={{ height: "100%" }} @@ -252,7 +265,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { {/* Models */}
- Models + {t("projects.projectDetailsPage.modelsLabel")} {(teamInfo.models?.length ?? 0) > 0 ? ( @@ -263,7 +276,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { ))} ) : ( - All models + {t("projects.projectDetailsPage.allModels")} )}
@@ -271,7 +284,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) {
- Spend + {t("projects.projectDetailsPage.spendLabel")} ${teamSpend.toFixed(2)} @@ -283,7 +296,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { ) : ( {" "} - (Unlimited) + ({t("projects.projectDetailsPage.unlimited")}) )} @@ -301,7 +314,7 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { {/* Members */} - Members + {t("projects.projectDetailsPage.membersLabel")} {teamInfo.members_with_roles?.length ?? 0} @@ -313,7 +326,10 @@ export function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { } size="small" /> ) : ( - + )} diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectKeysSection.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectKeysSection.tsx index 9f60db596e8..e1bdb094b94 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectKeysSection.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectKeysSection.tsx @@ -3,6 +3,7 @@ import { LoadingOutlined } from "@ant-design/icons"; import { Card, Flex, Input, Pagination, Spin } from "antd"; import { KeyIcon, SearchIcon } from "lucide-react"; import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { ProjectKeysTable } from "./ProjectKeysTable"; interface ProjectKeysSectionProps { @@ -12,6 +13,7 @@ interface ProjectKeysSectionProps { const PAGE_SIZE = 5; export function ProjectKeysSection({ projectId }: ProjectKeysSectionProps) { + const { t } = useTranslation(); const [page, setPage] = useState(1); const [keyAlias, setKeyAlias] = useState(""); @@ -33,7 +35,7 @@ export function ProjectKeysSection({ projectId }: ProjectKeysSectionProps) { title={ - Keys + {t("projects.projectKeysSection.title")} } style={{ height: "100%" }} @@ -41,7 +43,7 @@ export function ProjectKeysSection({ projectId }: ProjectKeysSectionProps) { } - placeholder="Filter by key name..." + placeholder={t("projects.projectKeysSection.filterPlaceholder")} style={{ maxWidth: 220 }} value={keyAlias} onChange={(e) => setKeyAlias(e.target.value)} @@ -55,7 +57,7 @@ export function ProjectKeysSection({ projectId }: ProjectKeysSectionProps) { onChange={setPage} size="small" showSizeChanger={false} - showTotal={(total) => `${total} keys`} + showTotal={(total) => t("projects.projectKeysSection.totalKeys", { count: total })} /> = [ +const getProjectKeysTableColumns = (t: TFunction): ColumnsType => [ { - title: "Key Name", + title: t("projects.projectKeysTable.colKeyName"), dataIndex: "key_alias", key: "key_alias", render: (alias: string | null) => alias || "—", }, { - title: "Owner", + title: t("projects.projectKeysTable.colOwner"), key: "owner", render: (_: unknown, record: KeyResponse) => { const email = record.user?.user_email ?? record.user_id ?? null; @@ -30,20 +33,23 @@ const columns: ColumnsType = [ }, }, { - title: "Created", + title: t("common.createdAt"), dataIndex: "created_at", key: "created_at", render: (date: string) => (date ? new Date(date).toLocaleDateString() : "—"), }, { - title: "Last Active", + title: t("projects.projectKeysTable.colLastActive"), dataIndex: "last_active", key: "last_active", - render: (date: string | null) => (date ? new Date(date).toLocaleDateString() : "Never"), + render: (date: string | null) => (date ? new Date(date).toLocaleDateString() : t("common.never")), }, ]; export function ProjectKeysTable({ keys, loading }: ProjectKeysTableProps) { + const { t } = useTranslation(); + const columns = useMemo(() => getProjectKeysTableColumns(t), [t]); + return ( }} + locale={{ + emptyText: ( + + ), + }} /> ); } diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx index c7d1f54bd47..d53f4ba0c93 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx @@ -2,6 +2,7 @@ import { Modal, Form, Button, Typography } from "antd"; import { FolderAddOutlined } from "@ant-design/icons"; import MessageManager from "@/components/molecules/message_manager"; import { useCreateProject, ProjectCreateParams } from "@/app/(dashboard)/hooks/projects/useCreateProject"; +import { useTranslation } from "react-i18next"; import { ProjectBaseForm, ProjectFormValues } from "./ProjectBaseForm"; import { buildProjectApiParams } from "./projectFormUtils"; @@ -11,6 +12,7 @@ interface CreateProjectModalProps { } export function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) { + const { t } = useTranslation(); const [form] = Form.useForm(); const createMutation = useCreateProject(); @@ -24,12 +26,12 @@ export function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) createMutation.mutate(params, { onSuccess: () => { - MessageManager.success("Project created successfully"); + MessageManager.success(t("projects.createProjectModal.createSuccess")); form.resetFields(); onClose(); }, onError: (error) => { - MessageManager.error(error.message || "Failed to create project"); + MessageManager.error(error.message || t("projects.createProjectModal.createFailed")); }, }); } catch (error) { @@ -46,7 +48,7 @@ export function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) - Create New Project + {t("projects.createProjectModal.title")} } open={isOpen} @@ -55,7 +57,7 @@ export function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) destroyOnHidden footer={[ , , ]} > diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx index d8d9973d156..04396e3b8c4 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx @@ -4,6 +4,7 @@ import { SaveOutlined } from "@ant-design/icons"; import MessageManager from "@/components/molecules/message_manager"; import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects"; import { useUpdateProject, ProjectUpdateParams } from "@/app/(dashboard)/hooks/projects/useUpdateProject"; +import { useTranslation } from "react-i18next"; import { ProjectBaseForm, ProjectFormValues } from "./ProjectBaseForm"; import { buildProjectApiParams } from "./projectFormUtils"; @@ -15,6 +16,7 @@ interface EditProjectModalProps { } export function EditProjectModal({ isOpen, project, onClose, onSuccess }: EditProjectModalProps) { + const { t } = useTranslation(); const [form] = Form.useForm(); const updateMutation = useUpdateProject(); @@ -72,12 +74,12 @@ export function EditProjectModal({ isOpen, project, onClose, onSuccess }: EditPr { projectId: project.project_id, params }, { onSuccess: () => { - MessageManager.success("Project updated successfully"); + MessageManager.success(t("projects.editProjectModal.updateSuccess")); onSuccess?.(); onClose(); }, onError: (error) => { - MessageManager.error(error.message || "Failed to update project"); + MessageManager.error(error.message || t("projects.editProjectModal.updateFailed")); }, }, ); @@ -90,7 +92,7 @@ export function EditProjectModal({ isOpen, project, onClose, onSuccess }: EditPr - Edit Project + {t("projects.editProjectModal.title")} } open={isOpen} @@ -99,7 +101,7 @@ export function EditProjectModal({ isOpen, project, onClose, onSuccess }: EditPr destroyOnHidden footer={[ , , ]} > diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectModals/ProjectBaseForm.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectModals/ProjectBaseForm.tsx index ea0f31b56f0..1a30404a322 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectModals/ProjectBaseForm.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectModals/ProjectBaseForm.tsx @@ -18,6 +18,7 @@ import { import type { FormInstance } from "antd"; import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { useTranslation } from "react-i18next"; import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams"; import { Team } from "../../key_team_helpers/key_list"; import { fetchTeamModels } from "../../organisms/create_key_button"; @@ -41,6 +42,7 @@ interface ProjectBaseFormProps { } export function ProjectBaseForm({ form }: ProjectBaseFormProps) { + const { t } = useTranslation(); const { accessToken, userId, userRole } = useAuthorized(); const { data: teams } = useTeams(); @@ -109,7 +111,7 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { letterSpacing: "0.05em", }} > - Basic Information + {t("projects.projectBaseForm.basicInfoTitle")} @@ -117,17 +119,21 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { - + - + - All Team Models + {t("projects.projectBaseForm.allTeamModels")} {modelsToPick.map((model) => ( @@ -193,7 +203,7 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { - + @@ -214,13 +224,13 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { key: "1", label: ( - Advanced Settings + {t("projects.projectBaseForm.advancedSettingsTitle")} ), children: ( <> - Block Project + {t("projects.projectBaseForm.blockProjectLabel")} @@ -232,7 +242,7 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { banner type="warning" showIcon - message="All API requests using keys under this project will be rejected." + message={t("projects.projectBaseForm.blockProjectWarning")} style={{ marginTop: 12 }} /> ) : null @@ -241,11 +251,15 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { - + + - + - + remove(name)} style={{ color: "#ef4444" }} /> ))} @@ -304,7 +318,7 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { - Metadata + {t("projects.projectBaseForm.metadataTitle")} {(fields, { add, remove }) => ( @@ -315,35 +329,35 @@ export function ProjectBaseForm({ form }: ProjectBaseFormProps) { {...restField} name={[name, "key"]} rules={[ - { required: true, message: "Missing key" }, + { required: true, message: t("projects.projectBaseForm.missingKey") }, { validator: (_, value) => { if (!value) return Promise.resolve(); const all = form.getFieldValue("metadata") ?? []; const dupes = all.filter((entry: { key?: string }) => entry?.key === value); if (dupes.length > 1) { - return Promise.reject(new Error("Duplicate key")); + return Promise.reject(new Error(t("projects.projectBaseForm.duplicateKey"))); } return Promise.resolve(); }, }, ]} > - + - + remove(name)} style={{ color: "#ef4444" }} /> ))} diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectsPage.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectsPage.tsx index 8a3fc178914..c506760361d 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectsPage.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectsPage.tsx @@ -19,6 +19,7 @@ import { import type { ColumnsType } from "antd/es/table"; import { LayersIcon, SearchIcon } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { CreateProjectModal } from "./ProjectModals/CreateProjectModal"; import { ProjectDetail } from "./ProjectDetailsPage"; @@ -26,6 +27,7 @@ const { Title, Text } = Typography; const { Content } = Layout; export function ProjectsPage() { + const { t } = useTranslation(); const { token } = theme.useToken(); const { data: projects, isLoading } = useProjects(); const { data: teams, isLoading: isTeamsLoading } = useTeams(); @@ -66,87 +68,94 @@ export function ProjectsPage() { }, [projects, searchText, teamAliasMap]); // ---------- Ant Design columns ---------- - const columns: ColumnsType = [ - { - title: "ID", - dataIndex: "project_id", - key: "project_id", - width: 170, - render: (id: string) => ( - - setSelectedProjectId(id)} - > - {id} - - - ), - }, - { - title: "Name", - dataIndex: "project_alias", - key: "project_alias", - sorter: (a, b) => (a.project_alias ?? "").localeCompare(b.project_alias ?? ""), - render: (alias: string | null) => alias ?? "—", - }, - { - title: "Team", - key: "team", - sorter: (a, b) => { - const aAlias = teamAliasMap.get(a.team_id ?? "") ?? ""; - const bAlias = teamAliasMap.get(b.team_id ?? "") ?? ""; - return aAlias.localeCompare(bAlias); - }, - render: (_: unknown, record: ProjectResponse) => { - if (!record.team_id) return "—"; - const alias = teamAliasMap.get(record.team_id); - if (alias) return alias; - if (isTeamsLoading) return } size="small" />; - return record.team_id; - }, - }, - { - title: "Models", - key: "models", - render: (_: unknown, record: ProjectResponse) => { - const models = record.models ?? []; - return ( - 0 ? models.join(", ") : "No models"}> - - - - {models.length} - - + const columns: ColumnsType = useMemo( + () => [ + { + title: "ID", + dataIndex: "project_id", + key: "project_id", + width: 170, + render: (id: string) => ( + + setSelectedProjectId(id)} + > + {id} + - ); + ), }, - }, - { - title: "Status", - dataIndex: "blocked", - key: "status", - render: (blocked: boolean) => {blocked ? "Blocked" : "Active"}, - }, - { - title: "Created", - dataIndex: "created_at", - key: "created_at", - sorter: (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime(), - responsive: ["lg"], - render: (date: string) => new Date(date).toLocaleDateString(), - }, - { - title: "Updated", - dataIndex: "updated_at", - key: "updated_at", - responsive: ["xl"], - render: (date: string) => new Date(date).toLocaleDateString(), - }, - ]; + { + title: t("common.name"), + dataIndex: "project_alias", + key: "project_alias", + sorter: (a, b) => (a.project_alias ?? "").localeCompare(b.project_alias ?? ""), + render: (alias: string | null) => alias ?? "—", + }, + { + title: t("projects.projectsPage.colTeam"), + key: "team", + sorter: (a, b) => { + const aAlias = teamAliasMap.get(a.team_id ?? "") ?? ""; + const bAlias = teamAliasMap.get(b.team_id ?? "") ?? ""; + return aAlias.localeCompare(bAlias); + }, + render: (_: unknown, record: ProjectResponse) => { + if (!record.team_id) return "—"; + const alias = teamAliasMap.get(record.team_id); + if (alias) return alias; + if (isTeamsLoading) return } size="small" />; + return record.team_id; + }, + }, + { + title: t("projects.projectsPage.colModels"), + key: "models", + render: (_: unknown, record: ProjectResponse) => { + const models = record.models ?? []; + return ( + 0 ? models.join(", ") : t("projects.projectsPage.noModels")}> + + + + {models.length} + + + + ); + }, + }, + { + title: t("common.status"), + dataIndex: "blocked", + key: "status", + render: (blocked: boolean) => ( + + {blocked ? t("projects.projectsPage.statusBlocked") : t("common.active")} + + ), + }, + { + title: t("common.createdAt"), + dataIndex: "created_at", + key: "created_at", + sorter: (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime(), + responsive: ["lg"], + render: (date: string) => new Date(date).toLocaleDateString(), + }, + { + title: t("common.updatedAt"), + dataIndex: "updated_at", + key: "updated_at", + responsive: ["xl"], + render: (date: string) => new Date(date).toLocaleDateString(), + }, + ], + [t, teamAliasMap, isTeamsLoading, setSelectedProjectId], + ); if (selectedProjectId) { return setSelectedProjectId(null)} />; @@ -157,12 +166,12 @@ export function ProjectsPage() { - Projects + {t("projects.projectsPage.title")} - Manage projects within your teams + {t("projects.projectsPage.subtitle")} @@ -170,7 +179,7 @@ export function ProjectsPage() { } - placeholder="Search projects by name, ID, description, or team..." + placeholder={t("projects.projectsPage.searchPlaceholder")} style={{ maxWidth: 400 }} value={searchText} onChange={(e) => setSearchText(e.target.value)} @@ -182,7 +191,7 @@ export function ProjectsPage() { pageSize={pageSize} onChange={(page) => setCurrentPage(page)} size="small" - showTotal={(total) => `${total} projects`} + showTotal={(total) => t("projects.projectsPage.totalProjects", { count: total })} showSizeChanger={false} /> diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index e9ddcbfae4c..b5b29123a16 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -6690,5 +6690,99 @@ "successCount_other": "Successfully made {{count}} model groups public!", "failedToMakePublic": "Failed to make model groups public. Please try again." } + }, + "projects": { + "projectDetailsPage": { + "notFound": "Project not found", + "statusBlocked": "Blocked", + "editProject": "Edit Project", + "detailsTitle": "Project Details", + "by": "by", + "budgetTitle": "Budget", + "budgetOf": "of ${{budget}} budget", + "noBudgetLimit": "No budget limit", + "utilized": "{{percent}}% utilized", + "spendByModel": "Spend by Model", + "noModelSpend": "No model spend recorded yet", + "keysTitle": "Keys", + "noKeysToDisplay": "No keys to display", + "teamTitle": "Team", + "modelsLabel": "Models", + "allModels": "All models", + "spendLabel": "Spend", + "unlimited": "Unlimited", + "membersLabel": "Members", + "noTeamAssigned": "No team assigned" + }, + "projectKeysSection": { + "title": "Keys", + "filterPlaceholder": "Filter by key name...", + "totalKeys": "{{count}} keys" + }, + "projectKeysTable": { + "colKeyName": "Key Name", + "colOwner": "Owner", + "colLastActive": "Last Active", + "noKeysFound": "No keys found" + }, + "createProjectModal": { + "title": "Create New Project", + "createBtn": "Create Project", + "createSuccess": "Project created successfully", + "createFailed": "Failed to create project" + }, + "editProjectModal": { + "title": "Edit Project", + "saveBtn": "Save Changes", + "updateSuccess": "Project updated successfully", + "updateFailed": "Failed to update project" + }, + "projectBaseForm": { + "basicInfoTitle": "Basic Information", + "projectNameLabel": "Project Name", + "projectNameRequired": "Please enter a project name", + "projectNamePlaceholder": "e.g. Customer Support Bot", + "teamLabel": "Team", + "teamRequired": "Please select a team", + "teamPlaceholder": "Search or select a team", + "descriptionPlaceholder": "Describe the purpose of this project", + "allowedModelsLabel": "Allowed Models (scoped to selected team's models)", + "selectTeamFirst": "Select a team first to see available models", + "selectModels": "Select models", + "selectTeamFirstPlaceholder": "Select a team first", + "allTeamModels": "All Team Models", + "maxBudgetLabel": "Max Budget (USD)", + "advancedSettingsTitle": "Advanced Settings", + "blockProjectLabel": "Block Project", + "blockProjectWarning": "All API requests using keys under this project will be rejected.", + "guardrailsLabel": "Guardrails", + "guardrailsHelp": "Select existing guardrails or enter new ones", + "guardrailsPlaceholder": "Select or enter guardrails", + "modelLimitsTitle": "Model-Specific Limits", + "missingModel": "Missing model", + "duplicateModel": "Duplicate model", + "modelNamePlaceholder": "Model name (e.g. gpt-4)", + "tpmPlaceholder": "TPM Limit", + "rpmPlaceholder": "RPM Limit", + "addModelLimit": "Add Model Limit", + "metadataTitle": "Metadata", + "missingKey": "Missing key", + "duplicateKey": "Duplicate key", + "keyPlaceholder": "Key", + "missingValue": "Missing value", + "valuePlaceholder": "Value", + "addKeyValue": "Add Key-Value Pair" + }, + "projectsPage": { + "title": "Projects", + "subtitle": "Manage projects within your teams", + "createBtn": "Create Project", + "searchPlaceholder": "Search projects by name, ID, description, or team...", + "totalProjects": "{{count}} projects", + "colTeam": "Team", + "colModels": "Models", + "noModels": "No models", + "statusBlocked": "Blocked" + } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 8f7c9c32806..1e82efdd22f 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -6690,5 +6690,99 @@ "successCount_other": "成功将 {{count}} 个模型组设为公开!", "failedToMakePublic": "将模型组设为公开失败,请重试。" } + }, + "projects": { + "projectDetailsPage": { + "notFound": "项目未找到", + "statusBlocked": "已封锁", + "editProject": "编辑项目", + "detailsTitle": "项目详情", + "by": "由", + "budgetTitle": "预算", + "budgetOf": "共 ${{budget}} 预算", + "noBudgetLimit": "无预算限制", + "utilized": "已使用 {{percent}}%", + "spendByModel": "按模型花费", + "noModelSpend": "暂无模型花费记录", + "keysTitle": "密钥", + "noKeysToDisplay": "暂无密钥可显示", + "teamTitle": "团队", + "modelsLabel": "模型", + "allModels": "所有模型", + "spendLabel": "花费", + "unlimited": "无限制", + "membersLabel": "成员", + "noTeamAssigned": "未分配团队" + }, + "projectKeysSection": { + "title": "密钥", + "filterPlaceholder": "按密钥名称筛选...", + "totalKeys": "共 {{count}} 个密钥" + }, + "projectKeysTable": { + "colKeyName": "密钥名称", + "colOwner": "所有者", + "colLastActive": "最后活跃", + "noKeysFound": "未找到密钥" + }, + "createProjectModal": { + "title": "新建项目", + "createBtn": "创建项目", + "createSuccess": "项目创建成功", + "createFailed": "创建项目失败" + }, + "editProjectModal": { + "title": "编辑项目", + "saveBtn": "保存更改", + "updateSuccess": "项目更新成功", + "updateFailed": "更新项目失败" + }, + "projectBaseForm": { + "basicInfoTitle": "基本信息", + "projectNameLabel": "项目名称", + "projectNameRequired": "请输入项目名称", + "projectNamePlaceholder": "例如:客服机器人", + "teamLabel": "团队", + "teamRequired": "请选择团队", + "teamPlaceholder": "搜索或选择团队", + "descriptionPlaceholder": "描述该项目的用途", + "allowedModelsLabel": "允许的模型(限定于所选团队的模型)", + "selectTeamFirst": "请先选择团队以查看可用模型", + "selectModels": "选择模型", + "selectTeamFirstPlaceholder": "请先选择团队", + "allTeamModels": "团队所有模型", + "maxBudgetLabel": "最大预算(美元)", + "advancedSettingsTitle": "高级设置", + "blockProjectLabel": "封锁项目", + "blockProjectWarning": "该项目下所有密钥的 API 请求将被拒绝。", + "guardrailsLabel": "护栏", + "guardrailsHelp": "选择已有护栏或输入新护栏", + "guardrailsPlaceholder": "选择或输入护栏", + "modelLimitsTitle": "模型专属限制", + "missingModel": "缺少模型名称", + "duplicateModel": "模型重复", + "modelNamePlaceholder": "模型名称(例如 gpt-4)", + "tpmPlaceholder": "TPM 限制", + "rpmPlaceholder": "RPM 限制", + "addModelLimit": "添加模型限制", + "metadataTitle": "元数据", + "missingKey": "缺少键名", + "duplicateKey": "键名重复", + "keyPlaceholder": "键", + "missingValue": "缺少值", + "valuePlaceholder": "值", + "addKeyValue": "添加键值对" + }, + "projectsPage": { + "title": "项目", + "subtitle": "管理团队内的项目", + "createBtn": "创建项目", + "searchPlaceholder": "按名称、ID、描述或团队搜索项目...", + "totalProjects": "共 {{count}} 个项目", + "colTeam": "团队", + "colModels": "模型", + "noModels": "无模型", + "statusBlocked": "已封锁" + } } }