refactor: advanced custom channel route editor (#6865)

* refactor: advanced custom channel route editor

* fix(channels): show raw balance response from balance cell
This commit is contained in:
Seefs
2026-08-18 17:31:21 +08:00
committed by GitHub
parent 3dda1d50c6
commit 2b0efd8484
21 changed files with 1551 additions and 506 deletions
+8
View File
@@ -22,6 +22,14 @@ func Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}
func IndentJson(data []byte) ([]byte, error) {
var buffer bytes.Buffer
if err := json.Indent(&buffer, data, "", " "); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func GetJsonType(data json.RawMessage) string {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 {
+130 -19
View File
@@ -5,13 +5,19 @@ import (
"errors"
"fmt"
"io"
"math"
"net/http"
"strconv"
"strings"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/relay/channel/advancedcustom"
relaycommon "github.com/QuantumNous/new-api/relay/common"
relayconstant "github.com/QuantumNous/new-api/relay/constant"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/types"
"github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/setting/operation_setting"
@@ -47,6 +53,13 @@ type OpenAICreditGrants struct {
TotalAvailable float64 `json:"total_available"`
}
const maxAdvancedCustomBalanceResponseBytes = 256 << 10
type channelBalanceResult struct {
Balance float64
RawResponse string
}
type OpenAIUsageResponse struct {
Object string `json:"object"`
//DailyCosts []OpenAIUsageDailyCost `json:"daily_costs"`
@@ -174,7 +187,7 @@ func updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := OpenAICreditGrants{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -189,7 +202,7 @@ func updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := OpenAISBUsageResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -213,7 +226,7 @@ func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := AIProxyUserOverviewResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -232,7 +245,7 @@ func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := API2GPTUsageResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -247,7 +260,7 @@ func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := SiliconFlowUsageResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -269,7 +282,7 @@ func updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := DeepSeekUsageResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -298,7 +311,7 @@ func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := APGC2DGPTUsageResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -313,7 +326,7 @@ func updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {
return 0, err
}
response := OpenRouterCreditResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -343,7 +356,7 @@ func updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {
}
response := MoonshotBalanceResponse{}
err = json.Unmarshal(body, &response)
err = common.Unmarshal(body, &response)
if err != nil {
return 0, err
}
@@ -356,7 +369,100 @@ func updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {
return availableBalanceUsd, nil
}
func updateChannelBalance(channel *model.Channel) (float64, error) {
func fetchAdvancedCustomBalance(channel *model.Channel) (channelBalanceResult, error) {
key := strings.TrimSpace(channel.Key)
info := &relaycommon.RelayInfo{
RelayFormat: types.RelayFormatOpenAI,
RelayMode: relayconstant.RelayModeUnknown,
RequestURLPath: dto.AdvancedCustomBalancePath,
ChannelMeta: &relaycommon.ChannelMeta{
ChannelType: constant.ChannelTypeAdvancedCustom,
ChannelBaseUrl: channel.GetBaseURL(),
ApiKey: key,
ChannelOtherSettings: channel.GetOtherSettings(),
},
}
requestURL, headers, err := (&advancedcustom.Adaptor{}).BuildBalanceRequest(info)
if err != nil {
return channelBalanceResult{}, sanitizeFetchModelsError(err, key)
}
if err := applyFetchModelsHeaderOverrides(channel, key, headers); err != nil {
return channelBalanceResult{}, sanitizeFetchModelsError(err, key)
}
request, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
return channelBalanceResult{}, sanitizeFetchModelsError(err, key)
}
for name, values := range headers {
for _, value := range values {
request.Header.Add(name, value)
}
if strings.EqualFold(name, "Host") {
request.Host = headers.Get(name)
}
}
client, err := service.GetHttpClientWithProxy(channel.GetSetting().Proxy)
if err != nil {
return channelBalanceResult{}, sanitizeFetchModelsError(err, key)
}
response, err := client.Do(request)
if err != nil {
return channelBalanceResult{}, sanitizeAdvancedCustomRequestError(err, key, requestURL)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return channelBalanceResult{}, fmt.Errorf("status code: %d", response.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(response.Body, maxAdvancedCustomBalanceResponseBytes+1))
if err != nil {
return channelBalanceResult{}, sanitizeAdvancedCustomRequestError(err, key, requestURL)
}
if len(body) > maxAdvancedCustomBalanceResponseBytes {
return channelBalanceResult{}, fmt.Errorf("balance response exceeds %d bytes", maxAdvancedCustomBalanceResponseBytes)
}
var validated json.RawMessage
if err := common.Unmarshal(body, &validated); err != nil {
return channelBalanceResult{}, fmt.Errorf("invalid balance JSON response: %w", err)
}
if common.GetJsonType(validated) == "object" {
var creditSummary struct {
Object string `json:"object"`
TotalAvailable json.RawMessage `json:"total_available"`
}
if err := common.Unmarshal(body, &creditSummary); err != nil {
return channelBalanceResult{}, fmt.Errorf("invalid balance JSON response: %w", err)
}
if creditSummary.Object == "credit_summary" &&
common.GetJsonType(creditSummary.TotalAvailable) == "number" {
var balance float64
if err := common.Unmarshal(creditSummary.TotalAvailable, &balance); err == nil &&
balance >= 0 &&
!math.IsNaN(balance) &&
!math.IsInf(balance, 0) {
channel.UpdateBalance(balance)
return channelBalanceResult{Balance: balance}, nil
}
}
}
formatted, err := common.IndentJson(body)
if err != nil {
return channelBalanceResult{}, fmt.Errorf("invalid balance JSON response: %w", err)
}
return channelBalanceResult{RawResponse: string(formatted)}, nil
}
func updateChannelBalance(channel *model.Channel) (channelBalanceResult, error) {
if channel.Type == constant.ChannelTypeAdvancedCustom {
return fetchAdvancedCustomBalance(channel)
}
balance, err := updateStandardChannelBalance(channel)
return channelBalanceResult{Balance: balance}, err
}
func updateStandardChannelBalance(channel *model.Channel) (float64, error) {
baseURL := constant.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() == "" {
channel.BaseURL = &baseURL
@@ -396,7 +502,7 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
return 0, err
}
subscription := OpenAISubscriptionResponse{}
err = json.Unmarshal(body, &subscription)
err = common.Unmarshal(body, &subscription)
if err != nil {
return 0, err
}
@@ -412,7 +518,7 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
return 0, err
}
usage := OpenAIUsageResponse{}
err = json.Unmarshal(body, &usage)
err = common.Unmarshal(body, &usage)
if err != nil {
return 0, err
}
@@ -439,16 +545,21 @@ func UpdateChannelBalance(c *gin.Context) {
})
return
}
balance, err := updateChannelBalance(channel)
result, err := updateChannelBalance(channel)
if err != nil {
common.ApiError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
response := gin.H{
"success": true,
"message": "",
"balance": balance,
})
}
if result.RawResponse == "" {
response["balance"] = result.Balance
} else {
response["raw_response"] = result.RawResponse
}
c.JSON(http.StatusOK, response)
}
func updateAllChannelsBalance() error {
@@ -467,12 +578,12 @@ func updateAllChannelsBalance() error {
//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
// continue
//}
balance, err := updateChannelBalance(channel)
result, err := updateChannelBalance(channel)
if err != nil {
continue
} else {
} else if result.RawResponse == "" {
// err is nil & balance <= 0 means quota is used up
if balance <= 0 {
if result.Balance <= 0 {
service.DisableChannel(*types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, "", channel.GetAutoBan()), "余额不足")
}
}
+29 -1
View File
@@ -304,6 +304,34 @@ func sanitizeFetchModelsError(err error, key string) error {
return errors.New(message)
}
func sanitizeAdvancedCustomRequestError(err error, key string, requestURL string) error {
err = sanitizeFetchModelsError(err, key)
if err == nil {
return nil
}
parsedURL, parseErr := url.Parse(requestURL)
if parseErr != nil {
return err
}
message := err.Error()
for _, value := range parsedURL.Query() {
for _, secret := range value {
if secret == "" {
continue
}
message = strings.ReplaceAll(message, secret, "[REDACTED]")
message = strings.ReplaceAll(message, url.QueryEscape(secret), "[REDACTED]")
message = strings.ReplaceAll(message, url.PathEscape(secret), "[REDACTED]")
}
}
if key != "" {
message = strings.ReplaceAll(message, key, "[REDACTED]")
message = strings.ReplaceAll(message, url.QueryEscape(key), "[REDACTED]")
message = strings.ReplaceAll(message, url.PathEscape(key), "[REDACTED]")
}
return errors.New(message)
}
func getFetchModelsResponseBody(method string, requestURL string, channel *model.Channel, headers http.Header) ([]byte, error) {
request, err := http.NewRequest(method, requestURL, nil)
if err != nil {
@@ -409,7 +437,7 @@ func fetchChannelUpstreamModelIDs(channel *model.Channel) ([]string, error) {
body, err := getFetchModelsResponseBody(http.MethodGet, url, channel, headers)
if err != nil {
return nil, sanitizeFetchModelsError(err, key)
return nil, sanitizeAdvancedCustomRequestError(err, key, url)
}
var result OpenAIModelsResponse
@@ -168,6 +168,15 @@ func TestFetchAdvancedCustomModelsRedactsQueryKeyFromTransportErrors(t *testing.
Err: errors.New("connection refused"),
}, secret)
require.EqualError(t, direct, "connection refused")
queryValue := "prefix-" + secret
queryError := sanitizeAdvancedCustomRequestError(
errors.New("dial "+queryValue+": connection refused"),
queryValue,
baseURL+"/v1/models?custom-token="+url.QueryEscape(queryValue),
)
require.NotContains(t, queryError.Error(), queryValue)
require.EqualError(t, queryError, "dial [REDACTED]: connection refused")
}
func TestFetchOrdinaryOpenAIModelsKeepsExistingEmptyDataBehavior(t *testing.T) {
+20 -3
View File
@@ -194,6 +194,14 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
}
func (a *Adaptor) BuildModelListRequest(info *relaycommon.RelayInfo) (string, http.Header, error) {
return a.buildManagementRequest(info, dto.AdvancedCustomModelListPath)
}
func (a *Adaptor) BuildBalanceRequest(info *relaycommon.RelayInfo) (string, http.Header, error) {
return a.buildManagementRequest(info, dto.AdvancedCustomBalancePath)
}
func (a *Adaptor) buildManagementRequest(info *relaycommon.RelayInfo, managementPath string) (string, http.Header, error) {
if info == nil {
return "", nil, errors.New("missing relay info")
}
@@ -204,16 +212,25 @@ func (a *Adaptor) BuildModelListRequest(info *relaycommon.RelayInfo) (string, ht
if err := config.Validate(); err != nil {
return "", nil, err
}
route, ok := config.ModelListRoute()
var route dto.AdvancedCustomRoute
var ok bool
switch managementPath {
case dto.AdvancedCustomModelListPath:
route, ok = config.ModelListRoute()
case dto.AdvancedCustomBalancePath:
route, ok = config.BalanceRoute()
default:
return "", nil, fmt.Errorf("unsupported advanced custom management path: %s", managementPath)
}
if !ok {
return "", nil, errors.New("advanced custom channel does not configure a /v1/models route")
return "", nil, fmt.Errorf("advanced custom channel does not configure a %s route", managementPath)
}
converter := strings.TrimSpace(route.Converter)
if converter == "" {
converter = relayconvert.ConverterNone
}
if converter != relayconvert.ConverterNone {
return "", nil, fmt.Errorf("converter %q does not support model list requests", converter)
return "", nil, fmt.Errorf("converter %q does not support %s requests", converter, managementPath)
}
requestURL, err := buildRouteURL(route, converter, info)
@@ -422,6 +422,49 @@ func TestAdaptorBuildModelListRequestRequiresConfiguredRoute(t *testing.T) {
assert.Contains(t, err.Error(), "does not configure a /v1/models route")
}
func TestAdaptorBuildBalanceRequestUsesConfiguredRoute(t *testing.T) {
info := advancedCustomRelayInfo(&dto.AdvancedCustomConfig{
Routes: []dto.AdvancedCustomRoute{
{
IncomingPath: dto.AdvancedCustomModelListPath,
UpstreamPath: "/provider/models",
},
{
IncomingPath: dto.AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance?existing=1",
Auth: &dto.AdvancedCustomRouteAuth{
Type: dto.AdvancedCustomAuthTypeQuery,
Name: "token",
Value: "prefix-{api_key}",
},
},
},
})
requestURL, header, err := (&Adaptor{}).BuildBalanceRequest(info)
require.NoError(t, err)
parsedURL, err := url.Parse(requestURL)
require.NoError(t, err)
assert.Equal(t, "/provider/balance", parsedURL.Path)
assert.Equal(t, "1", parsedURL.Query().Get("existing"))
assert.Equal(t, "prefix-sk-test", parsedURL.Query().Get("token"))
assert.Empty(t, header.Get("Authorization"))
}
func TestAdaptorBuildBalanceRequestRequiresConfiguredRoute(t *testing.T) {
info := advancedCustomRelayInfo(&dto.AdvancedCustomConfig{
Routes: []dto.AdvancedCustomRoute{{
IncomingPath: dto.AdvancedCustomModelListPath,
UpstreamPath: "/provider/models",
}},
})
_, _, err := (&Adaptor{}).BuildBalanceRequest(info)
require.Error(t, err)
assert.Contains(t, err.Error(), "does not configure a /v1/dashboard/billing/credit_grants route")
}
func TestAdaptorConvertsResponsesRequestToOpenAIChatUpstream(t *testing.T) {
adaptor := &Adaptor{}
info := advancedCustomRelayInfo(&dto.AdvancedCustomConfig{
+36 -9
View File
@@ -145,8 +145,12 @@ const (
advancedCustomEndpointPathEmbeddings = "/v1/embeddings"
)
// AdvancedCustomModelListPath identifies the optional OpenAI Models discovery route.
const AdvancedCustomModelListPath = "/v1/models"
const (
// AdvancedCustomModelListPath identifies the optional OpenAI Models discovery route.
AdvancedCustomModelListPath = "/v1/models"
// AdvancedCustomBalancePath identifies the optional balance lookup route used by channel management.
AdvancedCustomBalancePath = "/v1/dashboard/billing/credit_grants"
)
// MatchPath returns the first route whose IncomingPath matches requestPath.
// Matching mirrors the relay adaptor: exact match, {model} placeholder, and
@@ -193,6 +197,19 @@ func (c *AdvancedCustomConfig) ModelListRoute() (AdvancedCustomRoute, bool) {
return AdvancedCustomRoute{}, false
}
// BalanceRoute returns the explicitly configured channel-management balance route.
func (c *AdvancedCustomConfig) BalanceRoute() (AdvancedCustomRoute, bool) {
if c == nil {
return AdvancedCustomRoute{}, false
}
for _, route := range c.Routes {
if strings.TrimSpace(route.IncomingPath) == AdvancedCustomBalancePath {
return route, true
}
}
return AdvancedCustomRoute{}, false
}
// SupportsPath reports whether any route matches requestPath.
func (c *AdvancedCustomConfig) SupportsPath(requestPath string) bool {
_, ok := c.MatchPath(requestPath)
@@ -360,6 +377,7 @@ func (c *AdvancedCustomConfig) Validate() error {
paths := make(map[string]*advancedCustomPathModelState, len(c.Routes))
modelListRouteIndex := -1
balanceRouteIndex := -1
for i := range c.Routes {
route := c.Routes[i]
route.IncomingPath = strings.TrimSpace(route.IncomingPath)
@@ -378,19 +396,28 @@ func (c *AdvancedCustomConfig) Validate() error {
if strings.Contains(route.IncomingPath, "?") {
return fmt.Errorf("advanced_custom.advanced_routes[%d].incoming_path must not include query", i)
}
if route.IncomingPath == AdvancedCustomModelListPath {
if modelListRouteIndex >= 0 {
return fmt.Errorf("advanced_custom.advanced_routes[%d] duplicates the /v1/models route at advanced_routes[%d]", i, modelListRouteIndex)
if route.IncomingPath == AdvancedCustomModelListPath || route.IncomingPath == AdvancedCustomBalancePath {
managementRouteName := route.IncomingPath
previousIndex := modelListRouteIndex
if route.IncomingPath == AdvancedCustomBalancePath {
previousIndex = balanceRouteIndex
}
if previousIndex >= 0 {
return fmt.Errorf("advanced_custom.advanced_routes[%d] duplicates the %s route at advanced_routes[%d]", i, managementRouteName, previousIndex)
}
if route.IncomingPath == AdvancedCustomModelListPath {
modelListRouteIndex = i
} else {
balanceRouteIndex = i
}
modelListRouteIndex = i
if len(normalizeAdvancedCustomRouteModels(route.Models)) > 0 {
return fmt.Errorf("advanced_custom.advanced_routes[%d].models must be empty for /v1/models", i)
return fmt.Errorf("advanced_custom.advanced_routes[%d].models must be empty for %s", i, managementRouteName)
}
if route.Converter != advancedCustomConverterNone {
return fmt.Errorf("advanced_custom.advanced_routes[%d].converter must be none for /v1/models", i)
return fmt.Errorf("advanced_custom.advanced_routes[%d].converter must be none for %s", i, managementRouteName)
}
if strings.Contains(upstreamPath, advancedCustomModelPlaceholder) {
return fmt.Errorf("advanced_custom.advanced_routes[%d].upstream_path must not contain %s for /v1/models", i, advancedCustomModelPlaceholder)
return fmt.Errorf("advanced_custom.advanced_routes[%d].upstream_path must not contain %s for %s", i, advancedCustomModelPlaceholder, managementRouteName)
}
}
if err := validateAdvancedCustomRouteModels(i, route.IncomingPath, route.Models, paths); err != nil {
+64
View File
@@ -147,6 +147,70 @@ func TestAdvancedCustomModelListRouteRequiresExactIncomingPath(t *testing.T) {
assert.Equal(t, "/provider/models", route.UpstreamPath)
}
func TestAdvancedCustomValidateBalanceRouteConstraints(t *testing.T) {
valid := &AdvancedCustomConfig{
Routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance",
Converter: advancedCustomConverterNone,
}},
}
require.NoError(t, valid.Validate())
route, ok := valid.BalanceRoute()
require.True(t, ok)
assert.Equal(t, "/provider/balance", route.UpstreamPath)
tests := []struct {
name string
routes []AdvancedCustomRoute
want string
}{
{
name: "model matching rules",
routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance",
Models: []string{"gpt-4o"},
}},
want: "models must be empty",
},
{
name: "converter",
routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance",
Converter: advancedCustomConverterOpenAIChatToOpenAIResponses,
}},
want: "converter must be none",
},
{
name: "model placeholder",
routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/{model}/balance",
}},
want: "upstream_path must not contain {model}",
},
{
name: "duplicate routes",
routes: []AdvancedCustomRoute{
{IncomingPath: AdvancedCustomBalancePath, UpstreamPath: "/provider/balance"},
{IncomingPath: AdvancedCustomBalancePath, UpstreamPath: "/provider/credits"},
},
want: "duplicates the /v1/dashboard/billing/credit_grants route",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := (&AdvancedCustomConfig{Routes: tt.routes}).Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), tt.want)
})
}
}
func TestAdvancedCustomValidateDuplicateIncomingPathWithDisjointModels(t *testing.T) {
config := &AdvancedCustomConfig{
Routes: []AdvancedCustomRoute{
@@ -55,7 +55,7 @@ import {
import { formatTimestampToDate } from '@/lib/format'
import { truncateText } from '@/lib/utils'
import { getCodexUsage } from '../api'
import { getCodexUsage, updateChannelBalance } from '../api'
import { CHANNEL_STATUS_CONFIG, MODEL_FETCHABLE_TYPES } from '../constants'
import {
formatRelativeTime,
@@ -68,9 +68,9 @@ import {
parseModelsList,
parseGroupsList,
parseChannelSettings,
channelsQueryKeys,
handleUpdateChannelField,
handleUpdateTagField,
handleUpdateChannelBalance,
createChannelFieldUpdateScheduler,
isTagAggregateRow,
type TagRow,
@@ -81,6 +81,7 @@ import { ChannelRowActionsLayoutContext } from './channel-row-actions-context'
import { useChannels } from './channels-provider'
import { DataTableRowActions } from './data-table-row-actions'
import { DataTableTagRowActions } from './data-table-tag-row-actions'
import { BalanceQueryDialog } from './dialogs/balance-query-dialog'
import {
CodexUsageDialog,
type CodexUsageDialogData,
@@ -325,15 +326,18 @@ const SENSITIVE_MASK = '••••'
/**
* Balance cell component with click to update
*/
function BalanceCell({ channel }: { channel: Channel }) {
export function BalanceCell({ channel }: { channel: Channel }) {
const { t, i18n } = useTranslation()
const queryClient = useQueryClient()
const layout = useContext(ChannelRowActionsLayoutContext)
const { sensitiveVisible } = useChannels()
const { sensitiveVisible, setCurrentRow } = useChannels()
const isTagRow = isTagAggregateRow(channel)
const balance = channel.balance || 0
const usedQuota = channel.used_quota || 0
const [isUpdating, setIsUpdating] = useState(false)
const [rawBalanceResponse, setRawBalanceResponse] = useState<string | null>(
null
)
const [codexUsageOpen, setCodexUsageOpen] = useState(false)
const [codexUsageResponse, setCodexUsageResponse] =
useState<CodexUsageDialogData | null>(null)
@@ -442,8 +446,34 @@ function BalanceCell({ channel }: { channel: Channel }) {
return
}
await handleUpdateChannelBalance(channel.id, queryClient)
setIsUpdating(false)
try {
const response = await updateChannelBalance(channel.id)
if (response.success && response.balance !== undefined) {
toast.success(
t('Balance updated: {{balance}}', {
balance: formatCurrencyFromUSD(response.balance, {
digitsLarge: 2,
digitsSmall: 4,
abbreviate: false,
}),
})
)
void queryClient.invalidateQueries({
queryKey: channelsQueryKeys.lists(),
})
} else if (response.success && response.raw_response !== undefined) {
setCurrentRow(channel)
setRawBalanceResponse(response.raw_response)
} else {
toast.error(response.message || t('Failed to update balance'))
}
} catch (error: unknown) {
toast.error(
error instanceof Error ? error.message : t('Failed to update balance')
)
} finally {
setIsUpdating(false)
}
}
let remainingBadgeLabel = sensitiveVisible ? remainingDisplay : SENSITIVE_MASK
if (sensitiveVisible && isUpdating) {
@@ -536,6 +566,17 @@ function BalanceCell({ channel }: { channel: Channel }) {
}}
isRefreshing={isUpdating}
/>
{rawBalanceResponse !== null && (
<BalanceQueryDialog
initialRawResponse={rawBalanceResponse}
open
onOpenChange={(open) => {
if (!open) {
setRawBalanceResponse(null)
}
}}
/>
)}
</TooltipProvider>
)
}
File diff suppressed because it is too large Load Diff
@@ -22,7 +22,12 @@ import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import {
CodeBlock,
CodeBlockCopyButton,
} from '@/components/ai-elements/code-block'
import { Dialog } from '@/components/dialog'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { IconBadge } from '@/components/ui/icon-badge'
import { formatCurrencyFromUSD } from '@/lib/currency'
@@ -37,14 +42,12 @@ import {
} from './codex-usage-dialog'
type BalanceQueryDialogProps = {
initialRawResponse?: string
open: boolean
onOpenChange: (open: boolean) => void
}
export function BalanceQueryDialog({
open,
onOpenChange,
}: BalanceQueryDialogProps) {
export function BalanceQueryDialog(props: BalanceQueryDialogProps) {
const { t } = useTranslation()
const { currentRow, setCurrentRow } = useChannels()
const queryClient = useQueryClient()
@@ -53,6 +56,9 @@ export function BalanceQueryDialog({
const [balanceUpdatedTime, setBalanceUpdatedTime] = useState<number | null>(
null
)
const [rawResponse, setRawResponse] = useState<string | null>(
props.initialRawResponse ?? null
)
const [codexUsageResponse, setCodexUsageResponse] =
useState<CodexUsageDialogData | null>(null)
@@ -79,10 +85,10 @@ export function BalanceQueryDialog({
useEffect(() => {
if (!isCodex) return
if (!open) return
if (!props.open) return
handleQueryCodexUsage()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, isCodex])
}, [props.open, isCodex])
if (!currentRow) return null
@@ -109,6 +115,9 @@ export function BalanceQueryDialog({
await queryClient.invalidateQueries({
queryKey: channelsQueryKeys.lists(),
})
setRawResponse(null)
} else if (response.success && response.raw_response !== undefined) {
setRawResponse(response.raw_response)
} else {
toast.error(response.message || t('Failed to query balance'))
}
@@ -124,8 +133,9 @@ export function BalanceQueryDialog({
const handleClose = () => {
setBalance(null)
setBalanceUpdatedTime(null)
setRawResponse(null)
setCodexUsageResponse(null)
onOpenChange(false)
props.onOpenChange(false)
}
const formatBalance = (bal: number) =>
@@ -143,7 +153,7 @@ export function BalanceQueryDialog({
if (isCodex) {
return (
<CodexUsageDialog
open={open}
open={props.open}
onOpenChange={(v) => {
if (!v) handleClose()
}}
@@ -158,7 +168,7 @@ export function BalanceQueryDialog({
return (
<Dialog
open={open}
open={props.open}
onOpenChange={handleClose}
title={t('Query Balance')}
description={
@@ -176,24 +186,50 @@ export function BalanceQueryDialog({
}
>
<div className='space-y-4 py-4'>
{/* Current Balance Display */}
<div className='bg-muted/50 rounded-lg border p-4'>
<div className='text-muted-foreground mb-2 flex items-center gap-2 text-sm'>
<IconBadge tone='success' size='xs'>
<DollarSign />
</IconBadge>
<span>{t('Current Balance')}</span>
</div>
<div className='text-2xl font-bold'>
{balance !== null
? formatBalance(balance)
: formatBalance(currentRow.balance)}
</div>
<div className='text-muted-foreground mt-2 text-xs'>
{t('Last updated:')}{' '}
{formatDate(balanceUpdatedTime ?? currentRow.balance_updated_time)}
</div>
</div>
{rawResponse !== null ? (
<>
<Alert>
<AlertTitle>{t('Balance response not recognized')}</AlertTitle>
<AlertDescription>
{t(
'The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.'
)}
</AlertDescription>
</Alert>
<CodeBlock
code={rawResponse}
language='json'
maxExpandedLines={24}
showLineNumbers
title={t('Upstream JSON response')}
>
<CodeBlockCopyButton />
</CodeBlock>
</>
) : (
<>
{/* Current Balance Display */}
<div className='bg-muted/50 rounded-lg border p-4'>
<div className='text-muted-foreground mb-2 flex items-center gap-2 text-sm'>
<IconBadge tone='success' size='xs'>
<DollarSign />
</IconBadge>
<span>{t('Current Balance')}</span>
</div>
<div className='text-2xl font-bold'>
{balance !== null
? formatBalance(balance)
: formatBalance(currentRow.balance)}
</div>
<div className='text-muted-foreground mt-2 text-xs'>
{t('Last updated:')}{' '}
{formatDate(
balanceUpdatedTime ?? currentRow.balance_updated_time
)}
</div>
</div>
</>
)}
{/* Balance Update Button */}
<Button
+173 -112
View File
@@ -27,6 +27,9 @@ import type {
export const CHANNEL_TYPE_ADVANCED_CUSTOM = 58
export const ADVANCED_CUSTOM_MODEL_LIST_PATH = '/v1/models'
export const ADVANCED_CUSTOM_MODEL_LIST_LABEL = 'OpenAI Models'
export const ADVANCED_CUSTOM_BALANCE_PATH =
'/v1/dashboard/billing/credit_grants'
export const ADVANCED_CUSTOM_BALANCE_LABEL = 'Balance Query'
export const ADVANCED_CUSTOM_CONVERTER_OPTIONS: Array<{
value: AdvancedCustomConverter
@@ -109,11 +112,7 @@ export const ADVANCED_CUSTOM_INCOMING_PATH_OPTIONS: AdvancedCustomIncomingPathOp
},
{
value: '/v1/alpha/search',
label: 'OpenAI Alpha Search',
},
{
value: ADVANCED_CUSTOM_MODEL_LIST_PATH,
label: ADVANCED_CUSTOM_MODEL_LIST_LABEL,
label: 'Codex Alpha Search',
},
{
value: '/v1/embeddings',
@@ -145,7 +144,7 @@ export const ADVANCED_CUSTOM_INCOMING_PATH_OPTIONS: AdvancedCustomIncomingPathOp
},
{
value: '/v1/rerank',
label: 'OpenAI Rerank',
label: 'Rerank',
},
{
value: '/v1/realtime',
@@ -172,6 +171,7 @@ export const ADVANCED_CUSTOM_INCOMING_PATH_OPTIONS: AdvancedCustomIncomingPathOp
const ADVANCED_CUSTOM_ROUTE_SUMMARY_LABELS: Record<string, string> = {
'/v1/chat/completions': 'OpenAI Chat',
[ADVANCED_CUSTOM_MODEL_LIST_PATH]: ADVANCED_CUSTOM_MODEL_LIST_LABEL,
[ADVANCED_CUSTOM_BALANCE_PATH]: ADVANCED_CUSTOM_BALANCE_LABEL,
}
export type AdvancedCustomValidationError = {
@@ -217,122 +217,93 @@ const geminiQueryAuth = (): AdvancedCustomRouteAuth => ({
value: '{api_key}',
})
function createOpenAINativeRoutes(): AdvancedCustomRoute[] {
return [
'/v1/chat/completions',
'/v1/completions',
'/v1/responses',
'/v1/responses/compact',
'/v1/embeddings',
'/v1/images/generations',
'/v1/images/edits',
'/v1/audio/speech',
'/v1/audio/transcriptions',
'/v1/audio/translations',
'/v1/realtime',
].map((path) => ({
incoming_path: path,
upstream_path: path,
converter: 'none',
auth: bearerHeaderAuth(),
}))
}
function createClaudeNativeRoutes(): AdvancedCustomRoute[] {
return [
{
incoming_path: '/v1/messages',
upstream_path: '/v1/messages',
converter: 'none',
auth: apiKeyHeaderAuth(),
},
]
}
function createGeminiNativeRoutes(): AdvancedCustomRoute[] {
return [
'/v1beta/models/{model}:generateContent',
'/v1beta/models/{model}:embedContent',
'/v1beta/models/{model}:batchEmbedContents',
].map((path) => ({
incoming_path: path,
upstream_path: path,
converter: 'none',
auth: geminiQueryAuth(),
}))
}
function createGatewayNativeRoutes(): AdvancedCustomRoute[] {
return ['/v1/alpha/search', '/v1/rerank'].map((path) => ({
incoming_path: path,
upstream_path: path,
converter: 'none',
auth: bearerHeaderAuth(),
}))
}
export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
[
{
value: 'official_openai_chat',
label: 'Official OpenAI Chat',
value: 'all_protocols',
label: 'All routes',
config: {
advanced_routes: [
{
incoming_path: '/v1/chat/completions',
upstream_path: '/v1/chat/completions',
converter: 'none',
auth: bearerHeaderAuth(),
},
...createOpenAINativeRoutes(),
...createClaudeNativeRoutes(),
...createGeminiNativeRoutes(),
...createGatewayNativeRoutes(),
],
},
},
{
value: 'official_openai_responses',
label: 'Official OpenAI Responses',
value: 'openai_only',
label: 'OpenAI only',
config: {
advanced_routes: [
{
incoming_path: '/v1/responses',
upstream_path: '/v1/responses',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
advanced_routes: createOpenAINativeRoutes(),
},
},
{
value: 'official_openai_embeddings',
label: 'Official OpenAI Embeddings',
value: 'claude_only',
label: 'Claude only',
config: {
advanced_routes: [
{
incoming_path: '/v1/embeddings',
upstream_path: '/v1/embeddings',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
advanced_routes: createClaudeNativeRoutes(),
},
},
{
value: 'official_openai_images',
label: 'Official OpenAI Images',
value: 'gemini_only',
label: 'Gemini only',
config: {
advanced_routes: [
{
incoming_path: '/v1/images/generations',
upstream_path: '/v1/images/generations',
converter: 'none',
auth: bearerHeaderAuth(),
},
{
incoming_path: '/v1/images/edits',
upstream_path: '/v1/images/edits',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
},
},
{
value: 'official_claude_messages',
label: 'Official Claude Messages',
config: {
advanced_routes: [
{
incoming_path: '/v1/messages',
upstream_path: '/v1/messages',
converter: 'none',
auth: apiKeyHeaderAuth(),
},
],
},
},
{
value: 'official_gemini_native',
label: 'Official Gemini Native',
config: {
advanced_routes: [
{
incoming_path: '/v1beta/models/{model}:generateContent',
upstream_path: '/v1beta/models/{model}:generateContent',
converter: 'none',
auth: geminiQueryAuth(),
},
{
incoming_path: '/v1beta/models/{model}:embedContent',
upstream_path: '/v1beta/models/{model}:embedContent',
converter: 'none',
auth: geminiQueryAuth(),
},
{
incoming_path: '/v1beta/models/{model}:batchEmbedContents',
upstream_path: '/v1beta/models/{model}:batchEmbedContents',
converter: 'none',
auth: geminiQueryAuth(),
},
],
},
},
{
value: 'official_gemini_from_openai_chat',
label: 'Official Gemini from OpenAI Chat',
config: {
advanced_routes: [
{
incoming_path: '/v1/chat/completions',
upstream_path: '/v1beta/models/{model}:generateContent',
converter: 'openai_chat_completions_to_gemini_generate_content',
auth: geminiQueryAuth(),
},
],
advanced_routes: createGeminiNativeRoutes(),
},
},
]
@@ -340,7 +311,7 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
export function cloneAdvancedCustomConfig(
config: AdvancedCustomConfig
): AdvancedCustomConfig {
return JSON.parse(JSON.stringify(config)) as AdvancedCustomConfig
return structuredClone(config)
}
export function getAdvancedCustomTemplateConfig(
@@ -353,6 +324,78 @@ export function getAdvancedCustomTemplateConfig(
return cloneAdvancedCustomConfig(template.config)
}
export function isAdvancedCustomManagementPath(path: string): boolean {
return (
path === ADVANCED_CUSTOM_MODEL_LIST_PATH ||
path === ADVANCED_CUSTOM_BALANCE_PATH
)
}
export function getAdvancedCustomManagementRoute(
config: AdvancedCustomConfig,
path: string
): AdvancedCustomRoute | undefined {
return normalizeAdvancedCustomConfig(config).advanced_routes?.find(
(route) => route.incoming_path?.trim() === path
)
}
export function replaceAdvancedCustomManagementRoute(
config: AdvancedCustomConfig,
path: string,
route: AdvancedCustomRoute | null
): AdvancedCustomConfig {
const normalized = normalizeAdvancedCustomConfig(config)
const routes = [...(normalized.advanced_routes || [])]
const index = routes.findIndex(
(candidate) => candidate.incoming_path?.trim() === path
)
if (route === null) {
if (index >= 0) routes.splice(index, 1)
} else {
const managementRoute: AdvancedCustomRoute = {
incoming_path: path,
upstream_path: route.upstream_path || '',
converter: 'none',
models: [],
auth: route.auth,
}
if (index >= 0) routes[index] = managementRoute
else routes.push(managementRoute)
}
return { advanced_routes: routes }
}
export function replaceAdvancedCustomForwardingRoutes(
config: AdvancedCustomConfig,
forwardingRoutes: AdvancedCustomRoute[]
): AdvancedCustomConfig {
const normalized = normalizeAdvancedCustomConfig(config)
const routes = normalized.advanced_routes || []
const firstForwardingIndex = routes.findIndex(
(route) =>
!isAdvancedCustomManagementPath(route.incoming_path?.trim() || '')
)
const managementRoutes = routes.filter((route) =>
isAdvancedCustomManagementPath(route.incoming_path?.trim() || '')
)
if (firstForwardingIndex < 0) {
return { advanced_routes: [...managementRoutes, ...forwardingRoutes] }
}
const before = routes
.slice(0, firstForwardingIndex)
.filter((route) =>
isAdvancedCustomManagementPath(route.incoming_path?.trim() || '')
)
const after = routes
.slice(firstForwardingIndex)
.filter((route) =>
isAdvancedCustomManagementPath(route.incoming_path?.trim() || '')
)
return { advanced_routes: [...before, ...forwardingRoutes, ...after] }
}
export function createAdvancedCustomRoute(): AdvancedCustomRoute {
return {
incoming_path: openAIChatPath,
@@ -367,6 +410,17 @@ export function createAdvancedCustomConfig(): AdvancedCustomConfig {
}
}
export function createAdvancedCustomManagementRoute(
path: string
): AdvancedCustomRoute {
return {
incoming_path: path,
upstream_path: path,
converter: 'none',
models: [],
}
}
export function getAdvancedCustomUpstreamPathPlaceholder(
converter: AdvancedCustomConverter,
incomingPath = getDefaultAdvancedCustomIncomingPath(converter)
@@ -550,6 +604,7 @@ export function validateAdvancedCustomConfig(
{ catchAllIndex: number | null; models: Map<string, number> }
>()
let modelListRouteIndex: number | null = null
let balanceRouteIndex: number | null = null
for (let index = 0; index < routes.length; index += 1) {
const route = routes[index]
const incomingPath = route.incoming_path?.trim() || ''
@@ -569,30 +624,36 @@ export function validateAdvancedCustomConfig(
message: 'Incoming path must not include query',
}
}
if (incomingPath === ADVANCED_CUSTOM_MODEL_LIST_PATH) {
if (modelListRouteIndex !== null) {
if (isAdvancedCustomManagementPath(incomingPath)) {
const isModelListRoute = incomingPath === ADVANCED_CUSTOM_MODEL_LIST_PATH
const existingIndex = isModelListRoute
? modelListRouteIndex
: balanceRouteIndex
const routeLabel = isModelListRoute ? 'OpenAI Models' : 'Balance Query'
if (existingIndex !== null) {
return {
routeIndex: index,
message: 'Only one OpenAI Models route is allowed',
message: `Only one ${routeLabel} route is allowed`,
}
}
modelListRouteIndex = index
if (isModelListRoute) modelListRouteIndex = index
else balanceRouteIndex = index
if (routeModels.length > 0) {
return {
routeIndex: index,
message: 'OpenAI Models route does not support client model rules',
message: `${routeLabel} route does not support client model rules`,
}
}
if (converter !== 'none') {
return {
routeIndex: index,
message: 'OpenAI Models route must use native forwarding',
message: `${routeLabel} route must use native forwarding`,
}
}
if (upstreamPath.includes('{model}')) {
return {
routeIndex: index,
message: 'OpenAI Models upstream path must not contain {model}',
message: `${routeLabel} upstream path must not contain {model}`,
}
}
}
@@ -20,8 +20,6 @@ import type { QueryClient } from '@tanstack/react-query'
import i18next from 'i18next'
import { toast } from 'sonner'
import { formatCurrencyFromUSD } from '@/lib/currency'
import {
copyChannel,
deleteChannel,
@@ -38,7 +36,6 @@ import {
editTagChannels,
testAllChannels,
updateAllChannelsBalance,
updateChannelBalance,
} from '../api'
import { CHANNEL_STATUS, ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants'
import type { ChannelTestResponse, CopyChannelParams } from '../types'
@@ -362,41 +359,6 @@ export async function handleCopyChannel(
}
}
/**
* Update channel balance
*/
export async function handleUpdateChannelBalance(
id: number,
queryClient?: QueryClient,
onSuccess?: (balance: number) => void
): Promise<void> {
try {
const response = await updateChannelBalance(id)
if (response.success && response.balance !== undefined) {
const balance = response.balance
toast.success(
i18next.t('Balance updated: {{balance}}', {
balance: formatCurrencyFromUSD(balance, {
digitsLarge: 2,
digitsSmall: 4,
abbreviate: false,
}),
})
)
queryClient?.invalidateQueries({ queryKey: channelsQueryKeys.lists() })
onSuccess?.(balance)
} else {
toast.error(response.message || i18next.t('Failed to update balance'))
}
} catch (_error: unknown) {
toast.error(
_error instanceof Error
? _error.message
: i18next.t('Failed to update balance')
)
}
}
// ============================================================================
// Batch Actions
// ============================================================================
+1
View File
@@ -197,6 +197,7 @@ export interface ChannelBalanceResponse {
message?: string
balance?: number
currency?: string
raw_response?: string
}
export interface FetchModelsResponse {
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "{{modality}} supported",
"{{n}} model(s) selected": "{{n}} model(s) selected",
"{{processed}} of {{total}} log entries processed.": "{{processed}} of {{total}} log entries processed.",
"{{protocol}} auth name": "{{protocol}} auth name",
"{{protocol}} auth value": "{{protocol}} auth value",
"{{protocol}} authentication": "{{protocol}} authentication",
"{{success}} succeeded, {{failed}} failed": "{{success}} succeeded, {{failed}} failed",
"{{target}} test failed": "{{target}} test failed",
"{{target}} test succeeded": "{{target}} test succeeded",
@@ -188,6 +191,7 @@
"Add Group": "Add Group",
"Add group rate limit": "Add group rate limit",
"Add group rules": "Add group rules",
"Add management route": "Add management route",
"Add Mapping": "Add Mapping",
"Add method": "Add method",
"Add missing models": "Add missing models",
@@ -207,6 +211,7 @@
"Add Quota": "Add Quota",
"Add ratio override": "Add ratio override",
"Add route": "Add route",
"Add routes individually or replace them from a template.": "Add routes individually or replace them from a template.",
"Add Row": "Add Row",
"Add Rule": "Add Rule",
"Add rule group": "Add rule group",
@@ -215,6 +220,7 @@
"Add split": "Add split",
"Add subscription": "Add subscription",
"Add tags...": "Add tags...",
"Add template": "Add template",
"Add tier": "Add tier",
"Add time condition": "Add time condition",
"Add time rule group": "Add time rule group",
@@ -298,6 +304,7 @@
"All nodes": "All nodes",
"All playground messages saved in this browser will be removed. This cannot be undone.": "All playground messages saved in this browser will be removed. This cannot be undone.",
"All requests must include": "All requests must include",
"All routes": "All routes",
"All Status": "All Status",
"All Sync Status": "All Sync Status",
"All systems operational": "All systems operational",
@@ -427,6 +434,7 @@
"Apply Filters": "Apply Filters",
"Apply IP Filter to Resolved Domains": "Apply IP Filter to Resolved Domains",
"Apply Overwrite": "Apply Overwrite",
"Apply plan": "Apply plan",
"Apply reset": "Apply reset",
"Apply Sync": "Apply Sync",
"Applying...": "Applying...",
@@ -571,6 +579,8 @@
"Balance depleted": "Balance depleted",
"Balance is shown in quota units": "Balance is shown in quota units",
"Balance queried successfully": "Balance queried successfully",
"Balance Query": "Balance Query",
"Balance response not recognized": "Balance response not recognized",
"Balance updated successfully": "Balance updated successfully",
"Balance updated: {{balance}}": "Balance updated: {{balance}}",
"Bar Chart": "Bar Chart",
@@ -808,6 +818,7 @@
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"Chinese": "Chinese",
"Choose a complete upstream protocol plan or edit individual route groups.": "Choose a complete upstream protocol plan or edit individual route groups.",
"Choose a username": "Choose a username",
"Choose an amount and payment method": "Choose an amount and payment method",
"Choose and order the groups this API key will try.": "Choose and order the groups this API key will try.",
@@ -830,6 +841,7 @@
"Clamped to": "Clamped to",
"Claude": "Claude",
"Claude CLI Header Passthrough": "Claude CLI Header Passthrough",
"Claude only": "Claude only",
"Clean": "Clean",
"Clean history logs": "Clean history logs",
"Clean logs": "Clean logs",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "Codex credential must be a JSON object with access_token and account_id",
"Cohere": "Cohere",
"Collapse": "Collapse",
"Collapse all": "Collapse all",
"Collapse All": "Collapse All",
"Collect relay latency and success-rate metrics for the model square.": "Collect relay latency and success-rate metrics for the model square.",
"Color": "Color",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "Configure Waffo payment aggregation platform integration",
"Configure your account behavior preferences": "Configure your account behavior preferences",
"Configure your account preferences and integrations": "Configure your account preferences and integrations",
"Configured": "Configured",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.",
"Configured routes and latency checks": "Configured routes and latency checks",
"Confirm": "Confirm",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "Format: APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "Forward requests directly to upstream providers without any post-processing.",
"Forwarding Routes": "Forwarding Routes",
"Frames per second": "Frames per second",
"Free": "Free",
"Free: {{free}} / Total: {{total}}": "Free: {{free}} / Total: {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "Full Base URL (supports",
"Full Code": "Full Code",
"Full input length": "Full input length",
"Full JSON": "Full JSON",
"Full layout": "Full layout",
"Full width": "Full width",
"Function calling": "Function calling",
@@ -2101,6 +2117,7 @@
"Gemini": "Gemini",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content to OpenAI Chat",
"Gemini Image 4K": "Gemini Image 4K",
"Gemini only": "Gemini only",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.",
"General": "General",
"General Settings": "General Settings",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "Model fixed pricing",
"Model Group": "Model Group",
"Model Limits": "Model Limits",
"Model List": "Model List",
"Model Mapping": "Model Mapping",
"Model Mapping (JSON)": "Model Mapping (JSON)",
"Model Mapping must be a JSON object like": "Model Mapping must be a JSON object like",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "Name the channel, choose the provider, configure API access, and set credentials.",
"Name, provider type, and availability.": "Name, provider type, and availability.",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Native Claude Messages plus OpenAI Chat compatibility forwarding.",
"Native format": "Native format",
"Native forwarding": "Native forwarding",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "Native OpenAI routes plus optional Claude and Gemini compatibility routes.",
"Need a redemption code?": "Need a redemption code?",
"Needs API key": "Needs API key",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.",
@@ -3051,6 +3072,7 @@
"Not available": "Not available",
"Not backed up": "Not backed up",
"Not bound": "Not bound",
"Not configured": "Not configured",
"Not Equals": "Not Equals",
"Not in pricing table": "Not in pricing table",
"Not included": "Not included",
@@ -3149,6 +3171,7 @@
"Open in new tab": "Open in new tab",
"Open in New Tab": "Open in New Tab",
"Open menu": "Open menu",
"Open Query Balance to view the upstream JSON response": "Open Query Balance to view the upstream JSON response",
"Open release": "Open release",
"Open source": "Open source",
"Open Source": "Open Source",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat to Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat to OpenAI Responses",
"OpenAI Compatible": "OpenAI Compatible",
"OpenAI Compatible Upstream": "OpenAI Compatible Upstream",
"OpenAI Models route does not support client model rules": "OpenAI Models route does not support client model rules",
"OpenAI Models route is required to enable upstream model checks": "OpenAI Models route is required to enable upstream model checks",
"OpenAI Models route must use native forwarding": "OpenAI Models route must use native forwarding",
"OpenAI Models upstream path must not contain {model}": "OpenAI Models upstream path must not contain {model}",
"OpenAI only": "OpenAI only",
"OpenAI Organization": "OpenAI Organization",
"OpenAI Organization ID (optional)": "OpenAI Organization ID (optional)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses to Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "Replace",
"Replace all existing keys": "Replace all existing keys",
"Replace channel models": "Replace channel models",
"Replace forwarding routes?": "Replace forwarding routes?",
"Replace mode: Will completely replace all existing keys": "Replace mode: Will completely replace all existing keys",
"Replace With": "Replace With",
"replaced": "replaced",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "Route models must be unique for the same incoming path",
"Route, auth, and balance check in one place": "Route, auth, and balance check in one place",
"Routes": "Routes",
"Routes in this plan": "Routes in this plan",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.",
@@ -4073,6 +4100,7 @@
"Seed": "Seed",
"Select": "Select",
"Select a color": "Select a color",
"Select a complete plan, then keep only the routes you need.": "Select a complete plan, then keep only the routes you need.",
"Select a group": "Select a group",
"Select a group type": "Select a group type",
"Select a model to edit pricing": "Select a model to edit pricing",
@@ -4093,6 +4121,7 @@
"Select announcement type": "Select announcement type",
"Select at least one Auto group or restore global Auto.": "Select at least one Auto group or restore global Auto.",
"Select at least one field to overwrite.": "Select at least one field to overwrite.",
"Select at least one route": "Select at least one route",
"Select at least one target model": "Select at least one target model",
"Select at most {{max}} Auto groups": "Select at most {{max}} Auto groups",
"Select body font": "Select body font",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "The unique identifier for this model",
"The unique name for this vendor": "The unique name for this vendor",
"The upstream channel that served the requests": "The upstream channel that served the requests",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "The upstream natively supports all three protocols; every selected route is forwarded without conversion.",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.",
"The URL for this chat client.": "The URL for this chat client.",
"The user group applied to the requests": "The user group applied to the requests",
"The user who made the requests": "The user who made the requests",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "This project must be used in compliance with the",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "This removes {{count}} failed models from this channel. This action cannot be undone.",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "This route discovers upstream OpenAI models and cannot be split or matched by client model rules.",
"This route is used only by channel management to discover upstream models.": "This route is used only by channel management to discover upstream models.",
"This route is used only by channel management to query the upstream balance.": "This route is used only by channel management to query the upstream balance.",
"This session will lose access immediately and must sign in again.": "This session will lose access immediately and must sign in again.",
"This site currently has {{count}} models enabled": "This site currently has {{count}} models enabled",
"This Telegram account is already bound.": "This Telegram account is already bound.",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "This will permanently remove all log entries created before {{date}}.",
"This will permanently remove log entries before the selected timestamp.": "This will permanently remove log entries before the selected timestamp.",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?",
"This year": "This year",
@@ -4912,6 +4947,7 @@
"Upscale": "Upscale",
"Upstream": "Upstream",
"Upstream did not return reset credit details.": "Upstream did not return reset credit details.",
"Upstream JSON response": "Upstream JSON response",
"Upstream Model Detection Settings": "Upstream Model Detection Settings",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.",
"Upstream Model Update Check": "Upstream Model Update Check",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "Upstream path must be a full URL or a path starting with /",
"Upstream price sync": "Upstream price sync",
"Upstream prices fetched successfully": "Upstream prices fetched successfully",
"Upstream protocol plan": "Upstream protocol plan",
"Upstream ratios fetched successfully": "Upstream ratios fetched successfully",
"Upstream Request ID": "Upstream Request ID",
"Upstream Response": "Upstream Response",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.",
"Use authenticator code": "Use authenticator code",
"Use backup code": "Use backup code",
"Use Bearer for all protocols": "Use Bearer for all protocols",
"Use disk cache when request body exceeds this size": "Use disk cache when request body exceeds this size",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "Use exact client model names, separated by commas. Prefixes and wildcards are not supported.",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.",
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "{{modality}} pris en charge",
"{{n}} model(s) selected": "{{n}} modèle(s) sélectionné(s)",
"{{processed}} of {{total}} log entries processed.": "{{processed}} sur {{total}} entrées de journal traitées.",
"{{protocol}} auth name": "Nom dauthentification {{protocol}}",
"{{protocol}} auth value": "Valeur dauthentification {{protocol}}",
"{{protocol}} authentication": "Authentification {{protocol}}",
"{{success}} succeeded, {{failed}} failed": "{{success}} réussi(s), {{failed}} échoué(s)",
"{{target}} test failed": "Échec du test de {{target}}",
"{{target}} test succeeded": "Test de {{target}} réussi",
@@ -188,6 +191,7 @@
"Add Group": "Ajouter un groupe",
"Add group rate limit": "Ajouter une limite de taux de groupe",
"Add group rules": "Ajouter des règles de groupe",
"Add management route": "Ajouter une route de gestion",
"Add Mapping": "Ajouter un mappage",
"Add method": "Ajouter une méthode",
"Add missing models": "Ajouter les modèles manquants",
@@ -207,6 +211,7 @@
"Add Quota": "Ajouter un quota",
"Add ratio override": "Ajouter un remplacement de ratio",
"Add route": "Ajouter une route",
"Add routes individually or replace them from a template.": "Ajoutez des routes individuellement ou remplacez-les à partir dun modèle.",
"Add Row": "Ajouter une ligne",
"Add Rule": "Ajouter une règle",
"Add rule group": "Ajouter un groupe de règles",
@@ -215,6 +220,7 @@
"Add split": "Ajouter une branche",
"Add subscription": "Ajouter un abonnement",
"Add tags...": "Ajouter des étiquettes...",
"Add template": "Ajouter un modèle",
"Add tier": "Ajouter un palier",
"Add time condition": "Ajouter une condition temporelle",
"Add time rule group": "Ajouter un groupe de règles temporelles",
@@ -298,6 +304,7 @@
"All nodes": "Tous les nœuds",
"All playground messages saved in this browser will be removed. This cannot be undone.": "Tous les messages du Playground enregistrés dans ce navigateur seront supprimés. Cette action est irréversible.",
"All requests must include": "Toutes les requêtes doivent inclure",
"All routes": "Toutes les routes",
"All Status": "Tous les statuts",
"All Sync Status": "Tous les statuts de synchronisation",
"All systems operational": "Tous les systèmes opérationnels",
@@ -427,6 +434,7 @@
"Apply Filters": "Appliquer les filtres",
"Apply IP Filter to Resolved Domains": "Appliquer le filtre IP aux domaines résolus",
"Apply Overwrite": "Appliquer l'écrasement",
"Apply plan": "Appliquer le plan",
"Apply reset": "Appliquer la réinitialisation",
"Apply Sync": "Appliquer la synchronisation",
"Applying...": "Application en cours...",
@@ -571,6 +579,8 @@
"Balance depleted": "Solde épuisé",
"Balance is shown in quota units": "Le solde est affiché en unités de quota",
"Balance queried successfully": "Solde interrogé avec succès",
"Balance Query": "Consultation du solde",
"Balance response not recognized": "Réponse de solde non reconnue",
"Balance updated successfully": "Solde mis à jour avec succès",
"Balance updated: {{balance}}": "Solde mis à jour : {{balance}}",
"Bar Chart": "Graphique en barres",
@@ -808,6 +818,7 @@
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"Chinese": "Chinois",
"Choose a complete upstream protocol plan or edit individual route groups.": "Choisissez un plan de protocole amont complet ou modifiez les groupes de routes.",
"Choose a username": "Choisir un nom d'utilisateur",
"Choose an amount and payment method": "Choisir un montant et un mode de paiement",
"Choose and order the groups this API key will try.": "Sélectionnez et ordonnez les groupes que cette clé API essaiera.",
@@ -830,6 +841,7 @@
"Clamped to": "Limité à",
"Claude": "Claude",
"Claude CLI Header Passthrough": "Passthrough en-tête Claude CLI",
"Claude only": "Claude uniquement",
"Clean": "Sans conflit",
"Clean history logs": "Nettoyer les journaux d'historique",
"Clean logs": "Nettoyer les logs",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "L'identifiant Codex doit être un objet JSON avec access_token et account_id",
"Cohere": "Cohere",
"Collapse": "Réduire",
"Collapse all": "Tout replier",
"Collapse All": "Tout réduire",
"Collect relay latency and success-rate metrics for the model square.": "Collecte les métriques de latence Relay et de taux de réussite pour la place des modèles.",
"Color": "Couleur",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "Configurer l'intégration de la plateforme d'agrégation de paiement Waffo",
"Configure your account behavior preferences": "Configurer les préférences de comportement de votre compte",
"Configure your account preferences and integrations": "Configurer les préférences et les intégrations de votre compte",
"Configured": "Configuré",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "Enregistré comme JSON PayMethods. La valeur type décide du flux de paiement utilisé : stripe pour Stripe, waffo_pancake pour Waffo Pancake, et les autres valeurs sont envoyées à Epay comme paramètre type.",
"Configured routes and latency checks": "Routes configurées et contrôles de latence",
"Confirm": "Confirmer",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "Format : APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "Format : TokenHub API Key, ou l'ancien format AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "Transférer les requêtes directement aux fournisseurs amont sans aucun post-traitement.",
"Forwarding Routes": "Routes de transfert",
"Frames per second": "Images par seconde",
"Free": "Libre",
"Free: {{free}} / Total: {{total}}": "Disponible : {{free}} / Total : {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "URL de base complète (prend en charge",
"Full Code": "Code complet",
"Full input length": "Longueur complète de lentrée",
"Full JSON": "JSON complet",
"Full layout": "Disposition complète",
"Full width": "Pleine largeur",
"Function calling": "Appel de fonction",
@@ -2101,6 +2117,7 @@
"Gemini": "Gemini",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content vers OpenAI Chat",
"Gemini Image 4K": "Gemini Image 4K",
"Gemini only": "Gemini uniquement",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini continuera à détecter automatiquement le mode de pensée même avec l'adaptateur désactivé. Activez ceci uniquement lorsque vous avez besoin d'un contrôle plus fin sur la tarification et le budget.",
"General": "Général",
"General Settings": "Paramètres généraux",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "Tarification fixe du modèle",
"Model Group": "Groupe de modèles",
"Model Limits": "Limites du modèle",
"Model List": "Liste des modèles",
"Model Mapping": "Mappage de modèle",
"Model Mapping (JSON)": "Mappage de modèle (JSON)",
"Model Mapping must be a JSON object like": "Le mappage de modèle doit être un objet JSON tel que",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "Nommez le canal, choisissez le fournisseur, configurez laccès API et définissez les identifiants.",
"Name, provider type, and availability.": "Nom, type de fournisseur et disponibilité.",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Claude Messages natif avec transfert compatible OpenAI Chat.",
"Native format": "Format natif",
"Native forwarding": "Transfert natif",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Routes Gemini natives avec transfert compatible OpenAI Chat et Responses.",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "Routes OpenAI natives avec compatibilité Claude et Gemini en option.",
"Need a redemption code?": "Besoin d'un code d'échange ?",
"Needs API key": "Clé API requise",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "JSON imbriqué définissant des règles par groupe pour ajouter (+:), supprimer (-:), ou ajouter des groupes utilisables.",
@@ -3051,6 +3072,7 @@
"Not available": "Non disponible",
"Not backed up": "Non sauvegardé",
"Not bound": "Non lié",
"Not configured": "Non configuré",
"Not Equals": "Différent de",
"Not in pricing table": "Absent du tableau tarifaire",
"Not included": "Non inclus",
@@ -3149,6 +3171,7 @@
"Open in new tab": "Ouvrir dans un nouvel onglet",
"Open in New Tab": "Ouvrir dans un nouvel onglet",
"Open menu": "Ouvrir le menu",
"Open Query Balance to view the upstream JSON response": "Ouvrez « Consulter le solde » pour voir la réponse JSON amont",
"Open release": "Ouvrir la version",
"Open source": "Open source",
"Open Source": "Open source",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat vers Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat vers OpenAI Responses",
"OpenAI Compatible": "Compatible OpenAI",
"OpenAI Compatible Upstream": "Amont compatible OpenAI",
"OpenAI Models route does not support client model rules": "La route Modèles OpenAI ne prend pas en charge les règles de modèles clients",
"OpenAI Models route is required to enable upstream model checks": "La route Modèles OpenAI est requise pour activer la vérification des modèles en amont",
"OpenAI Models route must use native forwarding": "La route Modèles OpenAI doit utiliser le transfert natif",
"OpenAI Models upstream path must not contain {model}": "Le chemin amont de la route Modèles OpenAI ne doit pas contenir {model}",
"OpenAI only": "OpenAI uniquement",
"OpenAI Organization": "Organisation OpenAI",
"OpenAI Organization ID (optional)": "Identifiant d'organisation OpenAI (optionnel)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses vers Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "Remplacer",
"Replace all existing keys": "Remplacer toutes les clés existantes",
"Replace channel models": "Remplacer les modèles du canal",
"Replace forwarding routes?": "Remplacer les routes de transfert ?",
"Replace mode: Will completely replace all existing keys": "Mode remplacement : Remplacera complètement toutes les clés existantes",
"Replace With": "Remplacer par",
"replaced": "remplacé",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "Les modèles de route doivent être uniques pour le même chemin d'entrée",
"Route, auth, and balance check in one place": "Routage, authentification et solde au même endroit",
"Routes": "Routes",
"Routes in this plan": "Routes de ce plan",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "Les routes avec le même chemin dentrée sont associées par modèle. Laissez la portée de modèles vide uniquement pour la route de repli finale.",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "Les routes avec le même chemin entrant sont réparties selon les règles du model client. Les requêtes non appariées utilisent la dernière route de secours.",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "Les routes avec le même chemin dentrée sont réparties par modèle client exact. Les requêtes non associées utilisent le repli final.",
@@ -4073,6 +4100,7 @@
"Seed": "Graine",
"Select": "Sélectionner",
"Select a color": "Sélectionner une couleur",
"Select a complete plan, then keep only the routes you need.": "Sélectionnez un plan complet, puis gardez les routes nécessaires.",
"Select a group": "Sélectionner un groupe",
"Select a group type": "Sélectionner un type de groupe",
"Select a model to edit pricing": "Sélectionnez un modèle pour modifier sa tarification",
@@ -4093,6 +4121,7 @@
"Select announcement type": "Sélectionner le type d'annonce",
"Select at least one Auto group or restore global Auto.": "Sélectionnez au moins un groupe Auto ou restaurez lAuto global.",
"Select at least one field to overwrite.": "Sélectionnez au moins un champ à écraser.",
"Select at least one route": "Sélectionnez au moins une route",
"Select at least one target model": "Sélectionnez au moins un modèle cible",
"Select at most {{max}} Auto groups": "Sélectionnez au maximum {{max}} groupes Auto",
"Select body font": "Sélectionner la police du corps de texte",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "L'identifiant unique de ce modèle",
"The unique name for this vendor": "Le nom unique de ce fournisseur",
"The upstream channel that served the requests": "Le canal en amont ayant servi les requêtes",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "Le service amont prend nativement en charge les trois protocoles ; chaque route sélectionnée est transférée sans conversion.",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "La réponse amont est un JSON valide, mais ne correspond pas au format OpenAI credit_summary. Le solde du canal n'a pas été mis à jour.",
"The URL for this chat client.": "L'URL de ce client de discussion.",
"The user group applied to the requests": "Le groupe d'utilisateurs appliqué aux requêtes",
"The user who made the requests": "L'utilisateur à l'origine des requêtes",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "Ce projet doit être utilisé conformément aux",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "Cela supprime {{count}} modèles en échec de ce canal. Cette action est irréversible.",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "Cette route découvre les modèles OpenAI en amont et ne peut être ni divisée ni associée par des règles de modèles clients.",
"This route is used only by channel management to discover upstream models.": "Cette route sert uniquement à la gestion du canal pour découvrir les modèles amont.",
"This route is used only by channel management to query the upstream balance.": "Cette route sert uniquement à la gestion du canal pour consulter le solde amont.",
"This session will lose access immediately and must sign in again.": "Cette session perdra immédiatement laccès ; vous devrez vous reconnecter.",
"This site currently has {{count}} models enabled": "Ce site compte actuellement {{count}} modèles activés",
"This Telegram account is already bound.": "Ce compte Telegram est déjà lié.",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "Cela supprimera définitivement toutes les entrées de journal créées avant le {{date}}.",
"This will permanently remove log entries before the selected timestamp.": "Cela supprimera définitivement les entrées de journal antérieures à l'horodatage sélectionné.",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "Cette action reconstruit lindex de routage des canaux à partir de toutes les configurations, y compris les modèles pris en charge, les groupes, les priorités et les poids. Le routage peut être brièvement incomplet pendant la reconstruction. Continuer ?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "Cette action supprimera {{removed}} routes de transfert et créera les {{created}} routes sélectionnées. Les routes de modèles et de solde seront conservées.",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "Cette action supprimera {{removed}} routes de transfert et les remplacera par le modèle {{template}} ({{created}} routes). Les routes de liste des modèles et de solde seront conservées.",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "La priorité de tous les {{count}} canaux avec le tag \"{{tag}}\" sera mise à jour à {{value}}. Continuer ?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "Le poids de tous les {{count}} canaux avec le tag \"{{tag}}\" sera mis à jour à {{value}}. Continuer ?",
"This year": "Cette année",
@@ -4912,6 +4947,7 @@
"Upscale": "Agrandir",
"Upstream": "Amont",
"Upstream did not return reset credit details.": "L'amont n'a renvoyé aucun détail de crédit de réinitialisation.",
"Upstream JSON response": "Réponse JSON amont",
"Upstream Model Detection Settings": "Paramètres de détection des modèles en amont",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "Tâche de détection des modèles en amont démarrée. Suivez la progression dans Infos système, puis actualisez pour examiner les mises à jour en attente.",
"Upstream Model Update Check": "Vérification des mises à jour des modèles en amont",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "Le chemin amont doit être une URL complète ou un chemin commençant par /",
"Upstream price sync": "Synchronisation amont des prix",
"Upstream prices fetched successfully": "Prix amont récupérés avec succès",
"Upstream protocol plan": "Plan de protocole amont",
"Upstream ratios fetched successfully": "Ratios en amont récupérés avec succès",
"Upstream Request ID": "ID de requête en amont",
"Upstream Response": "Réponse amont",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Utilisez un chemin pour lajouter à la Base URL du canal, ou saisissez une URL complète pour remplacer la Base URL pour cette route.",
"Use authenticator code": "Utiliser le code de l'authentificateur",
"Use backup code": "Utiliser un code de secours",
"Use Bearer for all protocols": "Utiliser Bearer pour tous les protocoles",
"Use disk cache when request body exceeds this size": "Utiliser le cache disque quand le corps de requête dépasse cette taille",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "Utilisez les noms exacts des modèles client, séparés par des virgules. Les préfixes et jokers ne sont pas pris en charge.",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "Utilisez des noms de modèle exacts comme gpt-4o, ou des règles regex préfixées par re: comme re:^gemini-.",
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "{{modality}} をサポート",
"{{n}} model(s) selected": "{{n}} 件のモデルを選択済み",
"{{processed}} of {{total}} log entries processed.": "{{total}} 件中 {{processed}} 件のログを処理しました。",
"{{protocol}} auth name": "{{protocol}} 認証名",
"{{protocol}} auth value": "{{protocol}} 認証値",
"{{protocol}} authentication": "{{protocol}} 認証",
"{{success}} succeeded, {{failed}} failed": "{{success}} 件成功、{{failed}} 件失敗",
"{{target}} test failed": "{{target}} のテストに失敗しました",
"{{target}} test succeeded": "{{target}} のテストに成功しました",
@@ -188,6 +191,7 @@
"Add Group": "グループを追加",
"Add group rate limit": "グループレート制限を追加",
"Add group rules": "グループルールを追加",
"Add management route": "管理ルートを追加",
"Add Mapping": "マッピングを追加",
"Add method": "メソッドを追加",
"Add missing models": "不足しているモデルを追加",
@@ -207,6 +211,7 @@
"Add Quota": "クォータを追加",
"Add ratio override": "倍率オーバーライドを追加",
"Add route": "ルートを追加",
"Add routes individually or replace them from a template.": "ルートを個別に追加するか、テンプレートで置き換えます。",
"Add Row": "行を追加",
"Add Rule": "ルールを追加",
"Add rule group": "ルールグループを追加",
@@ -215,6 +220,7 @@
"Add split": "分岐を追加",
"Add subscription": "サブスクリプションを追加",
"Add tags...": "タグを追加...",
"Add template": "テンプレートを追加",
"Add tier": "ティアを追加",
"Add time condition": "時間条件を追加",
"Add time rule group": "時間ルールグループを追加",
@@ -298,6 +304,7 @@
"All nodes": "すべてのノード",
"All playground messages saved in this browser will be removed. This cannot be undone.": "このブラウザに保存されたすべての Playground メッセージが削除されます。この操作は元に戻せません。",
"All requests must include": "すべてのリクエストには",
"All routes": "すべてのルート",
"All Status": "すべてのステータス",
"All Sync Status": "すべての同期状態",
"All systems operational": "すべて正常稼働中",
@@ -427,6 +434,7 @@
"Apply Filters": "フィルターを適用",
"Apply IP Filter to Resolved Domains": "解決されたドメインにIPフィルターを適用",
"Apply Overwrite": "上書き適用",
"Apply plan": "プランを適用",
"Apply reset": "リセットを実行",
"Apply Sync": "同期を適用",
"Applying...": "適用中...",
@@ -571,6 +579,8 @@
"Balance depleted": "残高なし",
"Balance is shown in quota units": "残高はクォータ単位で表示されます",
"Balance queried successfully": "残高の取得に成功しました",
"Balance Query": "残高照会",
"Balance response not recognized": "残高レスポンスを認識できません",
"Balance updated successfully": "残高が正常に更新されました",
"Balance updated: {{balance}}": "残高更新:{{balance}}",
"Bar Chart": "棒グラフ",
@@ -808,6 +818,7 @@
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"Chinese": "中国語",
"Choose a complete upstream protocol plan or edit individual route groups.": "完全な上流プロトコルプランを選ぶか、ルートグループを個別に編集します。",
"Choose a username": "ユーザー名を選択",
"Choose an amount and payment method": "金額と支払い方法を選択してください",
"Choose and order the groups this API key will try.": "この API キーが試行するグループを選択して並べ替えます。",
@@ -830,6 +841,7 @@
"Clamped to": "制限後の値",
"Claude": "Claude",
"Claude CLI Header Passthrough": "Claude CLI ヘッダーパススルー",
"Claude only": "Claude のみ",
"Clean": "問題なし",
"Clean history logs": "履歴ログをクリーンアップ",
"Clean logs": "ログをクリア",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "Codex 認証情報は access_token と account_id を含む JSON オブジェクトである必要があります",
"Cohere": "Cohere",
"Collapse": "折りたたむ",
"Collapse all": "すべて折りたたむ",
"Collapse All": "すべて折りたたむ",
"Collect relay latency and success-rate metrics for the model square.": "モデル広場向けに Relay のレイテンシと成功率メトリクスを収集します。",
"Color": "カラー",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "Waffo決済アグリゲーションプラットフォームの連携を設定",
"Configure your account behavior preferences": "アカウントの動作設定を設定します。",
"Configure your account preferences and integrations": "アカウントの設定と統合を設定します。",
"Configured": "設定済み",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "PayMethods JSON として保存されます。type 値で使用する決済フローを決定します。stripe は Stripe、waffo_pancake は Waffo Pancake、それ以外の値は Epay の type パラメーターとして送信されます。",
"Configured routes and latency checks": "設定済みルートとレイテンシ確認",
"Confirm": "確認",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "形式: APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "形式: TokenHub API Key、または旧形式の AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "ポストプロセスなしで、リクエストをアップストリームプロバイダーに直接転送します。",
"Forwarding Routes": "転送ルート",
"Frames per second": "フレームレート",
"Free": "空き",
"Free: {{free}} / Total: {{total}}": "空き容量: {{free}} / 合計: {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "完全なベースURL (サポート",
"Full Code": "完全なコード",
"Full input length": "完全な入力長",
"Full JSON": "完全な JSON",
"Full layout": "フルレイアウト",
"Full width": "全幅",
"Function calling": "関数呼び出し",
@@ -2101,6 +2117,7 @@
"Gemini": "Gemini",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content から OpenAI Chat",
"Gemini Image 4K": "Gemini Image 4K",
"Gemini only": "Gemini のみ",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "アダプターが無効になっていても、Geminiは思考モードを自動検出します。価格設定と予算編成をより細かく制御する必要がある場合にのみ、これを有効にしてください。",
"General": "一般",
"General Settings": "一般設定",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "モデルの固定価格設定",
"Model Group": "モデルグループ",
"Model Limits": "モデル制限",
"Model List": "モデル一覧",
"Model Mapping": "モデルマッピング",
"Model Mapping (JSON)": "モデルマッピング (JSON)",
"Model Mapping must be a JSON object like": "モデルマッピングは次のようなJSONオブジェクトである必要があります",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "チャネル名を設定し、プロバイダーを選択し、API アクセスと認証情報を設定します。",
"Name, provider type, and availability.": "名前、プロバイダー種別、利用可否。",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Claude Messages ネイティブ転送と OpenAI Chat 互換転送。",
"Native format": "ネイティブ形式",
"Native forwarding": "ネイティブ転送",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Gemini ネイティブルートと OpenAI Chat / Responses 互換転送。",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "OpenAI ネイティブルートと、任意の Claude / Gemini 互換ルート。",
"Need a redemption code?": "引き換えコードが必要ですか?",
"Needs API key": "API キーが必要",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "追加 (+:)、削除 (-:)、または使用可能なグループの追加を行うグループごとのルールを定義するネストされたJSON。",
@@ -3051,6 +3072,7 @@
"Not available": "利用できません",
"Not backed up": "未バックアップ",
"Not bound": "未バインド",
"Not configured": "未設定",
"Not Equals": "等しくない",
"Not in pricing table": "料金グループ表にありません",
"Not included": "未登録",
@@ -3149,6 +3171,7 @@
"Open in new tab": "新しいタブで開く",
"Open in New Tab": "新しいタブで開く",
"Open menu": "メニューを開く",
"Open Query Balance to view the upstream JSON response": "「残高照会」を開いて上流の JSON レスポンスを確認してください",
"Open release": "リリースを開く",
"Open source": "オープンソース",
"Open Source": "オープンソース",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat から Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat から OpenAI Responses",
"OpenAI Compatible": "OpenAI互換",
"OpenAI Compatible Upstream": "OpenAI 互換アップストリーム",
"OpenAI Models route does not support client model rules": "OpenAI モデルルートはクライアントモデルルールに対応していません",
"OpenAI Models route is required to enable upstream model checks": "アップストリームモデルの確認を有効にするには OpenAI モデルルートが必要です",
"OpenAI Models route must use native forwarding": "OpenAI モデルルートではネイティブ転送を使用する必要があります",
"OpenAI Models upstream path must not contain {model}": "OpenAI モデルのアップストリームパスに {model} を含めることはできません",
"OpenAI only": "OpenAI のみ",
"OpenAI Organization": "OpenAI組織",
"OpenAI Organization ID (optional)": "OpenAI 組織 ID (オプション)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses から Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "置換",
"Replace all existing keys": "既存のすべてのキーを置き換える",
"Replace channel models": "チャネルモデルを置き換える",
"Replace forwarding routes?": "転送ルートを置き換えますか?",
"Replace mode: Will completely replace all existing keys": "置換モード: 既存のすべてのキーを完全に置き換えます",
"Replace With": "置換後",
"replaced": "置換済み",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "同じ入力パスではルートのモデルを一意にしてください",
"Route, auth, and balance check in one place": "ルート、認証、残高確認を一か所に集約",
"Routes": "ルート",
"Routes in this plan": "このプランのルート",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "同じ入口パスのルートはモデルで照合されます。モデル範囲を空にできるのは最後のフォールバックルートだけです。",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "同じ入口パスのルートはクライアント model ルールで分岐します。一致しないリクエストは最後のフォールバックを使います。",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "同じ入口パスのルートは、クライアント model の完全一致で分岐します。一致しないリクエストは最後のフォールバックを使います。",
@@ -4073,6 +4100,7 @@
"Seed": "シード",
"Select": "選択",
"Select a color": "色を選択",
"Select a complete plan, then keep only the routes you need.": "完全なプランを選び、必要なルートだけを残します。",
"Select a group": "グループを選択",
"Select a group type": "グループタイプを選択",
"Select a model to edit pricing": "料金を編集するモデルを選択",
@@ -4093,6 +4121,7 @@
"Select announcement type": "アナウンスメントタイプを選択",
"Select at least one Auto group or restore global Auto.": "Auto グループを1つ以上選択するか、グローバル Auto に戻してください。",
"Select at least one field to overwrite.": "上書きするフィールドを少なくとも 1 つ選択してください。",
"Select at least one route": "少なくとも1つのルートを選択してください",
"Select at least one target model": "少なくとも1つの対象モデルを選択してください",
"Select at most {{max}} Auto groups": "Auto グループは最大 {{max}} 個まで選択できます",
"Select body font": "本文フォントを選択",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "このモデルの一意の識別子",
"The unique name for this vendor": "このベンダーの一意の名前",
"The upstream channel that served the requests": "リクエストを処理した上流チャネル",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "上流が3つのプロトコルをネイティブ対応し、選択したルートを変換せず転送します。",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "上流レスポンスは有効な JSON ですが、OpenAI credit_summary 形式ではありません。チャネル残高は更新されていません。",
"The URL for this chat client.": "このチャットクライアントのURL。",
"The user group applied to the requests": "リクエストに適用されたユーザーグループ",
"The user who made the requests": "リクエストを行ったユーザー",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "このプロジェクトは、以下を遵守して使用する必要があります",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "この操作はこのチャンネルから失敗した {{count}} 個のモデルを削除します。元に戻せません。",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "このルートはアップストリームの OpenAI モデルを検出するためのもので、分割やクライアントモデルルールによる照合はできません。",
"This route is used only by channel management to discover upstream models.": "このルートは、チャネル管理で上流モデルを取得するためだけに使用されます。",
"This route is used only by channel management to query the upstream balance.": "このルートは、チャネル管理で上流残高を照会するためだけに使用されます。",
"This session will lose access immediately and must sign in again.": "このセッションは直ちにアクセスできなくなり、再度サインインが必要になります。",
"This site currently has {{count}} models enabled": "このサイトでは現在 {{count}} 個のモデルが有効です",
"This Telegram account is already bound.": "この Telegram アカウントはすでに連携されています。",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "{{date}} より前に作成されたすべてのログエントリが完全に削除されます。",
"This will permanently remove log entries before the selected timestamp.": "選択したタイムスタンプより前のログエントリが完全に削除されます。",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "すべてのチャネル設定からルーティングインデックスを再構築します。対応モデル、グループ、優先度、重みが含まれます。再構築中はルーティングが一時的に不完全になる可能性があります。続行しますか?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "{{removed}} 件の転送ルートを削除し、選択した {{created}} 件を作成します。モデル一覧と残高ルートは保持されます。",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "{{removed}} 件の転送ルートを削除し、{{template}} テンプレート({{created}} 件のルート)に置き換えます。モデル一覧と残高ルートは保持されます。",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "タグ \"{{tag}}\" の {{count}} 件すべてのチャネルの優先度を {{value}} に更新します。続行しますか?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "タグ \"{{tag}}\" の {{count}} 件すべてのチャネルの重みを {{value}} に更新します。続行しますか?",
"This year": "今年",
@@ -4912,6 +4947,7 @@
"Upscale": "アップスケール",
"Upstream": "アップストリーム",
"Upstream did not return reset credit details.": "上流からリセット回数の詳細が返されませんでした。",
"Upstream JSON response": "上流 JSON レスポンス",
"Upstream Model Detection Settings": "アップストリームモデル検出設定",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "上流モデル検出タスクを開始しました。システム情報で進捗を確認し、完了後に更新してステージングされた変更をご確認ください。",
"Upstream Model Update Check": "アップストリームモデル更新チェック",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "上流パスは完全な URL、または / で始まるパスである必要があります",
"Upstream price sync": "アップストリーム価格同期",
"Upstream prices fetched successfully": "上流価格を正常に取得しました",
"Upstream protocol plan": "上流プロトコルプラン",
"Upstream ratios fetched successfully": "アップストリーム比率が正常に取得されました",
"Upstream Request ID": "上流リクエストID",
"Upstream Response": "アップストリームレスポンス",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "パスを入力するとチャネルの Base URL に追加されます。完全な URL を入力すると、このルートでは Base URL を使わずその URL を使用します。",
"Use authenticator code": "認証コードを使用",
"Use backup code": "バックアップコードを使用",
"Use Bearer for all protocols": "すべてのプロトコルで Bearer を使用",
"Use disk cache when request body exceeds this size": "リクエストボディがこのサイズを超えた場合にディスクキャッシュを使用",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "クライアントの正確なモデル名をカンマ区切りで入力します。プレフィックスやワイルドカードは使えません。",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "gpt-4o のような完全一致のモデル名、または re:^gemini- のように re: で始まる正規表現ルールを使えます。",
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "{{modality}} поддерживается",
"{{n}} model(s) selected": "Выбрано моделей: {{n}}",
"{{processed}} of {{total}} log entries processed.": "Обработано {{processed}} из {{total}} записей журнала.",
"{{protocol}} auth name": "Имя аутентификации {{protocol}}",
"{{protocol}} auth value": "Значение аутентификации {{protocol}}",
"{{protocol}} authentication": "Аутентификация {{protocol}}",
"{{success}} succeeded, {{failed}} failed": "{{success}} успешно, {{failed}} с ошибкой",
"{{target}} test failed": "Тест {{target}} не выполнен",
"{{target}} test succeeded": "Тест {{target}} успешно выполнен",
@@ -188,6 +191,7 @@
"Add Group": "Добавить группу",
"Add group rate limit": "Добавить ограничение скорости группы",
"Add group rules": "Добавить правила группы",
"Add management route": "Добавить служебный маршрут",
"Add Mapping": "Добавить сопоставление",
"Add method": "Добавить метод",
"Add missing models": "Добавить отсутствующие модели",
@@ -207,6 +211,7 @@
"Add Quota": "Добавить квоту",
"Add ratio override": "Добавить переопределение коэффициента",
"Add route": "Добавить маршрут",
"Add routes individually or replace them from a template.": "Добавляйте маршруты по отдельности или замените их шаблоном.",
"Add Row": "Добавить строку",
"Add Rule": "Добавить правило",
"Add rule group": "Добавить группу правил",
@@ -215,6 +220,7 @@
"Add split": "Добавить ветку",
"Add subscription": "Добавить подписку",
"Add tags...": "Добавить теги...",
"Add template": "Добавить шаблон",
"Add tier": "Добавить уровень",
"Add time condition": "Добавить условие по времени",
"Add time rule group": "Добавить группу правил по времени",
@@ -298,6 +304,7 @@
"All nodes": "Все узлы",
"All playground messages saved in this browser will be removed. This cannot be undone.": "Все сообщения Playground, сохраненные в этом браузере, будут удалены. Это действие нельзя отменить.",
"All requests must include": "Все запросы должны содержать",
"All routes": "Все маршруты",
"All Status": "Все статусы",
"All Sync Status": "Все статусы синхронизации",
"All systems operational": "Все системы работают штатно",
@@ -427,6 +434,7 @@
"Apply Filters": "Применить фильтры",
"Apply IP Filter to Resolved Domains": "Применить IP-фильтр к разрешенным доменам",
"Apply Overwrite": "Применить перезапись",
"Apply plan": "Применить схему",
"Apply reset": "Выполнить сброс",
"Apply Sync": "Применить синхронизацию",
"Applying...": "Применение...",
@@ -571,6 +579,8 @@
"Balance depleted": "Баланс исчерпан",
"Balance is shown in quota units": "Баланс показан в единицах квоты",
"Balance queried successfully": "Баланс успешно запрошен",
"Balance Query": "Запрос баланса",
"Balance response not recognized": "Ответ с балансом не распознан",
"Balance updated successfully": "Баланс успешно обновлён",
"Balance updated: {{balance}}": "Баланс обновлён: {{balance}}",
"Bar Chart": "Столбчатая диаграмма",
@@ -808,6 +818,7 @@
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"Chinese": "Китайский",
"Choose a complete upstream protocol plan or edit individual route groups.": "Выберите полную схему протокола поставщика или измените группы маршрутов.",
"Choose a username": "Выберите имя пользователя",
"Choose an amount and payment method": "Выберите сумму и способ оплаты",
"Choose and order the groups this API key will try.": "Выберите и упорядочьте группы, которые будет использовать этот API-ключ.",
@@ -830,6 +841,7 @@
"Clamped to": "Ограничено до",
"Claude": "Клод",
"Claude CLI Header Passthrough": "Проброс заголовков Claude CLI",
"Claude only": "Только Claude",
"Clean": "Без конфликта",
"Clean history logs": "Очистить журналы истории",
"Clean logs": "Очистить логи",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "Учетные данные Codex должны быть JSON-объектом с access_token и account_id",
"Cohere": "Cohere",
"Collapse": "Свернуть",
"Collapse all": "Свернуть всё",
"Collapse All": "Свернуть все",
"Collect relay latency and success-rate metrics for the model square.": "Собирает метрики задержки Relay и доли успешных запросов для витрины моделей.",
"Color": "Цвет",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "Настроить интеграцию платёжной платформы Waffo",
"Configure your account behavior preferences": "Настроить предпочтения поведения вашей учетной записи",
"Configure your account preferences and integrations": "Настроить параметры и интеграции вашей учетной записи",
"Configured": "Настроено",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "Сохраняется как JSON PayMethods. Значение type определяет платежный сценарий: stripe для Stripe, waffo_pancake для Waffo Pancake, остальные значения отправляются в Epay как параметр type.",
"Configured routes and latency checks": "Настроенные маршруты и проверки задержки",
"Confirm": "Подтверждение",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "Формат: APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "Формат: TokenHub API Key или устаревший AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "Перенаправлять запросы напрямую upstream-провайдерам без какой-либо постобработки.",
"Forwarding Routes": "Маршруты пересылки",
"Frames per second": "Кадров в секунду",
"Free": "Свободно",
"Free: {{free}} / Total: {{total}}": "Свободно: {{free}} / Всего: {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "Полный базовый URL (поддерживает",
"Full Code": "Полный код",
"Full input length": "Полная длина входа",
"Full JSON": "Полный JSON",
"Full layout": "Полная разметка",
"Full width": "Полная ширина",
"Function calling": "Вызов функций",
@@ -2101,6 +2117,7 @@
"Gemini": "Gemini",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content в OpenAI Chat",
"Gemini Image 4K": "Gemini Image 4K",
"Gemini only": "Только Gemini",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini продолжит автоматически определять режим мышления, даже если адаптер отключен. Включайте это только тогда, когда вам нужен более тонкий контроль над ценообразованием и бюджетированием.",
"General": "Общие",
"General Settings": "Общие настройки",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "Фиксированная цена модели",
"Model Group": "Группа моделей",
"Model Limits": "Лимиты модели",
"Model List": "Список моделей",
"Model Mapping": "Сопоставление моделей",
"Model Mapping (JSON)": "Сопоставление моделей (JSON)",
"Model Mapping must be a JSON object like": "Сопоставление моделей должно быть JSON-объектом, например",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "Задайте имя канала, выберите провайдера, настройте доступ к API и учетные данные.",
"Name, provider type, and availability.": "Название, тип провайдера и доступность.",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Нативный Claude Messages и совместимая пересылка OpenAI Chat.",
"Native format": "Собственный формат",
"Native forwarding": "Нативная пересылка",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Нативные маршруты Gemini и совместимая пересылка OpenAI Chat и Responses.",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "Нативные маршруты OpenAI и дополнительные маршруты совместимости Claude и Gemini.",
"Need a redemption code?": "Нужен код активации?",
"Needs API key": "Нужен API-ключ",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "Вложенный JSON, определяющий правила для каждой группы для добавления (+:), удаления (-:) или добавления используемых групп.",
@@ -3051,6 +3072,7 @@
"Not available": "Недоступно",
"Not backed up": "Не сохранено",
"Not bound": "Не привязан",
"Not configured": "Не настроено",
"Not Equals": "Не равно",
"Not in pricing table": "Нет в таблице тарифных групп",
"Not included": "Не включена",
@@ -3149,6 +3171,7 @@
"Open in new tab": "Открыть в новой вкладке",
"Open in New Tab": "Открыть в новой вкладке",
"Open menu": "Открыть меню",
"Open Query Balance to view the upstream JSON response": "Откройте «Запрос баланса», чтобы просмотреть JSON-ответ поставщика",
"Open release": "Открыть выпуск",
"Open source": "Открытый исходный код",
"Open Source": "Открытый исходный код",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat в Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat в OpenAI Responses",
"OpenAI Compatible": "Совместимо с OpenAI",
"OpenAI Compatible Upstream": "Поставщик, совместимый с OpenAI",
"OpenAI Models route does not support client model rules": "Маршрут моделей OpenAI не поддерживает правила клиентских моделей",
"OpenAI Models route is required to enable upstream model checks": "Для проверки моделей вышестоящего сервиса требуется маршрут моделей OpenAI",
"OpenAI Models route must use native forwarding": "Маршрут моделей OpenAI должен использовать прямую передачу",
"OpenAI Models upstream path must not contain {model}": "Путь вышестоящего сервиса для моделей OpenAI не должен содержать {model}",
"OpenAI only": "Только OpenAI",
"OpenAI Organization": "Организация OpenAI",
"OpenAI Organization ID (optional)": "Идентификатор организации OpenAI (необязательно)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses в Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "Заменить",
"Replace all existing keys": "Заменить все существующие ключи",
"Replace channel models": "Замена моделей каналов",
"Replace forwarding routes?": "Заменить маршруты пересылки?",
"Replace mode: Will completely replace all existing keys": "Режим замены: полностью заменит все существующие ключи",
"Replace With": "Заменить на",
"replaced": "заменено",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "Модели маршрутов для одного входного пути должны быть уникальными",
"Route, auth, and balance check in one place": "Маршрут, аутентификация и баланс в одном месте",
"Routes": "Маршруты",
"Routes in this plan": "Маршруты в этой схеме",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "Маршруты с одним входным путем сопоставляются по модели. Оставляйте область моделей пустой только для последнего резервного маршрута.",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "Маршруты с одним входным путем разделяются правилами client model. Неподходящие запросы используют последний резервный маршрут.",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "Маршруты с одним входным путем разделяются по точной модели клиента. Несовпавшие запросы идут в последний резерв.",
@@ -4073,6 +4100,7 @@
"Seed": "Seed",
"Select": "Выбрать",
"Select a color": "Выбрать цвет",
"Select a complete plan, then keep only the routes you need.": "Выберите полную схему, затем оставьте только нужные маршруты.",
"Select a group": "Выбрать группу",
"Select a group type": "Выбрать тип группы",
"Select a model to edit pricing": "Выберите модель для редактирования тарифа",
@@ -4093,6 +4121,7 @@
"Select announcement type": "Выбрать тип объявления",
"Select at least one Auto group or restore global Auto.": "Выберите хотя бы одну группу Auto или восстановите глобальный порядок Auto.",
"Select at least one field to overwrite.": "Выберите хотя бы одно поле для перезаписи.",
"Select at least one route": "Выберите хотя бы один маршрут",
"Select at least one target model": "Выберите хотя бы одну целевую модель",
"Select at most {{max}} Auto groups": "Выберите не более {{max}} групп Auto",
"Select body font": "Выберите шрифт текста",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "Уникальный идентификатор для этой модели",
"The unique name for this vendor": "Уникальное имя для этого поставщика",
"The upstream channel that served the requests": "Вышестоящий канал, обслуживший запросы",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "Поставщик нативно поддерживает все три протокола; выбранные маршруты пересылаются без преобразования.",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "Ответ поставщика содержит допустимый JSON, но не соответствует формату OpenAI credit_summary. Баланс канала не обновлён.",
"The URL for this chat client.": "URL для этого чат-клиента.",
"The user group applied to the requests": "Группа пользователей, применённая к запросам",
"The user who made the requests": "Пользователь, отправивший запросы",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "Этот проект должен использоваться в соответствии с",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "Это удалит {{count}} неуспешных моделей из этого канала. Действие необратимо.",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "Этот маршрут обнаруживает модели OpenAI вышестоящего сервиса; его нельзя разделять или сопоставлять по правилам клиентских моделей.",
"This route is used only by channel management to discover upstream models.": "Этот маршрут используется только управлением каналами для получения моделей поставщика.",
"This route is used only by channel management to query the upstream balance.": "Этот маршрут используется только управлением каналами для запроса баланса поставщика.",
"This session will lose access immediately and must sign in again.": "Этот сеанс немедленно потеряет доступ, и потребуется повторный вход.",
"This site currently has {{count}} models enabled": "На этом сайте сейчас включено моделей: {{count}}",
"This Telegram account is already bound.": "Эта учётная запись Telegram уже привязана.",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "Это безвозвратно удалит все записи журнала, созданные до {{date}}.",
"This will permanently remove log entries before the selected timestamp.": "Это безвозвратно удалит записи журнала до выбранной временной метки.",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "Будет заново построен индекс маршрутизации каналов на основе всех конфигураций каналов, включая поддерживаемые модели, группы, приоритеты и веса. Во время перестроения маршрутизация может кратковременно быть неполной. Продолжить?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "Будут удалены маршруты пересылки ({{removed}}) и созданы выбранные маршруты ({{created}}). Маршруты моделей и баланса сохранятся.",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "Будет удалено маршрутов переадресации: {{removed}}. Они будут заменены шаблоном {{template}} (маршрутов: {{created}}). Маршруты списка моделей и баланса сохранятся.",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "Приоритет всех каналов ({{count}}) с тегом \"{{tag}}\" будет изменен на {{value}}. Продолжить?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "Вес всех каналов ({{count}}) с тегом \"{{tag}}\" будет изменен на {{value}}. Продолжить?",
"This year": "Этот год",
@@ -4912,6 +4947,7 @@
"Upscale": "Увеличение",
"Upstream": "Источник",
"Upstream did not return reset credit details.": "Вышестоящий сервис не вернул сведения о сбросах лимита.",
"Upstream JSON response": "JSON-ответ поставщика",
"Upstream Model Detection Settings": "Настройки обнаружения моделей провайдера",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "Задача обнаружения моделей вышестоящего источника запущена. Следите за ходом в разделе «Информация о системе», затем обновите, чтобы просмотреть подготовленные изменения.",
"Upstream Model Update Check": "Проверка обновлений моделей провайдера",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "Путь upstream должен быть полным URL или путем, начинающимся с /",
"Upstream price sync": "Синхронизация цен upstream",
"Upstream prices fetched successfully": "Цены провайдера успешно получены",
"Upstream protocol plan": "Схема протокола поставщика",
"Upstream ratios fetched successfully": "Коэффициенты upstream успешно получены",
"Upstream Request ID": "ID вышестоящего запроса",
"Upstream Response": "Ответ Upstream",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Укажите путь, чтобы добавить его к Base URL канала, или введите полный URL, чтобы переопределить Base URL для этого маршрута.",
"Use authenticator code": "Использовать код аутентификатора",
"Use backup code": "Использовать резервный код",
"Use Bearer for all protocols": "Использовать Bearer для всех протоколов",
"Use disk cache when request body exceeds this size": "Использовать дисковый кэш, когда тело запроса превышает этот размер",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "Укажите точные имена моделей клиента через запятую. Префиксы и подстановочные знаки не поддерживаются.",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "Используйте точные имена моделей, например gpt-4o, или regex-правила с префиксом re:, например re:^gemini-.",
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "Hỗ trợ {{modality}}",
"{{n}} model(s) selected": "Đã chọn {{n}} model",
"{{processed}} of {{total}} log entries processed.": "Đã xử lý {{processed}}/{{total}} mục nhật ký.",
"{{protocol}} auth name": "Tên xác thực {{protocol}}",
"{{protocol}} auth value": "Giá trị xác thực {{protocol}}",
"{{protocol}} authentication": "Xác thực {{protocol}}",
"{{success}} succeeded, {{failed}} failed": "{{success}} thành công, {{failed}} thất bại",
"{{target}} test failed": "Kiểm tra {{target}} thất bại",
"{{target}} test succeeded": "Kiểm tra {{target}} thành công",
@@ -188,6 +191,7 @@
"Add Group": "Thêm Nhóm",
"Add group rate limit": "Thêm giới hạn tốc độ nhóm",
"Add group rules": "Thêm quy tắc nhóm",
"Add management route": "Thêm route quản lý",
"Add Mapping": "Thêm ánh xạ",
"Add method": "Thêm phương thức",
"Add missing models": "Thêm mô hình còn thiếu",
@@ -207,6 +211,7 @@
"Add Quota": "Thêm Hạn mức",
"Add ratio override": "Thêm ghi đè tỷ lệ",
"Add route": "Thêm route",
"Add routes individually or replace them from a template.": "Thêm từng tuyến hoặc thay thế bằng một mẫu.",
"Add Row": "Thêm Hàng",
"Add Rule": "Thêm quy tắc",
"Add rule group": "Thêm nhóm quy tắc",
@@ -215,6 +220,7 @@
"Add split": "Thêm nhánh",
"Add subscription": "Thêm đăng ký",
"Add tags...": "Thêm thẻ...",
"Add template": "Thêm mẫu",
"Add tier": "Thêm bậc",
"Add time condition": "Thêm điều kiện thời gian",
"Add time rule group": "Thêm nhóm quy tắc theo thời gian",
@@ -298,6 +304,7 @@
"All nodes": "Tất cả nút",
"All playground messages saved in this browser will be removed. This cannot be undone.": "Tất cả tin nhắn Playground đã lưu trong trình duyệt này sẽ bị xóa. Không thể hoàn tác hành động này.",
"All requests must include": "Mọi yêu cầu phải có header",
"All routes": "Tất cả tuyến",
"All Status": "Tất cả trạng thái",
"All Sync Status": "Tất cả Trạng thái Đồng bộ",
"All systems operational": "Tất cả hệ thống hoạt động bình thường",
@@ -427,6 +434,7 @@
"Apply Filters": "Áp dụng bộ lọc",
"Apply IP Filter to Resolved Domains": "Áp dụng Bộ lọc IP cho Tên miền đã phân giải",
"Apply Overwrite": "Áp dụng Ghi đè",
"Apply plan": "Áp dụng cấu hình",
"Apply reset": "Thực hiện đặt lại",
"Apply Sync": "Áp dụng đồng bộ",
"Applying...": "Đang áp dụng...",
@@ -571,6 +579,8 @@
"Balance depleted": "Đã hết số dư",
"Balance is shown in quota units": "Số dư được hiển thị theo đơn vị hạn mức",
"Balance queried successfully": "Truy vấn số dư thành công",
"Balance Query": "Truy vấn số dư",
"Balance response not recognized": "Không nhận dạng được phản hồi số dư",
"Balance updated successfully": "Đã cập nhật số dư thành công",
"Balance updated: {{balance}}": "Số dư đã cập nhật: {{balance}}",
"Bar Chart": "Biểu đồ cột",
@@ -808,6 +818,7 @@
"checkout.session.completed": "thanh toán.phiên.hoàn thành",
"checkout.session.expired": "Phiên thanh toán đã hết hạn.",
"Chinese": "Tiếng Trung",
"Choose a complete upstream protocol plan or edit individual route groups.": "Chọn cấu hình giao thức thượng nguồn đầy đủ hoặc chỉnh sửa từng nhóm route.",
"Choose a username": "Chọn tên người dùng",
"Choose an amount and payment method": "Chọn số tiền và phương thức thanh toán",
"Choose and order the groups this API key will try.": "Chọn và sắp xếp các nhóm mà khóa API này sẽ thử.",
@@ -830,6 +841,7 @@
"Clamped to": "Giới hạn thành",
"Claude": "Claude",
"Claude CLI Header Passthrough": "Chuyển tiếp header Claude CLI",
"Claude only": "Chỉ Claude",
"Clean": "Không xung đột",
"Clean history logs": "Xóa nhật ký lịch sử",
"Clean logs": "Dọn dẹp nhật ký",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "Thông tin xác thực Codex phải là đối tượng JSON có access_token và account_id",
"Cohere": "Cohere",
"Collapse": "Thu gọn",
"Collapse all": "Thu gọn tất cả",
"Collapse All": "Thu gọn tất cả",
"Collect relay latency and success-rate metrics for the model square.": "Thu thập độ trễ Relay và tỷ lệ thành công cho quảng trường mô hình.",
"Color": "Màu",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "Cấu hình tích hợp nền tảng tổng hợp thanh toán Waffo",
"Configure your account behavior preferences": "Cấu hình tùy chọn hành vi tài khoản của bạn",
"Configure your account preferences and integrations": "Cấu hình các tùy chọn và tích hợp tài khoản của bạn",
"Configured": "Đã cấu hình",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "Được lưu dưới dạng JSON PayMethods. Giá trị type quyết định luồng thanh toán sẽ dùng: stripe cho Stripe, waffo_pancake cho Waffo Pancake, các giá trị khác được gửi tới Epay dưới dạng tham số type.",
"Configured routes and latency checks": "Tuyến đã cấu hình và kiểm tra độ trễ",
"Confirm": "Xác nhận",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "Định dạng: APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "Định dạng: TokenHub API Key, hoặc định dạng cũ AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "Chuyển tiếp các yêu cầu trực tiếp đến các nhà cung cấp ngược dòng mà không cần xử lý hậu kỳ nào.",
"Forwarding Routes": "Route chuyển tiếp",
"Frames per second": "Khung hình / giây",
"Free": "Trống",
"Free: {{free}} / Total: {{total}}": "Còn trống: {{free}} / Tổng: {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "URL cơ sở đầy đủ (hỗ trợ",
"Full Code": "Mã đầy đủ",
"Full input length": "Độ dài đầu vào đầy đủ",
"Full JSON": "JSON đầy đủ",
"Full layout": "Bố cục đầy đủ",
"Full width": "Toàn chiều rộng",
"Function calling": "Gọi hàm",
@@ -2101,6 +2117,7 @@
"Gemini": "Song Tử",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content sang OpenAI Chat",
"Gemini Image 4K": "Gemini Image 4K",
"Gemini only": "Chỉ Gemini",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini sẽ tiếp tục tự động phát hiện chế độ suy nghĩ ngay cả khi bộ điều hợp bị tắt. Chỉ bật tính năng này khi bạn cần kiểm soát chi tiết hơn về giá cả và lập ngân sách.",
"General": "Chung",
"General Settings": "General settings",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "Fixed-price model",
"Model Group": "Nhóm Mô hình",
"Model Limits": "Giới hạn Mô hình",
"Model List": "Danh sách mô hình",
"Model Mapping": "Ánh xạ mô hình",
"Model Mapping (JSON)": "Ánh xạ mô hình (JSON)",
"Model Mapping must be a JSON object like": "Ánh xạ Mô hình phải là một đối tượng JSON như",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "Đặt tên kênh, chọn nhà cung cấp, cấu hình truy cập API và thiết lập thông tin xác thực.",
"Name, provider type, and availability.": "Tên, loại nhà cung cấp và trạng thái khả dụng.",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Chuyển tiếp Claude Messages nguyên bản và tương thích OpenAI Chat.",
"Native format": "Định dạng gốc",
"Native forwarding": "Chuyển tiếp nguyên bản",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Route Gemini nguyên bản cùng chuyển tiếp tương thích OpenAI Chat và Responses.",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "Route OpenAI nguyên bản cùng các route tương thích Claude và Gemini tùy chọn.",
"Need a redemption code?": "Cần mã đổi thưởng?",
"Needs API key": "Cần khóa API",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "JSON lồng nhau xác định quy tắc theo nhóm để thêm (+:), xóa (-:), hoặc nối các nhóm có thể sử dụng.",
@@ -3051,6 +3072,7 @@
"Not available": "Không khả dụng",
"Not backed up": "Chưa sao lưu",
"Not bound": "Không bị ràng buộc",
"Not configured": "Chưa cấu hình",
"Not Equals": "Không bằng",
"Not in pricing table": "Không có trong bảng định giá",
"Not included": "Không bao gồm",
@@ -3149,6 +3171,7 @@
"Open in new tab": "Mở trong tab mới",
"Open in New Tab": "Mở trong tab mới",
"Open menu": "Mở menu",
"Open Query Balance to view the upstream JSON response": "Mở “Truy vấn số dư” để xem phản hồi JSON thượng nguồn",
"Open release": "Phát hành mở",
"Open source": "Mã nguồn mở",
"Open Source": "Mã nguồn mở",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat sang Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat sang OpenAI Responses",
"OpenAI Compatible": "Tương thích OpenAI",
"OpenAI Compatible Upstream": "Thượng nguồn tương thích OpenAI",
"OpenAI Models route does not support client model rules": "Tuyến Mô hình OpenAI không hỗ trợ quy tắc mô hình phía máy khách",
"OpenAI Models route is required to enable upstream model checks": "Cần có tuyến Mô hình OpenAI để bật kiểm tra mô hình thượng nguồn",
"OpenAI Models route must use native forwarding": "Tuyến Mô hình OpenAI phải dùng chuyển tiếp nguyên bản",
"OpenAI Models upstream path must not contain {model}": "Đường dẫn thượng nguồn của Mô hình OpenAI không được chứa {model}",
"OpenAI only": "Chỉ OpenAI",
"OpenAI Organization": "Tổ chức OpenAI",
"OpenAI Organization ID (optional)": "ID Tổ chức OpenAI (tùy chọn)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses sang Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "Thay thế",
"Replace all existing keys": "Thay thế tất cả các khóa hiện có",
"Replace channel models": "Thay thế mô hình kênh",
"Replace forwarding routes?": "Thay thế các route chuyển tiếp?",
"Replace mode: Will completely replace all existing keys": "Chế độ Thay thế: Sẽ thay thế hoàn toàn tất cả các khóa hiện có",
"Replace With": "Thay bằng",
"replaced": "thay thế",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "Các mô hình tuyến phải là duy nhất cho cùng đường dẫn đầu vào",
"Route, auth, and balance check in one place": "Kiểm tra tuyến, xác thực và số dư ở cùng một nơi",
"Routes": "Route",
"Routes in this plan": "Các route trong cấu hình",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "Các tuyến có cùng đường dẫn vào được khớp theo mô hình. Chỉ để trống phạm vi mô hình cho tuyến dự phòng cuối cùng.",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "Các route có cùng đường vào được phân nhánh theo quy tắc client model. Yêu cầu không khớp dùng nhánh dự phòng cuối.",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "Các tuyến có cùng đường dẫn vào được tách theo model client chính xác. Yêu cầu chưa khớp sẽ dùng nhánh dự phòng cuối cùng.",
@@ -4073,6 +4100,7 @@
"Seed": "Seed",
"Select": "Chọn",
"Select a color": "Chọn một màu",
"Select a complete plan, then keep only the routes you need.": "Chọn cấu hình đầy đủ, sau đó chỉ giữ các route cần thiết.",
"Select a group": "Chọn một nhóm",
"Select a group type": "Chọn loại nhóm",
"Select a model to edit pricing": "Chọn mô hình để chỉnh sửa giá",
@@ -4093,6 +4121,7 @@
"Select announcement type": "Select notification type",
"Select at least one Auto group or restore global Auto.": "Chọn ít nhất một nhóm Auto hoặc khôi phục Auto toàn cục.",
"Select at least one field to overwrite.": "Chọn ít nhất một trường để ghi đè.",
"Select at least one route": "Chọn ít nhất một route",
"Select at least one target model": "Chọn ít nhất một mô hình đích",
"Select at most {{max}} Auto groups": "Chọn tối đa {{max}} nhóm Auto",
"Select body font": "Chọn phông chữ nội dung",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "Mã định danh duy nhất cho mô hình này",
"The unique name for this vendor": "Tên duy nhất cho nhà cung cấp này",
"The upstream channel that served the requests": "Kênh thượng nguồn đã phục vụ các yêu cầu",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "Thượng nguồn hỗ trợ nguyên bản cả ba giao thức; mọi route đã chọn được chuyển tiếp mà không chuyển đổi.",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "Phản hồi thượng nguồn là JSON hợp lệ nhưng không khớp định dạng OpenAI credit_summary. Số dư kênh chưa được cập nhật.",
"The URL for this chat client.": "URL của ứng dụng chat này.",
"The user group applied to the requests": "Nhóm người dùng được áp dụng cho các yêu cầu",
"The user who made the requests": "Người dùng đã thực hiện các yêu cầu",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "Dự án này phải được sử dụng tuân thủ theo",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "Thao tác này sẽ xóa {{count}} mô hình thất bại khỏi kênh này. Không thể hoàn tác.",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "Tuyến này khám phá các mô hình OpenAI thượng nguồn và không thể tách hoặc đối sánh bằng quy tắc mô hình phía máy khách.",
"This route is used only by channel management to discover upstream models.": "Route này chỉ được quản lý kênh dùng để lấy mô hình thượng nguồn.",
"This route is used only by channel management to query the upstream balance.": "Route này chỉ được quản lý kênh dùng để truy vấn số dư thượng nguồn.",
"This session will lose access immediately and must sign in again.": "Phiên này sẽ mất quyền truy cập ngay lập tức và phải đăng nhập lại.",
"This site currently has {{count}} models enabled": "Trang này hiện đã bật {{count}} mô hình",
"This Telegram account is already bound.": "Tài khoản Telegram này đã được liên kết.",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "Thao tác này sẽ xóa vĩnh viễn tất cả các mục nhật ký được tạo trước {{date}}.",
"This will permanently remove log entries before the selected timestamp.": "Thao tác này sẽ xóa vĩnh viễn các mục nhật ký trước mốc thời gian đã chọn.",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "Thao tác này sẽ xây dựng lại chỉ mục định tuyến kênh từ toàn bộ cấu hình kênh, bao gồm mô hình được hỗ trợ, nhóm, độ ưu tiên và trọng số. Định tuyến có thể tạm thời chưa đầy đủ trong khi xây dựng lại. Tiếp tục?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "Thao tác này sẽ xóa {{removed}} route chuyển tiếp và tạo {{created}} route đã chọn. Route danh sách mô hình và số dư sẽ được giữ lại.",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "Thao tác này sẽ xóa {{removed}} tuyến chuyển tiếp và thay thế bằng mẫu {{template}} ({{created}} tuyến). Các tuyến danh sách mô hình và số dư sẽ được giữ lại.",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "Thao tác này sẽ cập nhật mức ưu tiên thành {{value}} cho tất cả {{count}} kênh có thẻ \"{{tag}}\". Tiếp tục?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "Thao tác này sẽ cập nhật trọng số thành {{value}} cho tất cả {{count}} kênh có thẻ \"{{tag}}\". Tiếp tục?",
"This year": "Năm nay",
@@ -4912,6 +4947,7 @@
"Upscale": "Phóng to",
"Upstream": "Thượng nguồn",
"Upstream did not return reset credit details.": "Upstream không trả về chi tiết lượt đặt lại.",
"Upstream JSON response": "Phản hồi JSON thượng nguồn",
"Upstream Model Detection Settings": "Cài đặt phát hiện mô hình nguồn",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "Đã bắt đầu tác vụ phát hiện mô hình thượng nguồn. Theo dõi tiến trình trong Thông tin hệ thống, sau đó làm mới để xem các cập nhật đang chờ.",
"Upstream Model Update Check": "Kiểm tra cập nhật mô hình nguồn",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "Đường dẫn upstream phải là URL đầy đủ hoặc đường dẫn bắt đầu bằng /",
"Upstream price sync": "Đồng bộ giá thượng nguồn",
"Upstream prices fetched successfully": "Lấy giá upstream thành công",
"Upstream protocol plan": "Cấu hình giao thức thượng nguồn",
"Upstream ratios fetched successfully": "Đã lấy tỷ lệ upstream thành công",
"Upstream Request ID": "ID yêu cầu thượng nguồn",
"Upstream Response": "Upstream feedback",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Dùng đường dẫn để nối vào Base URL của kênh, hoặc nhập URL đầy đủ để ghi đè Base URL cho tuyến này.",
"Use authenticator code": "Sử dụng mã xác thực",
"Use backup code": "Sử dụng mã dự phòng",
"Use Bearer for all protocols": "Dùng Bearer cho mọi giao thức",
"Use disk cache when request body exceeds this size": "Sử dụng bộ nhớ đệm đĩa khi nội dung yêu cầu vượt quá kích thước này",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "Nhập tên model chính xác từ yêu cầu client, ngăn cách bằng dấu phẩy. Không hỗ trợ tiền tố hoặc ký tự đại diện.",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "Dùng tên model chính xác như gpt-4o, hoặc quy tắc regex có tiền tố re: như re:^gemini-.",
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "支援 {{modality}}",
"{{n}} model(s) selected": "已選 {{n}} 個模型",
"{{processed}} of {{total}} log entries processed.": "已處理 {{processed}} / {{total}} 條日誌。",
"{{protocol}} auth name": "{{protocol}} 驗證名稱",
"{{protocol}} auth value": "{{protocol}} 驗證值",
"{{protocol}} authentication": "{{protocol}} 驗證",
"{{success}} succeeded, {{failed}} failed": "{{success}} 個成功,{{failed}} 個失敗",
"{{target}} test failed": "{{target}} 測試失敗",
"{{target}} test succeeded": "{{target}} 測試成功",
@@ -188,6 +191,7 @@
"Add Group": "新增分組",
"Add group rate limit": "新增組速率限制",
"Add group rules": "新增分組規則",
"Add management route": "新增管理路由",
"Add Mapping": "新增映射",
"Add method": "新增方式",
"Add missing models": "新增缺失模型",
@@ -207,6 +211,7 @@
"Add Quota": "新增配額",
"Add ratio override": "新增倍率覆蓋",
"Add route": "新增路由",
"Add routes individually or replace them from a template.": "逐一新增路由,或使用範本取代現有路由。",
"Add Row": "新增列",
"Add Rule": "新增規則",
"Add rule group": "新增規則組",
@@ -215,6 +220,7 @@
"Add split": "新增分流",
"Add subscription": "新增訂閱",
"Add tags...": "新增標籤...",
"Add template": "新增範本",
"Add tier": "新增檔位",
"Add time condition": "新增時間條件",
"Add time rule group": "新增時間規則組",
@@ -298,6 +304,7 @@
"All nodes": "全部節點",
"All playground messages saved in this browser will be removed. This cannot be undone.": "儲存在此瀏覽器中的所有遊樂場訊息都將被移除。此操作無法撤銷。",
"All requests must include": "所有請求必須攜帶",
"All routes": "全部路由",
"All Status": "所有狀態",
"All Sync Status": "所有同步狀態",
"All systems operational": "所有系統正常運作",
@@ -427,6 +434,7 @@
"Apply Filters": "套用篩選器",
"Apply IP Filter to Resolved Domains": "對已解析的網域套用 IP 篩選器",
"Apply Overwrite": "套用覆蓋",
"Apply plan": "套用方案",
"Apply reset": "執行重置",
"Apply Sync": "套用同步",
"Applying...": "正在套用...",
@@ -571,6 +579,8 @@
"Balance depleted": "餘額已耗盡",
"Balance is shown in quota units": "餘額以額度單位顯示",
"Balance queried successfully": "餘額查詢成功",
"Balance Query": "餘額查詢",
"Balance response not recognized": "無法識別餘額回應",
"Balance updated successfully": "餘額更新成功",
"Balance updated: {{balance}}": "餘額已更新:{{balance}}",
"Bar Chart": "柱狀圖",
@@ -808,6 +818,7 @@
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"Chinese": "中文",
"Choose a complete upstream protocol plan or edit individual route groups.": "選擇完整的上游協議方案,或逐組編輯路由。",
"Choose a username": "選擇一個用戶名",
"Choose an amount and payment method": "選擇金額和支付方式",
"Choose and order the groups this API key will try.": "選擇此 API 金鑰要依序嘗試的分組並排序。",
@@ -830,6 +841,7 @@
"Clamped to": "限制為",
"Claude": "Claude",
"Claude CLI Header Passthrough": "Claude CLI 請求頭透傳",
"Claude only": "僅 Claude",
"Clean": "無衝突",
"Clean history logs": "清理歷史日誌",
"Clean logs": "清理日誌",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "Codex 憑證必須是包含 access_token 和 account_id 的 JSON 物件",
"Cohere": "Cohere",
"Collapse": "收起",
"Collapse all": "全部收合",
"Collapse All": "全部收起",
"Collect relay latency and success-rate metrics for the model square.": "收集 Relay 延遲和成功率指標,用於模型廣場展示。",
"Color": "顏色",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "設定 Waffo 支付聚合平台整合",
"Configure your account behavior preferences": "設定您的用戶行為偏好",
"Configure your account preferences and integrations": "設定您的用戶偏好和整合",
"Configured": "已設定",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "儲存為 PayMethods JSON。type 值決定點擊後使用哪個支付流程:stripe 走 Stripewaffo_pancake 走 Waffo Pancake,其他值作為 Epay 的 type 參數提交。",
"Configured routes and latency checks": "已設定路由和延遲檢測",
"Confirm": "確認",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "格式:APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "格式:TokenHub API Key,或舊版 AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "將請求直接轉發給上游供應商,不進行任何後處理。",
"Forwarding Routes": "路由轉發",
"Frames per second": "幀率",
"Free": "可用",
"Free: {{free}} / Total: {{total}}": "可用空間: {{free}} / 總空間: {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "完整基礎 URL (支援",
"Full Code": "完整代碼",
"Full input length": "完整輸入長度",
"Full JSON": "完整 JSON",
"Full layout": "全屏佈局",
"Full width": "全寬",
"Function calling": "函數呼叫",
@@ -2101,6 +2117,7 @@
"Gemini": "Gemini",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content 到 OpenAI Chat",
"Gemini Image 4K": "Gemini 圖片 4K",
"Gemini only": "僅 Gemini",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "即使停用配接器,Gemini 也會繼續自動偵測思考模式。僅當您需要對定價和預算進行更精細的控制時才啟用此選項。",
"General": "常規",
"General Settings": "通用設定",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "模型固定定價",
"Model Group": "模型分組",
"Model Limits": "模型限制",
"Model List": "模型列表",
"Model Mapping": "模型映射",
"Model Mapping (JSON)": "模型映射 (JSON)",
"Model Mapping must be a JSON object like": "模型映射必須是如下所示的 JSON 物件",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "命名渠道、選擇供應商、設定 API 存取並設定憑證。",
"Name, provider type, and availability.": "名稱、供應商類型和可用狀態。",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Claude Messages 原生轉發,並相容 OpenAI Chat 轉換。",
"Native format": "原生格式",
"Native forwarding": "原生轉發",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Gemini 原生轉發,並相容 OpenAI Chat 和 Responses 轉換。",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "OpenAI 原生轉發,並提供可選的 Claude 和 Gemini 相容轉換。",
"Need a redemption code?": "需要兌換碼?",
"Needs API key": "需要 API 金鑰",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "嵌套 JSON,定義按分組新增(+:)、移除(-:)或追加可用分組的規則。",
@@ -3051,6 +3072,7 @@
"Not available": "不可用",
"Not backed up": "未備份",
"Not bound": "未連結",
"Not configured": "未設定",
"Not Equals": "不等於",
"Not in pricing table": "不在定價分組表中",
"Not included": "未加入",
@@ -3149,6 +3171,7 @@
"Open in new tab": "在新標籤頁中打開",
"Open in New Tab": "在新標籤頁中打開",
"Open menu": "打開選單",
"Open Query Balance to view the upstream JSON response": "請開啟「查詢餘額」檢視上游 JSON 回應",
"Open release": "打開版本",
"Open source": "開源",
"Open Source": "開源項目",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat 到 Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat 到 OpenAI Responses",
"OpenAI Compatible": "兼容 OpenAI",
"OpenAI Compatible Upstream": "OpenAI 相容上游",
"OpenAI Models route does not support client model rules": "OpenAI 模型路由不支援用戶端模型規則",
"OpenAI Models route is required to enable upstream model checks": "啟用上游模型檢查必須設定 OpenAI 模型路由",
"OpenAI Models route must use native forwarding": "OpenAI 模型路由必須使用原生轉發",
"OpenAI Models upstream path must not contain {model}": "OpenAI 模型上游路徑不得包含 {model}",
"OpenAI only": "僅 OpenAI",
"OpenAI Organization": "OpenAI 組織",
"OpenAI Organization ID (optional)": "OpenAI 組織 ID(可選)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses 轉 Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "替換",
"Replace all existing keys": "替換所有現有金鑰",
"Replace channel models": "覆蓋渠道模型",
"Replace forwarding routes?": "取代轉發路由?",
"Replace mode: Will completely replace all existing keys": "替換模式:將完全替換所有現有鍵",
"Replace With": "替換為",
"replaced": "已替換",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "同一入口路徑下的路由模型必須唯一",
"Route, auth, and balance check in one place": "路由、認證和餘額檢查集中展示",
"Routes": "路由",
"Routes in this plan": "方案中的路由",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "同一入口路徑的路由按模型匹配。僅最後一個兜底路由可留空模型範圍。",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "同一入口路徑按客戶端 model 規則分流;未命中的請求走最後的兜底。",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "同一入口路徑按客戶端 model 精確分流;未命中的請求走最後的兜底。",
@@ -4073,6 +4100,7 @@
"Seed": "隨機種子",
"Select": "選擇",
"Select a color": "選擇顏色",
"Select a complete plan, then keep only the routes you need.": "選擇完整方案,再保留需要的路由。",
"Select a group": "選擇一個分組",
"Select a group type": "選擇分組類型",
"Select a model to edit pricing": "選擇一個模型編輯定價",
@@ -4093,6 +4121,7 @@
"Select announcement type": "選擇公告類型",
"Select at least one Auto group or restore global Auto.": "請至少選擇一個 Auto 分組,或恢復全域 Auto。",
"Select at least one field to overwrite.": "請選擇至少一個要覆蓋的欄位。",
"Select at least one route": "請至少選擇一條路由",
"Select at least one target model": "請至少選擇一個目標模型",
"Select at most {{max}} Auto groups": "最多選擇 {{max}} 個 Auto 分組",
"Select body font": "選擇正文字體",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "此模型的唯一標識符",
"The unique name for this vendor": "此供應商的唯一名稱",
"The upstream channel that served the requests": "處理請求的上游渠道",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "上游原生支援三種協議,所選路由均不經轉換直接轉發。",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "上游回應是有效 JSON,但不符合 OpenAI credit_summary 格式,渠道餘額未更新。",
"The URL for this chat client.": "此聊天用戶端的 URL。",
"The user group applied to the requests": "請求所套用的用戶分組",
"The user who made the requests": "發起請求的用戶",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "此項目的使用必須遵守",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "此操作將從該渠道移除 {{count}} 個測試失敗的模型,且無法撤銷。",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "此路由用於探索上游 OpenAI 模型,無法拆分或使用用戶端模型規則配對。",
"This route is used only by channel management to discover upstream models.": "此路由僅供渠道管理取得上游模型。",
"This route is used only by channel management to query the upstream balance.": "此路由僅供渠道管理查詢上游餘額。",
"This session will lose access immediately and must sign in again.": "此工作階段將立即失去存取權限,且必須重新登入。",
"This site currently has {{count}} models enabled": "本站目前已啟用模型,總計 {{count}} 個",
"This Telegram account is already bound.": "此 Telegram 帳號已綁定。",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "這將永久刪除 {{date}} 之前建立的所有日誌條目。",
"This will permanently remove log entries before the selected timestamp.": "這將永久刪除所選時間戳之前的日誌條目。",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "這會根據所有渠道設定重建渠道路由索引,包括支援模型、分組、優先級和權重。重建期間路由可能短暫不完整。是否繼續?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "將刪除 {{removed}} 條轉發路由並建立 {{created}} 條所選路由。模型列表和餘額路由會保留。",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "這將移除 {{removed}} 條轉送路由,並取代為 {{template}} 範本({{created}} 條路由)。模型清單與餘額路由將保留。",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "這會將標籤「{{tag}}」下所有 {{count}} 個渠道的優先級更新為 {{value}}。繼續嗎?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "這會將標籤「{{tag}}」下所有 {{count}} 個渠道的權重更新為 {{value}}。繼續嗎?",
"This year": "本年",
@@ -4912,6 +4947,7 @@
"Upscale": "放大",
"Upstream": "上游",
"Upstream did not return reset credit details.": "上游未返回重置次數詳情。",
"Upstream JSON response": "上游 JSON 回應",
"Upstream Model Detection Settings": "偵測上游模型設定",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "上游模型檢測任務已開始。可在「系統資訊」中查看進度,完成後重新整理以查看待處理的更新。",
"Upstream Model Update Check": "上游模型更新檢查",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "上游路徑必須是完整 URL,或以 / 開頭的路徑",
"Upstream price sync": "上游價格同步",
"Upstream prices fetched successfully": "已成功獲取上游價格",
"Upstream protocol plan": "上游協議方案",
"Upstream ratios fetched successfully": "上游比率獲取成功",
"Upstream Request ID": "上游請求 ID",
"Upstream Response": "上游返回",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "填寫以 / 開頭的路徑時會自動拼接渠道 Base URL;填寫完整 URL 時,此路由會直接使用該 URL。",
"Use authenticator code": "使用驗證器代碼",
"Use backup code": "使用備用代碼",
"Use Bearer for all protocols": "所有協議統一使用 Bearer",
"Use disk cache when request body exceeds this size": "請求體超過此大小時使用磁碟緩存",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "填寫客戶端請求裡的精確 model 名,多個用英文逗號分隔。不支援前綴或萬用字元。",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "可以填寫 gpt-4o 這類精確模型名,也可以填寫 re:^gemini- 這類以 re: 開頭的正則規則。",
+38
View File
@@ -65,6 +65,9 @@
"{{modality}} supported": "支持 {{modality}}",
"{{n}} model(s) selected": "已选 {{n}} 个模型",
"{{processed}} of {{total}} log entries processed.": "已处理 {{processed}} / {{total}} 条日志。",
"{{protocol}} auth name": "{{protocol}} 认证名称",
"{{protocol}} auth value": "{{protocol}} 认证值",
"{{protocol}} authentication": "{{protocol}} 认证",
"{{success}} succeeded, {{failed}} failed": "{{success}} 个成功,{{failed}} 个失败",
"{{target}} test failed": "{{target}} 测试失败",
"{{target}} test succeeded": "{{target}} 测试成功",
@@ -188,6 +191,7 @@
"Add Group": "添加分组",
"Add group rate limit": "添加组速率限制",
"Add group rules": "添加分组规则",
"Add management route": "添加管理路由",
"Add Mapping": "添加映射",
"Add method": "添加方法",
"Add missing models": "添加缺失模型",
@@ -207,6 +211,7 @@
"Add Quota": "添加配额",
"Add ratio override": "添加倍率覆盖",
"Add route": "添加路由",
"Add routes individually or replace them from a template.": "单独添加路由,或使用模板替换现有路由。",
"Add Row": "添加行",
"Add Rule": "添加规则",
"Add rule group": "新增规则组",
@@ -215,6 +220,7 @@
"Add split": "添加分流",
"Add subscription": "新增订阅",
"Add tags...": "添加标签...",
"Add template": "添加模板",
"Add tier": "新增档位",
"Add time condition": "新增时间条件",
"Add time rule group": "新增时间规则组",
@@ -298,6 +304,7 @@
"All nodes": "全部节点",
"All playground messages saved in this browser will be removed. This cannot be undone.": "保存在此浏览器中的所有游乐场消息都将被移除。此操作无法撤销。",
"All requests must include": "所有请求必须携带",
"All routes": "全部路由",
"All Status": "所有状态",
"All Sync Status": "所有同步状态",
"All systems operational": "所有系统正常运行",
@@ -427,6 +434,7 @@
"Apply Filters": "应用筛选器",
"Apply IP Filter to Resolved Domains": "对已解析的域应用 IP 筛选器",
"Apply Overwrite": "应用覆盖",
"Apply plan": "应用方案",
"Apply reset": "执行重置",
"Apply Sync": "应用同步",
"Applying...": "正在应用...",
@@ -571,6 +579,8 @@
"Balance depleted": "余额已耗尽",
"Balance is shown in quota units": "余额以额度单位显示",
"Balance queried successfully": "余额查询成功",
"Balance Query": "余额查询",
"Balance response not recognized": "无法识别余额响应",
"Balance updated successfully": "余额更新成功",
"Balance updated: {{balance}}": "余额已更新:{{balance}}",
"Bar Chart": "柱状图",
@@ -808,6 +818,7 @@
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"Chinese": "中文",
"Choose a complete upstream protocol plan or edit individual route groups.": "选择完整的上游协议方案,或逐组编辑路由。",
"Choose a username": "选择一个用户名",
"Choose an amount and payment method": "选择金额和支付方式",
"Choose and order the groups this API key will try.": "选择并排列此 API 密钥将依次尝试的分组。",
@@ -830,6 +841,7 @@
"Clamped to": "钳制为",
"Claude": "Claude",
"Claude CLI Header Passthrough": "Claude CLI 请求头透传",
"Claude only": "仅 Claude",
"Clean": "无冲突",
"Clean history logs": "清理历史日志",
"Clean logs": "清理日志",
@@ -906,6 +918,7 @@
"Codex credential must be a JSON object with access_token and account_id": "Codex 凭据必须是包含 access_token 和 account_id 的 JSON 对象",
"Cohere": "Cohere",
"Collapse": "收起",
"Collapse all": "全部折叠",
"Collapse All": "全部收起",
"Collect relay latency and success-rate metrics for the model square.": "收集 Relay 延迟和成功率指标,用于模型广场展示。",
"Color": "颜色",
@@ -991,6 +1004,7 @@
"Configure Waffo payment aggregation platform integration": "配置 Waffo 支付聚合平台集成",
"Configure your account behavior preferences": "配置您的账户行为偏好",
"Configure your account preferences and integrations": "配置您的账户偏好和集成",
"Configured": "已配置",
"Configured as PayMethods JSON. The type value decides which payment flow is used: stripe for Stripe, waffo_pancake for Waffo Pancake, and other values are sent to Epay as the type parameter.": "保存为 PayMethods JSON。type 值决定点击后使用哪个支付流程:stripe 走 Stripewaffo_pancake 走 Waffo Pancake,其他值作为 Epay 的 type 参数提交。",
"Configured routes and latency checks": "已配置路由和延迟检测",
"Confirm": "确认",
@@ -2079,6 +2093,7 @@
"Format: APPID|APISecret|APIKey": "格式:APPID|APISecret|APIKey",
"Format: TokenHub API Key, or legacy AppId|SecretId|SecretKey": "格式:TokenHub API Key,或旧版 AppId|SecretId|SecretKey",
"Forward requests directly to upstream providers without any post-processing.": "将请求直接转发给上游提供商,不进行任何后处理。",
"Forwarding Routes": "路由转发",
"Frames per second": "帧率",
"Free": "可用",
"Free: {{free}} / Total: {{total}}": "可用空间: {{free}} / 总空间: {{total}}",
@@ -2091,6 +2106,7 @@
"Full Base URL (supports": "完整基础 URL (支持",
"Full Code": "完整代码",
"Full input length": "完整输入长度",
"Full JSON": "完整 JSON",
"Full layout": "全屏布局",
"Full width": "全宽",
"Function calling": "函数调用",
@@ -2101,6 +2117,7 @@
"Gemini": "Gemini",
"Gemini Generate Content to OpenAI Chat": "Gemini Generate Content 到 OpenAI Chat",
"Gemini Image 4K": "Gemini 图片 4K",
"Gemini only": "仅 Gemini",
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "即使禁用适配器,Gemini 也会继续自动检测思维模式。仅当您需要对定价和预算进行更精细的控制时才启用此选项。",
"General": "常规",
"General Settings": "通用设置",
@@ -2702,6 +2719,7 @@
"Model fixed pricing": "模型固定定价",
"Model Group": "模型分组",
"Model Limits": "模型限制",
"Model List": "模型列表",
"Model Mapping": "模型映射",
"Model Mapping (JSON)": "模型映射 (JSON)",
"Model Mapping must be a JSON object like": "模型映射必须是如下所示的 JSON 对象",
@@ -2836,8 +2854,11 @@
"Name the channel, choose the provider, configure API access, and set credentials.": "命名渠道、选择供应商、配置 API 访问并设置凭据。",
"Name, provider type, and availability.": "名称、供应商类型和可用状态。",
"name@example.com": "name@example.com",
"Native Claude Messages plus OpenAI Chat compatibility forwarding.": "Claude Messages 原生转发,并兼容 OpenAI Chat 转换。",
"Native format": "原生格式",
"Native forwarding": "原生转发",
"Native Gemini routes plus OpenAI Chat and Responses compatibility forwarding.": "Gemini 原生转发,并兼容 OpenAI Chat 和 Responses 转换。",
"Native OpenAI routes plus optional Claude and Gemini compatibility routes.": "OpenAI 原生转发,并提供可选的 Claude 和 Gemini 兼容转换。",
"Need a redemption code?": "需要兑换码?",
"Needs API key": "需要 API 密钥",
"Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "嵌套 JSON,定义按分组添加(+:)、移除(-:)或追加可用分组的规则。",
@@ -3051,6 +3072,7 @@
"Not available": "不可用",
"Not backed up": "未备份",
"Not bound": "未绑定",
"Not configured": "未配置",
"Not Equals": "不等于",
"Not in pricing table": "不在定价分组表中",
"Not included": "未加入",
@@ -3149,6 +3171,7 @@
"Open in new tab": "在新标签页中打开",
"Open in New Tab": "在新标签页中打开",
"Open menu": "打开菜单",
"Open Query Balance to view the upstream JSON response": "请打开“查询余额”查看上游 JSON 响应",
"Open release": "打开版本",
"Open source": "开源",
"Open Source": "开源项目",
@@ -3161,10 +3184,12 @@
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat 到 Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat 到 OpenAI Responses",
"OpenAI Compatible": "兼容 OpenAI",
"OpenAI Compatible Upstream": "OpenAI 兼容上游",
"OpenAI Models route does not support client model rules": "OpenAI 模型路由不支持客户端模型规则",
"OpenAI Models route is required to enable upstream model checks": "启用上游模型检查必须配置 OpenAI 模型路由",
"OpenAI Models route must use native forwarding": "OpenAI 模型路由必须使用原生转发",
"OpenAI Models upstream path must not contain {model}": "OpenAI 模型上游路径不能包含 {model}",
"OpenAI only": "仅 OpenAI",
"OpenAI Organization": "OpenAI 组织",
"OpenAI Organization ID (optional)": "OpenAI 组织 ID(可选)",
"OpenAI Responses to Gemini Generate Content": "OpenAI Responses 转 Gemini Generate Content",
@@ -3798,6 +3823,7 @@
"Replace": "替换",
"Replace all existing keys": "替换所有现有密钥",
"Replace channel models": "覆盖渠道模型",
"Replace forwarding routes?": "替换转发路由?",
"Replace mode: Will completely replace all existing keys": "替换模式:将完全替换所有现有键",
"Replace With": "替换为",
"replaced": "已替换",
@@ -3941,6 +3967,7 @@
"Route models must be unique for the same incoming path": "同一入口路径下的路由模型必须唯一",
"Route, auth, and balance check in one place": "路由、认证和余额检查集中展示",
"Routes": "路由",
"Routes in this plan": "方案中的路由",
"Routes with the same incoming path are matched by model. Leave the model scope empty only for the final fallback route.": "同一入口路径的路由按模型匹配。仅最后一个兜底路由可留空模型范围。",
"Routes with the same incoming path are split by client model rules. Unmatched requests use the final fallback.": "同一入口路径按客户端 model 规则分流;未命中的请求走最后的兜底。",
"Routes with the same incoming path are split by exact client model. Unmatched requests use the final fallback.": "同一入口路径按客户端 model 精确分流;未命中的请求走最后的兜底。",
@@ -4073,6 +4100,7 @@
"Seed": "随机种子",
"Select": "选择",
"Select a color": "选择颜色",
"Select a complete plan, then keep only the routes you need.": "选择完整方案,再保留需要的路由。",
"Select a group": "选择一个分组",
"Select a group type": "选择分组类型",
"Select a model to edit pricing": "选择一个模型编辑定价",
@@ -4093,6 +4121,7 @@
"Select announcement type": "选择公告类型",
"Select at least one Auto group or restore global Auto.": "请至少选择一个 Auto 分组,或恢复全局 Auto。",
"Select at least one field to overwrite.": "请选择至少一个要覆盖的字段。",
"Select at least one route": "请至少选择一条路由",
"Select at least one target model": "请至少选择一个目标模型",
"Select at most {{max}} Auto groups": "最多选择 {{max}} 个 Auto 分组",
"Select body font": "选择正文字体",
@@ -4557,6 +4586,8 @@
"The unique identifier for this model": "此模型的唯一标识符",
"The unique name for this vendor": "此供应商的唯一名称",
"The upstream channel that served the requests": "处理请求的上游渠道",
"The upstream natively supports all three protocols; every selected route is forwarded without conversion.": "上游原生支持三种协议,所选路由均不经转换直接转发。",
"The upstream response is valid JSON, but it does not match the OpenAI credit_summary format. The channel balance was not updated.": "上游响应是有效 JSON,但不符合 OpenAI credit_summary 格式,渠道余额未更新。",
"The URL for this chat client.": "此聊天客户端的 URL。",
"The user group applied to the requests": "请求所应用的用户分组",
"The user who made the requests": "发起请求的用户",
@@ -4606,6 +4637,8 @@
"This project must be used in compliance with the": "此项目的使用必须遵守",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "此操作将从该渠道移除 {{count}} 个测试失败的模型,且无法撤销。",
"This route discovers upstream OpenAI models and cannot be split or matched by client model rules.": "此路由用于发现上游 OpenAI 模型,不能拆分或使用客户端模型规则匹配。",
"This route is used only by channel management to discover upstream models.": "此路由仅供渠道管理获取上游模型。",
"This route is used only by channel management to query the upstream balance.": "此路由仅供渠道管理查询上游余额。",
"This session will lose access immediately and must sign in again.": "此会话将立即失去访问权限,并且必须重新登录。",
"This site currently has {{count}} models enabled": "本站当前已启用模型,总计 {{count}} 个",
"This Telegram account is already bound.": "此 Telegram 账户已被绑定。",
@@ -4632,6 +4665,8 @@
"This will permanently remove all log entries created before {{date}}.": "这将永久删除 {{date}} 之前创建的所有日志条目。",
"This will permanently remove log entries before the selected timestamp.": "这将永久删除所选时间戳之前的日志条目。",
"This will rebuild the channel routing index from every channel configuration, including supported models, groups, priorities, and weights. Routing may be briefly incomplete while the rebuild is running. Continue?": "这会根据所有渠道配置重建渠道路由索引,包括支持模型、分组、优先级和权重。重建期间路由可能短暂不完整。是否继续?",
"This will remove {{removed}} forwarding routes and create {{created}} selected routes. Model list and balance routes will be preserved.": "将删除 {{removed}} 条转发路由并创建 {{created}} 条所选路由。模型列表和余额路由会保留。",
"This will remove {{removed}} forwarding routes and replace them with the {{template}} template ({{created}} routes). Model list and balance routes will be preserved.": "这将移除 {{removed}} 条转发路由,并替换为 {{template}} 模板({{created}} 条路由)。模型列表和余额路由将保留。",
"This will update the priority to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "这会将标签 \"{{tag}}\" 下所有 {{count}} 个渠道的优先级更新为 {{value}}。继续吗?",
"This will update the weight to {{value}} for all {{count}} channel(s) with tag \"{{tag}}\". Continue?": "这会将标签 \"{{tag}}\" 下所有 {{count}} 个渠道的权重更新为 {{value}}。继续吗?",
"This year": "本年",
@@ -4912,6 +4947,7 @@
"Upscale": "放大",
"Upstream": "上游",
"Upstream did not return reset credit details.": "上游未返回重置次数详情。",
"Upstream JSON response": "上游 JSON 响应",
"Upstream Model Detection Settings": "检测上游模型设置",
"Upstream model detection task started. Track progress in System Info, then refresh to review staged updates.": "上游模型检测任务已开始。可在「系统信息」中查看进度,完成后刷新以查看待处理的更新。",
"Upstream Model Update Check": "上游模型更新检查",
@@ -4922,6 +4958,7 @@
"Upstream path must be a full URL or a path starting with /": "上游路径必须是完整 URL,或以 / 开头的路径",
"Upstream price sync": "上游价格同步",
"Upstream prices fetched successfully": "已成功获取上游价格",
"Upstream protocol plan": "上游协议方案",
"Upstream ratios fetched successfully": "上游比率获取成功",
"Upstream Request ID": "上游请求 ID",
"Upstream Response": "上游返回",
@@ -4963,6 +5000,7 @@
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "填写以 / 开头的路径时会自动拼接渠道 Base URL;填写完整 URL 时,此路由会直接使用该 URL。",
"Use authenticator code": "使用验证器代码",
"Use backup code": "使用备用代码",
"Use Bearer for all protocols": "所有协议统一使用 Bearer",
"Use disk cache when request body exceeds this size": "请求体超过此大小时使用磁盘缓存",
"Use exact client model names, separated by commas. Prefixes and wildcards are not supported.": "填写客户端请求里的精确 model 名,多个用英文逗号分隔。不支持前缀或通配符。",
"Use exact model names such as gpt-4o, or regex rules prefixed with re: such as re:^gemini-.": "可以填写 gpt-4o 这类精确模型名,也可以填写 re:^gemini- 这类以 re: 开头的正则规则。",