fix(ui): i18n audit repair round 3

This commit is contained in:
吹雪 2026-06-11 08:39:50 +09:00
parent edf4811067
commit 47d422c745
11 changed files with 97 additions and 41 deletions

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useMemo } from "react";
import {
Title,
Text,
@ -27,13 +27,21 @@ import { useDiscountConfig } from "./use_discount_config";
import { useMarginConfig } from "./use_margin_config";
import { fetchAvailableModels, ModelGroup } from "../playground/llm_calls/fetch_models";
const DOCS_LINKS = [
{ label: "Custom pricing for models", href: "https://docs.litellm.ai/docs/proxy/custom_pricing" },
{ label: "Spend tracking", href: "https://docs.litellm.ai/docs/proxy/cost_tracking" },
];
const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, userRole, accessToken }) => {
const { t } = useTranslation();
const docsLinks = useMemo(
() => [
{
label: t("costTracking.costTrackingSettings.docsLinkCustomPricing"),
href: "https://docs.litellm.ai/docs/proxy/custom_pricing",
},
{
label: t("costTracking.costTrackingSettings.docsLinkSpendTracking"),
href: "https://docs.litellm.ai/docs/proxy/cost_tracking",
},
],
[t],
);
const [selectedProvider, setSelectedProvider] = useState<string | undefined>(undefined);
const [newDiscount, setNewDiscount] = useState<string>("");
const [isFetching, setIsFetching] = useState(true);
@ -168,7 +176,7 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
<div>
<div className="flex items-center gap-2">
<Title>{t("costTracking.costTrackingSettings.pageTitle")}</Title>
<DocsMenu items={DOCS_LINKS} />
<DocsMenu items={docsLinks} />
</div>
<Text className="text-gray-500 mt-1">{t("costTracking.costTrackingSettings.pageSubtitle")}</Text>
</div>

View file

@ -646,7 +646,10 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
{selectedFile.name}
</Typography.Text>
<Typography.Text className={`block text-xs ${fileError ? "text-red-600" : "text-blue-600"}`}>
{(selectedFile.size / 1024).toFixed(1)} KB • {new Date().toLocaleDateString()}
{t("bulkCreateUsers.fileSizeKb", {
size: (selectedFile.size / 1024).toFixed(1),
date: new Date().toLocaleDateString(),
})}
</Typography.Text>
</div>
</div>

View file

@ -77,7 +77,10 @@ const EnvVarsSection: React.FC = () => {
},
]}
>
<Input placeholder="e.g. DB_PROTOCOL" className="rounded-md font-mono" />
<Input
placeholder={t("mcpTools.envVarsSection.varNamePlaceholder")}
className="rounded-md font-mono"
/>
</Form.Item>
<div style={{ flex: 1 }}>
<ScopedValueOrDescription name={name} restField={restField} />

View file

