feat(ui): i18n for cost tracking utils and hooks

This commit is contained in:
吹雪 2026-06-11 01:05:25 +09:00
parent 65f3306f46
commit 08f5303270
6 changed files with 231 additions and 69 deletions

View file

@ -1,3 +1,4 @@
import i18n from "@/lib/i18n";
import { CostEstimateResponse } from "../types";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { MultiModelResult } from "./types";
@ -16,44 +17,45 @@ const formatRequestsForExport = (value: number | null | undefined): string => {
};
const generateModelSection = (result: CostEstimateResponse): string => {
const t = i18n.t.bind(i18n);
return `
<div class="model-section">
<h3>${result.model} ${result.provider ? `<span class="provider">(${result.provider})</span>` : ""}</h3>
<div class="meta">
<p><strong>Input Tokens per Request:</strong> ${formatRequestsForExport(result.input_tokens)}</p>
<p><strong>Output Tokens per Request:</strong> ${formatRequestsForExport(result.output_tokens)}</p>
${result.num_requests_per_day ? `<p><strong>Requests per Day:</strong> ${formatRequestsForExport(result.num_requests_per_day)}</p>` : ""}
${result.num_requests_per_month ? `<p><strong>Requests per Month:</strong> ${formatRequestsForExport(result.num_requests_per_month)}</p>` : ""}
<p><strong>${t("costTracking.multiExportUtils.inputTokensPerRequest")}</strong> ${formatRequestsForExport(result.input_tokens)}</p>
<p><strong>${t("costTracking.multiExportUtils.outputTokensPerRequest")}</strong> ${formatRequestsForExport(result.output_tokens)}</p>
${result.num_requests_per_day ? `<p><strong>${t("costTracking.multiExportUtils.requestsPerDay")}</strong> ${formatRequestsForExport(result.num_requests_per_day)}</p>` : ""}
${result.num_requests_per_month ? `<p><strong>${t("costTracking.multiExportUtils.requestsPerMonth")}</strong> ${formatRequestsForExport(result.num_requests_per_month)}</p>` : ""}
</div>
<table>
<tr>
<th>Cost Type</th>
<th>Per Request</th>
${result.daily_cost !== null ? "<th>Daily</th>" : ""}
${result.monthly_cost !== null ? "<th>Monthly</th>" : ""}
<th>${t("costTracking.multiExportUtils.colCostType")}</th>
<th>${t("costTracking.multiExportUtils.colPerRequest")}</th>
${result.daily_cost !== null ? `<th>${t("costTracking.multiExportUtils.colDaily")}</th>` : ""}
${result.monthly_cost !== null ? `<th>${t("costTracking.multiExportUtils.colMonthly")}</th>` : ""}
</tr>
<tr>
<td>Input Cost</td>
<td>${t("costTracking.multiExportUtils.inputCost")}</td>
<td class="cost-value">${formatCostForExport(result.input_cost_per_request)}</td>
${result.daily_cost !== null ? `<td class="cost-value">${formatCostForExport(result.daily_input_cost)}</td>` : ""}
${result.monthly_cost !== null ? `<td class="cost-value">${formatCostForExport(result.monthly_input_cost)}</td>` : ""}
</tr>
<tr>
<td>Output Cost</td>
<td>${t("costTracking.multiExportUtils.outputCost")}</td>
<td class="cost-value">${formatCostForExport(result.output_cost_per_request)}</td>
${result.daily_cost !== null ? `<td class="cost-value">${formatCostForExport(result.daily_output_cost)}</td>` : ""}
${result.monthly_cost !== null ? `<td class="cost-value">${formatCostForExport(result.monthly_output_cost)}</td>` : ""}
</tr>
<tr>
<td>Margin/Fee</td>
<td>${t("costTracking.multiExportUtils.marginFee")}</td>
<td class="cost-value">${formatCostForExport(result.margin_cost_per_request)}</td>
${result.daily_cost !== null ? `<td class="cost-value">${formatCostForExport(result.daily_margin_cost)}</td>` : ""}
${result.monthly_cost !== null ? `<td class="cost-value">${formatCostForExport(result.monthly_margin_cost)}</td>` : ""}
</tr>
<tr class="total-row">
<td>Total</td>
<td>${t("common.total")}</td>
<td class="cost-value">${formatCostForExport(result.cost_per_request)}</td>
${result.daily_cost !== null ? `<td class="cost-value">${formatCostForExport(result.daily_cost)}</td>` : ""}
${result.monthly_cost !== null ? `<td class="cost-value">${formatCostForExport(result.monthly_cost)}</td>` : ""}
@ -64,9 +66,10 @@ const generateModelSection = (result: CostEstimateResponse): string => {
};
export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
const t = i18n.t.bind(i18n);
const printWindow = window.open("", "_blank");
if (!printWindow) {
alert("Please allow popups to export PDF");
alert(t("costTracking.multiExportUtils.popupBlockedAlert"));
return;
}
@ -193,22 +196,22 @@ export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
</style>
</head>
<body>
<h1>LLM Cost Estimate Report</h1>
<p style="color: #666; margin-top: -20px; margin-bottom: 30px;">${modelCount} model${modelCount !== 1 ? "s" : ""} configured</p>
<h1>${t("costTracking.multiExportUtils.reportTitle")}</h1>
<p style="color: #666; margin-top: -20px; margin-bottom: 30px;">${t("costTracking.multiExportUtils.modelCount", { count: modelCount })}</p>
<div class="summary-box">
<h2>Combined Totals</h2>
<h2>${t("costTracking.multiExportUtils.combinedTotals")}</h2>
<div class="summary-grid">
<div class="summary-item">
<div class="label">Total Per Request</div>
<div class="label">${t("costTracking.multiExportUtils.totalPerRequest")}</div>
<div class="value blue">${formatCostForExport(multiResult.totals.cost_per_request)}</div>
</div>
<div class="summary-item">
<div class="label">Total Daily</div>
<div class="label">${t("costTracking.multiExportUtils.totalDaily")}</div>
<div class="value green">${formatCostForExport(multiResult.totals.daily_cost)}</div>
</div>
<div class="summary-item">
<div class="label">Total Monthly</div>
<div class="label">${t("costTracking.multiExportUtils.totalMonthly")}</div>
<div class="value purple">${formatCostForExport(multiResult.totals.monthly_cost)}</div>
</div>
</div>
@ -217,15 +220,15 @@ export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
? `
<div class="summary-grid" style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd;">
<div class="summary-item">
<div class="label">Margin/Request</div>
<div class="label">${t("costTracking.multiExportUtils.marginPerRequest")}</div>
<div class="value" style="color: #faad14;">${formatCostForExport(multiResult.totals.margin_per_request)}</div>
</div>
<div class="summary-item">
<div class="label">Daily Margin</div>
<div class="label">${t("costTracking.multiExportUtils.dailyMargin")}</div>
<div class="value" style="color: #faad14;">${formatCostForExport(multiResult.totals.daily_margin)}</div>
</div>
<div class="summary-item">
<div class="label">Monthly Margin</div>
<div class="label">${t("costTracking.multiExportUtils.monthlyMargin")}</div>
<div class="value" style="color: #faad14;">${formatCostForExport(multiResult.totals.monthly_margin)}</div>
</div>
</div>
@ -234,11 +237,11 @@ export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
}
</div>
<h2>Model Breakdown</h2>
<h2>${t("costTracking.multiExportUtils.modelBreakdown")}</h2>
${validEntries.map((e) => generateModelSection(e.result!)).join("")}
<div class="footer">
<p>Generated by LiteLLM Pricing Calculator on ${new Date().toLocaleString()}</p>
<p>${t("costTracking.multiExportUtils.generatedOn", { date: new Date().toLocaleString() })}</p>
</div>
</body>
</html>
@ -252,36 +255,39 @@ export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
};
export const exportMultiToCSV = (multiResult: MultiModelResult): void => {
const t = i18n.t.bind(i18n);
const validEntries = multiResult.entries.filter((e) => e.result !== null);
const rows: string[][] = [["LLM Multi-Model Cost Estimate Report"], ["Generated", new Date().toLocaleString()], [""]];
const rows: string[][] = [
[t("costTracking.multiExportUtils.csvReportTitle")],
[t("costTracking.multiExportUtils.csvGenerated"), new Date().toLocaleString()],
[""],
];
// Summary section
rows.push(
["COMBINED TOTALS"],
["Total Per Request", multiResult.totals.cost_per_request.toString()],
["Total Daily", multiResult.totals.daily_cost?.toString() || "-"],
["Total Monthly", multiResult.totals.monthly_cost?.toString() || "-"],
["Margin Per Request", multiResult.totals.margin_per_request.toString()],
["Daily Margin", multiResult.totals.daily_margin?.toString() || "-"],
["Monthly Margin", multiResult.totals.monthly_margin?.toString() || "-"],
[t("costTracking.multiExportUtils.csvCombinedTotals")],
[t("costTracking.multiExportUtils.csvTotalPerRequest"), multiResult.totals.cost_per_request.toString()],
[t("costTracking.multiExportUtils.csvTotalDaily"), multiResult.totals.daily_cost?.toString() || "-"],
[t("costTracking.multiExportUtils.csvTotalMonthly"), multiResult.totals.monthly_cost?.toString() || "-"],
[t("costTracking.multiExportUtils.csvMarginPerRequest"), multiResult.totals.margin_per_request.toString()],
[t("costTracking.multiExportUtils.csvDailyMargin"), multiResult.totals.daily_margin?.toString() || "-"],
[t("costTracking.multiExportUtils.csvMonthlyMargin"), multiResult.totals.monthly_margin?.toString() || "-"],
[""],
);
// Summary table header
rows.push([
"Model",
"Provider",
"Input Tokens",
"Output Tokens",
"Requests/Day",
"Requests/Month",
"Cost/Request",
"Daily Cost",
"Monthly Cost",
"Input Cost/Req",
"Output Cost/Req",
"Margin/Req",
t("costTracking.multiExportUtils.csvColModel"),
t("costTracking.multiExportUtils.csvColProvider"),
t("costTracking.multiExportUtils.csvColInputTokens"),
t("costTracking.multiExportUtils.csvColOutputTokens"),
t("costTracking.multiExportUtils.csvColRequestsPerDay"),
t("costTracking.multiExportUtils.csvColRequestsPerMonth"),
t("costTracking.multiExportUtils.csvColCostPerRequest"),
t("costTracking.multiExportUtils.csvColDailyCost"),
t("costTracking.multiExportUtils.csvColMonthlyCost"),
t("costTracking.multiExportUtils.csvColInputCostPerReq"),
t("costTracking.multiExportUtils.csvColOutputCostPerReq"),
t("costTracking.multiExportUtils.csvColMarginPerReq"),
]);
// Add each model's data

View file

@ -1,4 +1,5 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { TextInput, Icon, Text } from "@tremor/react";
import { TrashIcon, PencilAltIcon, CheckIcon, XIcon } from "@heroicons/react/outline";
import { SimpleTable } from "../common_components/simple_table";
@ -21,6 +22,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
onMarginChange,
onRemoveProvider,
}) => {
const { t } = useTranslation();
const [editingProvider, setEditingProvider] = useState<string | null>(null);
const [editPercentage, setEditPercentage] = useState<string>("");
const [editFixedAmount, setEditFixedAmount] = useState<string>("");
@ -106,12 +108,12 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
data={data}
columns={[
{
header: "Provider",
header: t("costTracking.providerMarginTable.colProvider"),
cell: (row) => {
if (row.provider === "global") {
return (
<div className="flex items-center space-x-2">
<span className="font-medium">Global (All Providers)</span>
<span className="font-medium">{t("costTracking.providerMarginTable.globalProviderLabel")}</span>
</div>
);
}
@ -121,7 +123,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
{logo && (
<img
src={logo}
alt={`${displayName} logo`}
alt={t("costTracking.providerMarginTable.logoAlt", { displayName })}
className="w-5 h-5"
onError={(e) => handleImageError(e, displayName)}
/>
@ -132,7 +134,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
},
},
{
header: "Margin",
header: t("costTracking.providerMarginTable.colMargin"),
cell: (row) => (
<div className="flex items-center gap-2">
{editingProvider === row.provider ? (
@ -184,7 +186,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
width: "350px",
},
{
header: "Actions",
header: t("costTracking.providerMarginTable.colActions"),
cell: (row) => {
const displayName = row.provider === "global" ? "Global" : getProviderDisplayInfo(row.provider).displayName;
return (
@ -200,7 +202,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
},
]}
getRowKey={(row) => row.provider}
emptyMessage="No provider margins configured"
emptyMessage={t("costTracking.providerMarginTable.emptyMessage")}
/>
);
};

View file

@ -1,4 +1,5 @@
import { useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
import NotificationsManager from "../molecules/notifications_manager";
import { DiscountConfig } from "./types";
@ -20,6 +21,7 @@ export interface UseDiscountConfigReturn {
}
export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseDiscountConfigReturn {
const { t } = useTranslation();
const [discountConfig, setDiscountConfig] = useState<DiscountConfig>({});
const fetchDiscountConfig = useCallback(async () => {
@ -43,7 +45,7 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
}
} catch (error) {
console.error("Error fetching discount config:", error);
NotificationsManager.fromBackend("Failed to fetch discount configuration");
NotificationsManager.fromBackend(t("costTracking.useDiscountConfig.fetchFailed"));
}
}, [accessToken]);
@ -63,7 +65,7 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
});
if (response.ok) {
NotificationsManager.success("Discount configuration updated successfully");
NotificationsManager.success(t("costTracking.useDiscountConfig.updateSuccess"));
await fetchDiscountConfig();
} else {
const errorData = await response.json();
@ -72,7 +74,7 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
}
} catch (error) {
console.error("Error updating discount config:", error);
NotificationsManager.fromBackend("Failed to update discount configuration");
NotificationsManager.fromBackend(t("costTracking.useDiscountConfig.updateFailed"));
}
},
[accessToken, fetchDiscountConfig],
@ -81,26 +83,28 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
const handleAddProvider = useCallback(
async (selectedProvider: string | undefined, newDiscount: string): Promise<boolean> => {
if (!selectedProvider || !newDiscount) {
NotificationsManager.fromBackend("Please select a provider and enter discount percentage");
NotificationsManager.fromBackend(t("costTracking.useDiscountConfig.selectProviderAndDiscount"));
return false;
}
const percentageValue = parseFloat(newDiscount);
if (isNaN(percentageValue) || percentageValue < 0 || percentageValue > 100) {
NotificationsManager.fromBackend("Discount must be between 0% and 100%");
NotificationsManager.fromBackend(t("costTracking.useDiscountConfig.invalidDiscountRange"));
return false;
}
const providerValue = getProviderBackendValue(selectedProvider);
if (!providerValue) {
NotificationsManager.fromBackend("Invalid provider selected");
NotificationsManager.fromBackend(t("costTracking.useDiscountConfig.invalidProvider"));
return false;
}
if (discountConfig[providerValue]) {
NotificationsManager.fromBackend(
`Discount for ${Providers[selectedProvider as keyof typeof Providers]} already exists. Edit it in the table above.`,
t("costTracking.useDiscountConfig.discountAlreadyExists", {
providerName: Providers[selectedProvider as keyof typeof Providers],
}),
);
return false;
}

View file

@ -1,4 +1,5 @@
import { useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
import NotificationsManager from "../molecules/notifications_manager";
import { MarginConfig } from "./types";
@ -30,6 +31,7 @@ export interface AddMarginParams {
}
export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMarginConfigReturn {
const { t } = useTranslation();
const [marginConfig, setMarginConfig] = useState<MarginConfig>({});
const fetchMarginConfig = useCallback(async () => {
@ -53,7 +55,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
}
} catch (error) {
console.error("Error fetching margin config:", error);
NotificationsManager.fromBackend("Failed to fetch margin configuration");
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.fetchFailed"));
}
}, [accessToken]);
@ -73,7 +75,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
});
if (response.ok) {
NotificationsManager.success("Margin configuration updated successfully");
NotificationsManager.success(t("costTracking.useMarginConfig.updateSuccess"));
await fetchMarginConfig();
} else {
const errorData = await response.json();
@ -82,7 +84,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
}
} catch (error) {
console.error("Error updating margin config:", error);
NotificationsManager.fromBackend("Failed to update margin configuration");
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.updateFailed"));
}
},
[accessToken, fetchMarginConfig],
@ -93,7 +95,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
const { selectedProvider, marginType, percentageValue, fixedAmountValue } = params;
if (!selectedProvider) {
NotificationsManager.fromBackend("Please select a provider");
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.selectProvider"));
return false;
}
@ -103,7 +105,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
} else {
const backendValue = getProviderBackendValue(selectedProvider);
if (!backendValue) {
NotificationsManager.fromBackend("Invalid provider selected");
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.invalidProvider"));
return false;
}
providerValue = backendValue;
@ -112,7 +114,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
if (marginConfig[providerValue]) {
const displayName =
providerValue === "global" ? "Global" : Providers[selectedProvider as keyof typeof Providers];
NotificationsManager.fromBackend(`Margin for ${displayName} already exists. Edit it in the table above.`);
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.marginAlreadyExists", { displayName }));
return false;
}
@ -120,14 +122,14 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
if (marginType === "percentage") {
const percentValue = parseFloat(percentageValue);
if (isNaN(percentValue) || percentValue < 0 || percentValue > 1000) {
NotificationsManager.fromBackend("Percentage must be between 0% and 1000%");
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.invalidPercentageRange"));
return false;
}
marginValue = percentValue / 100;
} else {
const fixedValue = parseFloat(fixedAmountValue);
if (isNaN(fixedValue) || fixedValue < 0) {
NotificationsManager.fromBackend("Fixed amount must be non-negative");
NotificationsManager.fromBackend(t("costTracking.useMarginConfig.invalidFixedAmount"));
return false;
}
marginValue = { fixed_amount: fixedValue };

View file

@ -4793,6 +4793,80 @@
"multiExportDropdown": {
"exportAsPdf": "Export as PDF",
"exportAsCsv": "Export as CSV"
},
"multiExportUtils": {
"popupBlockedAlert": "Please allow popups to export PDF",
"reportTitle": "LLM Cost Estimate Report",
"modelCount_one": "{{count}} model configured",
"modelCount_other": "{{count}} models configured",
"combinedTotals": "Combined Totals",
"totalPerRequest": "Total Per Request",
"totalDaily": "Total Daily",
"totalMonthly": "Total Monthly",
"marginPerRequest": "Margin/Request",
"dailyMargin": "Daily Margin",
"monthlyMargin": "Monthly Margin",
"modelBreakdown": "Model Breakdown",
"generatedOn": "Generated by LiteLLM Pricing Calculator on {{date}}",
"inputTokensPerRequest": "Input Tokens per Request:",
"outputTokensPerRequest": "Output Tokens per Request:",
"requestsPerDay": "Requests per Day:",
"requestsPerMonth": "Requests per Month:",
"colCostType": "Cost Type",
"colPerRequest": "Per Request",
"colDaily": "Daily",
"colMonthly": "Monthly",
"inputCost": "Input Cost",
"outputCost": "Output Cost",
"marginFee": "Margin/Fee",
"csvReportTitle": "LLM Multi-Model Cost Estimate Report",
"csvGenerated": "Generated",
"csvCombinedTotals": "COMBINED TOTALS",
"csvTotalPerRequest": "Total Per Request",
"csvTotalDaily": "Total Daily",
"csvTotalMonthly": "Total Monthly",
"csvMarginPerRequest": "Margin Per Request",
"csvDailyMargin": "Daily Margin",
"csvMonthlyMargin": "Monthly Margin",
"csvColModel": "Model",
"csvColProvider": "Provider",
"csvColInputTokens": "Input Tokens",
"csvColOutputTokens": "Output Tokens",
"csvColRequestsPerDay": "Requests/Day",
"csvColRequestsPerMonth": "Requests/Month",
"csvColCostPerRequest": "Cost/Request",
"csvColDailyCost": "Daily Cost",
"csvColMonthlyCost": "Monthly Cost",
"csvColInputCostPerReq": "Input Cost/Req",
"csvColOutputCostPerReq": "Output Cost/Req",
"csvColMarginPerReq": "Margin/Req"
},
"providerMarginTable": {
"colProvider": "Provider",
"colMargin": "Margin",
"colActions": "Actions",
"globalProviderLabel": "Global (All Providers)",
"logoAlt": "{{displayName}} logo",
"emptyMessage": "No provider margins configured"
},
"useDiscountConfig": {
"fetchFailed": "Failed to fetch discount configuration",
"updateFailed": "Failed to update discount configuration",
"updateSuccess": "Discount configuration updated successfully",
"selectProviderAndDiscount": "Please select a provider and enter discount percentage",
"invalidDiscountRange": "Discount must be between 0% and 100%",
"invalidProvider": "Invalid provider selected",
"discountAlreadyExists": "Discount for {{providerName}} already exists. Edit it in the table above."
},
"useMarginConfig": {
"fetchFailed": "Failed to fetch margin configuration",
"updateFailed": "Failed to update margin configuration",
"updateSuccess": "Margin configuration updated successfully",
"selectProvider": "Please select a provider",
"invalidProvider": "Invalid provider selected",
"marginAlreadyExists": "Margin for {{displayName}} already exists. Edit it in the table above.",
"invalidPercentageRange": "Percentage must be between 0% and 1000%",
"invalidFixedAmount": "Fixed amount must be non-negative"
}
}
}

