feat(ui): i18n for credentials management

This commit is contained in:
吹雪 2026-06-11 05:33:45 +09:00
parent 7ddaa3a005
commit 4b0371ae44
6 changed files with 152 additions and 45 deletions

View file

@ -2,6 +2,7 @@ import { TextInput } from "@tremor/react";
import { Select as AntdSelect, Button, Form, Modal, Tooltip, Typography } from "antd";
import type { UploadProps } from "antd/es/upload";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import ProviderSpecificFields from "../add_model/provider_specific_fields";
import { Providers, providerLogoMap } from "../provider_info_helpers";
const { Link } = Typography;
@ -14,6 +15,7 @@ interface AddCredentialsModalProps {
}
const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({ open, onCancel, onAddCredential, uploadProps }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const [selectedProvider, setSelectedProvider] = useState<Providers>(Providers.OpenAI);
@ -30,7 +32,7 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({ open, onCance
return (
<Modal
title="Add New Credential"
title={t("modelAdd.addCredentialModal.title")}
open={open}
onCancel={() => {
onCancel();
@ -42,19 +44,19 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({ open, onCance
<Form form={form} onFinish={handleSubmit} layout="vertical">
{/* Credential Name */}
<Form.Item
label="Credential Name:"
label={t("modelAdd.addCredentialModal.credentialNameLabel")}
name="credential_name"
rules={[{ required: true, message: "Credential name is required" }]}
rules={[{ required: true, message: t("modelAdd.addCredentialModal.credentialNameRequired") }]}
>
<TextInput placeholder="Enter a friendly name for these credentials" />
<TextInput placeholder={t("modelAdd.addCredentialModal.credentialNamePlaceholder")} />
</Form.Item>
{/* Provider Selection */}
<Form.Item
rules={[{ required: true, message: "Required" }]}
label="Provider:"
rules={[{ required: true, message: t("common.required") }]}
label={t("modelAdd.addCredentialModal.providerLabel")}
name="custom_llm_provider"
tooltip="Helper to auto-populate provider specific fields"
tooltip={t("modelAdd.addCredentialModal.providerTooltip")}
>
<AntdSelect
showSearch
@ -93,8 +95,8 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({ open, onCance
{/* Modal Footer */}
<div className="flex justify-between items-center">
<Tooltip title="Get help on our github">
<Link href="https://github.com/BerriAI/litellm/issues">Need Help?</Link>
<Tooltip title={t("modelAdd.addCredentialModal.needHelpTooltip")}>
<Link href="https://github.com/BerriAI/litellm/issues">{t("modelAdd.addCredentialModal.needHelp")}</Link>
</Tooltip>
<div>
@ -105,9 +107,9 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({ open, onCance
}}
style={{ marginRight: 10 }}
>
Cancel
{t("common.cancel")}
</Button>
<Button htmlType="submit">{"Add Credential"}</Button>
<Button htmlType="submit">{t("modelAdd.addCredentialModal.addCredential")}</Button>
</div>
</div>
</Form>

View file

@ -2,6 +2,7 @@ import { TextInput } from "@tremor/react";
import { Select as AntdSelect, Button, Form, Modal, Tooltip, Typography } from "antd";
import type { UploadProps } from "antd/es/upload";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import ProviderSpecificFields from "../add_model/provider_specific_fields";
import { CredentialItem } from "../networking";
import { Providers, providerLogoMap } from "../provider_info_helpers";
@ -22,6 +23,7 @@ export default function EditCredentialsModal({
uploadProps,
existingCredential,
}: EditCredentialsModalProps) {
const { t } = useTranslation();
const [form] = Form.useForm();
const [selectedProvider, setSelectedProvider] = useState<Providers>(Providers.Anthropic);
@ -58,7 +60,7 @@ export default function EditCredentialsModal({
return (
<Modal
title="Edit Credential"
title={t("modelAdd.editCredentialModal.title")}
open={open}
onCancel={() => {
onCancel();
@ -71,23 +73,23 @@ export default function EditCredentialsModal({
<Form form={form} onFinish={handleSubmit} layout="vertical">
{/* Credential Name */}
<Form.Item
label="Credential Name:"
label={t("modelAdd.editCredentialModal.credentialNameLabel")}
name="credential_name"
rules={[{ required: true, message: "Credential name is required" }]}
rules={[{ required: true, message: t("modelAdd.editCredentialModal.credentialNameRequired") }]}
initialValue={existingCredential?.credential_name}
>
<TextInput
placeholder="Enter a friendly name for these credentials"
placeholder={t("modelAdd.editCredentialModal.credentialNamePlaceholder")}
disabled={existingCredential?.credential_name ? true : false}
/>
</Form.Item>
{/* Provider Selection */}
<Form.Item
rules={[{ required: true, message: "Required" }]}
label="Provider:"
rules={[{ required: true, message: t("common.required") }]}
label={t("modelAdd.editCredentialModal.providerLabel")}
name="custom_llm_provider"
tooltip="Helper to auto-populate provider specific fields"
tooltip={t("modelAdd.editCredentialModal.providerTooltip")}
>
<AntdSelect
showSearch
@ -126,8 +128,8 @@ export default function EditCredentialsModal({
{/* Modal Footer */}
<div className="flex justify-between items-center">
<Tooltip title="Get help on our github">
<Link href="https://github.com/BerriAI/litellm/issues">Need Help?</Link>
<Tooltip title={t("modelAdd.editCredentialModal.needHelpTooltip")}>
<Link href="https://github.com/BerriAI/litellm/issues">{t("modelAdd.editCredentialModal.needHelp")}</Link>
</Tooltip>
<div>
@ -138,9 +140,9 @@ export default function EditCredentialsModal({
}}
style={{ marginRight: 10 }}
>
Cancel
{t("common.cancel")}
</Button>
<Button htmlType="submit">{"Update Credential"}</Button>
<Button htmlType="submit">{t("modelAdd.editCredentialModal.updateCredential")}</Button>
</div>
</div>
</Form>

View file

@ -20,6 +20,7 @@ import {
import { Form } from "antd";
import { UploadProps } from "antd/es/upload";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import DeleteResourceModal from "../common_components/DeleteResourceModal";
import NotificationsManager from "../molecules/notifications_manager";
import AddCredentialsTab from "./AddCredentialModal";
@ -32,6 +33,7 @@ interface CredentialsPanelProps {
}
const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ uploadProps }) => {
const { t } = useTranslation();
const { accessToken, userRole } = useAuthorized();
// Admin Viewer follows the read-parity rule: see credentials, do not modify.
const canModifyCredentials = isProxyAdminRole(userRole ?? "");
@ -65,7 +67,7 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ uploadProps }) => {
};
await credentialUpdateCall(accessToken, values.credential_name, newCredential);
NotificationsManager.success("Credential updated successfully");
NotificationsManager.success(t("modelAdd.credentials.updatedSuccess"));
setIsUpdateModalOpen(false);
await refetchCredentials();
};
@ -89,7 +91,7 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ uploadProps }) => {
// Add to list and close modal
await credentialCreateCall(accessToken, newCredential);
NotificationsManager.success("Credential added successfully");
NotificationsManager.success(t("modelAdd.credentials.addedSuccess"));
setIsAddModalOpen(false);
await refetchCredentials();
};
@ -117,10 +119,10 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ uploadProps }) => {
setIsCredentialDeleting(true);
try {
await credentialDeleteCall(accessToken, credentialToDelete.credential_name);
NotificationsManager.success("Credential deleted successfully");
NotificationsManager.success(t("modelAdd.credentials.deletedSuccess"));
await refetchCredentials();
} catch (error) {
NotificationsManager.error("Failed to delete credential");
NotificationsManager.error(t("modelAdd.credentials.deleteFailed"));
} finally {
setCredentialToDelete(null);
setIsDeleteModalOpen(false);
@ -140,25 +142,27 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ uploadProps }) => {
return (
<div className="w-full mx-auto flex-auto overflow-y-auto p-2">
{canModifyCredentials && <Button onClick={() => setIsAddModalOpen(true)}>Add Credential</Button>}
{canModifyCredentials && (
<Button onClick={() => setIsAddModalOpen(true)}>{t("modelAdd.credentials.addCredential")}</Button>
)}
<div className="flex justify-between items-center mt-4 mb-4">
<Text>Configured credentials for different AI providers. Add and manage your API credentials.</Text>
<Text>{t("modelAdd.credentials.configuredCredentials")}</Text>
</div>
<Card>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Credential Name</TableHeaderCell>
<TableHeaderCell>Provider</TableHeaderCell>
<TableHeaderCell>Actions</TableHeaderCell>
<TableHeaderCell>{t("modelAdd.credentials.credentialName")}</TableHeaderCell>
<TableHeaderCell>{t("modelAdd.credentials.provider")}</TableHeaderCell>
<TableHeaderCell>{t("common.actions")}</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{!credentialList || credentialList.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className="text-center py-4 text-gray-500">
No credentials configured
{t("modelAdd.credentials.noCredentials")}
</TableCell>
</TableRow>
) : (
@ -219,12 +223,15 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ uploadProps }) => {
isOpen={isDeleteModalOpen}
onCancel={closeDeleteModal}
onOk={handleDeleteCredential}
title="Delete Credential?"
message="Are you sure you want to delete this credential? This action cannot be undone and may break existing integrations."
resourceInformationTitle="Credential Information"
title={t("modelAdd.credentials.deleteTitle")}
message={t("modelAdd.credentials.deleteMessage")}
resourceInformationTitle={t("modelAdd.credentials.credentialInformation")}
resourceInformation={[
{ label: "Credential Name", value: credentialToDelete?.credential_name },
{ label: "Provider", value: credentialToDelete?.credential_info?.custom_llm_provider || "-" },
{ label: t("modelAdd.credentials.credentialName"), value: credentialToDelete?.credential_name },
{
label: t("modelAdd.credentials.provider"),
value: credentialToDelete?.credential_info?.custom_llm_provider || "-",
},
]}
confirmLoading={isCredentialDeleting}
requiredConfirmation={credentialToDelete?.credential_name}

View file

@ -1,6 +1,7 @@
import React from "react";
import { Form, Button, Tooltip, Typography, Modal } from "antd";
import { TextInput } from "@tremor/react";
import { useTranslation } from "react-i18next";
import { CredentialItem } from "../networking";
const { Title, Link } = Typography;
@ -19,6 +20,7 @@ const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
existingCredential,
setIsCredentialModalOpen,
}) => {
const { t } = useTranslation();
const [form] = Form.useForm();
console.log(`existingCredential in add credentials tab: ${JSON.stringify(existingCredential)}`);
@ -31,7 +33,7 @@ const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
return (
<Modal
title="Reuse Credentials"
title={t("modelAdd.reuseCredentials.title")}
open={isVisible}
onCancel={() => {
onCancel();
@ -43,12 +45,12 @@ const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
<Form form={form} onFinish={handleSubmit} layout="vertical">
{/* Credential Name */}
<Form.Item
label="Credential Name:"
label={t("modelAdd.reuseCredentials.credentialNameLabel")}
name="credential_name"
rules={[{ required: true, message: "Credential name is required" }]}
rules={[{ required: true, message: t("modelAdd.reuseCredentials.credentialNameRequired") }]}
initialValue={existingCredential?.credential_name}
>
<TextInput placeholder="Enter a friendly name for these credentials" />
<TextInput placeholder={t("modelAdd.reuseCredentials.credentialNamePlaceholder")} />
</Form.Item>
{/* Display Credential Values of existingCredential, don't allow user to edit. Credential values is a dictionary */}
@ -60,8 +62,8 @@ const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
{/* Modal Footer */}
<div className="flex justify-between items-center">
<Tooltip title="Get help on our github">
<Link href="https://github.com/BerriAI/litellm/issues">Need Help?</Link>
<Tooltip title={t("modelAdd.reuseCredentials.needHelpTooltip")}>
<Link href="https://github.com/BerriAI/litellm/issues">{t("modelAdd.reuseCredentials.needHelp")}</Link>
</Tooltip>
<div>
@ -72,9 +74,9 @@ const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
}}
style={{ marginRight: 10 }}
>
Cancel
{t("common.cancel")}
</Button>
<Button htmlType="submit">Reuse Credentials</Button>
<Button htmlType="submit">{t("modelAdd.reuseCredentials.reuseCredentials")}</Button>
</div>
</div>
</Form>

View file

@ -7569,5 +7569,52 @@
"tagUpdatedSuccess": "Tag updated successfully",
"errorUpdatingTag": "Error updating tag: {{error}}"
}
},
"modelAdd": {
"addCredentialModal": {
"title": "Add New Credential",
"credentialNameLabel": "Credential Name:",
"credentialNameRequired": "Credential name is required",
"credentialNamePlaceholder": "Enter a friendly name for these credentials",
"providerLabel": "Provider:",
"providerTooltip": "Helper to auto-populate provider specific fields",
"needHelpTooltip": "Get help on our github",
"needHelp": "Need Help?",
"addCredential": "Add Credential"
},
"editCredentialModal": {
"title": "Edit Credential",
"credentialNameLabel": "Credential Name:",
"credentialNameRequired": "Credential name is required",
"credentialNamePlaceholder": "Enter a friendly name for these credentials",
"providerLabel": "Provider:",
"providerTooltip": "Helper to auto-populate provider specific fields",
"needHelpTooltip": "Get help on our github",
"needHelp": "Need Help?",
"updateCredential": "Update Credential"
},
"credentials": {
"addCredential": "Add Credential",
"configuredCredentials": "Configured credentials for different AI providers. Add and manage your API credentials.",
"credentialName": "Credential Name",
"provider": "Provider",
"noCredentials": "No credentials configured",
"updatedSuccess": "Credential updated successfully",
"addedSuccess": "Credential added successfully",
"deletedSuccess": "Credential deleted successfully",
"deleteFailed": "Failed to delete credential",
"deleteTitle": "Delete Credential?",
"deleteMessage": "Are you sure you want to delete this credential? This action cannot be undone and may break existing integrations.",
"credentialInformation": "Credential Information"
},
"reuseCredentials": {
"title": "Reuse Credentials",
"credentialNameLabel": "Credential Name:",
"credentialNameRequired": "Credential name is required",
"credentialNamePlaceholder": "Enter a friendly name for these credentials",
"needHelpTooltip": "Get help on our github",
"needHelp": "Need Help?",
"reuseCredentials": "Reuse Credentials"
}
}
}

View file

@ -7569,5 +7569,52 @@
"tagUpdatedSuccess": "标签更新成功",
"errorUpdatingTag": "更新标签失败:{{error}}"
}
},
"modelAdd": {
"addCredentialModal": {
"title": "添加新凭据",
"credentialNameLabel": "凭据名称:",
"credentialNameRequired": "凭据名称不能为空",
"credentialNamePlaceholder": "输入这组凭据的友好名称",
"providerLabel": "提供商:",
"providerTooltip": "帮助自动填充提供商特定字段",
"needHelpTooltip": "在我们的 GitHub 上获取帮助",
"needHelp": "需要帮助?",
"addCredential": "添加凭据"
},
"editCredentialModal": {
"title": "编辑凭据",
"credentialNameLabel": "凭据名称:",
"credentialNameRequired": "凭据名称不能为空",
"credentialNamePlaceholder": "输入这组凭据的友好名称",
"providerLabel": "提供商:",
"providerTooltip": "帮助自动填充提供商特定字段",
"needHelpTooltip": "在我们的 GitHub 上获取帮助",
"needHelp": "需要帮助?",
"updateCredential": "更新凭据"
},
"credentials": {
"addCredential": "添加凭据",
"configuredCredentials": "已为不同 AI 提供商配置的凭据。添加并管理您的 API 凭据。",
"credentialName": "凭据名称",
"provider": "提供商",
"noCredentials": "暂无已配置的凭据",
"updatedSuccess": "凭据更新成功",
"addedSuccess": "凭据添加成功",
"deletedSuccess": "凭据删除成功",
"deleteFailed": "删除凭据失败",
"deleteTitle": "删除凭据?",
"deleteMessage": "确定要删除此凭据吗?此操作不可撤销,且可能破坏现有集成。",
"credentialInformation": "凭据信息"
},
"reuseCredentials": {
"title": "复用凭据",
"credentialNameLabel": "凭据名称:",
"credentialNameRequired": "凭据名称不能为空",
"credentialNamePlaceholder": "输入这组凭据的友好名称",
"needHelpTooltip": "在我们的 GitHub 上获取帮助",
"needHelp": "需要帮助?",
"reuseCredentials": "复用凭据"
}
}
}