feat(ui): i18n for team batch 1

This commit is contained in:
吹雪 2026-06-11 02:42:35 +09:00
parent 1927d31e42
commit 4882456643
5 changed files with 232 additions and 57 deletions

View file

@ -1,6 +1,7 @@
import { Text, TextInput } from "@tremor/react";
import { Button as AntButton, Form, Modal, Select } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import NumericalInput from "../shared/numerical_input";
import BudgetDurationDropdown from "../common_components/budget_duration_dropdown";
@ -48,6 +49,7 @@ const MemberModal = <T extends BaseMember>({
mode,
config,
}: MemberModalProps<T>) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const [isSubmitting, setIsSubmitting] = useState(false);
@ -135,7 +137,7 @@ const MemberModal = <T extends BaseMember>({
step={field.step || 1}
min={field.min || 0}
style={{ width: "100%" }}
placeholder={field.placeholder || "Enter a numerical value"}
placeholder={field.placeholder || t("teamPage.editMembership.numericalPlaceholder")}
/>
);
case "select":
@ -152,7 +154,7 @@ const MemberModal = <T extends BaseMember>({
return (
<Select
mode="multiple"
placeholder={field.placeholder || "Select options"}
placeholder={field.placeholder || t("teamPage.editMembership.selectOptionsPlaceholder")}
options={field.options}
allowClear
/>
@ -166,7 +168,10 @@ const MemberModal = <T extends BaseMember>({
return (
<Modal
title={config.title || (mode === "add" ? "Add Member" : "Edit Member")}
title={
config.title ||
(mode === "add" ? t("teamPage.editMembership.addMemberTitle") : t("teamPage.editMembership.editMemberTitle"))
}
open={visible}
width={1000}
footer={null}
@ -175,39 +180,41 @@ const MemberModal = <T extends BaseMember>({
<Form form={form} onFinish={handleSubmit} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
{config.showEmail && (
<Form.Item
label="Email"
label={t("teamPage.editMembership.emailLabel")}
name="user_email"
className="mb-4"
rules={[{ type: "email", message: "Please enter a valid email!" }]}
rules={[{ type: "email", message: t("teamPage.editMembership.emailInvalid") }]}
>
<TextInput placeholder="user@example.com" />
<TextInput placeholder={t("teamPage.editMembership.emailPlaceholder")} />
</Form.Item>
)}
{config.showEmail && config.showUserId && (
<div className="text-center mb-4">
<Text>OR</Text>
<Text>{t("teamPage.editMembership.orDivider")}</Text>
</div>
)}
{config.showUserId && (
<Form.Item label="User ID" name="user_id" className="mb-4">
<TextInput placeholder="user_123" />
<Form.Item label={t("teamPage.editMembership.userIdLabel")} name="user_id" className="mb-4">
<TextInput placeholder={t("teamPage.editMembership.userIdPlaceholder")} />
</Form.Item>
)}
<Form.Item
label={
<div className="flex items-center gap-2">
<span>Role</span>
<span>{t("teamPage.editMembership.roleLabel")}</span>
{mode === "edit" && initialData && (
<span className="text-gray-500 text-sm">(Current: {getRoleLabel(initialData.role)})</span>
<span className="text-gray-500 text-sm">
{t("teamPage.editMembership.roleCurrent", { role: getRoleLabel(initialData.role) })}
</span>
)}
</div>
}
name="role"
className="mb-4"
rules={[{ required: true, message: "Please select a role!" }]}
rules={[{ required: true, message: t("teamPage.editMembership.roleRequired") }]}
>
<Select>
{mode === "edit" && initialData
@ -237,10 +244,16 @@ const MemberModal = <T extends BaseMember>({
<div className="text-right mt-6">
<AntButton onClick={onCancel} className="mr-2" disabled={isSubmitting}>
Cancel
{t("common.cancel")}
</AntButton>
<AntButton type="default" htmlType="submit" loading={isSubmitting}>
{mode === "add" ? (isSubmitting ? "Adding..." : "Add Member") : isSubmitting ? "Saving..." : "Save Changes"}
{mode === "add"
? isSubmitting
? t("teamPage.editMembership.addingButton")
: t("teamPage.editMembership.addMemberButton")
: isSubmitting
? t("teamPage.editMembership.savingButton")
: t("teamPage.editMembership.saveChangesButton")}
</AntButton>
</div>
</Form>

View file

@ -5,6 +5,7 @@ import { Select, Tooltip, Divider } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";
import { Button, Card, TextInput } from "@tremor/react";
import { PlusIcon, TrashIcon, CogIcon, BanIcon } from "@heroicons/react/outline";
import { useTranslation } from "react-i18next";
import { callbackInfo, callback_map, mapDisplayToInternalNames } from "../callback_info_helpers";
import NumericalInput from "../shared/numerical_input";
@ -29,6 +30,8 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
disabledCallbacks = [],
onDisabledCallbacksChange,
}) => {
const { t } = useTranslation();
// Get callbacks that support team and key logging
const supportedCallbacks = Object.entries(callbackInfo)
.filter(([_, info]) => info.supports_key_team_logging)
@ -110,7 +113,9 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
<div className="w-3 h-3 bg-blue-100 rounded-full flex items-center justify-center">
<div className="w-1.5 h-1.5 bg-blue-500 rounded-full"></div>
</div>
<span className="text-sm font-medium text-gray-700">Integration Parameters</span>
<span className="text-sm font-medium text-gray-700">
{t("teamPage.loggingSettings.integrationParametersTitle")}
</span>
</div>
<div className="grid grid-cols-1 gap-4">
{Object.entries(dynamicParams).map(([paramName, paramType]) => (
@ -122,16 +127,18 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
</Tooltip>
{paramType === "password" && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">
Sensitive
{t("teamPage.loggingSettings.sensitiveBadge")}
</span>
)}
{paramType === "number" && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">
Number
{t("teamPage.loggingSettings.numberBadge")}
</span>
)}
</label>
{paramType === "number" && <span className="text-xs text-gray-500">Value must be between 0 and 1</span>}
{paramType === "number" && (
<span className="text-xs text-gray-500">{t("teamPage.loggingSettings.numberRangeHint")}</span>
)}
{paramType === "number" ? (
<NumericalInput
step={0.01}
@ -161,17 +168,21 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
<div className="space-y-4">
<div className="flex items-center space-x-2">
<BanIcon className="w-5 h-5 text-red-500" />
<span className="text-base font-semibold text-gray-800">Disabled Callbacks</span>
<Tooltip title="Select callbacks to disable for this key. Disabled callbacks will not receive any logging data.">
<span className="text-base font-semibold text-gray-800">
{t("teamPage.loggingSettings.disabledCallbacksTitle")}
</span>
<Tooltip title={t("teamPage.loggingSettings.disabledCallbacksTooltip")}>
<InfoCircleOutlined className="text-gray-400 cursor-help" />
</Tooltip>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Disabled Callbacks</label>
<label className="text-sm font-medium text-gray-700">
{t("teamPage.loggingSettings.disabledCallbacksLabel")}
</label>
<Select
mode="multiple"
placeholder="Select callbacks to disable"
placeholder={t("teamPage.loggingSettings.disabledCallbacksPlaceholder")}
value={disabledCallbacks}
onChange={handleDisabledCallbacksChange}
style={{ width: "100%" }}
@ -210,9 +221,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
);
})}
</Select>
<div className="text-xs text-gray-500">
Select callbacks that should be disabled for this key. These callbacks will not receive any logging data.
</div>
<div className="text-xs text-gray-500">{t("teamPage.loggingSettings.disabledCallbacksHelp")}</div>
</div>
</div>
@ -222,8 +231,10 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
<div className="flex justify-between items-center">
<div className="flex items-center space-x-2">
<CogIcon className="w-5 h-5 text-blue-500" />
<span className="text-base font-semibold text-gray-800">Logging Integrations</span>
<Tooltip title="Configure callback logging integrations for this team.">
<span className="text-base font-semibold text-gray-800">
{t("teamPage.loggingSettings.loggingIntegrationsTitle")}
</span>
<Tooltip title={t("teamPage.loggingSettings.loggingIntegrationsTooltip")}>
<InfoCircleOutlined className="text-gray-400 cursor-help" />
</Tooltip>
</div>
@ -235,7 +246,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
className="hover:border-blue-400 hover:text-blue-500"
type="button"
>
Add Integration
{t("teamPage.loggingSettings.addIntegrationButton")}
</Button>
</div>
@ -256,7 +267,10 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
<div className="flex justify-between items-start mb-4">
<div className="flex items-center space-x-2">
{logoUrl && <img src={logoUrl} alt={callbackDisplayName} className="w-5 h-5 object-contain" />}
<span className="text-sm font-medium">{callbackDisplayName || "New Integration"} Configuration</span>
<span className="text-sm font-medium">
{callbackDisplayName || t("teamPage.loggingSettings.newIntegrationFallback")}{" "}
{t("teamPage.loggingSettings.configurationSuffix")}
</span>
</div>
<Button
variant="light"
@ -267,16 +281,18 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
className="hover:bg-red-50"
type="button"
>
Remove
{t("teamPage.loggingSettings.removeButton")}
</Button>
</div>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Integration Type</label>
<label className="text-sm font-medium text-gray-700">
{t("teamPage.loggingSettings.integrationTypeLabel")}
</label>
<Select
value={callbackDisplayName}
placeholder="Select integration"
placeholder={t("teamPage.loggingSettings.selectIntegrationPlaceholder")}
onChange={(value) => updateLoggingConfig(index, "callback_name", value)}
className="w-full"
optionLabelProp="label"
@ -317,7 +333,9 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Event Type</label>
<label className="text-sm font-medium text-gray-700">
{t("teamPage.loggingSettings.eventTypeLabel")}
</label>
<Select
value={config.callback_type}
onChange={(value) => updateLoggingConfig(index, "callback_type", value)}
@ -326,19 +344,19 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
<Option value="success">
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span>Success Only</span>
<span>{t("teamPage.loggingSettings.successOnly")}</span>
</div>
</Option>
<Option value="failure">
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-red-500 rounded-full"></div>
<span>Failure Only</span>
<span>{t("teamPage.loggingSettings.failureOnly")}</span>
</div>
</Option>
<Option value="success_and_failure">
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<span>Success & Failure</span>
<span>{t("teamPage.loggingSettings.successAndFailure")}</span>
</div>
</Option>
</Select>
@ -355,8 +373,8 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
{value.length === 0 && (
<div className="text-center py-12 text-gray-500 border-2 border-dashed border-gray-200 rounded-lg bg-gray-50/50">
<CogIcon className="w-12 h-12 text-gray-300 mb-3 mx-auto" />
<div className="text-base font-medium mb-1">No logging integrations configured</div>
<div className="text-sm text-gray-400">Click "Add Integration" to configure logging for this team</div>
<div className="text-base font-medium mb-1">{t("teamPage.loggingSettings.emptyTitle")}</div>
<div className="text-sm text-gray-400">{t("teamPage.loggingSettings.emptySubtitle")}</div>
</div>
)}
</div>

View file

@ -3,6 +3,7 @@ import { formatNumberWithCommas } from "@/utils/dataUtils";
import { InfoCircleOutlined } from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { useMyTeamMember } from "./useMyTeamMember";
interface MyUserTabProps {
@ -23,18 +24,19 @@ const formatNumber = (value: number | null | undefined, digits = 4): string => {
return formatNumberWithCommas(value, digits);
};
const formatRateLimit = (value: number | null | undefined): string => {
if (value === null || value === undefined) return "Unlimited";
const formatRateLimit = (value: number | null | undefined, unlimitedLabel: string): string => {
if (value === null || value === undefined) return unlimitedLabel;
return formatNumberWithCommas(value, 0);
};
export default function MyUserTab({ teamId }: MyUserTabProps) {
const { t } = useTranslation();
const { data, isLoading, error } = useMyTeamMember(teamId);
if (isLoading) {
return (
<Card>
<Typography.Text type="secondary">Loading your membership info…</Typography.Text>
<Typography.Text type="secondary">{t("teamPage.myUserTab.loadingText")}</Typography.Text>
</Card>
);
}
@ -43,7 +45,7 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
return (
<Card>
<Typography.Text type="danger">
{error instanceof Error ? error.message : "Failed to load your membership info for this team."}
{error instanceof Error ? error.message : t("teamPage.myUserTab.errorFallback")}
</Typography.Text>
</Card>
);
@ -52,9 +54,7 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
if (!data) {
return (
<Card>
<Typography.Text type="secondary">
No membership info available for the current user in this team.
</Typography.Text>
<Typography.Text type="secondary">{t("teamPage.myUserTab.noDataText")}</Typography.Text>
</Card>
);
}
@ -68,12 +68,14 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
const budgetReset = formatBudgetReset(budgetTable?.budget_reset_at);
const allowedModels = budgetTable?.allowed_models ?? null;
const unlimitedLabel = t("teamPage.myUserTab.unlimited");
return (
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Card>
<Row gutter={[24, 16]}>
<Col xs={24} sm={12} md={8}>
<Typography.Text type="secondary">User</Typography.Text>
<Typography.Text type="secondary">{t("teamPage.myUserTab.userLabel")}</Typography.Text>
<div style={{ marginTop: 4 }}>
<Typography.Text strong>{data.user_email || data.user_id}</Typography.Text>
</div>
@ -82,7 +84,7 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
</Typography.Text>
</Col>
<Col xs={24} sm={12} md={8}>
<Typography.Text type="secondary">Team Role</Typography.Text>
<Typography.Text type="secondary">{t("teamPage.myUserTab.teamRoleLabel")}</Typography.Text>
<div style={{ marginTop: 4 }}>
<Tag color={data.role === "admin" ? "blue" : "default"}>{data.role || "user"}</Tag>
</div>
@ -94,20 +96,24 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
<Col xs={24} md={12}>
<Card>
{labelWithTooltip(
"Current Cycle Spend (USD)",
"Spend for the current budget cycle. Resets to $0 when the budget window rolls over.",
t("teamPage.myUserTab.currentCycleSpendLabel"),
t("teamPage.myUserTab.currentCycleSpendTooltip"),
)}
<div style={{ marginTop: 8 }}>
<Typography.Title level={3} style={{ margin: 0 }}>
${formatNumber(spend, 4)}
</Typography.Title>
<Typography.Text type="secondary">
of {maxBudget === null ? "Unlimited" : `$${formatNumber(maxBudget, 4)}`}
{t("teamPage.myUserTab.ofBudget", {
budget: maxBudget === null ? unlimitedLabel : `$${formatNumber(maxBudget, 4)}`,
})}
</Typography.Text>
</div>
{budgetReset && (
<div style={{ marginTop: 4 }}>
<Typography.Text type="secondary">Resets {budgetReset}</Typography.Text>
<Typography.Text type="secondary">
{t("teamPage.myUserTab.resetsAt", { when: budgetReset })}
</Typography.Text>
</div>
)}
</Card>
@ -115,18 +121,22 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
<Col xs={24} md={12}>
<Card>
{labelWithTooltip("Rate Limits", "Your per-member rate limits within this team.")}
{labelWithTooltip(t("teamPage.myUserTab.rateLimitsLabel"), t("teamPage.myUserTab.rateLimitsTooltip"))}
<div style={{ marginTop: 8 }}>
<Typography.Text>TPM: {formatRateLimit(tpmLimit)}</Typography.Text>
<Typography.Text>
{t("teamPage.myUserTab.tpmValue", { value: formatRateLimit(tpmLimit, unlimitedLabel) })}
</Typography.Text>
<br />
<Typography.Text>RPM: {formatRateLimit(rpmLimit)}</Typography.Text>
<Typography.Text>
{t("teamPage.myUserTab.rpmValue", { value: formatRateLimit(rpmLimit, unlimitedLabel) })}
</Typography.Text>
</div>
</Card>
</Col>
<Col xs={24} md={12}>
<Card>
{labelWithTooltip("Total Spend (USD)", "Cumulative spend across all budget cycles within this team.")}
{labelWithTooltip(t("teamPage.myUserTab.totalSpendLabel"), t("teamPage.myUserTab.totalSpendTooltip"))}
<div style={{ marginTop: 8 }}>
<Typography.Title level={4} style={{ margin: 0 }}>
${formatNumber(totalSpend, 4)}
@ -137,7 +147,7 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
<Col xs={24} md={12}>
<Card>
{labelWithTooltip("Model Scope", "Models you can access within this team.")}
{labelWithTooltip(t("teamPage.myUserTab.modelScopeLabel"), t("teamPage.myUserTab.modelScopeTooltip"))}
<div style={{ marginTop: 8 }}>
{allowedModels && allowedModels.length > 0 ? (
<Space wrap>
@ -146,7 +156,7 @@ export default function MyUserTab({ teamId }: MyUserTabProps) {
))}
</Space>
) : (
<Typography.Text>All Team Models</Typography.Text>
<Typography.Text>{t("teamPage.myUserTab.allTeamModels")}</Typography.Text>
)}
</div>
</Card>

View file

@ -6181,5 +6181,72 @@
"descriptionPlaceholder": "Describe what this agent does...",
"credentialFieldRequired": "Please enter {{label}}"
}
},
"teamPage": {
"editMembership": {
"emailLabel": "Email",
"emailPlaceholder": "user@example.com",
"emailInvalid": "Please enter a valid email!",
"orDivider": "OR",
"userIdLabel": "User ID",
"userIdPlaceholder": "user_123",
"roleLabel": "Role",
"roleCurrent": "(Current: {{role}})",
"roleRequired": "Please select a role!",
"numericalPlaceholder": "Enter a numerical value",
"selectOptionsPlaceholder": "Select options",
"addMemberTitle": "Add Member",
"editMemberTitle": "Edit Member",
"addingButton": "Adding...",
"addMemberButton": "Add Member",
"savingButton": "Saving...",
"saveChangesButton": "Save Changes"
},
"loggingSettings": {
"disabledCallbacksTitle": "Disabled Callbacks",
"disabledCallbacksTooltip": "Select callbacks to disable for this key. Disabled callbacks will not receive any logging data.",
"disabledCallbacksLabel": "Disabled Callbacks",
"disabledCallbacksPlaceholder": "Select callbacks to disable",
"disabledCallbacksHelp": "Select callbacks that should be disabled for this key. These callbacks will not receive any logging data.",
"loggingIntegrationsTitle": "Logging Integrations",
"loggingIntegrationsTooltip": "Configure callback logging integrations for this team.",
"addIntegrationButton": "Add Integration",
"integrationParametersTitle": "Integration Parameters",
"sensitiveBadge": "Sensitive",
"numberBadge": "Number",
"numberRangeHint": "Value must be between 0 and 1",
"newIntegrationFallback": "New Integration",
"configurationSuffix": "Configuration",
"removeButton": "Remove",
"integrationTypeLabel": "Integration Type",
"selectIntegrationPlaceholder": "Select integration",
"eventTypeLabel": "Event Type",
"successOnly": "Success Only",
"failureOnly": "Failure Only",
"successAndFailure": "Success & Failure",
"emptyTitle": "No logging integrations configured",
"emptySubtitle": "Click \"Add Integration\" to configure logging for this team"
},
"myUserTab": {
"loadingText": "Loading your membership info…",
"errorFallback": "Failed to load your membership info for this team.",
"noDataText": "No membership info available for the current user in this team.",
"userLabel": "User",
"teamRoleLabel": "Team Role",
"currentCycleSpendLabel": "Current Cycle Spend (USD)",
"currentCycleSpendTooltip": "Spend for the current budget cycle. Resets to $0 when the budget window rolls over.",
"ofBudget": "of {{budget}}",
"unlimited": "Unlimited",
"resetsAt": "Resets {{when}}",
"rateLimitsLabel": "Rate Limits",
"rateLimitsTooltip": "Your per-member rate limits within this team.",
"tpmValue": "TPM: {{value}}",
"rpmValue": "RPM: {{value}}",
"totalSpendLabel": "Total Spend (USD)",
"totalSpendTooltip": "Cumulative spend across all budget cycles within this team.",
"modelScopeLabel": "Model Scope",
"modelScopeTooltip": "Models you can access within this team.",
"allTeamModels": "All Team Models"
}
}
}

View file

@ -6181,5 +6181,72 @@
"descriptionPlaceholder": "描述该 Agent 的功能...",
"credentialFieldRequired": "请输入{{label}}"
}
},
"teamPage": {
"editMembership": {
"emailLabel": "邮箱",
"emailPlaceholder": "user@example.com",
"emailInvalid": "请输入有效的邮箱地址!",
"orDivider": "或",
"userIdLabel": "用户 ID",
"userIdPlaceholder": "user_123",
"roleLabel": "角色",
"roleCurrent": "(当前:{{role}})",
"roleRequired": "请选择角色!",
"numericalPlaceholder": "请输入数值",
"selectOptionsPlaceholder": "请选择选项",
"addMemberTitle": "添加成员",
"editMemberTitle": "编辑成员",
"addingButton": "添加中...",
"addMemberButton": "添加成员",
"savingButton": "保存中...",
"saveChangesButton": "保存更改"
},
"loggingSettings": {
"disabledCallbacksTitle": "已禁用的回调",
"disabledCallbacksTooltip": "选择要为此密钥禁用的回调。已禁用的回调不会收到任何日志数据。",
"disabledCallbacksLabel": "已禁用的回调",
"disabledCallbacksPlaceholder": "选择要禁用的回调",
"disabledCallbacksHelp": "选择应为此密钥禁用的回调。这些回调不会收到任何日志数据。",
"loggingIntegrationsTitle": "日志集成",
"loggingIntegrationsTooltip": "为此团队配置回调日志集成。",
"addIntegrationButton": "添加集成",
"integrationParametersTitle": "集成参数",
"sensitiveBadge": "敏感",
"numberBadge": "数值",
"numberRangeHint": "值必须在 0 到 1 之间",
"newIntegrationFallback": "新集成",
"configurationSuffix": "配置",
"removeButton": "移除",
"integrationTypeLabel": "集成类型",
"selectIntegrationPlaceholder": "选择集成",
"eventTypeLabel": "事件类型",
"successOnly": "仅成功",
"failureOnly": "仅失败",
"successAndFailure": "成功与失败",
"emptyTitle": "未配置日志集成",
"emptySubtitle": "点击「添加集成」为此团队配置日志"
},
"myUserTab": {
"loadingText": "正在加载您的成员信息…",
"errorFallback": "加载您在此团队的成员信息失败。",
"noDataText": "当前用户在此团队中没有可用的成员信息。",
"userLabel": "用户",
"teamRoleLabel": "团队角色",
"currentCycleSpendLabel": "当前周期花费(USD)",
"currentCycleSpendTooltip": "当前预算周期的花费。预算窗口滚动时将重置为 $0。",
"ofBudget": "共 {{budget}}",
"unlimited": "不限",
"resetsAt": "重置于 {{when}}",
"rateLimitsLabel": "速率限制",
"rateLimitsTooltip": "您在此团队中的每成员速率限制。",
"tpmValue": "TPM:{{value}}",
"rpmValue": "RPM:{{value}}",
"totalSpendLabel": "累计花费(USD)",
"totalSpendTooltip": "此团队所有预算周期的累计花费。",
"modelScopeLabel": "模型范围",
"modelScopeTooltip": "您在此团队中可访问的模型。",
"allTeamModels": "所有团队模型"
}
}
}