View file

@ -4793,6 +4793,80 @@
"multiExportDropdown": {
"exportAsPdf": "导出为 PDF",
"exportAsCsv": "导出为 CSV"
},
"multiExportUtils": {
"popupBlockedAlert": "请允许弹出窗口以导出 PDF",
"reportTitle": "LLM 成本估算报告",
"modelCount_one": "已配置 {{count}} 个模型",
"modelCount_other": "已配置 {{count}} 个模型",
"combinedTotals": "合计",
"totalPerRequest": "每次请求总计",
"totalDaily": "每日总计",
"totalMonthly": "每月总计",
"marginPerRequest": "利润率/请求",
"dailyMargin": "每日利润率",
"monthlyMargin": "每月利润率",
"modelBreakdown": "模型明细",
"generatedOn": "由 LiteLLM 价格计算器生成于 {{date}}",
"inputTokensPerRequest": "每次请求输入 Token:",
"outputTokensPerRequest": "每次请求输出 Token:",
"requestsPerDay": "每日请求数:",
"requestsPerMonth": "每月请求数:",
"colCostType": "成本类型",
"colPerRequest": "每次请求",
"colDaily": "每日",
"colMonthly": "每月",
"inputCost": "输入成本",
"outputCost": "输出成本",
"marginFee": "利润率/费用",
"csvReportTitle": "LLM 多模型成本估算报告",
"csvGenerated": "生成时间",
"csvCombinedTotals": "合计",
"csvTotalPerRequest": "每次请求总计",
"csvTotalDaily": "每日总计",
"csvTotalMonthly": "每月总计",
"csvMarginPerRequest": "每次请求利润率",
"csvDailyMargin": "每日利润率",
"csvMonthlyMargin": "每月利润率",
"csvColModel": "模型",
"csvColProvider": "提供商",
"csvColInputTokens": "输入 Token",
"csvColOutputTokens": "输出 Token",
"csvColRequestsPerDay": "请求数/天",
"csvColRequestsPerMonth": "请求数/月",
"csvColCostPerRequest": "成本/请求",
"csvColDailyCost": "每日成本",
"csvColMonthlyCost": "每月成本",
"csvColInputCostPerReq": "输入成本/请求",
"csvColOutputCostPerReq": "输出成本/请求",
"csvColMarginPerReq": "利润率/请求"
},
"providerMarginTable": {
"colProvider": "提供商",
"colMargin": "利润率",
"colActions": "操作",
"globalProviderLabel": "全局(所有提供商)",
"logoAlt": "{{displayName}} 徽标",
"emptyMessage": "未配置提供商利润率"
},
"useDiscountConfig": {
"fetchFailed": "获取折扣配置失败",
"updateFailed": "更新折扣配置失败",
"updateSuccess": "折扣配置已成功更新",
"selectProviderAndDiscount": "请选择提供商并输入折扣百分比",
"invalidDiscountRange": "折扣必须在 0% 到 100% 之间",
"invalidProvider": "所选提供商无效",
"discountAlreadyExists": "{{providerName}} 的折扣已存在,请在上方表格中编辑。"
},
"useMarginConfig": {
"fetchFailed": "获取利润率配置失败",
"updateFailed": "更新利润率配置失败",
"updateSuccess": "利润率配置已成功更新",
"selectProvider": "请选择提供商",
"invalidProvider": "所选提供商无效",
"marginAlreadyExists": "{{displayName}} 的利润率已存在,请在上方表格中编辑。",
"invalidPercentageRange": "百分比必须在 0% 到 1000% 之间",
"invalidFixedAmount": "固定金额不能为负数"
}
}
}