@ -806,7 +806,7 @@ export function ToolTestPanel({
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={content.url}
alt="Tool result"
alt={t("mcpTools.toolTestPanel.toolResultAlt")}
className="max-w-full h-auto rounded shadow-sm"
/>
</div>

View file

@ -182,7 +182,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
aliasName: e.target.value,
})
}
placeholder="e.g., gpt-4o"
placeholder={t("modelGroupAliasSettings.aliasNamePlaceholder")}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
/>
</div>
@ -199,7 +199,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
targetModelGroup: e.target.value,
})
}
placeholder="e.g., gpt-4o-mini-openai"
placeholder={t("modelGroupAliasSettings.targetModelGroupPlaceholder")}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
/>
</div>
@ -325,7 +325,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
{Object.keys(aliasObject).length === 0 ? (
<span className="text-gray-500">
<br />
&nbsp;&nbsp;&nbsp;&nbsp;# No aliases configured yet
&nbsp;&nbsp;&nbsp;&nbsp;{t("modelGroupAliasSettings.noAliasesConfigured")}
</span>
) : (
Object.entries(aliasObject).map(([key, value]) => (

View file

@ -104,7 +104,7 @@ const Navbar: React.FC<NavbarProps> = ({
<div className="flex h-10 max-w-48 items-center justify-center overflow-hidden">
<img
src={imageUrl}
alt="LiteLLM Brand"
alt={t("navbar.logoAlt")}
className="h-auto max-h-full w-auto max-w-full object-contain"
/>
</div>
@ -144,7 +144,7 @@ const Navbar: React.FC<NavbarProps> = ({
)}
<nav
aria-label="Product documentation"
aria-label={t("navbar.productDocsAriaLabel")}
className={`flex min-w-0 items-center gap-2 ${showWorkerSwitch ? "border-l border-gray-200 pl-4" : ""}`}
>
<a

View file

@ -1906,7 +1906,9 @@ const ChatUI: React.FC<ChatUIProps> = ({
<SoundOutlined style={{ fontSize: "20px", color: "#666" }} />
<span className="text-sm font-medium">{uploadedAudio.name}</span>
<span className="text-xs text-gray-500">
({(uploadedAudio.size / 1024 / 1024).toFixed(2)} MB)
{t("playground.chatUi.audioFileSizeMb", {
size: (uploadedAudio.size / 1024 / 1024).toFixed(2),
})}
</span>
</div>
<button

View file

@ -167,7 +167,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
() => [
{
name: "Organization ID",
label: "Organization ID",
label: t("teamPage.teamVirtualKeysTable.filterOrgId"),
isSearchable: true,
searchFn: async (searchText: string) => {
const { organizationIds } = teamFilterOptions;
@ -179,7 +179,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
},
{
name: "Key Alias",
label: "Key Alias",
label: t("teamPage.teamVirtualKeysTable.filterKeyAlias"),
isSearchable: true,
searchFn: async (searchText: string) => {
const { keyAliases } = teamFilterOptions;
@ -190,7 +190,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
},
{
name: "User ID",
label: "User ID",
label: t("teamPage.teamVirtualKeysTable.filterUserId"),
isSearchable: true,
searchFn: async (searchText: string) => {
const { userIds } = teamFilterOptions;
@ -547,8 +547,14 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
const key = row.original;
return (
<div>
<div>TPM: {key.tpm_limit !== null ? key.tpm_limit : t("teamPage.teamVirtualKeysTable.unlimited")}</div>
<div>RPM: {key.rpm_limit !== null ? key.rpm_limit : t("teamPage.teamVirtualKeysTable.unlimited")}</div>
<div>
{t("teamPage.teamVirtualKeysTable.tpmLabel")}{" "}
{key.tpm_limit !== null ? key.tpm_limit : t("teamPage.teamVirtualKeysTable.unlimited")}
</div>
<div>
{t("teamPage.teamVirtualKeysTable.rpmLabel")}{" "}
{key.rpm_limit !== null ? key.rpm_limit : t("teamPage.teamVirtualKeysTable.unlimited")}
</div>
</div>
);
},

View file

@ -551,8 +551,8 @@ const CollapsibleJsonCell = ({ jsonData }: { jsonData: any }) => {
return (
<div>
<button onClick={() => setIsExpanded(!isExpanded)} className="text-blue-500 hover:text-blue-700 text-xs">
{isExpanded ? i18n.t("viewLogs.columns.hideJson") : i18n.t("viewLogs.columns.showJson")} (
{Object.keys(jsonData).length} fields)
{isExpanded ? i18n.t("viewLogs.columns.hideJson") : i18n.t("viewLogs.columns.showJson")}{" "}
{i18n.t("viewLogs.columns.fieldCount", { count: Object.keys(jsonData).length })}
</button>
{isExpanded && (
<pre className="mt-2 p-2 bg-gray-50 border rounded text-xs overflow-auto max-h-60">{jsonString}</pre>

View file

@ -66,7 +66,9 @@
"notifications": "Notifications",
"agentPlatformDesc": "Open-source agent infra — sandboxes, durable sessions, and workers on AWS Fargate.",
"markAsRead": "Mark as read"
}
},
"logoAlt": "LiteLLM Brand",
"productDocsAriaLabel": "Product documentation"
},
"user": {
"logout": "Logout",
@ -1088,7 +1090,8 @@
"parseError": "Error parsing CSV: {{error}}",
"parseFileFailed": "Failed to parse CSV file: {{error}}",
"invitationLinkFailed": "User created but failed to generate invitation link",
"failedToCreateUser": "Failed to create user"
"failedToCreateUser": "Failed to create user",
"fileSizeKb": "{{size}} KB • {{date}}"
},
"cacheDashboard": {
"tabAnalytics": "Cache Analytics",
@ -2033,7 +2036,10 @@
"saveFailed": "Failed to save model group alias settings",
"addSuccess": "Alias added successfully",
"updateSuccess": "Alias updated successfully",
"deleteSuccess": "Alias deleted successfully"
"deleteSuccess": "Alias deleted successfully",
"aliasNamePlaceholder": "e.g., gpt-4o",
"targetModelGroupPlaceholder": "e.g., gpt-4o-mini-openai",
"noAliasesConfigured": "# No aliases configured yet"
},
"modelHubTableColumns": {
"publicModelName": "Public Model Name",
@ -3133,7 +3139,8 @@
"showJson": "Show JSON",
"copied": "Copied!",
"colAction": "Action",
"colChangedBy": "Changed By"
"colChangedBy": "Changed By",
"fieldCount": "({{count}} fields)"
},
"auditLogDrawer": {
"tableKeys": "Keys",
@ -3362,7 +3369,8 @@
"hintLabel": "Hint",
"perUserPlaceholder": "e.g. Your DB username",
"valuePlaceholder": "e.g. postgresql",
"addVariable": "Add Variable"
"addVariable": "Add Variable",
"varNamePlaceholder": "e.g. DB_PROTOCOL"
},
"mcpLogoSelector": {
"logoLabel": "Logo",
@ -3848,7 +3856,8 @@
"resultCopied": "Result copied to clipboard",
"copyResultFailed": "Failed to copy result",
"toolNameCopied": "Tool name copied to clipboard",
"copyToolNameFailed": "Failed to copy tool name"
"copyToolNameFailed": "Failed to copy tool name",
"toolResultAlt": "Tool result"
},
"mcpServerEdit": {
"oauthAuthSuccess": "OAuth authorization successful! Please click 'Update MCP Server' to save the credentials.",
@ -4342,7 +4351,8 @@
"toolsetsStep4": "The tool call is routed to the correct underlying MCP server automatically.",
"toolsetsExample": "<strong>Example:</strong> A \"GitHub Read-only\" toolset might include only <code>list_repos</code> and <code>get_file</code> from a GitHub MCP server — preventing agents from making writes.",
"toolsetsCreatingTitle": "Creating toolsets:",
"toolsetsCreatingDesc": "Admins can create and manage toolsets from the <strong>MCP</strong> page → <strong>Toolsets</strong> tab. Toolsets can then be assigned to keys and teams to scope their tool access."
"toolsetsCreatingDesc": "Admins can create and manage toolsets from the <strong>MCP</strong> page → <strong>Toolsets</strong> tab. Toolsets can then be assigned to keys and teams to scope their tool access.",
"audioFileSizeMb": "({{size}} MB)"
},
"codeInterpreterOutput": {
"pythonCodeExecuted": "Python Code Executed",
@ -5619,7 +5629,9 @@
"addDiscountModalTitle": "Add Provider Discount",
"addDiscountModalDesc": "Select a provider and set its discount percentage. Enter a value between 0% and 100% (e.g., 5 for a 5% discount).",
"addMarginModalTitle": "Add Provider Margin",
"addMarginModalDesc": "Select a provider (or \"Global\" for all providers) and configure the margin. You can use percentage-based or fixed amount."
"addMarginModalDesc": "Select a provider (or \"Global\" for all providers) and configure the margin. You can use percentage-based or fixed amount.",
"docsLinkCustomPricing": "Custom pricing for models",
"docsLinkSpendTracking": "Spend tracking"
},
"howItWorks": {
"costCalculationTitle": "Cost Calculation",
@ -6713,7 +6725,12 @@
"moreModel_other": "+{{count}} more models",
"pageOf": "Page {{current}} of {{total}}",
"loadingKeys": "Loading keys...",
"noKeysFound": "No keys found"
"noKeysFound": "No keys found",
"filterOrgId": "Organization ID",
"filterKeyAlias": "Key Alias",
"filterUserId": "User ID",
"tpmLabel": "TPM:",
"rpmLabel": "RPM:"
},
"availableTeams": {
"colTeamName": "Team Name",

View file

@ -66,7 +66,9 @@
"notifications": "通知",
"agentPlatformDesc": "开源 Agent 基础设施 — 沙箱、持久会话以及运行在 AWS Fargate 上的 worker。",
"markAsRead": "标记为已读"
}
},
"logoAlt": "LiteLLM 品牌",
"productDocsAriaLabel": "产品文档"
},
"user": {
"logout": "退出登录",
@ -1088,7 +1090,8 @@
"parseError": "解析 CSV 时出错:{{error}}",
"parseFileFailed": "解析 CSV 文件失败:{{error}}",
"invitationLinkFailed": "用户已创建,但生成邀请链接失败",
"failedToCreateUser": "创建用户失败"
"failedToCreateUser": "创建用户失败",
"fileSizeKb": "{{size}} KB • {{date}}"
},
"cacheDashboard": {
"tabAnalytics": "缓存分析",
@ -2033,7 +2036,10 @@
"saveFailed": "保存模型组别名设置失败",
"addSuccess": "别名添加成功",
"updateSuccess": "别名更新成功",
"deleteSuccess": "别名删除成功"
"deleteSuccess": "别名删除成功",
"aliasNamePlaceholder": "例如:gpt-4o",
"targetModelGroupPlaceholder": "例如:gpt-4o-mini-openai",
"noAliasesConfigured": "# 尚未配置别名"
},
"modelHubTableColumns": {
"publicModelName": "公开模型名称",
@ -3133,7 +3139,8 @@
"showJson": "显示 JSON",
"copied": "已复制!",
"colAction": "操作",
"colChangedBy": "变更人"
"colChangedBy": "变更人",
"fieldCount": "({{count}} 个字段)"
},
"auditLogDrawer": {
"tableKeys": "密钥",
@ -3362,7 +3369,8 @@
"hintLabel": "提示",
"perUserPlaceholder": "例如:您的数据库用户名",
"valuePlaceholder": "例如:postgresql",
"addVariable": "添加变量"
"addVariable": "添加变量",
"varNamePlaceholder": "例如:DB_PROTOCOL"
},
"mcpLogoSelector": {
"logoLabel": "Logo",
@ -3848,7 +3856,8 @@
"resultCopied": "结果已复制到剪贴板",
"copyResultFailed": "复制结果失败",
"toolNameCopied": "工具名称已复制到剪贴板",
"copyToolNameFailed": "复制工具名称失败"
"copyToolNameFailed": "复制工具名称失败",
"toolResultAlt": "工具结果"
},
"mcpServerEdit": {
"oauthAuthSuccess": "OAuth 授权成功!请点击「更新 MCP 服务器」以保存凭据。",
@ -4342,7 +4351,8 @@
"toolsetsStep4": "工具调用会自动路由到正确的底层 MCP 服务器。",
"toolsetsExample": "<strong>示例:</strong>一个「GitHub 只读」工具集可能只包含来自 GitHub MCP 服务器的 <code>list_repos</code> 和 <code>get_file</code>——防止 Agent 进行写操作。",
"toolsetsCreatingTitle": "创建工具集:",
"toolsetsCreatingDesc": "管理员可以从 <strong>MCP</strong> 页面 → <strong>工具集</strong>标签页创建和管理工具集。工具集随后可分配给密钥和团队以限定其工具访问范围。"
"toolsetsCreatingDesc": "管理员可以从 <strong>MCP</strong> 页面 → <strong>工具集</strong>标签页创建和管理工具集。工具集随后可分配给密钥和团队以限定其工具访问范围。",
"audioFileSizeMb": "({{size}} MB)"
},
"codeInterpreterOutput": {
"pythonCodeExecuted": "已执行的 Python 代码",
@ -5619,7 +5629,9 @@
"addDiscountModalTitle": "添加提供商折扣",
"addDiscountModalDesc": "选择提供商并设置折扣百分比,输入 0% 到 100% 之间的值(例如:5 表示 5% 折扣)。",
"addMarginModalTitle": "添加提供商利润率",
"addMarginModalDesc": "选择提供商(或选择「全局」以覆盖所有提供商)并配置利润率,可使用百分比或固定金额。"
"addMarginModalDesc": "选择提供商(或选择「全局」以覆盖所有提供商)并配置利润率,可使用百分比或固定金额。",
"docsLinkCustomPricing": "模型自定义定价",
"docsLinkSpendTracking": "花费追踪"
},
"howItWorks": {
"costCalculationTitle": "成本计算",
@ -6713,7 +6725,12 @@
"moreModel_other": "+{{count}} 个模型",
"pageOf": "第 {{current}} 页,共 {{total}} 页",
"loadingKeys": "正在加载密钥...",
"noKeysFound": "未找到密钥"
"noKeysFound": "未找到密钥",
"filterOrgId": "组织 ID",
"filterKeyAlias": "密钥别名",
"filterUserId": "用户 ID",
"tpmLabel": "TPM:",
"rpmLabel": "RPM:"
},
"availableTeams": {
"colTeamName": "团队名称",