feat(ui): i18n for key info header and edit view

This commit is contained in:
吹雪 2026-06-11 06:03:39 +09:00
parent 04778d2dad
commit 4eaf3508c1
4 changed files with 295 additions and 91 deletions

View file

@ -13,6 +13,7 @@ import {
TransactionOutlined,
FieldTimeOutlined,
} from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import LabeledField from "../common_components/LabeledField";
import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
@ -45,13 +46,14 @@ interface KeyInfoHeaderProps {
}
function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null; userEmail: string; userId: string }) {
const { t } = useTranslation();
const labelEl = (
<Space size={4}>
<Text type="secondary">
<UserOutlined />
</Text>
<Text type="secondary" style={{ fontSize: 12, textTransform: "uppercase", letterSpacing: "0.05em" }}>
User
{t("templates.keyInfoHeader.user")}
</Text>
</Space>
);
@ -74,9 +76,9 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
const popoverContent = (
<div className="flex flex-col gap-2 text-xs min-w-[200px] max-w-[300px]">
{[
{ label: "User Alias", value: userAlias ?? null },
{ label: "User Email", value: userEmail || null },
{ label: "User ID", value: userId || null },
{ label: t("templates.keyInfoHeader.userAlias"), value: userAlias ?? null },
{ label: t("templates.keyInfoHeader.userEmail"), value: userEmail || null },
{ label: t("templates.keyInfoHeader.userId"), value: userId || null },
].map(({ label, value }) => (
<div key={label} className="flex flex-col min-w-0">
<span className="text-gray-400">{label}</span>
@ -134,33 +136,42 @@ export function KeyInfoHeader({
onDelete,
onResetSpend,
canModifyKey = true,
backButtonText = "Back to Keys",
backButtonText,
regenerateDisabled = false,
regenerateTooltip,
}: KeyInfoHeaderProps) {
const { t } = useTranslation();
const resolvedBackButtonText = backButtonText ?? t("templates.keyInfoHeader.backToKeys");
return (
<div>
{onCreateNew && (
<div style={{ marginBottom: 16 }}>
<Button type="primary" icon={<PlusOutlined />} onClick={onCreateNew}>
Create New Key
{t("templates.keyInfoHeader.createNewKey")}
</Button>
</div>
)}
<div style={{ marginBottom: 16 }}>
<Button type="text" icon={<ArrowLeftOutlined />} onClick={onBack}>
{backButtonText}
{resolvedBackButtonText}
</Button>
</div>
<Flex justify="space-between" align="start" style={{ marginBottom: 20 }}>
<div>
<Title level={3} copyable={{ tooltips: ["Copy Key Alias", "Copied!"] }} style={{ margin: 0 }}>
<Title
level={3}
copyable={{ tooltips: [t("templates.keyInfoHeader.copyKeyAlias"), t("common.copied")] }}
style={{ margin: 0 }}
>
{data.keyName}
</Title>
<Text type="secondary" copyable={{ text: data.keyId, tooltips: ["Copy Key ID", "Copied!"] }}>
Key ID: {data.keyId}
<Text
type="secondary"
copyable={{ text: data.keyId, tooltips: [t("templates.keyInfoHeader.copyKeyId"), t("common.copied")] }}
>
{t("templates.keyInfoHeader.keyId", { keyId: data.keyId })}
</Text>
</div>
{canModifyKey && (
@ -168,17 +179,17 @@ export function KeyInfoHeader({
<Tooltip title={regenerateTooltip || ""}>
<span>
<Button icon={<SyncOutlined />} onClick={onRegenerate} disabled={regenerateDisabled}>
Regenerate Key
{t("templates.keyInfoHeader.regenerateKey")}
</Button>
</span>
</Tooltip>
{onResetSpend && (
<Button danger icon={<TransactionOutlined />} onClick={onResetSpend}>
Reset Spend
{t("templates.keyInfoHeader.resetSpend")}
</Button>
)}
<Button danger icon={<DeleteOutlined />} onClick={onDelete}>
Delete Key
{t("templates.keyInfoHeader.deleteKey")}
</Button>
</Space>
)}
@ -187,15 +198,19 @@ export function KeyInfoHeader({
<Flex align="stretch" gap={40} style={{ marginBottom: 40 }}>
<Space direction="vertical" size={16}>
<UserField userAlias={data.userAlias} userEmail={data.userEmail} userId={data.userId} />
<LabeledField label="Expires" value={data.expires} icon={<FieldTimeOutlined />} />
<LabeledField
label={t("templates.keyInfoHeader.expires")}
value={data.expires}
icon={<FieldTimeOutlined />}
/>
</Space>
<Divider type="vertical" style={{ height: "auto" }} />
<Space direction="vertical" size={16}>
<LabeledField label="Created At" value={data.createdAt} icon={<CalendarOutlined />} />
<LabeledField label={t("common.createdAt")} value={data.createdAt} icon={<CalendarOutlined />} />
<LabeledField
label="Created By"
label={t("templates.keyInfoHeader.createdBy")}
value={data.createdBy}
icon={<SafetyCertificateOutlined />}
truncate
@ -207,8 +222,16 @@ export function KeyInfoHeader({
<Divider type="vertical" style={{ height: "auto" }} />
<Space direction="vertical" size={16}>
<LabeledField label="Last Updated" value={data.lastUpdated} icon={<ClockCircleOutlined />} />
<LabeledField label="Last Active" value={data.lastActive} icon={<ThunderboltOutlined />} />
<LabeledField
label={t("templates.keyInfoHeader.lastUpdated")}
value={data.lastUpdated}
icon={<ClockCircleOutlined />}
/>
<LabeledField
label={t("templates.keyInfoHeader.lastActive")}
value={data.lastActive}
icon={<ThunderboltOutlined />}
/>
</Space>
</Flex>
</div>

View file

@ -7,6 +7,7 @@ import { InfoCircleOutlined } from "@ant-design/icons";
import { TextInput, Button as TremorButton } from "@tremor/react";
import { Form, Input, Select, Switch, Tooltip } from "antd";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { rolesWithWriteAccess } from "../../utils/roles";
import AgentSelector from "../agent_management/AgentSelector";
import AccessGroupSelector from "../common_components/AccessGroupSelector";
@ -88,6 +89,7 @@ export function KeyEditView({
userRole,
premiumUser = false,
}: KeyEditViewProps) {
const { t } = useTranslation();
const canEditGuardrails = premiumUser || (userRole != null && rolesWithWriteAccess.includes(userRole));
const [form] = Form.useForm();
const [promptsList, setPromptsList] = useState<string[]>([]);
@ -250,7 +252,7 @@ export function KeyEditView({
const response = await tagListCall(accessToken);
setTagsList(response);
} catch (error) {
NotificationsManager.fromBackend("Error fetching tags: " + error);
NotificationsManager.fromBackend(t("templates.keyEditView.errorFetchingTags", { error }));
}
};
fetchTags();
@ -303,11 +305,11 @@ export function KeyEditView({
return (
<Form form={form} onFinish={handleSubmit} initialValues={initialValues} layout="vertical">
<Form.Item label="Key Alias" name="key_alias">
<Form.Item label={t("templates.keyEditView.keyAlias")} name="key_alias">
<TextInput />
</Form.Item>
<Form.Item label="Models" name="models">
<Form.Item label={t("templates.keyEditView.models")} name="models">
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
@ -331,14 +333,16 @@ export function KeyEditView({
<>
<Select
mode="multiple"
placeholder="Select models"
placeholder={t("templates.keyEditView.selectModels")}
style={{ width: "100%" }}
disabled={isDisabled}
value={isDisabled ? [] : models}
onChange={(value) => setFieldValue("models", value)}
>
{/* Only show All Team Models if team has models */}
{availableModels.length > 0 && <Select.Option value="all-team-models">All Team Models</Select.Option>}
{availableModels.length > 0 && (
<Select.Option value="all-team-models">{t("templates.keyEditView.allTeamModels")}</Select.Option>
)}
{availableModels.map((model) => (
<Select.Option key={model} value={model}>
{model}
@ -347,7 +351,7 @@ export function KeyEditView({
</Select>
{isDisabled && (
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
Models field is disabled for this key type
{t("templates.keyEditView.modelsDisabled")}
</div>
)}
</>
@ -356,7 +360,7 @@ export function KeyEditView({
</Form.Item>
</Form.Item>
<Form.Item label="Key Type">
<Form.Item label={t("templates.keyEditView.keyType")}>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.allowed_routes !== currentValues.allowed_routes}
@ -375,7 +379,7 @@ export function KeyEditView({
return (
<Select
placeholder="Select key type"
placeholder={t("templates.keyEditView.selectKeyType")}
style={{ width: "100%" }}
optionLabelProp="label"
value={keyTypeValue}
@ -394,27 +398,27 @@ export function KeyEditView({
}
}}
>
<Select.Option value="default" label="Full Access">
<Select.Option value="default" label={t("templates.keyEditView.fullAccess")}>
<div style={{ padding: "4px 0" }}>
<div style={{ fontWeight: 500 }}>Full Access</div>
<div style={{ fontWeight: 500 }}>{t("templates.keyEditView.fullAccess")}</div>
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
Can call all routes (AI APIs, Management, and read-only)
{t("templates.keyEditView.fullAccessDesc")}
</div>
</div>
</Select.Option>
<Select.Option value="llm_api" label="AI APIs">
<Select.Option value="llm_api" label={t("templates.keyEditView.aiApis")}>
<div style={{ padding: "4px 0" }}>
<div style={{ fontWeight: 500 }}>AI APIs</div>
<div style={{ fontWeight: 500 }}>{t("templates.keyEditView.aiApis")}</div>
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
Can call only AI API routes (chat/completions, embeddings, etc.)
{t("templates.keyEditView.aiApisDesc")}
</div>
</div>
</Select.Option>
<Select.Option value="management" label="Management">
<Select.Option value="management" label={t("templates.keyEditView.management")}>
<div style={{ padding: "4px 0" }}>
<div style={{ fontWeight: 500 }}>Management</div>
<div style={{ fontWeight: 500 }}>{t("templates.keyEditView.management")}</div>
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
Can call only management routes (user/team/key management)
{t("templates.keyEditView.managementDesc")}
</div>
</div>
</Select.Option>
@ -427,34 +431,38 @@ export function KeyEditView({
<Form.Item
label={
<span>
Allowed Routes{" "}
<Tooltip title="List of allowed routes for the key (comma-separated). Can be specific routes (e.g., '/chat/completions') or route patterns (e.g., 'llm_api_routes', 'management_routes', '/keys/*'). Leave empty to allow all routes.">
{t("templates.keyEditView.allowedRoutes")}{" "}
<Tooltip title={t("templates.keyEditView.allowedRoutesTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="allowed_routes"
>
<Input placeholder="Enter allowed routes (comma-separated). Special values: llm_api_routes, management_routes. Examples: llm_api_routes, /chat/completions, /keys/*. Leave empty to allow all routes" />
<Input placeholder={t("templates.keyEditView.allowedRoutesPlaceholder")} />
</Form.Item>
<Form.Item label="Max Budget (USD)" name="max_budget">
<NumericalInput step={0.01} style={{ width: "100%" }} placeholder="Enter a numerical value" />
<Form.Item label={t("templates.keyEditView.maxBudget")} name="max_budget">
<NumericalInput
step={0.01}
style={{ width: "100%" }}
placeholder={t("templates.keyEditView.enterNumericalValue")}
/>
</Form.Item>
<Form.Item label="Reset Budget" name="budget_duration">
<Form.Item label={t("templates.keyEditView.resetBudget")} name="budget_duration">
<Select placeholder="n/a">
<Select.Option value="daily">Daily</Select.Option>
<Select.Option value="weekly">Weekly</Select.Option>
<Select.Option value="monthly">Monthly</Select.Option>
<Select.Option value="daily">{t("templates.keyEditView.daily")}</Select.Option>
<Select.Option value="weekly">{t("templates.keyEditView.weekly")}</Select.Option>
<Select.Option value="monthly">{t("templates.keyEditView.monthly")}</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={
<span>
Budget Windows{" "}
<Tooltip title="Set multiple independent budget windows (e.g., hourly $10 AND monthly $200). Each window tracks spend separately and resets on its own schedule.">
{t("templates.keyEditView.budgetWindows")}{" "}
<Tooltip title={t("templates.keyEditView.budgetWindowsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -463,31 +471,31 @@ export function KeyEditView({
<BudgetWindowsEditor value={budgetLimits} onChange={setBudgetLimits} />
</Form.Item>
<Form.Item label="TPM Limit" name="tpm_limit">
<Form.Item label={t("templates.keyEditView.tpmLimit")} name="tpm_limit">
<NumericalInput min={0} />
</Form.Item>
<RateLimitTypeFormItem type="tpm" name="tpm_limit_type" showDetailedDescriptions={false} />
<Form.Item label="RPM Limit" name="rpm_limit">
<Form.Item label={t("templates.keyEditView.rpmLimit")} name="rpm_limit">
<NumericalInput min={0} />
</Form.Item>
<RateLimitTypeFormItem type="rpm" name="rpm_limit_type" showDetailedDescriptions={false} />
<Form.Item label="Max Parallel Requests" name="max_parallel_requests">
<Form.Item label={t("templates.keyEditView.maxParallelRequests")} name="max_parallel_requests">
<NumericalInput min={0} />
</Form.Item>
<Form.Item label="Model TPM Limit" name="model_tpm_limit">
<Form.Item label={t("templates.keyEditView.modelTpmLimit")} name="model_tpm_limit">
<Input.TextArea rows={4} placeholder='{"gpt-4": 100, "claude-v1": 200}' />
</Form.Item>
<Form.Item label="Model RPM Limit" name="model_rpm_limit">
<Form.Item label={t("templates.keyEditView.modelRpmLimit")} name="model_rpm_limit">
<Input.TextArea rows={4} placeholder='{"gpt-4": 100, "claude-v1": 200}' />
</Form.Item>
<Form.Item label="Guardrails" name="guardrails">
<Form.Item label={t("templates.keyEditView.guardrails")} name="guardrails">
{accessToken && (
<GuardrailSelector
onChange={(v) => {
@ -502,8 +510,8 @@ export function KeyEditView({
<Form.Item
label={
<span>
Disable Global Guardrails{" "}
<Tooltip title="When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)">
{t("templates.keyEditView.disableGlobalGuardrails")}{" "}
<Tooltip title={t("templates.keyEditView.disableGlobalGuardrailsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -511,14 +519,14 @@ export function KeyEditView({
name="disable_global_guardrails"
valuePropName="checked"
>
<Switch disabled={!canEditGuardrails} checkedChildren="Yes" unCheckedChildren="No" />
<Switch disabled={!canEditGuardrails} checkedChildren={t("common.yes")} unCheckedChildren={t("common.no")} />
</Form.Item>
<Form.Item
label={
<span>
Policies{" "}
<Tooltip title="Apply policies to this key to control guardrails and other settings">
{t("templates.keyEditView.policies")}{" "}
<Tooltip title={t("templates.keyEditView.policiesTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -536,11 +544,11 @@ export function KeyEditView({
)}
</Form.Item>
<Form.Item label="Tags" name="tags">
<Form.Item label={t("templates.keyEditView.tags")} name="tags">
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Select or enter tags"
placeholder={t("templates.keyEditView.selectOrEnterTags")}
options={Object.values(tagsList).map((tag) => ({
value: tag.name,
label: tag.name,
@ -549,18 +557,18 @@ export function KeyEditView({
/>
</Form.Item>
<Form.Item label="Prompts" name="prompts">
<Tooltip title={!premiumUser ? "Setting prompts by key is a premium feature" : ""} placement="top">
<Form.Item label={t("templates.keyEditView.prompts")} name="prompts">
<Tooltip title={!premiumUser ? t("templates.keyEditView.promptsPremiumTooltip") : ""} placement="top">
<Select
mode="tags"
style={{ width: "100%" }}
disabled={!premiumUser}
placeholder={
!premiumUser
? "Premium feature - Upgrade to set prompts by key"
? t("templates.keyEditView.promptsPremiumPlaceholder")
: Array.isArray(keyData.metadata?.prompts) && keyData.metadata.prompts.length > 0
? `Current: ${keyData.metadata.prompts.join(", ")}`
: "Select or enter prompts"
? t("templates.keyEditView.currentPrompts", { prompts: keyData.metadata.prompts.join(", ") })
: t("templates.keyEditView.selectOrEnterPrompts")
}
options={promptsList.map((name) => ({ value: name, label: name }))}
/>
@ -570,54 +578,53 @@ export function KeyEditView({
<Form.Item
label={
<span>
Access Groups{" "}
<Tooltip title="Assign access groups to this key. Access groups control which models, MCP servers, and agents this key can use">
{t("templates.keyEditView.accessGroups")}{" "}
<Tooltip title={t("templates.keyEditView.accessGroupsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="access_group_ids"
>
<AccessGroupSelector placeholder="Select access groups (optional)" />
<AccessGroupSelector placeholder={t("templates.keyEditView.selectAccessGroups")} />
</Form.Item>
<Form.Item label="Allowed Pass Through Routes" name="allowed_passthrough_routes">
<Tooltip
title={!premiumUser ? "Setting allowed pass through routes by key is a premium feature" : ""}
placement="top"
>
<Form.Item label={t("templates.keyEditView.allowedPassThroughRoutes")} name="allowed_passthrough_routes">
<Tooltip title={!premiumUser ? t("templates.keyEditView.passThroughPremiumTooltip") : ""} placement="top">
<PassThroughRoutesSelector
onChange={(values: string[]) => form.setFieldValue("allowed_passthrough_routes", values)}
value={form.getFieldValue("allowed_passthrough_routes")}
accessToken={accessToken || ""}
placeholder={
!premiumUser
? "Premium feature - Upgrade to set allowed pass through routes by key"
? t("templates.keyEditView.passThroughPremiumPlaceholder")
: Array.isArray(keyData.metadata?.allowed_passthrough_routes) &&
keyData.metadata.allowed_passthrough_routes.length > 0
? `Current: ${keyData.metadata.allowed_passthrough_routes.join(", ")}`
: "Select or enter allowed pass through routes"
? t("templates.keyEditView.currentPassThroughRoutes", {
routes: keyData.metadata.allowed_passthrough_routes.join(", "),
})
: t("templates.keyEditView.selectOrEnterPassThroughRoutes")
}
disabled={!premiumUser}
/>
</Tooltip>
</Form.Item>
<Form.Item label="Vector Stores" name="vector_stores">
<Form.Item label={t("templates.keyEditView.vectorStores")} name="vector_stores">
<VectorStoreSelector
onChange={(values: string[]) => form.setFieldValue("vector_stores", values)}
value={form.getFieldValue("vector_stores")}
accessToken={accessToken || ""}
placeholder="Select vector stores"
placeholder={t("templates.keyEditView.selectVectorStores")}
/>
</Form.Item>
<Form.Item label="MCP Servers / Access Groups" name="mcp_servers_and_groups">
<Form.Item label={t("templates.keyEditView.mcpServers")} name="mcp_servers_and_groups">
<MCPServerSelector
onChange={(val) => form.setFieldValue("mcp_servers_and_groups", val)}
value={form.getFieldValue("mcp_servers_and_groups")}
accessToken={accessToken || ""}
placeholder="Select MCP servers or access groups (optional)"
placeholder={t("templates.keyEditView.selectMcpServers")}
/>
</Form.Item>
@ -645,20 +652,20 @@ export function KeyEditView({
)}
</Form.Item>
<Form.Item label="Agents / Access Groups" name="agents_and_groups">
<Form.Item label={t("templates.keyEditView.agents")} name="agents_and_groups">
<AgentSelector
onChange={(val) => form.setFieldValue("agents_and_groups", val)}
value={form.getFieldValue("agents_and_groups")}
accessToken={accessToken || ""}
placeholder="Select agents or access groups (optional)"
placeholder={t("templates.keyEditView.selectAgents")}
/>
</Form.Item>
<Form.Item
label={
<span>
Organization{" "}
<Tooltip title="The organization this key belongs to. Selecting an organization filters the available teams.">
{t("templates.keyEditView.organization")}{" "}
<Tooltip title={t("templates.keyEditView.organizationTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -677,12 +684,12 @@ export function KeyEditView({
</Form.Item>
<Form.Item
label="Team ID"
label={t("templates.keyEditView.teamId")}
name="team_id"
help={enableProjectsUI && hasProject ? "Team is locked because this key belongs to a project" : undefined}
help={enableProjectsUI && hasProject ? t("templates.keyEditView.teamLockedByProject") : undefined}
>
<Select
placeholder="Select team"
placeholder={t("templates.keyEditView.selectTeam")}
showSearch
disabled={enableProjectsUI && hasProject}
style={{ width: "100%" }}
@ -715,11 +722,11 @@ export function KeyEditView({
</Select>
</Form.Item>
{enableProjectsUI && hasProject && (
<Form.Item label="Project">
<Form.Item label={t("templates.keyEditView.project")}>
<Input value={projectDisplay ?? ""} disabled />
</Form.Item>
)}
<Form.Item label="Logging Settings" name="logging_settings">
<Form.Item label={t("templates.keyEditView.loggingSettings")} name="logging_settings">
<EditLoggingSettings
value={form.getFieldValue("logging_settings")}
onChange={(values) => form.setFieldValue("logging_settings", values)}
@ -734,7 +741,7 @@ export function KeyEditView({
/>
</Form.Item>
<Form.Item label="Metadata" name="metadata">
<Form.Item label={t("templates.keyEditView.metadata")} name="metadata">
<Input.TextArea rows={10} />
</Form.Item>
@ -775,10 +782,10 @@ export function KeyEditView({
<div className="sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]">
<div className="flex justify-end items-center gap-2">
<TremorButton variant="secondary" onClick={onCancel} disabled={isKeySaving}>
Cancel
{t("common.cancel")}
</TremorButton>
<TremorButton type="submit" loading={isKeySaving}>
Save Changes
{t("templates.keyEditView.saveChanges")}
</TremorButton>
</div>
</div>

View file

@ -7784,5 +7784,92 @@
"saveFailed": "Failed to save routing group",
"deleteFailed": "Failed to delete routing group"
}
},
"templates": {
"keyInfoHeader": {
"user": "User",
"userAlias": "User Alias",
"userEmail": "User Email",
"userId": "User ID",
"createNewKey": "Create New Key",
"backToKeys": "Back to Keys",
"copyKeyAlias": "Copy Key Alias",
"copyKeyId": "Copy Key ID",
"keyId": "Key ID: {{keyId}}",
"regenerateKey": "Regenerate Key",
"resetSpend": "Reset Spend",
"deleteKey": "Delete Key",
"expires": "Expires",
"createdBy": "Created By",
"lastUpdated": "Last Updated",
"lastActive": "Last Active"
},
"keyEditView": {
"keyAlias": "Key Alias",
"models": "Models",
"selectModels": "Select models",
"allTeamModels": "All Team Models",
"modelsDisabled": "Models field is disabled for this key type",
"keyType": "Key Type",
"selectKeyType": "Select key type",
"fullAccess": "Full Access",
"fullAccessDesc": "Can call all routes (AI APIs, Management, and read-only)",
"aiApis": "AI APIs",
"aiApisDesc": "Can call only AI API routes (chat/completions, embeddings, etc.)",
"management": "Management",
"managementDesc": "Can call only management routes (user/team/key management)",
"allowedRoutes": "Allowed Routes",
"allowedRoutesTooltip": "List of allowed routes for the key (comma-separated). Can be specific routes (e.g., '/chat/completions') or route patterns (e.g., 'llm_api_routes', 'management_routes', '/keys/*'). Leave empty to allow all routes.",
"allowedRoutesPlaceholder": "Enter allowed routes (comma-separated). Special values: llm_api_routes, management_routes. Examples: llm_api_routes, /chat/completions, /keys/*. Leave empty to allow all routes",
"maxBudget": "Max Budget (USD)",
"enterNumericalValue": "Enter a numerical value",
"resetBudget": "Reset Budget",
"daily": "Daily",
"weekly": "Weekly",
"monthly": "Monthly",
"budgetWindows": "Budget Windows",
"budgetWindowsTooltip": "Set multiple independent budget windows (e.g., hourly $10 AND monthly $200). Each window tracks spend separately and resets on its own schedule.",
"tpmLimit": "TPM Limit",
"rpmLimit": "RPM Limit",
"maxParallelRequests": "Max Parallel Requests",
"modelTpmLimit": "Model TPM Limit",
"modelRpmLimit": "Model RPM Limit",
"guardrails": "Guardrails",
"disableGlobalGuardrails": "Disable Global Guardrails",
"disableGlobalGuardrailsTooltip": "When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)",
"policies": "Policies",
"policiesTooltip": "Apply policies to this key to control guardrails and other settings",
"tags": "Tags",
"selectOrEnterTags": "Select or enter tags",
"prompts": "Prompts",
"promptsPremiumTooltip": "Setting prompts by key is a premium feature",
"promptsPremiumPlaceholder": "Premium feature - Upgrade to set prompts by key",
"currentPrompts": "Current: {{prompts}}",
"selectOrEnterPrompts": "Select or enter prompts",
"accessGroups": "Access Groups",
"accessGroupsTooltip": "Assign access groups to this key. Access groups control which models, MCP servers, and agents this key can use",
"selectAccessGroups": "Select access groups (optional)",
"allowedPassThroughRoutes": "Allowed Pass Through Routes",
"passThroughPremiumTooltip": "Setting allowed pass through routes by key is a premium feature",
"passThroughPremiumPlaceholder": "Premium feature - Upgrade to set allowed pass through routes by key",
"currentPassThroughRoutes": "Current: {{routes}}",
"selectOrEnterPassThroughRoutes": "Select or enter allowed pass through routes",
"vectorStores": "Vector Stores",
"selectVectorStores": "Select vector stores",
"mcpServers": "MCP Servers / Access Groups",
"selectMcpServers": "Select MCP servers or access groups (optional)",
"agents": "Agents / Access Groups",
"selectAgents": "Select agents or access groups (optional)",
"organization": "Organization",
"organizationTooltip": "The organization this key belongs to. Selecting an organization filters the available teams.",
"teamId": "Team ID",
"teamLockedByProject": "Team is locked because this key belongs to a project",
"selectTeam": "Select team",
"project": "Project",
"loggingSettings": "Logging Settings",
"metadata": "Metadata",
"saveChanges": "Save Changes",
"errorFetchingTags": "Error fetching tags: {{error}}"
}
}
}

View file

@ -7784,5 +7784,92 @@
"saveFailed": "保存路由组失败",
"deleteFailed": "删除路由组失败"
}
},
"templates": {
"keyInfoHeader": {
"user": "用户",
"userAlias": "用户别名",
"userEmail": "用户邮箱",
"userId": "用户 ID",
"createNewKey": "创建新密钥",
"backToKeys": "返回密钥列表",
"copyKeyAlias": "复制密钥别名",
"copyKeyId": "复制密钥 ID",
"keyId": "密钥 ID:{{keyId}}",
"regenerateKey": "重新生成密钥",
"resetSpend": "重置花费",
"deleteKey": "删除密钥",
"expires": "过期时间",
"createdBy": "创建人",
"lastUpdated": "最近更新",
"lastActive": "最近活跃"
},
"keyEditView": {
"keyAlias": "密钥别名",
"models": "模型",
"selectModels": "选择模型",
"allTeamModels": "所有团队模型",
"modelsDisabled": "该密钥类型已禁用模型字段",
"keyType": "密钥类型",
"selectKeyType": "选择密钥类型",
"fullAccess": "完全访问",
"fullAccessDesc": "可调用所有路由(AI API、管理及只读)",
"aiApis": "AI API",
"aiApisDesc": "仅可调用 AI API 路由(chat/completions、embeddings 等)",
"management": "管理",
"managementDesc": "仅可调用管理路由(用户/团队/密钥管理)",
"allowedRoutes": "允许的路由",
"allowedRoutesTooltip": "密钥允许的路由列表(逗号分隔)。可以是具体路由(如 '/chat/completions')或路由模式(如 'llm_api_routes'、'management_routes'、'/keys/*')。留空表示允许所有路由。",
"allowedRoutesPlaceholder": "输入允许的路由(逗号分隔)。特殊值:llm_api_routes、management_routes。示例:llm_api_routes, /chat/completions, /keys/*。留空表示允许所有路由",
"maxBudget": "最大预算(USD)",
"enterNumericalValue": "请输入数值",
"resetBudget": "重置预算",
"daily": "每日",
"weekly": "每周",
"monthly": "每月",
"budgetWindows": "预算窗口",
"budgetWindowsTooltip": "设置多个独立的预算窗口(例如每小时 $10 且每月 $200)。每个窗口独立追踪花费并按各自周期重置。",
"tpmLimit": "TPM 限制",
"rpmLimit": "RPM 限制",
"maxParallelRequests": "最大并发请求数",
"modelTpmLimit": "模型 TPM 限制",
"modelRpmLimit": "模型 RPM 限制",
"guardrails": "护栏",
"disableGlobalGuardrails": "禁用全局护栏",
"disableGlobalGuardrailsTooltip": "启用后,该密钥将跳过配置为对每个请求运行的护栏(全局护栏)",
"policies": "策略",
"policiesTooltip": "为该密钥应用策略以控制护栏和其他设置",
"tags": "标签",
"selectOrEnterTags": "选择或输入标签",
"prompts": "提示词",
"promptsPremiumTooltip": "按密钥设置提示词是高级功能",
"promptsPremiumPlaceholder": "高级功能 - 升级后可按密钥设置提示词",
"currentPrompts": "当前:{{prompts}}",
"selectOrEnterPrompts": "选择或输入提示词",
"accessGroups": "访问组",
"accessGroupsTooltip": "为该密钥分配访问组。访问组控制该密钥可使用的模型、MCP 服务器和 Agent",
"selectAccessGroups": "选择访问组(可选)",
"allowedPassThroughRoutes": "允许的透传路由",
"passThroughPremiumTooltip": "按密钥设置允许的透传路由是高级功能",
"passThroughPremiumPlaceholder": "高级功能 - 升级后可按密钥设置允许的透传路由",
"currentPassThroughRoutes": "当前:{{routes}}",
"selectOrEnterPassThroughRoutes": "选择或输入允许的透传路由",
"vectorStores": "向量存储",
"selectVectorStores": "选择向量存储",
"mcpServers": "MCP 服务器 / 访问组",
"selectMcpServers": "选择 MCP 服务器或访问组(可选)",
"agents": "Agent / 访问组",
"selectAgents": "选择 Agent 或访问组(可选)",
"organization": "组织",
"organizationTooltip": "该密钥所属的组织。选择组织可筛选可用团队。",
"teamId": "团队 ID",
"teamLockedByProject": "团队已锁定,因为该密钥属于某个项目",
"selectTeam": "选择团队",
"project": "项目",
"loggingSettings": "日志设置",
"metadata": "元数据",
"saveChanges": "保存更改",
"errorFetchingTags": "获取标签时出错:{{error}}"
}
}
}