diff --git a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx index cbfd5b2f7fa..c8f8518638c 100644 --- a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx +++ b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx @@ -1,34 +1,30 @@ import React, { useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; import { Button } from "antd"; +import { Trans, useTranslation } from "react-i18next"; import MessageManager from "@/components/molecules/message_manager"; interface CreatedKeyDisplayProps { apiKey: string; } -/** - * Shared component for displaying a newly-created virtual key. - * Used on the Virtual Keys page and in the Add Agent wizard. - */ const CreatedKeyDisplay: React.FC = ({ apiKey }) => { + const { t } = useTranslation(); const [copied, setCopied] = useState(false); const handleCopy = () => { setCopied(true); - MessageManager.success("Key copied to clipboard"); + MessageManager.success(t("shared.createdKeyDisplay.copiedToClipboard")); setTimeout(() => setCopied(false), 2000); }; return (

- Please save this secret key somewhere safe and accessible. For security reasons,{" "} - you will not be able to view it again through your LiteLLM account. If you lose this secret key, you will - need to generate a new one. + }} />

-

Virtual Key:

+

{t("shared.createdKeyDisplay.virtualKeyLabel")}

= ({ apiKey }) => {
diff --git a/ui/litellm-dashboard/src/components/shared/advanced_date_picker.tsx b/ui/litellm-dashboard/src/components/shared/advanced_date_picker.tsx index 4293657b71a..9cc4630b495 100644 --- a/ui/litellm-dashboard/src/components/shared/advanced_date_picker.tsx +++ b/ui/litellm-dashboard/src/components/shared/advanced_date_picker.tsx @@ -1,7 +1,9 @@ import { CalendarOutlined, ClockCircleOutlined } from "@ant-design/icons"; import { Button, DateRangePickerValue, Text } from "@tremor/react"; import moment from "moment"; -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TFunction } from "i18next"; interface AdvancedDatePickerProps { value: DateRangePickerValue; @@ -17,42 +19,42 @@ interface RelativeTimeOption { getValue: () => { from: Date; to: Date }; } -const relativeTimeOptions: RelativeTimeOption[] = [ +const getRelativeTimeOptions = (t: TFunction): RelativeTimeOption[] => [ { - label: "Today", - shortLabel: "today", + label: t("shared.advancedDatePicker.today"), + shortLabel: t("shared.advancedDatePicker.todayShort"), getValue: () => ({ from: moment().startOf("day").toDate(), to: moment().endOf("day").toDate(), }), }, { - label: "Last 7 days", - shortLabel: "7d", + label: t("shared.advancedDatePicker.last7Days"), + shortLabel: t("shared.advancedDatePicker.last7DaysShort"), getValue: () => ({ from: moment().subtract(7, "days").startOf("day").toDate(), to: moment().endOf("day").toDate(), }), }, { - label: "Last 30 days", - shortLabel: "30d", + label: t("shared.advancedDatePicker.last30Days"), + shortLabel: t("shared.advancedDatePicker.last30DaysShort"), getValue: () => ({ from: moment().subtract(30, "days").startOf("day").toDate(), to: moment().endOf("day").toDate(), }), }, { - label: "Month to date", - shortLabel: "MTD", + label: t("shared.advancedDatePicker.monthToDate"), + shortLabel: t("shared.advancedDatePicker.monthToDateShort"), getValue: () => ({ from: moment().startOf("month").toDate(), to: moment().endOf("day").toDate(), }), }, { - label: "Year to date", - shortLabel: "YTD", + label: t("shared.advancedDatePicker.yearToDate"), + shortLabel: t("shared.advancedDatePicker.yearToDateShort"), getValue: () => ({ from: moment().startOf("year").toDate(), to: moment().endOf("day").toDate(), @@ -60,51 +62,50 @@ const relativeTimeOptions: RelativeTimeOption[] = [ }, ]; -/** - * Advanced Date Range Picker with dropdown, relative times, and custom inputs - */ const AdvancedDatePicker: React.FC = ({ value, onValueChange, - label = "Select Time Range", + label, showTimeRange = true, }) => { + const { t } = useTranslation(); + const relativeTimeOptions = useMemo(() => getRelativeTimeOptions(t), [t]); + const resolvedLabel = label !== undefined ? label : t("shared.advancedDatePicker.selectTimeRange"); + const [isOpen, setIsOpen] = useState(false); const [tempValue, setTempValue] = useState(value); const [selectedOption, setSelectedOption] = useState(null); - // Custom date inputs only - removed time inputs const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const dropdownRef = useRef(null); - // Function to check if current value matches a relative time option - const getMatchingOption = useCallback((currentValue: DateRangePickerValue): string | null => { - if (!currentValue.from || !currentValue.to) return null; + const getMatchingOption = useCallback( + (currentValue: DateRangePickerValue): string | null => { + if (!currentValue.from || !currentValue.to) return null; - for (const option of relativeTimeOptions) { - const optionRange = option.getValue(); + for (const option of relativeTimeOptions) { + const optionRange = option.getValue(); - // Compare dates with some tolerance (to account for time differences) - const fromMatches = moment(currentValue.from).isSame(moment(optionRange.from), "day"); - const toMatches = moment(currentValue.to).isSame(moment(optionRange.to), "day"); + const fromMatches = moment(currentValue.from).isSame(moment(optionRange.from), "day"); + const toMatches = moment(currentValue.to).isSame(moment(optionRange.to), "day"); - if (fromMatches && toMatches) { - return option.shortLabel; + if (fromMatches && toMatches) { + return option.shortLabel; + } } - } - return null; - }, []); + return null; + }, + [relativeTimeOptions], + ); - // Update selected option when value changes useEffect(() => { const matchingOption = getMatchingOption(value); setSelectedOption(matchingOption); }, [value, getMatchingOption]); - // Validation logic - simplified for dates only const validateDateRange = useCallback(() => { if (!startDate || !endDate) { return { isValid: true, error: "" }; @@ -114,19 +115,18 @@ const AdvancedDatePicker: React.FC = ({ const end = moment(endDate, "YYYY-MM-DD"); if (!start.isValid() || !end.isValid()) { - return { isValid: false, error: "Invalid date format" }; + return { isValid: false, error: t("shared.advancedDatePicker.invalidDateFormat") }; } if (end.isBefore(start)) { - return { isValid: false, error: "End date cannot be before start date" }; + return { isValid: false, error: t("shared.advancedDatePicker.endBeforeStart") }; } return { isValid: true, error: "" }; - }, [startDate, endDate]); + }, [startDate, endDate, t]); const validation = validateDateRange(); - // Initialize form inputs when component mounts or value changes useEffect(() => { if (value.from) { setStartDate(moment(value.from).format("YYYY-MM-DD")); @@ -137,7 +137,6 @@ const AdvancedDatePicker: React.FC = ({ setTempValue(value); }, [value]); - // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { @@ -154,17 +153,19 @@ const AdvancedDatePicker: React.FC = ({ }; }, [isOpen]); - const formatDisplayRange = useCallback((from: Date | undefined, to: Date | undefined) => { - if (!from || !to) return "Select date range"; + const formatDisplayRange = useCallback( + (from: Date | undefined, to: Date | undefined) => { + if (!from || !to) return t("shared.advancedDatePicker.selectDateRange"); - const formatDateTime = (date: Date) => { - return moment(date).format("D MMM, HH:mm"); - }; + const formatDateTime = (date: Date) => { + return moment(date).format("D MMM, HH:mm"); + }; - return `${formatDateTime(from)} - ${formatDateTime(to)}`; - }, []); + return `${formatDateTime(from)} - ${formatDateTime(to)}`; + }, + [t], + ); - // CRITICAL: Apply the same date adjustment logic as the original component const adjustDateRange = useCallback((newValue: DateRangePickerValue): DateRangePickerValue => { if (!newValue.from) return newValue; @@ -178,15 +179,8 @@ const AdvancedDatePicker: React.FC = ({ adjustedEndTime = new Date(newValue.from); } - const isSameDay = adjustedStartTime.toDateString() === adjustedEndTime.toDateString(); - - if (isSameDay) { - adjustedStartTime.setHours(0, 0, 0, 0); - adjustedEndTime.setHours(23, 59, 59, 999); - } else { - adjustedStartTime.setHours(0, 0, 0, 0); - adjustedEndTime.setHours(23, 59, 59, 999); - } + adjustedStartTime.setHours(0, 0, 0, 0); + adjustedEndTime.setHours(23, 59, 59, 999); adjustedValue.from = adjustedStartTime; adjustedValue.to = adjustedEndTime; @@ -198,21 +192,16 @@ const AdvancedDatePicker: React.FC = ({ const { from, to } = option.getValue(); const newValue = { from, to }; - // Update local state to reflect the selection (don't apply immediately) setTempValue(newValue); setSelectedOption(option.shortLabel); - // Update the form inputs to reflect the selection setStartDate(moment(from).format("YYYY-MM-DD")); setEndDate(moment(to).format("YYYY-MM-DD")); - - // Don't close the dropdown - let user click Apply to confirm }; const updateTempValueFromInputs = useCallback(() => { try { if (startDate && endDate && validation.isValid) { - // Set times to start and end of day const from = moment(startDate, "YYYY-MM-DD").startOf("day"); const to = moment(endDate, "YYYY-MM-DD").endOf("day"); @@ -220,7 +209,6 @@ const AdvancedDatePicker: React.FC = ({ const newValue = { from: from.toDate(), to: to.toDate() }; setTempValue(newValue); - // Check if this matches any preset option const matchingOption = getMatchingOption(newValue); setSelectedOption(matchingOption); } @@ -230,17 +218,14 @@ const AdvancedDatePicker: React.FC = ({ } }, [startDate, endDate, validation.isValid, getMatchingOption]); - // Update tempValue when inputs change useEffect(() => { updateTempValueFromInputs(); }, [updateTempValueFromInputs]); const handleApply = () => { if (tempValue.from && tempValue.to && validation.isValid) { - // First call with immediate value for UI responsiveness onValueChange(tempValue); - // Then do the same background adjustment logic as the original component requestIdleCallback( () => { const adjustedValue = adjustDateRange(tempValue); @@ -254,10 +239,8 @@ const AdvancedDatePicker: React.FC = ({ }; const handleCancel = () => { - // Reset to original value setTempValue(value); - // Reset form inputs if (value.from) { setStartDate(moment(value.from).format("YYYY-MM-DD")); } @@ -265,7 +248,6 @@ const AdvancedDatePicker: React.FC = ({ setEndDate(moment(value.to).format("YYYY-MM-DD")); } - // Reset selected option const matchingOption = getMatchingOption(value); setSelectedOption(matchingOption); @@ -274,9 +256,8 @@ const AdvancedDatePicker: React.FC = ({ return (
- {label && {label}} + {resolvedLabel && {resolvedLabel}}
- {/* Main input display */}
setIsOpen(!isOpen)} @@ -297,21 +278,21 @@ const AdvancedDatePicker: React.FC = ({
- {/* Dropdown panel */} {isOpen && (
- {/* Left side - Relative time options */}
- Relative time + + {t("shared.advancedDatePicker.relativeTime")} +
{relativeTimeOptions.map((option) => { const isSelected = selectedOption === option.shortLabel; return (
= ({
- {/* Right side - Custom date selection */}
- Start and end dates + + {t("shared.advancedDatePicker.startAndEndDates")} +
- {/* Start date */}
- + = ({ />
- {/* End date */}
- + = ({ />
- {/* Error message */} {!validation.isValid && validation.error && (
@@ -390,15 +371,14 @@ const AdvancedDatePicker: React.FC = ({
)} - {/* Current selection time range */} {tempValue.from && tempValue.to && validation.isValid && (
- From:{" "} + {t("shared.advancedDatePicker.from")}{" "} {moment(tempValue.from).format("MMM D, YYYY [at] HH:mm:ss")}
- To:{" "} + {t("shared.advancedDatePicker.to")}{" "} {moment(tempValue.to).format("MMM D, YYYY [at] HH:mm:ss")}
@@ -408,10 +388,10 @@ const AdvancedDatePicker: React.FC = ({
diff --git a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx index 72ce5042647..f51ed45c2b1 100644 --- a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx +++ b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useState, useRef } from "react"; import { DateRangePicker, DateRangePickerValue, Text } from "@tremor/react"; +import { useTranslation } from "react-i18next"; interface UsageDatePickerProps { value: DateRangePickerValue; @@ -15,10 +16,12 @@ interface UsageDatePickerProps { const UsageDatePicker: React.FC = ({ value, onValueChange, - label = "Select Time Range", + label, className = "", showTimeRange = true, }) => { + const { t } = useTranslation(); + const resolvedLabel = label !== undefined ? label : t("shared.usageDatePicker.selectTimeRange"); const [showSelectedFeedback, setShowSelectedFeedback] = useState(false); const datePickerRef = useRef(null); @@ -110,22 +113,20 @@ const UsageDatePicker: React.FC = ({ return (
- {label && {label}} + {resolvedLabel && {resolvedLabel}} - {/* Container with relative positioning for absolute placement */}
- {/* ONLY SHOW AFTER ACTUAL SELECTION IS COMPLETED */} {showSelectedFeedback && (
= ({
✓
- Selected + {t("shared.usageDatePicker.selected")}
)}
- {/* Time range display */} {showTimeRange && value.from && value.to && ( {formatTimeRange(value.from, value.to)} )} diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 1b13eb08b4e..49f950692b5 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -98,6 +98,41 @@ "adminUiDisabledBody": "The Admin UI has been disabled by the administrator. To re-enable it, please update the following environment variable:", "ssoEnabledNotice": "Single Sign-On (SSO) is enabled. LiteLLM no longer automatically redirects to the SSO login flow upon loading this page. To re-enable auto-redirect-to-SSO, set AUTO_REDIRECT_UI_LOGIN_TO_SSO=true in your environment configuration." }, + "shared": { + "createdKeyDisplay": { + "copiedToClipboard": "Key copied to clipboard", + "saveWarning": "Please save this secret key somewhere safe and accessible. For security reasons, you will not be able to view it again through your LiteLLM account. If you lose this secret key, you will need to generate a new one.", + "virtualKeyLabel": "Virtual Key:", + "copyButton": "Copy Virtual Key" + }, + "advancedDatePicker": { + "selectTimeRange": "Select Time Range", + "selectDateRange": "Select date range", + "relativeTime": "Relative time", + "startAndEndDates": "Start and end dates", + "startDate": "Start date", + "endDate": "End date", + "invalidDateFormat": "Invalid date format", + "endBeforeStart": "End date cannot be before start date", + "from": "From:", + "to": "To:", + "today": "Today", + "todayShort": "today", + "last7Days": "Last 7 days", + "last7DaysShort": "7d", + "last30Days": "Last 30 days", + "last30DaysShort": "30d", + "monthToDate": "Month to date", + "monthToDateShort": "MTD", + "yearToDate": "Year to date", + "yearToDateShort": "YTD" + }, + "usageDatePicker": { + "selectTimeRange": "Select Time Range", + "selectDateRange": "Select date range", + "selected": "Selected" + } + }, "common": { "save": "Save", "cancel": "Cancel", diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 0d5e8d1ca19..fb4318c2965 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -98,6 +98,41 @@ "adminUiDisabledBody": "管理员已禁用管理界面。如需重新启用,请更新以下环境变量:", "ssoEnabledNotice": "单点登录(SSO)已启用。LiteLLM 不再在加载此页面时自动跳转到 SSO 登录流程。如需重新启用自动跳转,请在环境配置中设置 AUTO_REDIRECT_UI_LOGIN_TO_SSO=true。" }, + "shared": { + "createdKeyDisplay": { + "copiedToClipboard": "密钥已复制到剪贴板", + "saveWarning": "请将此密钥保存在安全且易于访问的地方。出于安全原因,您将无法通过 LiteLLM 账户再次查看它。如果丢失此密钥,您需要重新生成一个。", + "virtualKeyLabel": "虚拟密钥:", + "copyButton": "复制虚拟密钥" + }, + "advancedDatePicker": { + "selectTimeRange": "选择时间范围", + "selectDateRange": "选择日期范围", + "relativeTime": "相对时间", + "startAndEndDates": "开始和结束日期", + "startDate": "开始日期", + "endDate": "结束日期", + "invalidDateFormat": "日期格式无效", + "endBeforeStart": "结束日期不能早于开始日期", + "from": "从:", + "to": "至:", + "today": "今天", + "todayShort": "今天", + "last7Days": "过去 7 天", + "last7DaysShort": "7天", + "last30Days": "过去 30 天", + "last30DaysShort": "30天", + "monthToDate": "本月至今", + "monthToDateShort": "MTD", + "yearToDate": "今年至今", + "yearToDateShort": "YTD" + }, + "usageDatePicker": { + "selectTimeRange": "选择时间范围", + "selectDateRange": "选择日期范围", + "selected": "已选择" + } + }, "common": { "save": "保存", "cancel": "取消",