♻️ refactor(channels): rebuild channel create/edit drawer with modular sections and improved form UX

Restructure the default-theme channel create/edit experience to align with
classic frontend behavior, modern form UX patterns, and the project's Base UI
design system.

Channel editor architecture:
- Split the monolithic channel mutate drawer into focused section components
  (basic, API access, auth, models, advanced) with shared drawer layout
  primitives
- Extract submission, toast handling, and react-query cache invalidation into
  `useChannelMutateForm`
- Add a dedicated loading skeleton for channel detail fetch during edit mode
- Remove the top-level configuration summary block per UX feedback

Form validation and data handling:
- Strengthen `channel-form` Zod schema with JSON, model mapping, status code
  mapping, Codex credential, and Vertex AI key refinements
- Move type-specific conditional validation into `superRefine`
- Normalize base URL formatting and tighten model mapping value validation

Model mapping editor:
- Add Visual/JSON tabbed editing with inline JSON and duplicate-key feedback
- Improve accessibility for icon-only actions and add model suggestion datalists

MultiSelect component:
- Replace the custom cmdk-based implementation with Base UI Combobox chips
- Align focus, border, ring, disabled, and invalid states with standard Input
  styling via `ComboboxChips`
- Preserve existing API (`options`, `selected`, `onChange`, `allowCreate`,
  `createLabel`) for all current callers
- Support inline custom value creation, comma/newline batch input, searchable
  options, portal-based dropdown positioning, and chip removal

Models & groups UX:
- Integrate manual custom model entry directly into the model MultiSelect
- Remove the separate manual model input/button block
- Keep selected-model count and existing model-mapping guardrail behavior

i18n:
- Add and sync translation keys for new editor sections, validation messages,
  model mapping UI, and MultiSelect empty/create labels across en, zh, fr, ja,
  ru, and vi
- Remove obsolete keys tied to the deprecated summary and manual model entry UI

Affected areas:
- `web/default/src/features/channels/components/drawers/`
- `web/default/src/features/channels/hooks/use-channel-mutate-form.ts`
- `web/default/src/features/channels/lib/channel-form.ts`
- `web/default/src/features/channels/lib/model-mapping-validation.ts`
- `web/default/src/features/channels/components/model-mapping-editor.tsx`
- `web/default/src/components/multi-select.tsx`
- `web/default/src/i18n/locales/*.json`
This commit is contained in:
t0ng7u 2026-05-26 01:22:49 +08:00
parent 583da45296
commit 3d850d38b6
19 changed files with 3465 additions and 2478 deletions

View File

@ -8,21 +8,31 @@ License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import * as React from 'react'
import { Command as CommandPrimitive } from 'cmdk'
import { X } from 'lucide-react'
import { Add01Icon } from '@hugeicons/core-free-icons'
import { HugeiconsIcon } from '@hugeicons/react'
import { useTranslation } from 'react-i18next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Command, CommandGroup, CommandItem } from '@/components/ui/command'
import { cn } from '@/lib/utils'
import {
Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxCollection,
ComboboxContent,
ComboboxEmpty,
ComboboxItem,
ComboboxList,
ComboboxValue,
} from '@/components/ui/combobox'
export type Option = {
label: string
@ -35,116 +45,232 @@ interface MultiSelectProps {
onChange: (values: string[]) => void
placeholder?: string
className?: string
allowCreate?: boolean
/**
* Label shown for the "create" item in the dropdown.
* Supports the `{{value}}` placeholder which is replaced with the typed input.
* Falls back to `Add "{{value}}"` when omitted.
*/
createLabel?: string
/** Empty state text. Defaults to "No matching items". */
emptyText?: string
/** Optional `id` to wire labels/aria-describedby to the input. */
id?: string
/** Disable the entire control. */
disabled?: boolean
}
export function MultiSelect({
options,
selected,
onChange,
placeholder,
className,
}: MultiSelectProps) {
const { t } = useTranslation()
const resolvedPlaceholder = placeholder ?? t('Select items...')
const inputRef = React.useRef<HTMLInputElement>(null)
const [open, setOpen] = React.useState(false)
const [inputValue, setInputValue] = React.useState('')
const COMMA_REGEX = /[,\n]/
const handleUnselect = (value: string) => {
onChange(selected.filter((s) => s !== value))
function splitDraft(value: string): { completed: string[]; draft: string } {
if (!COMMA_REGEX.test(value)) {
return { completed: [], draft: value }
}
const normalized = value.replaceAll('', ',').replaceAll('\n', ',')
const parts = normalized.split(',')
const draft = parts.at(-1) ?? ''
const completed = parts
.slice(0, -1)
.map((part) => part.trim())
.filter(Boolean)
return { completed, draft }
}
/**
* MultiSelect tags/chips style multi-select built on Base UI Combobox.
*
* Behaviour:
* - Search filters built-in options (Base UI handles fuzzy filtering).
* - When `allowCreate` is true, custom values can be added inline:
* - Type and press Enter / "," to add a single value.
* - Paste a comma- (or newline-) separated list to add many at once.
* - A "Add \"<value>\"" item appears at the top of the dropdown when the
* typed text doesn't match any option.
* - Backspace on an empty input removes the last selected chip (Base UI default).
*
* Focus/border styling is inherited from `ComboboxChips`, which uses the same
* tokens as `Input` so it stays visually consistent with other form fields.
*/
export function MultiSelect(props: MultiSelectProps) {
const { t } = useTranslation()
const placeholder = props.placeholder ?? t('Select items...')
const [inputValue, setInputValue] = React.useState('')
const [open, setOpen] = React.useState(false)
const selectedSet = React.useMemo(
() => new Set(props.selected),
[props.selected]
)
// Lookup of value -> display label so chips and items can show friendly names
// even when the underlying option list changes (e.g. custom-added values).
const labelMap = React.useMemo(() => {
const map = new Map<string, string>()
for (const option of props.options) {
map.set(option.value, option.label)
}
return map
}, [props.options])
const trimmedInput = inputValue.trim()
const inputMatchesExisting =
trimmedInput.length > 0 &&
(selectedSet.has(trimmedInput) ||
props.options.some(
(option) =>
option.value.toLowerCase() === trimmedInput.toLowerCase() ||
option.label.toLowerCase() === trimmedInput.toLowerCase()
))
const canCreate =
props.allowCreate === true &&
trimmedInput.length > 0 &&
!inputMatchesExisting
// We expose all known option values + every currently selected value to Base
// UI's items list. This way Base UI filters them by the search query and the
// user can still see the chip labels mapped correctly.
const items = React.useMemo(() => {
const set = new Set<string>(props.options.map((option) => option.value))
for (const value of props.selected) {
set.add(value)
}
if (canCreate) {
set.add(trimmedInput)
}
return Array.from(set)
}, [props.options, props.selected, canCreate, trimmedInput])
const addValues = React.useCallback(
(values: string[]) => {
const next: string[] = []
const seen = new Set<string>(props.selected)
for (const raw of values) {
const value = raw.trim()
if (!value) continue
if (seen.has(value)) continue
seen.add(value)
next.push(value)
}
if (next.length === 0) return
props.onChange([...props.selected, ...next])
},
[props]
)
const handleInputValueChange = (value: string) => {
if (!props.allowCreate) {
setInputValue(value)
return
}
const parsed = splitDraft(value)
if (parsed.completed.length > 0) {
addValues(parsed.completed)
setInputValue(parsed.draft)
return
}
setInputValue(value)
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current
if (input) {
if (e.key === 'Delete' || e.key === 'Backspace') {
if (input.value === '' && selected.length > 0) {
onChange(selected.slice(0, -1))
}
}
if (e.key === 'Escape') {
input.blur()
const handleValueChange = (next: string[]) => {
props.onChange(next)
// When an item is picked (multiple mode), Base UI keeps the input but most
// UX patterns clear it. Clearing once a value is added makes batch picking
// feel snappier and matches popular chip-style multiselects.
if (next.length > props.selected.length) {
setInputValue('')
}
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
// Enter without a highlighted option commits the typed value.
if (event.key === 'Enter' && props.allowCreate && canCreate) {
// Only fire when Base UI has no highlighted item to select. We rely on
// the highlighted item's data attribute on the popup. If the popup is
// closed or empty, manually commit the typed value.
const popup = document.querySelector<HTMLElement>(
'[data-slot="combobox-content"][data-open]'
)
const hasHighlight = popup?.querySelector('[data-highlighted]') != null
if (!hasHighlight) {
event.preventDefault()
addValues([trimmedInput])
setInputValue('')
}
}
}
const selectables = options.filter(
(option) => !selected.includes(option.value)
)
return (
<Command
onKeyDown={handleKeyDown}
className={`overflow-visible bg-transparent ${className || ''}`}
<Combobox
multiple
items={items}
value={props.selected}
onValueChange={handleValueChange}
inputValue={inputValue}
onInputValueChange={handleInputValueChange}
open={open}
onOpenChange={setOpen}
disabled={props.disabled}
>
<div className='group border-input ring-offset-background focus-within:ring-ring rounded-md border px-3 py-2 text-sm focus-within:ring-2 focus-within:ring-offset-2'>
<div className='flex flex-wrap gap-1'>
{selected.map((value) => {
const option = options.find((o) => o.value === value)
return (
<Badge key={value} variant='secondary'>
{option?.label || value}
<Button
variant='ghost'
size='icon-sm'
aria-label='Remove'
className='ml-1 size-auto p-0'
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleUnselect(value)
}
}}
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onClick={() => handleUnselect(value)}
<ComboboxChips className={cn('w-full', props.className)}>
<ComboboxValue>
{(values: string[]) =>
values.map((value) => (
<ComboboxChip key={value}>
<span className='max-w-[16rem] truncate'>
{labelMap.get(value) ?? value}
</span>
</ComboboxChip>
))
}
</ComboboxValue>
<ComboboxChipsInput
id={props.id}
placeholder={props.selected.length === 0 ? placeholder : undefined}
onKeyDown={handleKeyDown}
aria-label={placeholder}
/>
</ComboboxChips>
<ComboboxContent>
<ComboboxList>
<ComboboxCollection>
{(item: string) => {
const isCreate = canCreate && item === trimmedInput
const label = labelMap.get(item) ?? item
return (
<ComboboxItem
key={item}
value={item}
className={isCreate ? 'text-foreground' : undefined}
>
<X
className='text-muted-foreground hover:text-foreground h-3 w-3'
aria-hidden='true'
/>
</Button>
</Badge>
)
})}
<CommandPrimitive.Input
ref={inputRef}
value={inputValue}
onValueChange={setInputValue}
onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
placeholder={selected.length === 0 ? resolvedPlaceholder : ''}
className='placeholder:text-muted-foreground flex-1 bg-transparent outline-none'
/>
</div>
</div>
<div className='relative'>
{open && selectables.length > 0 ? (
<div className='bg-popover text-popover-foreground animate-in absolute top-0 z-10 w-full rounded-md border shadow-md outline-none'>
<CommandGroup className='h-full max-h-60 overflow-auto'>
{selectables.map((option) => {
return (
<CommandItem
key={option.value}
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onSelect={() => {
setInputValue('')
onChange([...selected, option.value])
}}
className='cursor-pointer'
>
{option.label}
</CommandItem>
)
})}
</CommandGroup>
</div>
) : null}
</div>
</Command>
{isCreate ? (
<>
<HugeiconsIcon
icon={Add01Icon}
strokeWidth={2}
className='text-muted-foreground'
aria-hidden='true'
/>
<span className='truncate'>
{props.createLabel
? t(props.createLabel, { value: item })
: t('Add "{{value}}"', { value: item })}
</span>
</>
) : (
<span className='truncate'>{label}</span>
)}
</ComboboxItem>
)
}}
</ComboboxCollection>
</ComboboxList>
<ComboboxEmpty>
{props.emptyText ?? t('No matching items')}
</ComboboxEmpty>
</ComboboxContent>
</Combobox>
)
}

View File

@ -0,0 +1,78 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import type { ReactNode } from 'react'
import { ChevronDown, Settings } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible'
type ChannelAdvancedSectionProps = {
children: ReactNode
open: boolean
onOpenChange: (open: boolean) => void
}
export function ChannelAdvancedSection(props: ChannelAdvancedSectionProps) {
const { t } = useTranslation()
return (
<Collapsible open={props.open} onOpenChange={props.onOpenChange}>
<CollapsibleTrigger
render={
<button
type='button'
className='hover:bg-muted/40 border-border/60 flex w-full items-center justify-between rounded-lg border px-3 py-3 text-left transition-colors'
aria-expanded={props.open}
/>
}
>
<div className='flex items-start gap-3'>
<span className='bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md'>
<Settings className='h-4 w-4' aria-hidden='true' />
</span>
<div className='flex flex-col gap-0.5'>
<div className='text-[13px] font-semibold'>
{t('Advanced Settings')}
</div>
<div className='text-muted-foreground text-xs'>
{t(
'Request overrides, routing behavior, and upstream model automation'
)}
</div>
</div>
</div>
<ChevronDown
className={cn(
'text-muted-foreground h-4 w-4 shrink-0 transition-transform',
props.open && 'rotate-180'
)}
aria-hidden='true'
/>
</CollapsibleTrigger>
<CollapsibleContent className='mt-5 flex flex-col gap-5'>
{props.children}
</CollapsibleContent>
</Collapsible>
)
}

View File

@ -0,0 +1,46 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import type { ReactNode } from 'react'
import { Link2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import {
SideDrawerSection,
SideDrawerSectionHeader,
} from '@/components/drawer-layout'
type ChannelApiAccessSectionProps = {
children: ReactNode
}
export function ChannelApiAccessSection(props: ChannelApiAccessSectionProps) {
const { t } = useTranslation()
return (
<SideDrawerSection>
<SideDrawerSectionHeader
title={t('API Access')}
description={t(
'Endpoint, provider-specific settings, and credentials.'
)}
icon={<Link2 className='h-4 w-4' aria-hidden='true' />}
/>
{props.children}
</SideDrawerSection>
)
}

View File

@ -0,0 +1,44 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import type { ReactNode } from 'react'
import { KeyRound } from 'lucide-react'
import { useTranslation } from 'react-i18next'
type ChannelAuthSectionProps = {
children: ReactNode
}
export function ChannelAuthSection(props: ChannelAuthSectionProps) {
const { t } = useTranslation()
return (
<div className='border-border/60 flex flex-col gap-4 border-t pt-4'>
<div className='flex items-center gap-2'>
<KeyRound
className='text-muted-foreground h-3.5 w-3.5'
aria-hidden='true'
/>
<h4 className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
{t('Authentication')}
</h4>
</div>
{props.children}
</div>
)
}

View File

@ -0,0 +1,44 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import type { ReactNode } from 'react'
import { Server } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import {
SideDrawerSection,
SideDrawerSectionHeader,
} from '@/components/drawer-layout'
type ChannelBasicSectionProps = {
children: ReactNode
}
export function ChannelBasicSection(props: ChannelBasicSectionProps) {
const { t } = useTranslation()
return (
<SideDrawerSection>
<SideDrawerSectionHeader
title={t('Basic Information')}
description={t('Name, provider type, and availability.')}
icon={<Server className='h-4 w-4' aria-hidden='true' />}
/>
{props.children}
</SideDrawerSection>
)
}

View File

@ -0,0 +1,44 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useTranslation } from 'react-i18next'
import { Skeleton } from '@/components/ui/skeleton'
export function ChannelEditorLoadingState() {
const { t } = useTranslation()
return (
<div
className='border-border/60 flex flex-col gap-4 rounded-lg border p-4'
aria-live='polite'
>
<div>
<p className='text-sm font-medium'>{t('Loading channel details')}</p>
<p className='text-muted-foreground mt-1 text-xs'>
{t('Please wait before editing to avoid overwriting saved values.')}
</p>
</div>
<div className='grid gap-4 sm:grid-cols-2'>
<Skeleton className='h-10 w-full' />
<Skeleton className='h-10 w-full' />
</div>
<Skeleton className='h-24 w-full' />
<Skeleton className='h-32 w-full' />
</div>
)
}

View File

@ -0,0 +1,44 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import type { ReactNode } from 'react'
import { Boxes } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import {
SideDrawerSection,
SideDrawerSectionHeader,
} from '@/components/drawer-layout'
type ChannelModelsSectionProps = {
children: ReactNode
}
export function ChannelModelsSection(props: ChannelModelsSectionProps) {
const { t } = useTranslation()
return (
<SideDrawerSection>
<SideDrawerSectionHeader
title={t('Models & Groups')}
description={t('Published models, groups, and model remapping rules.')}
icon={<Boxes className='h-4 w-4' aria-hidden='true' />}
/>
{props.children}
</SideDrawerSection>
)
}

View File

@ -0,0 +1,24 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
export * from './channel-advanced-section'
export * from './channel-api-access-section'
export * from './channel-auth-section'
export * from './channel-basic-section'
export * from './channel-editor-loading-state'
export * from './channel-models-section'

View File

@ -16,18 +16,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useState, useEffect, useRef } from 'react'
import { Code, Table, Plus, Trash2 } from 'lucide-react'
import { useEffect, useId, useMemo, useRef, useState } from 'react'
import { Code, Plus, Table, Trash2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea'
type ModelMappingEditorProps = {
value: string
onChange: (value: string) => void
disabled?: boolean
sourceModelOptions?: string[]
targetModelOptions?: string[]
}
type MappingRow = {
@ -36,30 +40,59 @@ type MappingRow = {
to: string
}
export function ModelMappingEditor({
value,
onChange,
disabled = false,
}: ModelMappingEditorProps) {
const DUPLICATE_MAPPING_SENTINEL = '{ "duplicate_source_models": '
function getDuplicateSources(rows: MappingRow[]): string[] {
const seen = new Set<string>()
const duplicates = new Set<string>()
for (const row of rows) {
const source = row.from.trim()
if (!source) continue
if (seen.has(source)) {
duplicates.add(source)
} else {
seen.add(source)
}
}
return Array.from(duplicates)
}
export function ModelMappingEditor(props: ModelMappingEditorProps) {
const { t } = useTranslation()
const sourceListId = useId()
const targetListId = useId()
const [mode, setMode] = useState<'visual' | 'json'>('visual')
const [rows, setRows] = useState<MappingRow[]>([])
const [jsonValue, setJsonValue] = useState(value)
const [jsonValue, setJsonValue] = useState(props.value)
const [jsonError, setJsonError] = useState<string | null>(null)
const nextRowIdRef = useRef(0)
const duplicateSources = useMemo(() => getDuplicateSources(rows), [rows])
const createRowId = () => {
nextRowIdRef.current += 1
return `mapping-${nextRowIdRef.current}`
}
const parseJsonToRows = (json: string) => {
const parseJsonToRows = (json: string): boolean => {
try {
if (!json.trim()) {
setRows([])
return
setJsonError(null)
return true
}
const parsed = JSON.parse(json)
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
setJsonError(t('Model mapping must be a valid JSON object'))
return false
}
const entries = Object.entries(parsed)
const invalidValue = entries.find(([, to]) => typeof to !== 'string')
if (invalidValue) {
setJsonError(t('Model mapping values must be strings'))
return false
}
setRows((previousRows) => {
const remainingRows = [...previousRows]
return entries.map(([from, to], index) => {
@ -85,17 +118,20 @@ export function ModelMappingEditor({
}
})
})
setJsonError(null)
return true
} catch (_error) {
// Invalid JSON, keep current rows
setJsonError(t('Model mapping must be valid JSON format'))
return false
}
}
// Parse JSON to rows when value changes externally
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setJsonValue(value)
parseJsonToRows(value)
}, [value])
setJsonValue(props.value)
parseJsonToRows(props.value)
}, [props.value])
const convertRowsToJson = (updatedRows: MappingRow[]): string => {
if (updatedRows.length === 0) {
@ -110,22 +146,33 @@ export function ModelMappingEditor({
return JSON.stringify(obj, null, 2)
}
const syncRows = (updatedRows: MappingRow[]) => {
setRows(updatedRows)
const duplicates = getDuplicateSources(updatedRows)
if (duplicates.length > 0) {
setJsonError(t('Duplicate source model mappings are not allowed'))
setJsonValue(DUPLICATE_MAPPING_SENTINEL)
props.onChange(DUPLICATE_MAPPING_SENTINEL)
return
}
const json = convertRowsToJson(updatedRows)
setJsonError(null)
setJsonValue(json)
props.onChange(json)
}
const handleAddRow = () => {
const newRow: MappingRow = {
id: createRowId(),
from: '',
to: '',
}
const updatedRows = [...rows, newRow]
setRows(updatedRows)
syncRows([...rows, newRow])
}
const handleDeleteRow = (id: string) => {
const updatedRows = rows.filter((row) => row.id !== id)
setRows(updatedRows)
const json = convertRowsToJson(updatedRows)
setJsonValue(json)
onChange(json)
syncRows(rows.filter((row) => row.id !== id))
}
const handleRowChange = (
@ -136,15 +183,12 @@ export function ModelMappingEditor({
const updatedRows = rows.map((row) =>
row.id === id ? { ...row, [field]: newValue } : row
)
setRows(updatedRows)
const json = convertRowsToJson(updatedRows)
setJsonValue(json)
onChange(json)
syncRows(updatedRows)
}
const handleJsonChange = (newJson: string) => {
setJsonValue(newJson)
onChange(newJson)
props.onChange(newJson)
parseJsonToRows(newJson)
}
@ -155,62 +199,69 @@ export function ModelMappingEditor({
2
)
setJsonValue(template)
onChange(template)
props.onChange(template)
parseJsonToRows(template)
}
const toggleMode = () => {
if (mode === 'visual') {
// Switching to JSON mode: sync rows to JSON
const json = convertRowsToJson(rows)
setJsonValue(json)
onChange(json)
const handleModeChange = (nextMode: string) => {
if (nextMode !== 'visual' && nextMode !== 'json') return
if (nextMode === 'json') {
const duplicates = getDuplicateSources(rows)
if (duplicates.length === 0) {
const json = convertRowsToJson(rows)
setJsonValue(json)
props.onChange(json)
}
setMode('json')
} else {
// Switching to visual mode: sync JSON to rows
parseJsonToRows(jsonValue)
setMode('visual')
return
}
parseJsonToRows(jsonValue)
setMode('visual')
}
return (
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<div className='flex gap-2'>
<Button
type='button'
variant='outline'
size='sm'
onClick={toggleMode}
disabled={disabled}
>
{mode === 'visual' ? (
<>
<Code className='mr-2 h-4 w-4' />
{t('JSON Mode')}
</>
) : (
<>
<Table className='mr-2 h-4 w-4' />
{t('Visual Mode')}
</>
)}
</Button>
<Tabs value={mode} onValueChange={handleModeChange} className='space-y-2'>
<div className='flex items-center justify-between gap-3'>
<TabsList>
<TabsTrigger value='visual'>
<Table className='h-4 w-4' aria-hidden='true' />
{t('Visual')}
</TabsTrigger>
<TabsTrigger value='json'>
<Code className='h-4 w-4' aria-hidden='true' />
{t('JSON')}
</TabsTrigger>
</TabsList>
<Button
type='button'
variant='link'
size='sm'
className='h-auto p-0'
onClick={handleFillTemplate}
disabled={disabled}
disabled={props.disabled}
>
{t('Fill Template')}
</Button>
</div>
</div>
{mode === 'visual' ? (
<div className='space-y-2'>
{jsonError && (
<Alert variant='destructive'>
<AlertDescription>{jsonError}</AlertDescription>
</Alert>
)}
{duplicateSources.length > 0 && (
<Alert>
<AlertDescription>
{t('Duplicate source model(s): {{models}}', {
models: duplicateSources.join(', '),
})}
</AlertDescription>
</Alert>
)}
<TabsContent value='visual' className='space-y-2'>
{rows.length > 0 ? (
<div className='space-y-2'>
<div className='grid grid-cols-[1fr_1fr_auto] gap-2 text-sm font-medium'>
@ -229,7 +280,8 @@ export function ModelMappingEditor({
handleRowChange(row.id, 'from', e.target.value)
}
placeholder='gpt-3.5-turbo'
disabled={disabled}
disabled={props.disabled}
list={sourceListId}
/>
<Input
value={row.to}
@ -237,17 +289,19 @@ export function ModelMappingEditor({
handleRowChange(row.id, 'to', e.target.value)
}
placeholder='gpt-3.5-turbo-0125'
disabled={disabled}
disabled={props.disabled}
list={targetListId}
/>
<Button
type='button'
variant='ghost'
size='icon'
onClick={() => handleDeleteRow(row.id)}
disabled={disabled}
disabled={props.disabled}
className='h-10 w-10'
aria-label={t('Delete mapping')}
>
<Trash2 className='h-4 w-4' />
<Trash2 className='h-4 w-4' aria-hidden='true' />
</Button>
</div>
))}
@ -264,22 +318,42 @@ export function ModelMappingEditor({
variant='outline'
size='sm'
onClick={handleAddRow}
disabled={disabled}
disabled={props.disabled}
className='w-full'
>
<Plus className='mr-2 h-4 w-4' />
{t('Add Mapping')}
</Button>
</div>
) : (
<Textarea
value={jsonValue}
onChange={(e) => handleJsonChange(e.target.value)}
placeholder={t('{"original-model": "replacement-model"}')}
disabled={disabled}
rows={8}
className={cn('font-mono text-sm')}
/>
</TabsContent>
<TabsContent value='json'>
<Textarea
value={jsonValue}
onChange={(e) => handleJsonChange(e.target.value)}
placeholder={t('{"original-model": "replacement-model"}')}
disabled={props.disabled}
rows={8}
className={cn(
'font-mono text-sm',
jsonError && 'border-destructive'
)}
aria-invalid={Boolean(jsonError)}
/>
</TabsContent>
</Tabs>
{props.sourceModelOptions && props.sourceModelOptions.length > 0 && (
<datalist id={sourceListId}>
{props.sourceModelOptions.map((model) => (
<option key={model} value={model} />
))}
</datalist>
)}
{props.targetModelOptions && props.targetModelOptions.length > 0 && (
<datalist id={targetListId}>
{props.targetModelOptions.map((model) => (
<option key={model} value={model} />
))}
</datalist>
)}
</div>
)

View File

@ -0,0 +1,106 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useMutation } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { createChannel, updateChannel } from '../api'
import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants'
import {
transformFormDataToCreatePayload,
transformFormDataToUpdatePayload,
type ChannelFormValues,
} from '../lib'
import type { Channel } from '../types'
type UseChannelMutateFormParams = {
currentRow?: Channel | null
isEditing: boolean
isMultiKeyChannel: boolean
onSuccess: () => void
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function getErrorMessage(error: unknown): string | undefined {
if (error instanceof Error && typeof error.message === 'string') {
return error.message
}
if (!isRecord(error)) return undefined
const response = error.response
if (isRecord(response)) {
const data = response.data
if (isRecord(data)) {
const message = data.message
if (typeof message === 'string') return message
}
}
const message = error.message
if (typeof message === 'string') return message
return undefined
}
export function useChannelMutateForm(props: UseChannelMutateFormParams) {
const { t } = useTranslation()
return useMutation({
mutationFn: async (data: ChannelFormValues): Promise<string> => {
if (props.isEditing && props.currentRow) {
const payload = transformFormDataToUpdatePayload(
data,
props.currentRow.id
)
const payloadWithKeyMode =
props.isMultiKeyChannel && data.key_mode
? {
...payload,
key_mode: data.key_mode,
}
: payload
const response = await updateChannel(
props.currentRow.id,
payloadWithKeyMode
)
if (!response.success) {
throw new Error(response.message || t(ERROR_MESSAGES.UPDATE_FAILED))
}
return SUCCESS_MESSAGES.UPDATED
}
const payload = transformFormDataToCreatePayload(data)
const response = await createChannel(payload)
if (!response.success) {
throw new Error(response.message || t(ERROR_MESSAGES.CREATE_FAILED))
}
return SUCCESS_MESSAGES.CREATED
},
onSuccess: (messageKey) => {
toast.success(t(messageKey))
props.onSuccess()
},
onError: (error: unknown) => {
toast.error(getErrorMessage(error) || t(ERROR_MESSAGES.CREATE_FAILED))
},
})
}

View File

@ -17,68 +17,249 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { z } from 'zod'
import { CHANNEL_STATUS, MODEL_FETCHABLE_TYPES } from '../constants'
import {
CHANNEL_STATUS,
ERROR_MESSAGES,
MODEL_FETCHABLE_TYPES,
} from '../constants'
import type { Channel } from '../types'
// ============================================================================
// Form Validation Schema
// ============================================================================
export const channelFormSchema = z.object({
name: z.string().min(1, 'Channel name is required'),
type: z.number().min(0, 'Channel type is required'),
base_url: z.string().optional(),
key: z.string(),
openai_organization: z.string().optional(),
models: z.string().min(1, 'At least one model is required'),
group: z.array(z.string()).min(1, 'At least one group is required'),
model_mapping: z.string().optional(),
priority: z.number().optional(),
weight: z.number().optional(),
test_model: z.string().optional(),
auto_ban: z.number().optional(),
status: z.number(),
status_code_mapping: z.string().optional(),
tag: z.string().optional(),
remark: z
.string()
.max(255, 'Remark must be less than 255 characters')
.optional(),
setting: z.string().optional(),
param_override: z.string().optional(),
header_override: z.string().optional(),
settings: z.string().optional(),
other: z.string().optional(),
// Multi-key options (not sent to backend directly)
multi_key_mode: z.enum(['single', 'batch', 'multi_to_single']).optional(),
multi_key_type: z.enum(['random', 'polling']).optional(),
batch_add_set_key_prefix_2_name: z.boolean().optional(),
key_mode: z.enum(['append', 'replace']).optional(), // For editing multi-key channels
// Channel extra settings (stored in setting JSON, not sent directly)
force_format: z.boolean().optional(),
thinking_to_content: z.boolean().optional(),
proxy: z.string().optional(),
pass_through_body_enabled: z.boolean().optional(),
system_prompt: z.string().optional(),
system_prompt_override: z.boolean().optional(),
// Type-specific settings (stored in settings JSON)
is_enterprise_account: z.boolean().optional(), // OpenRouter specific
vertex_key_type: z.enum(['json', 'api_key']).optional(), // Vertex AI specific
aws_key_type: z.enum(['ak_sk', 'api_key']).optional(), // AWS specific
azure_responses_version: z.string().optional(), // Azure specific
// Field passthrough controls (stored in settings JSON)
allow_service_tier: z.boolean().optional(), // OpenAI/Anthropic
disable_store: z.boolean().optional(), // OpenAI only
allow_safety_identifier: z.boolean().optional(), // OpenAI only
allow_include_obfuscation: z.boolean().optional(), // OpenAI: include usage obfuscation
allow_inference_geo: z.boolean().optional(), // OpenAI/Anthropic: inference geography
allow_speed: z.boolean().optional(), // Anthropic: speed mode control
claude_beta_query: z.boolean().optional(), // Anthropic: beta query passthrough
// Upstream model update settings (stored in settings JSON)
upstream_model_update_check_enabled: z.boolean().optional(),
upstream_model_update_auto_sync_enabled: z.boolean().optional(),
upstream_model_update_ignored_models: z.string().optional(),
})
function parseOptionalJson(value: string | undefined): unknown {
if (!value?.trim()) return undefined
return JSON.parse(value)
}
function isJsonObjectValue(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function isOptionalJsonObject(value: string | undefined): boolean {
try {
const parsed = parseOptionalJson(value)
return parsed === undefined || isJsonObjectValue(parsed)
} catch {
return false
}
}
function isOptionalModelMapping(value: string | undefined): boolean {
try {
const parsed = parseOptionalJson(value)
if (parsed === undefined) return true
if (!isJsonObjectValue(parsed)) return false
return Object.values(parsed).every((item) => typeof item === 'string')
} catch {
return false
}
}
function isOptionalStatusCodeMapping(value: string | undefined): boolean {
try {
const parsed = parseOptionalJson(value)
if (parsed === undefined) return true
if (!isJsonObjectValue(parsed)) return false
return Object.entries(parsed).every(([from, to]) => {
const fromCode = Number(from)
const toCode = Number(to)
return (
Number.isInteger(fromCode) &&
Number.isInteger(toCode) &&
fromCode >= 100 &&
fromCode <= 599 &&
toCode >= 100 &&
toCode <= 599
)
})
} catch {
return false
}
}
function isCodexCredential(value: string | undefined): boolean {
try {
const parsed = parseOptionalJson(value)
if (parsed === undefined) return true
return (
isJsonObjectValue(parsed) &&
typeof parsed.access_token === 'string' &&
parsed.access_token.trim().length > 0 &&
typeof parsed.account_id === 'string' &&
parsed.account_id.trim().length > 0
)
} catch {
return false
}
}
function isVertexJsonKey(value: string | undefined): boolean {
try {
const parsed = parseOptionalJson(value)
if (parsed === undefined) return true
if (Array.isArray(parsed)) {
return parsed.every((item) => isJsonObjectValue(item))
}
return isJsonObjectValue(parsed)
} catch {
return false
}
}
function addRequiredIssue(
ctx: z.RefinementCtx,
path: string,
message: string
): void {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [path],
message,
})
}
export const channelFormSchema = z
.object({
name: z.string().min(1, ERROR_MESSAGES.REQUIRED_NAME),
type: z.number().min(0, ERROR_MESSAGES.REQUIRED_TYPE),
base_url: z.string().optional(),
key: z.string(),
openai_organization: z.string().optional(),
models: z.string().min(1, ERROR_MESSAGES.REQUIRED_MODELS),
group: z.array(z.string()).min(1, ERROR_MESSAGES.REQUIRED_GROUP),
model_mapping: z
.string()
.optional()
.refine(
isOptionalModelMapping,
'Model mapping must be a JSON object with string values'
),
priority: z.number().optional(),
weight: z.number().optional(),
test_model: z.string().optional(),
auto_ban: z.number().optional(),
status: z.number(),
status_code_mapping: z
.string()
.optional()
.refine(
isOptionalStatusCodeMapping,
'Status code mapping must use valid HTTP status codes'
),
tag: z.string().optional(),
remark: z
.string()
.max(255, 'Remark must be less than 255 characters')
.optional(),
setting: z
.string()
.optional()
.refine(isOptionalJsonObject, ERROR_MESSAGES.INVALID_JSON),
param_override: z
.string()
.optional()
.refine(isOptionalJsonObject, ERROR_MESSAGES.INVALID_JSON),
header_override: z
.string()
.optional()
.refine(isOptionalJsonObject, ERROR_MESSAGES.INVALID_JSON),
settings: z
.string()
.optional()
.refine(isOptionalJsonObject, ERROR_MESSAGES.INVALID_JSON),
other: z.string().optional(),
// Multi-key options (not sent to backend directly)
multi_key_mode: z.enum(['single', 'batch', 'multi_to_single']).optional(),
multi_key_type: z.enum(['random', 'polling']).optional(),
batch_add_set_key_prefix_2_name: z.boolean().optional(),
key_mode: z.enum(['append', 'replace']).optional(), // For editing multi-key channels
// Channel extra settings (stored in setting JSON, not sent directly)
force_format: z.boolean().optional(),
thinking_to_content: z.boolean().optional(),
proxy: z.string().optional(),
pass_through_body_enabled: z.boolean().optional(),
system_prompt: z.string().optional(),
system_prompt_override: z.boolean().optional(),
// Type-specific settings (stored in settings JSON)
is_enterprise_account: z.boolean().optional(), // OpenRouter specific
vertex_key_type: z.enum(['json', 'api_key']).optional(), // Vertex AI specific
aws_key_type: z.enum(['ak_sk', 'api_key']).optional(), // AWS specific
azure_responses_version: z.string().optional(), // Azure specific
// Field passthrough controls (stored in settings JSON)
allow_service_tier: z.boolean().optional(), // OpenAI/Anthropic
disable_store: z.boolean().optional(), // OpenAI only
allow_safety_identifier: z.boolean().optional(), // OpenAI only
allow_include_obfuscation: z.boolean().optional(), // OpenAI: include usage obfuscation
allow_inference_geo: z.boolean().optional(), // OpenAI/Anthropic: inference geography
allow_speed: z.boolean().optional(), // Anthropic: speed mode control
claude_beta_query: z.boolean().optional(), // Anthropic: beta query passthrough
// Upstream model update settings (stored in settings JSON)
upstream_model_update_check_enabled: z.boolean().optional(),
upstream_model_update_auto_sync_enabled: z.boolean().optional(),
upstream_model_update_ignored_models: z.string().optional(),
})
.superRefine((data, ctx) => {
if ([3, 8, 36, 45].includes(data.type) && !data.base_url?.trim()) {
addRequiredIssue(
ctx,
'base_url',
'Base URL is required for this channel type'
)
}
if ([3, 18, 21, 39, 41, 49].includes(data.type) && !data.other?.trim()) {
addRequiredIssue(
ctx,
'other',
'This channel type requires additional configuration'
)
}
if (data.type === 57) {
if (data.multi_key_mode && data.multi_key_mode !== 'single') {
addRequiredIssue(
ctx,
'multi_key_mode',
'Codex channels do not support batch creation'
)
}
if (data.key?.trim() && !isCodexCredential(data.key)) {
addRequiredIssue(
ctx,
'key',
'Codex credential must be a JSON object with access_token and account_id'
)
}
}
if (
data.type === 41 &&
data.vertex_key_type === 'json' &&
data.key?.trim() &&
!isVertexJsonKey(data.key)
) {
addRequiredIssue(
ctx,
'key',
'Vertex AI service account key must be valid JSON'
)
}
if (
data.type === 41 &&
data.vertex_key_type === 'api_key' &&
data.multi_key_mode &&
data.multi_key_mode !== 'single'
) {
addRequiredIssue(
ctx,
'multi_key_mode',
'Vertex AI API Key mode does not support batch creation'
)
}
})
export type ChannelFormValues = z.infer<typeof channelFormSchema>
@ -389,6 +570,12 @@ function buildSettingsJSON(formData: ChannelFormValues): string {
return JSON.stringify(settingsObj)
}
function normalizeBaseUrl(value: string | undefined): string {
return String(value || '')
.trim()
.replace(/\/+$/, '')
}
/**
* Transform form data to API payload for creating channel
*/
@ -403,7 +590,7 @@ export function transformFormDataToCreatePayload(formData: ChannelFormValues): {
const channel: Partial<Channel> = {
name: formData.name,
type: formData.type,
base_url: formData.base_url || null,
base_url: normalizeBaseUrl(formData.base_url) || null,
key: formData.key,
openai_organization: formData.openai_organization || null,
models: formData.models,
@ -452,7 +639,7 @@ export function transformFormDataToUpdatePayload(
id: channelId,
name: formData.name,
type: formData.type,
base_url: formData.base_url || null,
base_url: normalizeBaseUrl(formData.base_url) || null,
openai_organization: formData.openai_organization || null,
models: formData.models,
group: formatGroups(formData.group),
@ -485,7 +672,7 @@ export function transformFormDataToUpdatePayload(
})
// Send explicit empty strings for nullable fields so GORM updates can clear them.
payload.base_url = formData.base_url || ''
payload.base_url = normalizeBaseUrl(formData.base_url) || ''
payload.openai_organization = formData.openai_organization || ''
payload.test_model = formData.test_model || ''
payload.tag = formData.tag || ''

View File

@ -179,6 +179,12 @@ export function validateModelMappingJson(modelMapping: string): {
error: 'Model mapping must be a valid JSON object',
}
}
if (Object.values(parsed).some((value) => typeof value !== 'string')) {
return {
valid: false,
error: 'Model mapping values must be strings',
}
}
return { valid: true }
} catch {
return {

View File

@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} IP(s)",
"{{count}} log entries removed.": "{{count}} log entries removed.",
"{{count}} minutes ago": "{{count}} minutes ago",
"{{count}} model(s)": "{{count}} model(s)",
"{{count}} models": "{{count}} models",
"{{count}} months ago": "{{count}} months ago",
"{{count}} override": "{{count}} override",
@ -135,6 +134,7 @@
"Actual Model": "Actual Model",
"Actual Model:": "Actual Model:",
"Add": "Add",
"Add \"{{value}}\"": "Add \"{{value}}\"",
"Add {{title}}": "Add {{title}}",
"Add a group identifier to the auto assignment list.": "Add a group identifier to the auto assignment list.",
"Add a new API key by providing necessary info.": "Add a new API key by providing necessary info.",
@ -152,7 +152,7 @@
"Add condition": "Add condition",
"Add Condition": "Add Condition",
"Add credits": "Add credits",
"Add custom model(s), comma-separated": "Add custom model(s), comma-separated",
"Add custom model \"{{value}}\"": "Add custom model \"{{value}}\"",
"Add discount tier": "Add discount tier",
"Add each model or tag you want to include.": "Add each model or tag you want to include.",
"Add FAQ": "Add FAQ",
@ -164,6 +164,7 @@
"Add group rules": "Add group rules",
"Add Mapping": "Add Mapping",
"Add method": "Add method",
"Add missing models": "Add missing models",
"Add Mode": "Add Mode",
"Add model": "Add model",
"Add Model": "Add Model",
@ -192,7 +193,6 @@
"Add User": "Add User",
"Add user group": "Add user group",
"Add your API keys, set up channels and configure access permissions": "Add your API keys, set up channels and configure access permissions",
"Added {{count}} custom model(s)": "Added {{count}} custom model(s)",
"Added {{count}} model(s)": "Added {{count}} model(s)",
"Added successfully": "Added successfully",
"Additional Conditions": "Additional Conditions",
@ -201,8 +201,8 @@
"Additional Limit": "Additional Limit",
"Additional Limits": "Additional Limits",
"Additional metered capability": "Additional metered capability",
"Adjust Quota": "Adjust Quota",
"Adjust filters, then search to refresh the logs.": "Adjust filters, then search to refresh the logs.",
"Adjust Quota": "Adjust Quota",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "Adjust response formatting, prompt behavior, proxy, and upstream automation.",
"Adjust the appearance and layout to suit your preferences.": "Adjust the appearance and layout to suit your preferences.",
"Admin": "Admin",
@ -424,6 +424,11 @@
"Audio Tokens": "Audio Tokens",
"Auth configured": "Auth configured",
"Auth Style": "Auth Style",
"auth.resetPasswordConfirm.backToLogin": "Return to login",
"auth.resetPasswordConfirm.confirm": "Confirm reset password",
"auth.resetPasswordConfirm.description": "Confirm the reset request to generate a new password.",
"auth.resetPasswordConfirm.retry": "Retry ({{seconds}}s)",
"auth.resetPasswordConfirm.success": "Your password has been reset successfully",
"Authentication": "Authentication",
"Authenticator code": "Authenticator code",
"Authorization Endpoint": "Authorization Endpoint",
@ -502,6 +507,7 @@
"Base Price": "Base Price",
"Base rate limit windows for this account.": "Base rate limit windows for this account.",
"Base URL": "Base URL",
"Base URL is required for this channel type": "Base URL is required for this channel type",
"Base URL of your Uptime Kuma instance": "Base URL of your Uptime Kuma instance",
"Basic Authentication": "Basic Authentication",
"Basic Configuration": "Basic Configuration",
@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Codex Account & Usage",
"Codex Authorization": "Codex Authorization",
"Codex channels do not support batch creation": "Codex channels do not support batch creation",
"Codex channels use an OAuth JSON credential as the key.": "Codex channels use an OAuth JSON credential as the key.",
"Codex CLI Header Passthrough": "Codex CLI Header Passthrough",
"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",
@ -867,8 +875,6 @@
"Confirm New Password": "Confirm New Password",
"Confirm password": "Confirm password",
"Confirm Payment": "Confirm Payment",
"auth.resetPasswordConfirm.confirm": "Confirm reset password",
"auth.resetPasswordConfirm.description": "Confirm the reset request to generate a new password.",
"Confirm Selection": "Confirm Selection",
"Confirm settings and finish setup": "Confirm settings and finish setup",
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "Create, revoke, and audit API tokens.",
"Created": "Created",
"Created At": "Created At",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.",
"Creating...": "Creating...",
"Creation failed": "Creation failed",
"Credential generated": "Credential generated",
@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "Delete invalid redemption codes",
"Delete Invalid Redemption Codes?": "Delete Invalid Redemption Codes?",
"Delete logs": "Delete logs",
"Delete mapping": "Delete mapping",
"Delete Model": "Delete Model",
"Delete Models?": "Delete Models?",
"Delete Provider": "Delete Provider",
@ -1249,6 +1257,8 @@
"Drawing task records": "Drawing task records",
"Duplicate": "Duplicate",
"Duplicate group names: {{names}}": "Duplicate group names: {{names}}",
"Duplicate source model mappings are not allowed": "Duplicate source model mappings are not allowed",
"Duplicate source model(s): {{models}}": "Duplicate source model(s): {{models}}",
"Duration": "Duration",
"Duration (hours)": "Duration (hours)",
"Duration Settings": "Duration Settings",
@ -2177,6 +2187,7 @@
"Load template...": "Load template...",
"Loader": "Loader",
"Loading": "Loading",
"Loading channel details": "Loading channel details",
"Loading configuration": "Loading configuration",
"Loading content settings...": "Loading content settings...",
"Loading current models...": "Loading current models...",
@ -2293,7 +2304,6 @@
"Minimum:": "Minimum:",
"Minor blips in the last 30 days": "Minor blips in the last 30 days",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Mint a fresh pair below — or pick an existing one further down. Click Save when ready.",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.",
"Minute": "Minute",
"minutes": "minutes",
"Missing code": "Missing code",
@ -2324,7 +2334,9 @@
"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",
"Model mapping must be a JSON object with string values": "Model mapping must be a JSON object with string values",
"Model mapping must be valid JSON": "Model mapping must be valid JSON",
"Model mapping values must be strings": "Model mapping values must be strings",
"Model name": "Model name",
"Model Name": "Model Name",
"Model Name *": "Model Name *",
@ -2333,8 +2345,8 @@
"Model not found": "Model not found",
"Model performance metrics": "Model performance metrics",
"Model Price": "Model Price",
"Model Price Not Configured": "Model Price Not Configured",
"Model price is not configured. Please complete model pricing in settings.": "Model price is not configured. Please complete model pricing in settings.",
"Model Price Not Configured": "Model Price Not Configured",
"Model prices": "Model prices",
"Model prices reset successfully": "Model prices reset successfully",
"Model Pricing": "Model Pricing",
@ -2532,6 +2544,7 @@
"No Logs Found": "No Logs Found",
"No mappings configured. Click \"Add Row\" to get started.": "No mappings configured. Click \"Add Row\" to get started.",
"No matches found": "No matches found",
"No matching items": "No matching items",
"No matching results": "No matching results",
"No matching rules": "No matching rules",
"No messages yet": "No messages yet",
@ -2950,6 +2963,7 @@
"Please upload key file(s)": "Please upload key file(s)",
"Please wait a moment before trying again.": "Please wait a moment before trying again.",
"Please wait a moment, human check is initializing...": "Please wait a moment, human check is initializing...",
"Please wait before editing to avoid overwriting saved values.": "Please wait before editing to avoid overwriting saved values.",
"Policy JSON": "Policy JSON",
"Polling": "Polling",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded",
@ -2983,6 +2997,7 @@
"Prepend to Start": "Prepend to Start",
"Prepend value to array / string / object start": "Prepend value to array / string / object start",
"Preserve the original field when applying this rule": "Preserve the original field when applying this rule",
"Preset groups": "Preset groups",
"Preset recharge amounts (JSON array)": "Preset recharge amounts (JSON array)",
"Preset recharge amounts displayed to users": "Preset recharge amounts displayed to users",
"Preset Template": "Preset Template",
@ -3098,6 +3113,7 @@
"Querying...": "Querying...",
"Question": "Question",
"Queued": "Queued",
"Quick actions": "Quick actions",
"Quick insert common payment methods": "Quick insert common payment methods",
"Quick Range": "Quick Range",
"Quick Setup from Preset": "Quick Setup from Preset",
@ -3219,6 +3235,7 @@
"Remaining:": "Remaining:",
"Remark": "Remark",
"Remove": "Remove",
"Remove {{value}}": "Remove {{value}}",
"Remove ${{amount}}": "Remove ${{amount}}",
"Remove all log entries created before the selected timestamp.": "Remove all log entries created before the selected timestamp.",
"Remove attachment": "Remove attachment",
@ -3226,6 +3243,7 @@
"Remove Duplicates": "Remove Duplicates",
"Remove filter": "Remove filter",
"Remove functionResponse.id field": "Remove functionResponse.id field",
"Remove mapped targets": "Remove mapped targets",
"Remove Models": "Remove Models",
"Remove Passkey": "Remove Passkey",
"Remove Passkey?": "Remove Passkey?",
@ -3329,7 +3347,6 @@
"Retain last N files": "Retain last N files",
"Retention days": "Retention days",
"Retry": "Retry",
"auth.resetPasswordConfirm.retry": "Retry ({{seconds}}s)",
"Retry Chain": "Retry Chain",
"Retry Suggestion": "Retry Suggestion",
"Retry Times": "Retry Times",
@ -3339,7 +3356,6 @@
"Return Error": "Return Error",
"Return per-token log probabilities": "Return per-token log probabilities",
"Return to dashboard": "Return to dashboard",
"auth.resetPasswordConfirm.backToLogin": "Return to login",
"Return vector embeddings for inputs": "Return vector embeddings for inputs",
"Reveal API key": "Reveal API key",
"Reveal key": "Reveal key",
@ -3491,7 +3507,6 @@
"Select all (filtered)": "Select all (filtered)",
"Select all models": "Select all models",
"Select All Visible": "Select All Visible",
"Select model {{model}}": "Select model {{model}}",
"Select an operation mode and enter the amount": "Select an operation mode and enter the amount",
"Select announcement type": "Select announcement type",
"Select at least one field to overwrite.": "Select at least one field to overwrite.",
@ -3518,6 +3533,7 @@
"Select layout style": "Select layout style",
"Select locations": "Select locations",
"Select Model": "Select Model",
"Select model {{model}}": "Select model {{model}}",
"Select models (empty for allow all)": "Select models (empty for allow all)",
"Select models and apply to channel models list.": "Select models and apply to channel models list.",
"Select models or add custom ones": "Select models or add custom ones",
@ -3547,6 +3563,7 @@
"Select vendor": "Select vendor",
"Selectable groups": "Selectable groups",
"selected": "selected",
"Selected {{count}}": "Selected {{count}}",
"selected channel(s). Leave empty to remove tag.": "selected channel(s). Leave empty to remove tag.",
"Selected conflicts were overwritten successfully.": "Selected conflicts were overwritten successfully.",
"Selected when creating a token and used as the default billing group for API calls.": "Selected when creating a token and used as the default billing group for API calls.",
@ -3690,6 +3707,7 @@
"Status & Sync": "Status & Sync",
"Status Code": "Status Code",
"Status Code Mapping": "Status Code Mapping",
"Status code mapping must use valid HTTP status codes": "Status code mapping must use valid HTTP status codes",
"Status Page Slug": "Status Page Slug",
"Status short": "Status",
"Status:": "Status:",
@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "This action will permanently remove 2FA protection from your account.",
"This channel is not an Ollama channel.": "This channel is not an Ollama channel.",
"This channel type does not support fetching models": "This channel type does not support fetching models",
"This channel type requires additional configuration": "This channel type requires additional configuration",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.",
"This data may be unreliable, use with caution": "This data may be unreliable, use with caution",
@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "Use external tools to extend capabilities",
"Use our unified OpenAI-compatible endpoint in your applications": "Use our unified OpenAI-compatible endpoint in your applications",
"Use Passkey to sign in without entering your password.": "Use Passkey to sign in without entering your password.",
"Use presets or upstream discovery to populate the model list faster.": "Use presets or upstream discovery to populate the model list faster.",
"Use secure connection when sending emails": "Use secure connection when sending emails",
"Use sidebar shortcut": "Use sidebar shortcut",
"Use the full-width table to scan prices, then select a row to edit it here.": "Use the full-width table to scan prices, then select a row to edit it here.",
@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "Verifying credentials and pulling stores from your Pancake account...",
"Version Overrides": "Version Overrides",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Vertex AI API Key mode does not support batch creation",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.",
"Vertex AI Key Format": "Vertex AI Key Format",
"Vertex AI service account key must be valid JSON": "Vertex AI service account key must be valid JSON",
"Video": "Video",
"Video length in seconds": "Video length in seconds",
"Video Remix": "Video Remix",
@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "Your GitHub OAuth Client ID",
"Your GitHub OAuth Client Secret": "Your GitHub OAuth Client Secret",
"Your new backup codes are ready": "Your new backup codes are ready",
"auth.resetPasswordConfirm.success": "Your password has been reset successfully",
"Your Referral Link": "Your Referral Link",
"Your setup guide is collapsed so usage stays in focus.": "Your setup guide is collapsed so usage stays in focus.",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "Your system access token for API authentication. Keep it secure and don't share it with others.",

View File

@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} IP",
"{{count}} log entries removed.": "{{count}} entrées de journal supprimées.",
"{{count}} minutes ago": "il y a {{count}} minutes",
"{{count}} model(s)": "{{count}} modèle(s)",
"{{count}} models": "{{count}} modèles",
"{{count}} months ago": "il y a {{count}} mois",
"{{count}} override": "{{count}} remplacement",
@ -135,6 +134,7 @@
"Actual Model": "Modèle réel",
"Actual Model:": "Modèle réel :",
"Add": "Ajouter",
"Add \"{{value}}\"": "Ajouter \"{{value}}\"",
"Add {{title}}": "Ajouter {{title}}",
"Add a group identifier to the auto assignment list.": "Ajouter un identifiant de groupe à la liste d'affectation automatique.",
"Add a new API key by providing necessary info.": "Ajoutez une nouvelle clé API en fournissant les informations nécessaires.",
@ -152,7 +152,7 @@
"Add condition": "Ajouter une condition",
"Add Condition": "Ajouter une condition",
"Add credits": "Ajouter des crédits",
"Add custom model(s), comma-separated": "Ajouter un ou plusieurs modèles personnalisés, séparés par des virgules",
"Add custom model \"{{value}}\"": "Ajouter le modèle personnalisé « {{value}} »",
"Add discount tier": "Ajouter un niveau de réduction",
"Add each model or tag you want to include.": "Ajoutez chaque modèle ou étiquette que vous souhaitez inclure.",
"Add FAQ": "Ajouter une FAQ",
@ -164,6 +164,7 @@
"Add group rules": "Ajouter des règles de groupe",
"Add Mapping": "Ajouter un mappage",
"Add method": "Ajouter une méthode",
"Add missing models": "Ajouter les modèles manquants",
"Add Mode": "Ajouter un mode",
"Add model": "Ajouter un modèle",
"Add Model": "Ajouter un modèle",
@ -192,7 +193,6 @@
"Add User": "Ajouter un utilisateur",
"Add user group": "Ajouter un groupe d'utilisateurs",
"Add your API keys, set up channels and configure access permissions": "Ajoutez vos clés API, configurez les canaux et les permissions d'accès",
"Added {{count}} custom model(s)": "{{count}} modèle(s) personnalisé(s) ajouté(s)",
"Added {{count}} model(s)": "{{count}} modèle(s) ajouté(s)",
"Added successfully": "Ajouté avec succès",
"Additional Conditions": "Conditions supplémentaires",
@ -201,8 +201,8 @@
"Additional Limit": "Limite supplémentaire",
"Additional Limits": "Limites supplémentaires",
"Additional metered capability": "Fonctionnalité supplémentaire facturée à lusage",
"Adjust Quota": "Ajuster le quota",
"Adjust filters, then search to refresh the logs.": "Ajustez les filtres, puis lancez la recherche pour actualiser les journaux.",
"Adjust Quota": "Ajuster le quota",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "Ajustez le formatage des réponses, le comportement des prompts, le proxy et lautomatisation amont.",
"Adjust the appearance and layout to suit your preferences.": "Ajustez l'apparence et la mise en page selon vos préférences.",
"Admin": "Administrateur",
@ -424,6 +424,11 @@
"Audio Tokens": "Jetons audio",
"Auth configured": "Authentification configurée",
"Auth Style": "Style d'authentification",
"auth.resetPasswordConfirm.backToLogin": "Retour à la connexion",
"auth.resetPasswordConfirm.confirm": "Confirmer la réinitialisation du mot de passe",
"auth.resetPasswordConfirm.description": "Confirmez la demande de réinitialisation pour générer un nouveau mot de passe.",
"auth.resetPasswordConfirm.retry": "Réessayer ({{seconds}}s)",
"auth.resetPasswordConfirm.success": "Votre mot de passe a été réinitialisé avec succès",
"Authentication": "Authentification",
"Authenticator code": "Code d'authentification",
"Authorization Endpoint": "Point de terminaison d'autorisation",
@ -502,6 +507,7 @@
"Base Price": "Prix de base",
"Base rate limit windows for this account.": "Fenêtres de limitation de débit de base pour ce compte.",
"Base URL": "URL de base",
"Base URL is required for this channel type": "L'URL de base est requise pour ce type de canal",
"Base URL of your Uptime Kuma instance": "URL de base de votre instance Uptime Kuma",
"Basic Authentication": "Authentification de base",
"Basic Configuration": "Configuration de base",
@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Compte et utilisation Codex",
"Codex Authorization": "Autorisation Codex",
"Codex channels do not support batch creation": "Les canaux Codex ne prennent pas en charge la création par lot",
"Codex channels use an OAuth JSON credential as the key.": "Les canaux Codex utilisent un identifiant OAuth JSON comme clé.",
"Codex CLI Header Passthrough": "Passthrough en-tête Codex CLI",
"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 réduire",
@ -867,8 +875,6 @@
"Confirm New Password": "Confirmer le nouveau mot de passe",
"Confirm password": "Confirmer le mot de passe",
"Confirm Payment": "Confirmer le paiement",
"auth.resetPasswordConfirm.confirm": "Confirmer la réinitialisation du mot de passe",
"auth.resetPasswordConfirm.description": "Confirmez la demande de réinitialisation pour générer un nouveau mot de passe.",
"Confirm Selection": "Confirmer la sélection",
"Confirm settings and finish setup": "Confirmez les paramètres et terminez la configuration",
"confirm that I bear legal responsibility arising from deployment": "confirme assumer la responsabilité juridique découlant du déploiement",
@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "Créer, révoquer et auditer les jetons API.",
"Created": "Créé",
"Created At": "Créé le",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Crée un produit Pancake dans la boutique enregistrée avec le titre et le prix de ce forfait. Waffo Pancake doit dabord être entièrement configuré dans les paramètres de paiement.",
"Creating...": "Création...",
"Creation failed": "Échec de la création",
"Credential generated": "Identifiant généré",
@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "Supprimer les codes de rachat invalides",
"Delete Invalid Redemption Codes?": "Supprimer les codes de rachat invalides ?",
"Delete logs": "Supprimer les journaux",
"Delete mapping": "Supprimer le mappage",
"Delete Model": "Supprimer le modèle",
"Delete Models?": "Supprimer les modèles ?",
"Delete Provider": "Supprimer le fournisseur",
@ -1249,6 +1257,8 @@
"Drawing task records": "Historique des tâches de dessin",
"Duplicate": "Dupliquer",
"Duplicate group names: {{names}}": "Noms de groupe en double : {{names}}",
"Duplicate source model mappings are not allowed": "Les mappages de modèles source en double ne sont pas autorisés",
"Duplicate source model(s): {{models}}": "Modèle(s) source en double : {{models}}",
"Duration": "Durée",
"Duration (hours)": "Durée (heures)",
"Duration Settings": "Paramètres de durée",
@ -1935,7 +1945,7 @@
"How client credentials are sent to the token endpoint": "Comment les informations d'identification client sont envoyées au point de terminaison de jeton",
"How frequently the system tests all channels": "Fréquence à laquelle le système teste tous les canaux",
"How It Works": "Comment ça marche",
"How model mapping works": "Comment fonctionne le mappage de modèle",
"How model mapping works": "Fonctionnement du mappage des modèles",
"How much to charge for each US dollar of balance (Epay)": "Montant à facturer pour chaque dollar US de solde (Epay)",
"How this model name should match requests": "Comment ce nom de modèle doit correspondre aux requêtes",
"How to deliver the resulting image": "Comment délivrer l'image résultante",
@ -2177,6 +2187,7 @@
"Load template...": "Charger le modèle...",
"Loader": "Chargeur",
"Loading": "Chargement",
"Loading channel details": "Chargement des détails du canal",
"Loading configuration": "Chargement de la configuration",
"Loading content settings...": "Chargement des paramètres de contenu...",
"Loading current models...": "Chargement des modèles actuels...",
@ -2293,7 +2304,6 @@
"Minimum:": "Minimum :",
"Minor blips in the last 30 days": "Légères perturbations sur les 30 derniers jours",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Créez une nouvelle paire ci-dessous, ou choisissez une paire existante plus bas. Cliquez sur Enregistrer lorsque vous êtes prêt.",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Crée un produit Pancake dans la boutique enregistrée avec le titre et le prix de ce forfait. Waffo Pancake doit dabord être entièrement configuré dans les paramètres de paiement.",
"Minute": "Minute",
"minutes": "minutes",
"Missing code": "Code manquant",
@ -2324,7 +2334,9 @@
"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",
"Model mapping must be a JSON object with string values": "Le mappage de modèles doit être un objet JSON avec des valeurs de chaîne",
"Model mapping must be valid JSON": "La cartographie des modèles doit être un JSON valide",
"Model mapping values must be strings": "Les valeurs du mappage de modèles doivent être des chaînes",
"Model name": "Nom du modèle",
"Model Name": "Nom du modèle",
"Model Name *": "Nom du modèle *",
@ -2333,8 +2345,8 @@
"Model not found": "Modèle introuvable",
"Model performance metrics": "Indicateurs de performance des modèles",
"Model Price": "Prix du modèle",
"Model Price Not Configured": "Prix du modèle non configuré",
"Model price is not configured. Please complete model pricing in settings.": "Le prix du modèle n'est pas configuré. Veuillez compléter la tarification du modèle dans les paramètres.",
"Model Price Not Configured": "Prix du modèle non configuré",
"Model prices": "Prix des modèles",
"Model prices reset successfully": "Prix des modèles réinitialisés avec succès",
"Model Pricing": "Tarification des modèles",
@ -2532,6 +2544,7 @@
"No Logs Found": "Aucun journal trouvé",
"No mappings configured. Click \"Add Row\" to get started.": "Aucun mappage configuré. Cliquez sur « Ajouter une ligne » pour commencer.",
"No matches found": "Aucune correspondance trouvée",
"No matching items": "Aucun élément correspondant",
"No matching results": "Aucun résultat correspondant",
"No matching rules": "Aucune règle correspondante",
"No messages yet": "Pas encore de messages",
@ -2950,6 +2963,7 @@
"Please upload key file(s)": "Veuillez télécharger le (s) fichier(s) clé (s)",
"Please wait a moment before trying again.": "Veuillez patienter un instant avant de réessayer.",
"Please wait a moment, human check is initializing...": "Veuillez patienter un instant, la vérification humaine s'initialise...",
"Please wait before editing to avoid overwriting saved values.": "Veuillez patienter avant de modifier afin d'éviter d'écraser les valeurs enregistrées.",
"Policy JSON": "JSON de stratégie",
"Polling": "Sondage",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "Le mode d'interrogation nécessite Redis et un cache mémoire, sinon les performances seront considérablement dégradées",
@ -2983,6 +2997,7 @@
"Prepend to Start": "Ajouter au début",
"Prepend value to array / string / object start": "Ajouter la valeur au début du tableau / chaîne / objet",
"Preserve the original field when applying this rule": "Conserver le champ original lors de lapplication de cette règle",
"Preset groups": "Groupes prédéfinis",
"Preset recharge amounts (JSON array)": "Montants de recharge prédéfinis (tableau JSON)",
"Preset recharge amounts displayed to users": "Montants de recharge prédéfinis affichés aux utilisateurs",
"Preset Template": "Modèle prédéfini",
@ -3098,6 +3113,7 @@
"Querying...": "Recherche en cours...",
"Question": "Question",
"Queued": "En file",
"Quick actions": "Actions rapides",
"Quick insert common payment methods": "Insérer rapidement les méthodes de paiement courantes",
"Quick Range": "Plage rapide",
"Quick Setup from Preset": "Configuration rapide à partir d'un préréglage",
@ -3219,6 +3235,7 @@
"Remaining:": "Restant :",
"Remark": "Remarque",
"Remove": "Supprimer",
"Remove {{value}}": "Retirer {{value}}",
"Remove ${{amount}}": "Supprimer ${{amount}}",
"Remove all log entries created before the selected timestamp.": "Supprimer toutes les entrées de journal créées avant l'horodatage sélectionné.",
"Remove attachment": "Supprimer la pièce jointe",
@ -3226,6 +3243,7 @@
"Remove Duplicates": "Supprimer les doublons",
"Remove filter": "Supprimer le filtre",
"Remove functionResponse.id field": "Supprimer le champ functionResponse.id",
"Remove mapped targets": "Retirer les cibles mappées",
"Remove Models": "Supprimer des modèles",
"Remove Passkey": "Supprimer le Passkey",
"Remove Passkey?": "Supprimer la clé d'accès ?",
@ -3329,7 +3347,6 @@
"Retain last N files": "Conserver les N derniers fichiers",
"Retention days": "Jours de rétention",
"Retry": "Réessayer",
"auth.resetPasswordConfirm.retry": "Réessayer ({{seconds}}s)",
"Retry Chain": "Chaîne de tentatives",
"Retry Suggestion": "Suggestion de relance",
"Retry Times": "Nombre de tentatives",
@ -3339,7 +3356,6 @@
"Return Error": "Retourner l'erreur",
"Return per-token log probabilities": "Retourner les log-probabilités par jeton",
"Return to dashboard": "Retour au tableau de bord",
"auth.resetPasswordConfirm.backToLogin": "Retour à la connexion",
"Return vector embeddings for inputs": "Renvoyer des embeddings vectoriels pour les entrées",
"Reveal API key": "Afficher la clé API",
"Reveal key": "Révéler la clé",
@ -3491,7 +3507,6 @@
"Select all (filtered)": "Tout sélectionner (filtré)",
"Select all models": "Sélectionner tous les modèles",
"Select All Visible": "Sélectionner tout ce qui est visible",
"Select model {{model}}": "Sélectionner le modèle {{model}}",
"Select an operation mode and enter the amount": "Sélectionnez un mode d'opération et entrez le montant",
"Select announcement type": "Sélectionner le type d'annonce",
"Select at least one field to overwrite.": "Sélectionnez au moins un champ à écraser.",
@ -3518,6 +3533,7 @@
"Select layout style": "Sélectionner le style de mise en page",
"Select locations": "Sélectionner des emplacements",
"Select Model": "Sélectionner le modèle",
"Select model {{model}}": "Sélectionner le modèle {{model}}",
"Select models (empty for allow all)": "Sélectionner les modèles (vide pour autoriser tout)",
"Select models and apply to channel models list.": "Sélectionnez les modèles et appliquez-les à la liste des modèles de canaux.",
"Select models or add custom ones": "Sélectionner des modèles ou en ajouter des personnalisés",
@ -3547,6 +3563,7 @@
"Select vendor": "Sélectionner le fournisseur",
"Selectable groups": "Groupes sélectionnables",
"selected": "sélectionné",
"Selected {{count}}": "{{count}} sélectionné(s)",
"selected channel(s). Leave empty to remove tag.": "canal(aux) sélectionné(s). Laisser vide pour supprimer l'étiquette.",
"Selected conflicts were overwritten successfully.": "Les conflits sélectionnés ont été écrasés avec succès.",
"Selected when creating a token and used as the default billing group for API calls.": "Sélectionné lors de la création dun jeton et utilisé comme groupe de facturation par défaut pour les appels API.",
@ -3690,6 +3707,7 @@
"Status & Sync": "Statut et synchronisation",
"Status Code": "Code de statut",
"Status Code Mapping": "Mappage des codes d'état",
"Status code mapping must use valid HTTP status codes": "Le mappage des codes d'état doit utiliser des codes d'état HTTP valides",
"Status Page Slug": "Slug de la page d'état",
"Status short": "État",
"Status:": "Statut :",
@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "Cette action supprimera définitivement la protection 2FA de votre compte.",
"This channel is not an Ollama channel.": "Ce canal n'est pas un canal Ollama.",
"This channel type does not support fetching models": "Ce type de canal ne prend pas en charge la récupération de modèles",
"This channel type requires additional configuration": "Ce type de canal nécessite une configuration supplémentaire",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Cette confirmation déverrouille les fonctionnalités de paiement, de codes de兑换, de forfaits dabonnement et de récompenses dinvitation. Veuillez lire attentivement les déclarations.",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Ce réglage contrôle la limitation des requêtes de modèles. La limitation des routes Web/API se configure via les variables d'environnement et peut encore renvoyer 429.",
"This data may be unreliable, use with caution": "Ces données peuvent être peu fiables, utilisez-les avec prudence",
@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "Utiliser des outils externes pour étendre les capacités",
"Use our unified OpenAI-compatible endpoint in your applications": "Utilisez notre point de terminaison unifié compatible OpenAI dans vos applications",
"Use Passkey to sign in without entering your password.": "Utilisez une clé d'accès (Passkey) pour vous connecter sans saisir votre mot de passe.",
"Use presets or upstream discovery to populate the model list faster.": "Utilisez des préréglages ou la découverte en amont pour remplir plus vite la liste des modèles.",
"Use secure connection when sending emails": "Utiliser une connexion sécurisée lors de l'envoi d'e-mails",
"Use sidebar shortcut": "Utiliser le raccourci de la barre latérale",
"Use the full-width table to scan prices, then select a row to edit it here.": "Parcourez les prix dans le tableau, puis sélectionnez une ligne pour la modifier ici.",
@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "Vérification des identifiants et récupération des boutiques depuis votre compte Pancake...",
"Version Overrides": "Remplacements de version",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Le mode clé API Vertex AI ne prend pas en charge la création par lot",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI ne prend pas en charge functionResponse.id. Activez ceci pour supprimer automatiquement ce champ.",
"Vertex AI Key Format": "Format de clé Vertex AI",
"Vertex AI service account key must be valid JSON": "La clé de compte de service Vertex AI doit être un JSON valide",
"Video": "Vidéo",
"Video length in seconds": "Durée de la vidéo en secondes",
"Video Remix": "Remix vidéo",
@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "Votre ID Client OAuth GitHub",
"Your GitHub OAuth Client Secret": "Votre Secret Client OAuth GitHub",
"Your new backup codes are ready": "Vos nouveaux codes de secours sont prêts",
"auth.resetPasswordConfirm.success": "Votre mot de passe a été réinitialisé avec succès",
"Your Referral Link": "Votre lien de parrainage",
"Your setup guide is collapsed so usage stays in focus.": "Le guide de configuration est réduit afin de garder l'utilisation au premier plan.",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "Votre jeton d'accès système pour l'authentification API. Gardez-le en sécurité et ne le partagez pas avec d'autres.",

View File

@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} IP",
"{{count}} log entries removed.": "{{count}} 件のログエントリを削除しました。",
"{{count}} minutes ago": "{{count}} 分前",
"{{count}} model(s)": "{{count}} モデル",
"{{count}} models": "{{count}} モデル",
"{{count}} months ago": "{{count}} ヶ月前",
"{{count}} override": "{{count}} 個のオーバーライド",
@ -135,6 +134,7 @@
"Actual Model": "実際のモデル",
"Actual Model:": "実際のモデル:",
"Add": "追加",
"Add \"{{value}}\"": "\"{{value}}\" を追加",
"Add {{title}}": "{{title}}を追加",
"Add a group identifier to the auto assignment list.": "自動割り当てリストにグループ識別子を追加します。",
"Add a new API key by providing necessary info.": "必要な情報を提供して新しいAPIキーを追加。",
@ -152,7 +152,7 @@
"Add condition": "条件を追加",
"Add Condition": "条件を追加",
"Add credits": "クレジットを追加",
"Add custom model(s), comma-separated": "カスタムモデルを追加 (コンマ区切り)",
"Add custom model \"{{value}}\"": "カスタムモデル「{{value}}」を追加",
"Add discount tier": "割引ティアを追加",
"Add each model or tag you want to include.": "含めたい各モデルまたはタグを追加。",
"Add FAQ": "FAQ追加",
@ -164,6 +164,7 @@
"Add group rules": "グループルールを追加",
"Add Mapping": "マッピングを追加",
"Add method": "メソッドを追加",
"Add missing models": "不足しているモデルを追加",
"Add Mode": "モードを追加",
"Add model": "モデル追加",
"Add Model": "モデルを追加",
@ -192,7 +193,6 @@
"Add User": "ユーザーを追加",
"Add user group": "ユーザーグループを追加",
"Add your API keys, set up channels and configure access permissions": "APIキーを追加し、チャネルを設定してアクセス権限を構成します",
"Added {{count}} custom model(s)": "{{count}} 個のカスタムモデルを追加しました",
"Added {{count}} model(s)": "{{count}} 個のモデルを追加しました",
"Added successfully": "追加に成功しました",
"Additional Conditions": "追加条件",
@ -201,8 +201,8 @@
"Additional Limit": "追加上限",
"Additional Limits": "追加上限",
"Additional metered capability": "追加の従量制機能",
"Adjust Quota": "クォータを調整",
"Adjust filters, then search to refresh the logs.": "フィルターを調整してから検索し、ログを更新します。",
"Adjust Quota": "クォータを調整",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "レスポンス形式、プロンプト動作、プロキシ、上流自動化を調整します。",
"Adjust the appearance and layout to suit your preferences.": "好みに合わせて外観とレイアウトを調整します。",
"Admin": "管理者",
@ -424,6 +424,11 @@
"Audio Tokens": "音声トークン",
"Auth configured": "認証設定済み",
"Auth Style": "認証スタイル",
"auth.resetPasswordConfirm.backToLogin": "ログインに戻る",
"auth.resetPasswordConfirm.confirm": "パスワードリセットを確認",
"auth.resetPasswordConfirm.description": "新しいパスワードを生成するには、リセット要求を確認してください。",
"auth.resetPasswordConfirm.retry": "再試行 ({{seconds}}秒)",
"auth.resetPasswordConfirm.success": "パスワードが正常にリセットされました",
"Authentication": "認証",
"Authenticator code": "認証コード",
"Authorization Endpoint": "認可エンドポイント",
@ -502,6 +507,7 @@
"Base Price": "基本価格",
"Base rate limit windows for this account.": "このアカウント向けの基本レート制限ウィンドウ。",
"Base URL": "ベースURL",
"Base URL is required for this channel type": "このチャンネルタイプには Base URL が必要です",
"Base URL of your Uptime Kuma instance": "Uptime KumaインスタンスのベースURL",
"Basic Authentication": "基本認証",
"Basic Configuration": "基本設定",
@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Codex アカウントと使用量",
"Codex Authorization": "Codex認証",
"Codex channels do not support batch creation": "Codex チャンネルは一括作成をサポートしていません",
"Codex channels use an OAuth JSON credential as the key.": "CodexチャンネルはOAuth JSON認証情報をキーとして使用します。",
"Codex CLI Header Passthrough": "Codex CLI ヘッダーパススルー",
"Codex credential must be a JSON object with access_token and account_id": "Codex 認証情報は access_token と account_id を含む JSON オブジェクトである必要があります",
"Cohere": "Cohere",
"Collapse": "折りたたむ",
"Collapse All": "すべて折りたたむ",
@ -867,8 +875,6 @@
"Confirm New Password": "新しいパスワードの確認",
"Confirm password": "パスワードの確認",
"Confirm Payment": "支払いの確認",
"auth.resetPasswordConfirm.confirm": "パスワードリセットを確認",
"auth.resetPasswordConfirm.description": "新しいパスワードを生成するには、リセット要求を確認してください。",
"Confirm Selection": "選択の確認",
"Confirm settings and finish setup": "設定を確認してセットアップを完了",
"confirm that I bear legal responsibility arising from deployment": "デプロイに起因する法的責任を負うことを確認します",
@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "APIトークンを作成、取り消し、監査。",
"Created": "作成済み",
"Created At": "作成日時",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "保存済みストアに、このプランのタイトルと価格を使って Pancake 商品を作成します。事前に支払い設定で Waffo Pancake を完全に設定する必要があります。",
"Creating...": "作成中...",
"Creation failed": "作成に失敗しました",
"Credential generated": "認証情報を生成しました",
@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "無効な引き換えコードを削除",
"Delete Invalid Redemption Codes?": "無効な引き換えコードを削除しますか?",
"Delete logs": "ログを削除",
"Delete mapping": "マッピングを削除",
"Delete Model": "モデルを削除",
"Delete Models?": "モデルを削除しますか?",
"Delete Provider": "プロバイダーを削除",
@ -1249,6 +1257,8 @@
"Drawing task records": "描画タスク記録",
"Duplicate": "複製",
"Duplicate group names: {{names}}": "重複するグループ名: {{names}}",
"Duplicate source model mappings are not allowed": "重複したソースモデルのマッピングは許可されていません",
"Duplicate source model(s): {{models}}": "重複したソースモデル: {{models}}",
"Duration": "所要時間",
"Duration (hours)": "期間(時間)",
"Duration Settings": "有効期間設定",
@ -2177,6 +2187,7 @@
"Load template...": "テンプレートをロード...",
"Loader": "ローダー",
"Loading": "読み込み中",
"Loading channel details": "チャンネル詳細を読み込み中",
"Loading configuration": "設定を読み込んでいます",
"Loading content settings...": "コンテンツ設定をロード中...",
"Loading current models...": "現在のモデルをロード中...",
@ -2293,7 +2304,6 @@
"Minimum:": "最小:",
"Minor blips in the last 30 days": "直近 30 日で軽微な障害あり",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "下で新しいペアを作成するか、さらに下で既存のものを選択してください。準備ができたら保存をクリックします。",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "保存済みストアに、このプランのタイトルと価格を使って Pancake 商品を作成します。事前に支払い設定で Waffo Pancake を完全に設定する必要があります。",
"Minute": "分",
"minutes": "分",
"Missing code": "コードが不足しています",
@ -2324,7 +2334,9 @@
"Model Mapping": "モデルマッピング",
"Model Mapping (JSON)": "モデルマッピング (JSON)",
"Model Mapping must be a JSON object like": "モデルマッピングは次のようなJSONオブジェクトである必要があります",
"Model mapping must be a JSON object with string values": "モデルマッピングは文字列値を持つ JSON オブジェクトである必要があります",
"Model mapping must be valid JSON": "モデルマッピングは有効な JSON である必要があります",
"Model mapping values must be strings": "モデルマッピングの値は文字列である必要があります",
"Model name": "モデル名",
"Model Name": "モデル名",
"Model Name *": "モデル名 *",
@ -2333,8 +2345,8 @@
"Model not found": "モデルが見つかりません",
"Model performance metrics": "モデル性能メトリクス",
"Model Price": "モデル価格",
"Model Price Not Configured": "モデル価格が未設定",
"Model price is not configured. Please complete model pricing in settings.": "モデル価格が未設定です。設定でモデル料金を補完してください。",
"Model Price Not Configured": "モデル価格が未設定",
"Model prices": "モデル価格",
"Model prices reset successfully": "モデル価格が正常にリセットされました",
"Model Pricing": "モデル料金",
@ -2532,6 +2544,7 @@
"No Logs Found": "ログが見つかりません",
"No mappings configured. Click \"Add Row\" to get started.": "マッピングが設定されていません。「行を追加」をクリックして開始してください。",
"No matches found": "一致するものが見つかりません",
"No matching items": "一致する項目がありません",
"No matching results": "一致する結果がありません",
"No matching rules": "一致するルールがありません",
"No messages yet": "まだメッセージがありません",
@ -2950,6 +2963,7 @@
"Please upload key file(s)": "キーファイルをアップロードしてください",
"Please wait a moment before trying again.": "しばらく待ってからもう一度お試しください。",
"Please wait a moment, human check is initializing...": "しばらくお待ちください、人間チェックを初期化中です...",
"Please wait before editing to avoid overwriting saved values.": "保存済みの値を上書きしないよう、編集前に読み込み完了をお待ちください。",
"Policy JSON": "ポリシーJSON",
"Polling": "ポーリング",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "ポーリングモードにはRedisとメモリキャッシュが必要です。そうでない場合、パフォーマンスが大幅に低下します",
@ -2983,6 +2997,7 @@
"Prepend to Start": "先頭に追加",
"Prepend value to array / string / object start": "配列/文字列/オブジェクトの先頭に値を追加",
"Preserve the original field when applying this rule": "このルール適用時に元のフィールドを保持します",
"Preset groups": "プリセットグループ",
"Preset recharge amounts (JSON array)": "プリセットチャージ金額 (JSON配列)",
"Preset recharge amounts displayed to users": "ユーザーに表示されるプリセットチャージ金額",
"Preset Template": "プリセットテンプレート",
@ -3098,6 +3113,7 @@
"Querying...": "クエリ中...",
"Question": "質問",
"Queued": "キュー中",
"Quick actions": "クイック操作",
"Quick insert common payment methods": "一般的な支払い方法をすばやく挿入",
"Quick Range": "クイック範囲",
"Quick Setup from Preset": "プリセットからクイックセットアップ",
@ -3219,6 +3235,7 @@
"Remaining:": "残り:",
"Remark": "備考",
"Remove": "削除",
"Remove {{value}}": "{{value}} を削除",
"Remove ${{amount}}": "${{amount}}を削除",
"Remove all log entries created before the selected timestamp.": "選択したタイムスタンプより前に作成されたすべてのログエントリを削除します。",
"Remove attachment": "添付ファイルを削除",
@ -3226,6 +3243,7 @@
"Remove Duplicates": "重複を削除",
"Remove filter": "フィルターを削除",
"Remove functionResponse.id field": "functionResponse.id フィールドを削除",
"Remove mapped targets": "マッピング先を削除",
"Remove Models": "モデルを削除",
"Remove Passkey": "Passkey連携解除",
"Remove Passkey?": "Passkeyを削除しますか",
@ -3329,7 +3347,6 @@
"Retain last N files": "最新N個のファイルを保持",
"Retention days": "保持日数",
"Retry": "再試行",
"auth.resetPasswordConfirm.retry": "再試行 ({{seconds}}秒)",
"Retry Chain": "リトライチェーン",
"Retry Suggestion": "リトライ提案",
"Retry Times": "再試行回数",
@ -3339,7 +3356,6 @@
"Return Error": "エラーを返す",
"Return per-token log probabilities": "トークンごとの対数確率を返します",
"Return to dashboard": "ダッシュボードに戻る",
"auth.resetPasswordConfirm.backToLogin": "ログインに戻る",
"Return vector embeddings for inputs": "入力に対してベクトル埋め込みを返却",
"Reveal API key": "APIキーを表示",
"Reveal key": "キーを表示",
@ -3491,7 +3507,6 @@
"Select all (filtered)": "フィルタ結果をすべて選択(S)",
"Select all models": "すべてのモデルを選択",
"Select All Visible": "表示中のすべてを選択",
"Select model {{model}}": "モデル {{model}} を選択",
"Select an operation mode and enter the amount": "操作モードを選択し、金額を入力してください",
"Select announcement type": "アナウンスメントタイプを選択",
"Select at least one field to overwrite.": "上書きするフィールドを少なくとも 1 つ選択してください。",
@ -3518,6 +3533,7 @@
"Select layout style": "レイアウトスタイルを選択",
"Select locations": "ロケーションを選択",
"Select Model": "モデルを選択",
"Select model {{model}}": "モデル {{model}} を選択",
"Select models (empty for allow all)": "モデルを選択 (すべて許可する場合は空)",
"Select models and apply to channel models list.": "モデルを選択し、チャンネルモデルリストに適用します。",
"Select models or add custom ones": "モデルを選択するか、カスタムモデルを追加",
@ -3547,6 +3563,7 @@
"Select vendor": "ベンダーを選択",
"Selectable groups": "選択可能なグループ",
"selected": "選択済み",
"Selected {{count}}": "{{count}} 件選択済み",
"selected channel(s). Leave empty to remove tag.": "選択されたチャンネル。タグを削除するには空のままにしてください。",
"Selected conflicts were overwritten successfully.": "選択した競合が正常に上書きされました。",
"Selected when creating a token and used as the default billing group for API calls.": "トークン作成時に選択され、API 呼び出しのデフォルト課金グループとして使われます。",
@ -3690,6 +3707,7 @@
"Status & Sync": "ステータスと同期",
"Status Code": "ステータスコード",
"Status Code Mapping": "ステータスコードマッピング",
"Status code mapping must use valid HTTP status codes": "ステータスコードマッピングには有効な HTTP ステータスコードを使用する必要があります",
"Status Page Slug": "ステータスページスラッグ",
"Status short": "状態",
"Status:": "ステータス:",
@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "この操作により、アカウントから2FA保護が完全に削除されます。",
"This channel is not an Ollama channel.": "このチャンネルはOllamaチャンネルではありません。",
"This channel type does not support fetching models": "このチャンネルタイプはモデルの取得をサポートしていません",
"This channel type requires additional configuration": "このチャンネルタイプには追加設定が必要です",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "この確認により、支払い、引換コード、サブスクリプションプラン、招待報酬の機能が解除されます。各項目をよく読んでください。",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "これはモデルリクエストのレート制限を制御します。Web/API ルートのスロットリングは環境変数で設定され、引き続き 429 を返す場合があります。",
"This data may be unreliable, use with caution": "このデータは信頼できない可能性があります。注意して使用してください",
@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "外部ツールを利用して機能を拡張",
"Use our unified OpenAI-compatible endpoint in your applications": "アプリケーションでOpenAI互換の統一エンドポイントを使用",
"Use Passkey to sign in without entering your password.": "パスワードを入力せずにサインインするには、パスキーを使用してください。",
"Use presets or upstream discovery to populate the model list faster.": "プリセットまたは上流検出を使ってモデルリストをすばやく入力します。",
"Use secure connection when sending emails": "メール送信時に安全な接続を使用する",
"Use sidebar shortcut": "サイドバーのショートカットを使用",
"Use the full-width table to scan prices, then select a row to edit it here.": "表で価格を確認し、行を選択してここで編集します。",
@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "認証情報を検証し、Pancake アカウントからストアを取得しています...",
"Version Overrides": "バージョンオーバーライド",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Vertex AI API Key モードは一括作成をサポートしていません",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI は functionResponse.id フィールドをサポートしません。有効にすると自動的に削除します。",
"Vertex AI Key Format": "Vertex AIキー形式",
"Vertex AI service account key must be valid JSON": "Vertex AI サービスアカウントキーは有効な JSON である必要があります",
"Video": "動画",
"Video length in seconds": "動画の長さ(秒)",
"Video Remix": "動画 Remix",
@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "あなたのGitHub OAuthクライアントID",
"Your GitHub OAuth Client Secret": "あなたのGitHub OAuthクライアントシークレット",
"Your new backup codes are ready": "新しいバックアップコードの準備ができました",
"auth.resetPasswordConfirm.success": "パスワードが正常にリセットされました",
"Your Referral Link": "あなたの紹介リンク",
"Your setup guide is collapsed so usage stays in focus.": "利用状況に集中できるよう、セットアップガイドを折りたたみました。",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "API認証用のシステムアクセストークンです。安全に保管し、他者と共有しないでください。",

View File

@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} IP",
"{{count}} log entries removed.": "Удалено {{count}} записей журнала.",
"{{count}} minutes ago": "{{count}} минут назад",
"{{count}} model(s)": "{{count}} модел(ей)",
"{{count}} models": "моделей: {{count}}",
"{{count}} months ago": "{{count}} месяцев назад",
"{{count}} override": "{{count}} переопределений",
@ -135,6 +134,7 @@
"Actual Model": "Фактическая модель",
"Actual Model:": "Фактическая модель:",
"Add": "Добавить",
"Add \"{{value}}\"": "Добавить \"{{value}}\"",
"Add {{title}}": "Добавить {{title}}",
"Add a group identifier to the auto assignment list.": "Добавить идентификатор группы в список автоматического назначения.",
"Add a new API key by providing necessary info.": "Добавьте новый API-ключ, предоставив необходимую информацию.",
@ -152,7 +152,7 @@
"Add condition": "Добавить условие",
"Add Condition": "Добавить условие",
"Add credits": "Добавить средства",
"Add custom model(s), comma-separated": "Добавить пользовательскую модель(и), через запятую",
"Add custom model \"{{value}}\"": "Добавить пользовательскую модель «{{value}}»",
"Add discount tier": "Добавить уровень скидки",
"Add each model or tag you want to include.": "Добавьте каждую модель или тег, который хотите включить.",
"Add FAQ": "Добавить вопрос-ответ",
@ -164,6 +164,7 @@
"Add group rules": "Добавить правила группы",
"Add Mapping": "Добавить сопоставление",
"Add method": "Добавить метод",
"Add missing models": "Добавить отсутствующие модели",
"Add Mode": "Добавить режим",
"Add model": "Добавить модель",
"Add Model": "Добавить модель",
@ -192,7 +193,6 @@
"Add User": "Добавить пользователя",
"Add user group": "Добавить группу пользователей",
"Add your API keys, set up channels and configure access permissions": "Добавьте ваши API-ключи, настройте каналы и права доступа",
"Added {{count}} custom model(s)": "Добавлено {{count}} пользовательских моделей",
"Added {{count}} model(s)": "Добавлено {{count}} моделей",
"Added successfully": "Успешно добавлено",
"Additional Conditions": "Дополнительные условия",
@ -201,8 +201,8 @@
"Additional Limit": "Доп. лимит",
"Additional Limits": "Дополнительные лимиты",
"Additional metered capability": "Дополнительная зарезервированная ёмкость (metered)",
"Adjust Quota": "Изменить квоту",
"Adjust filters, then search to refresh the logs.": "Настройте фильтры, затем выполните поиск, чтобы обновить журналы.",
"Adjust Quota": "Изменить квоту",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "Настройте форматирование ответов, поведение промпта, прокси и автоматизацию upstream.",
"Adjust the appearance and layout to suit your preferences.": "Настройте внешний вид и макет в соответствии с вашими предпочтениями.",
"Admin": "Администратор",
@ -424,6 +424,11 @@
"Audio Tokens": "Аудио токены",
"Auth configured": "Аутентификация настроена",
"Auth Style": "Стиль аутентификации",
"auth.resetPasswordConfirm.backToLogin": "Вернуться ко входу",
"auth.resetPasswordConfirm.confirm": "Подтвердить сброс пароля",
"auth.resetPasswordConfirm.description": "Подтвердите запрос на сброс, чтобы создать новый пароль.",
"auth.resetPasswordConfirm.retry": "Повторить ({{seconds}}с)",
"auth.resetPasswordConfirm.success": "Ваш пароль успешно сброшен",
"Authentication": "Аутентификация",
"Authenticator code": "Код аутентификатора",
"Authorization Endpoint": "Конечная точка авторизации",
@ -502,6 +507,7 @@
"Base Price": "Базовая цена",
"Base rate limit windows for this account.": "Окна базовых лимитов для этого аккаунта.",
"Base URL": "Адрес API",
"Base URL is required for this channel type": "Для этого типа канала требуется Base URL",
"Base URL of your Uptime Kuma instance": "Базовый URL вашего экземпляра Uptime Kuma",
"Basic Authentication": "Базовая аутентификация",
"Basic Configuration": "Базовая конфигурация",
@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Аккаунт и использование Codex",
"Codex Authorization": "Авторизация Codex",
"Codex channels do not support batch creation": "Каналы Codex не поддерживают пакетное создание",
"Codex channels use an OAuth JSON credential as the key.": "Каналы Codex используют учётные данные OAuth в формате JSON в качестве ключа.",
"Codex CLI Header Passthrough": "Проброс заголовков Codex CLI",
"Codex credential must be a JSON object with access_token and account_id": "Учетные данные Codex должны быть JSON-объектом с access_token и account_id",
"Cohere": "Cohere",
"Collapse": "Свернуть",
"Collapse All": "Свернуть все",
@ -867,8 +875,6 @@
"Confirm New Password": "Подтвердить новый пароль",
"Confirm password": "Подтвердить пароль",
"Confirm Payment": "Подтвердить оплату",
"auth.resetPasswordConfirm.confirm": "Подтвердить сброс пароля",
"auth.resetPasswordConfirm.description": "Подтвердите запрос на сброс, чтобы создать новый пароль.",
"Confirm Selection": "Подтвердить выбор",
"Confirm settings and finish setup": "Подтвердите настройки и завершите установку",
"confirm that I bear legal responsibility arising from deployment": "подтверждаю, что несу юридическую ответственность, возникающую из развертывания",
@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "Создать, отозвать и аудитировать токены API.",
"Created": "Создано",
"Created At": "Дата создания",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Создает продукт Pancake в сохраненном магазине с названием и ценой этого плана. Сначала необходимо полностью настроить Waffo Pancake в настройках платежей.",
"Creating...": "Создание...",
"Creation failed": "Создание не удалось",
"Credential generated": "Учётные данные созданы",
@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "Удалить недействительные коды активации",
"Delete Invalid Redemption Codes?": "Удалить недействительные коды активации?",
"Delete logs": "Удалить логи",
"Delete mapping": "Удалить сопоставление",
"Delete Model": "Удалить модель",
"Delete Models?": "Удалить модели?",
"Delete Provider": "Удалить провайдер",
@ -1249,6 +1257,8 @@
"Drawing task records": "Записи задач рисования",
"Duplicate": "Дублировать",
"Duplicate group names: {{names}}": "Повторяющиеся имена групп: {{names}}",
"Duplicate source model mappings are not allowed": "Повторяющиеся сопоставления исходных моделей не допускаются",
"Duplicate source model(s): {{models}}": "Повторяющиеся исходные модели: {{models}}",
"Duration": "Длительность",
"Duration (hours)": "Длительность (часы)",
"Duration Settings": "Настройки срока действия",
@ -2177,6 +2187,7 @@
"Load template...": "Загрузить шаблон...",
"Loader": "Загрузчик",
"Loading": "Загрузка",
"Loading channel details": "Загрузка сведений о канале",
"Loading configuration": "Загрузка конфигурации",
"Loading content settings...": "Загрузка настроек контента...",
"Loading current models...": "Загрузка текущих моделей...",
@ -2293,7 +2304,6 @@
"Minimum:": "Минимум:",
"Minor blips in the last 30 days": "Небольшие сбои за последние 30 дней",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Создайте новую пару ниже или выберите существующую дальше. Когда будете готовы, нажмите Сохранить.",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Создает продукт Pancake в сохраненном магазине с названием и ценой этого плана. Сначала необходимо полностью настроить Waffo Pancake в настройках платежей.",
"Minute": "Минута",
"minutes": "минут",
"Missing code": "Код отсутствует",
@ -2324,7 +2334,9 @@
"Model Mapping": "Сопоставление моделей",
"Model Mapping (JSON)": "Сопоставление моделей (JSON)",
"Model Mapping must be a JSON object like": "Сопоставление моделей должно быть JSON-объектом, например",
"Model mapping must be a JSON object with string values": "Сопоставление моделей должно быть JSON-объектом со строковыми значениями",
"Model mapping must be valid JSON": "Сопоставление моделей должно быть допустимым JSON",
"Model mapping values must be strings": "Значения сопоставления моделей должны быть строками",
"Model name": "Имя модели",
"Model Name": "Название модели",
"Model Name *": "Имя модели *",
@ -2333,8 +2345,8 @@
"Model not found": "Модель не найдена",
"Model performance metrics": "Метрики производительности моделей",
"Model Price": "Цена модели",
"Model Price Not Configured": "Цена модели не настроена",
"Model price is not configured. Please complete model pricing in settings.": "Цена модели не настроена. Заполните тарификацию модели в настройках.",
"Model Price Not Configured": "Цена модели не настроена",
"Model prices": "Цены моделей",
"Model prices reset successfully": "Цены моделей успешно сброшены",
"Model Pricing": "Тарификация моделей",
@ -2532,6 +2544,7 @@
"No Logs Found": "Логи не найдены",
"No mappings configured. Click \"Add Row\" to get started.": "Нет настроенных сопоставлений. Нажмите \"Добавить строку\", чтобы начать.",
"No matches found": "Совпадений не найдено",
"No matching items": "Нет подходящих элементов",
"No matching results": "Нет совпадений",
"No matching rules": "Нет совпадающих правил",
"No messages yet": "Сообщений пока нет",
@ -2950,6 +2963,7 @@
"Please upload key file(s)": "Загрузите ключевой файл(ы)",
"Please wait a moment before trying again.": "Пожалуйста, подождите немного и попробуйте снова.",
"Please wait a moment, human check is initializing...": "Пожалуйста, подождите немного, инициализация проверки человеком...",
"Please wait before editing to avoid overwriting saved values.": "Дождитесь загрузки перед редактированием, чтобы не перезаписать сохраненные значения.",
"Policy JSON": "JSON политики",
"Polling": "Опрос",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "Режим опроса требует Redis и кэш памяти, в противном случае производительность будет значительно снижена",
@ -2983,6 +2997,7 @@
"Prepend to Start": "Добавить в начало",
"Prepend value to array / string / object start": "Добавить значение в начало массива / строки / объекта",
"Preserve the original field when applying this rule": "Сохранять исходное поле при применении этого правила",
"Preset groups": "Предустановленные группы",
"Preset recharge amounts (JSON array)": "Предустановленные суммы пополнения (массив JSON)",
"Preset recharge amounts displayed to users": "Предустановленные суммы пополнения, отображаемые пользователям",
"Preset Template": "Предустановленный шаблон",
@ -3098,6 +3113,7 @@
"Querying...": "Выполняется запрос...",
"Question": "Вопрос",
"Queued": "В очереди",
"Quick actions": "Быстрые действия",
"Quick insert common payment methods": "Быстрая вставка распространенных способов оплаты",
"Quick Range": "Быстрый диапазон",
"Quick Setup from Preset": "Быстрая настройка из предустановки",
@ -3219,6 +3235,7 @@
"Remaining:": "Осталось:",
"Remark": "Примечания",
"Remove": "Удалить",
"Remove {{value}}": "Удалить {{value}}",
"Remove ${{amount}}": "Удалить ${{amount}}",
"Remove all log entries created before the selected timestamp.": "Удалить все записи журнала, созданные до выбранной отметки времени.",
"Remove attachment": "Удалить вложение",
@ -3226,6 +3243,7 @@
"Remove Duplicates": "Удалить дубликаты",
"Remove filter": "Удалить фильтр",
"Remove functionResponse.id field": "Удалить поле functionResponse.id",
"Remove mapped targets": "Удалить сопоставленные цели",
"Remove Models": "Удалить модели",
"Remove Passkey": "Отвязать Passkey",
"Remove Passkey?": "Удалить ключ доступа?",
@ -3329,7 +3347,6 @@
"Retain last N files": "Хранить последние N файлов",
"Retention days": "Дней хранения",
"Retry": "Повторить попытку",
"auth.resetPasswordConfirm.retry": "Повторить ({{seconds}}с)",
"Retry Chain": "Цепочка повторов",
"Retry Suggestion": "Рекомендация по повтору",
"Retry Times": "Количество повторных попыток",
@ -3339,7 +3356,6 @@
"Return Error": "Вернуть ошибку",
"Return per-token log probabilities": "Возвращать логарифмические вероятности по токенам",
"Return to dashboard": "Вернуться на панель управления",
"auth.resetPasswordConfirm.backToLogin": "Вернуться ко входу",
"Return vector embeddings for inputs": "Возвращать векторные эмбеддинги для входных данных",
"Reveal API key": "Показать API ключ",
"Reveal key": "Показать ключ",
@ -3491,7 +3507,6 @@
"Select all (filtered)": "& Выбрать все отфильтрованные",
"Select all models": "Выбрать все модели",
"Select All Visible": "Выбрать все видимые",
"Select model {{model}}": "Выбрать модель {{model}}",
"Select an operation mode and enter the amount": "Выберите режим операции и введите сумму",
"Select announcement type": "Выбрать тип объявления",
"Select at least one field to overwrite.": "Выберите хотя бы одно поле для перезаписи.",
@ -3518,6 +3533,7 @@
"Select layout style": "Выбрать стиль макета",
"Select locations": "Выбрать локации",
"Select Model": "Выбрать модель",
"Select model {{model}}": "Выбрать модель {{model}}",
"Select models (empty for allow all)": "Выбрать модели (пусто для разрешения всех)",
"Select models and apply to channel models list.": "Выберите модели и примените к списку моделей каналов.",
"Select models or add custom ones": "Выбрать модели или добавить пользовательские",
@ -3547,6 +3563,7 @@
"Select vendor": "Выбрать поставщика",
"Selectable groups": "Выбираемые группы",
"selected": "выбрано",
"Selected {{count}}": "Выбрано: {{count}}",
"selected channel(s). Leave empty to remove tag.": "выбранный канал(ы). Оставьте пустым, чтобы удалить тег.",
"Selected conflicts were overwritten successfully.": "Выбранные конфликты успешно перезаписаны.",
"Selected when creating a token and used as the default billing group for API calls.": "Выбирается при создании токена и используется как группа тарификации по умолчанию для вызовов API.",
@ -3690,6 +3707,7 @@
"Status & Sync": "Статус и синхронизация",
"Status Code": "Код статуса",
"Status Code Mapping": "Сопоставление кодов состояния",
"Status code mapping must use valid HTTP status codes": "Сопоставление кодов состояния должно использовать допустимые HTTP-коды",
"Status Page Slug": "Slug страницы статуса",
"Status short": "Стат.",
"Status:": "Статус:",
@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "Это действие безвозвратно удалит защиту 2FA из вашей учетной записи.",
"This channel is not an Ollama channel.": "Этот канал не является каналом Ollama.",
"This channel type does not support fetching models": "Этот тип канала не поддерживает получение моделей",
"This channel type requires additional configuration": "Для этого типа канала требуется дополнительная конфигурация",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Это подтверждение разблокирует функции платежей, кодов пополнения, планов подписки и наград за приглашения. Внимательно прочитайте заявления.",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Этот параметр управляет ограничением частоты запросов к моделям. Ограничение маршрутов Web/API настраивается переменными окружения и всё ещё может возвращать 429.",
"This data may be unreliable, use with caution": "Эти данные могут быть ненадежными, используйте с осторожностью",
@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "Использовать внешние инструменты для расширения возможностей",
"Use our unified OpenAI-compatible endpoint in your applications": "Используйте наш единый OpenAI-совместимый эндпоинт в ваших приложениях",
"Use Passkey to sign in without entering your password.": "Используйте ключ доступа для входа без ввода пароля.",
"Use presets or upstream discovery to populate the model list faster.": "Используйте пресеты или обнаружение upstream, чтобы быстрее заполнить список моделей.",
"Use secure connection when sending emails": "Использовать безопасное соединение при отправке электронных писем",
"Use sidebar shortcut": "Использовать ярлык боковой панели",
"Use the full-width table to scan prices, then select a row to edit it here.": "Просмотрите цены в таблице, затем выберите строку для редактирования здесь.",
@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "Проверяем учетные данные и загружаем магазины из вашего аккаунта Pancake...",
"Version Overrides": "Переопределения версий",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Режим API Key Vertex AI не поддерживает пакетное создание",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI не поддерживает functionResponse.id. Включите, чтобы автоматически удалить это поле.",
"Vertex AI Key Format": "Формат ключа Vertex AI",
"Vertex AI service account key must be valid JSON": "Ключ сервисного аккаунта Vertex AI должен быть допустимым JSON",
"Video": "Видео",
"Video length in seconds": "Длительность видео в секундах",
"Video Remix": "Ремикс видео",
@ -4334,7 +4356,7 @@
"Visit Settings → General and adjust quota options...": "Перейдите в Настройки → Общие и настройте параметры квоты...",
"Visitors must authenticate before accessing the pricing directory.": "Посетители должны пройти аутентификацию перед доступом к каталогу цен.",
"Visitors must authenticate before accessing the rankings page.": "Посетители должны пройти аутентификацию перед доступом к странице рейтингов.",
"Visual": "Визуальный",
"Visual": "Визуально",
"Visual edit": "Визуальное редактирование",
"Visual editor": "Визуальный редактор",
"Visual Editor": "Визуальный редактор",
@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "Ваш ID клиента GitHub OAuth",
"Your GitHub OAuth Client Secret": "Ваш секрет клиента GitHub OAuth",
"Your new backup codes are ready": "Ваши новые резервные коды готовы",
"auth.resetPasswordConfirm.success": "Ваш пароль успешно сброшен",
"Your Referral Link": "Ваша реферальная ссылка",
"Your setup guide is collapsed so usage stays in focus.": "Руководство свернуто, чтобы основные показатели оставались в фокусе.",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "Ваш системный токен доступа для аутентификации API. Храните его в безопасности и не делитесь им с другими.",

View File

@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} IP",
"{{count}} log entries removed.": "Đã xóa {{count}} mục nhật ký.",
"{{count}} minutes ago": "{{count}} phút trước",
"{{count}} model(s)": "{{count}} mô hình",
"{{count}} models": "{{count}} mô hình",
"{{count}} months ago": "{{count}} tháng trước",
"{{count}} override": "{{count}} ghi đè",
@ -135,6 +134,7 @@
"Actual Model": "Mô hình thực tế",
"Actual Model:": "Mô hình thực tế:",
"Add": "Add",
"Add \"{{value}}\"": "Thêm \"{{value}}\"",
"Add {{title}}": "Thêm {{title}}",
"Add a group identifier to the auto assignment list.": "Thêm một mã định danh nhóm vào danh sách phân công tự động.",
"Add a new API key by providing necessary info.": "Thêm khóa API mới bằng cách cung cấp thông tin cần thiết.",
@ -152,7 +152,7 @@
"Add condition": "Thêm điều kiện",
"Add Condition": "Thêm điều kiện",
"Add credits": "Thêm tín dụng",
"Add custom model(s), comma-separated": "Thêm mô hình tùy chỉnh, phân tách bằng dấu phẩy",
"Add custom model \"{{value}}\"": "Thêm mô hình tùy chỉnh \"{{value}}\"",
"Add discount tier": "Thêm bậc giảm giá",
"Add each model or tag you want to include.": "Thêm mỗi mô hình hoặc thẻ bạn muốn đưa vào.",
"Add FAQ": "Thêm FAQ",
@ -164,6 +164,7 @@
"Add group rules": "Thêm quy tắc nhóm",
"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",
"Add Mode": "Thêm Chế độ",
"Add model": "Thêm mô hình",
"Add Model": "Thêm Mô hình",
@ -192,7 +193,6 @@
"Add User": "Thêm người dùng",
"Add user group": "Thêm nhóm người dùng",
"Add your API keys, set up channels and configure access permissions": "Thêm khóa API, thiết lập kênh và cấu hình quyền truy cập",
"Added {{count}} custom model(s)": "Đã thêm {{count}} mô hình tùy chỉnh",
"Added {{count}} model(s)": "Đã thêm {{count}} mô hình",
"Added successfully": "Thêm thành công",
"Additional Conditions": "Điều kiện bổ sung",
@ -201,8 +201,8 @@
"Additional Limit": "Hạn mức bổ sung",
"Additional Limits": "Các hạn mức bổ sung",
"Additional metered capability": "Tính năng tính phí theo mức dùng bổ sung",
"Adjust Quota": "Điều chỉnh hạn mức",
"Adjust filters, then search to refresh the logs.": "Điều chỉnh bộ lọc, sau đó tìm kiếm để làm mới nhật ký.",
"Adjust Quota": "Điều chỉnh hạn mức",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "Điều chỉnh định dạng phản hồi, hành vi prompt, proxy và tự động hóa upstream.",
"Adjust the appearance and layout to suit your preferences.": "Điều chỉnh giao diện và bố cục để phù hợp với sở thích của bạn.",
"Admin": "Quản trị viên",
@ -424,6 +424,11 @@
"Audio Tokens": "Token âm thanh",
"Auth configured": "Đã cấu hình xác thực",
"Auth Style": "Kiểu xác thực",
"auth.resetPasswordConfirm.backToLogin": "Quay lại đăng nhập",
"auth.resetPasswordConfirm.confirm": "Xác nhận đặt lại mật khẩu",
"auth.resetPasswordConfirm.description": "Xác nhận yêu cầu đặt lại để tạo mật khẩu mới.",
"auth.resetPasswordConfirm.retry": "Thử lại ({{seconds}} giây)",
"auth.resetPasswordConfirm.success": "Mật khẩu của bạn đã được đặt lại thành công",
"Authentication": "Xác thực",
"Authenticator code": "Mã xác thực",
"Authorization Endpoint": "Điểm cuối ủy quyền",
@ -502,6 +507,7 @@
"Base Price": "Giá cơ bản",
"Base rate limit windows for this account.": "Cửa sổ giới hạn tốc độ cơ bản cho tài khoản này.",
"Base URL": "URL cơ sở",
"Base URL is required for this channel type": "Loại kênh này yêu cầu Base URL",
"Base URL of your Uptime Kuma instance": "URL cơ sở của phiên bản Uptime Kuma của bạn",
"Basic Authentication": "Xác thực cơ bản",
"Basic Configuration": "Cấu hình cơ bản",
@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Tài khoản và sử dụng Codex",
"Codex Authorization": "Ủy quyền Codex",
"Codex channels do not support batch creation": "Kênh Codex không hỗ trợ tạo hàng loạt",
"Codex channels use an OAuth JSON credential as the key.": "Kênh Codex dùng thông tin xác thực OAuth JSON làm khóa.",
"Codex CLI Header Passthrough": "Chuyển tiếp header Codex CLI",
"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ả",
@ -867,8 +875,6 @@
"Confirm New Password": "Xác nhận mật khẩu mới",
"Confirm password": "Xác nhận mật khẩu",
"Confirm Payment": "Xác nhận Thanh toán",
"auth.resetPasswordConfirm.confirm": "Xác nhận đặt lại mật khẩu",
"auth.resetPasswordConfirm.description": "Xác nhận yêu cầu đặt lại để tạo mật khẩu mới.",
"Confirm Selection": "Xác nhận lựa chọn",
"Confirm settings and finish setup": "Xác nhận cài đặt và hoàn tất thiết lập",
"confirm that I bear legal responsibility arising from deployment": "xác nhận rằng tôi chịu trách nhiệm pháp lý phát sinh từ việc triển khai",
@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "Tạo, thu hồi và kiểm toán token API.",
"Created": "Đã tạo",
"Created At": "Ngày tạo",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Tạo một sản phẩm Pancake trong cửa hàng đã lưu bằng tiêu đề và giá của gói này. Trước tiên cần cấu hình đầy đủ Waffo Pancake trong cài đặt Thanh toán.",
"Creating...": "Đang tạo...",
"Creation failed": "Tạo thất bại",
"Credential generated": "Đã tạo thông tin xác thực",
@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "Xóa mã đổi thưởng không hợp lệ",
"Delete Invalid Redemption Codes?": "Xóa Mã đổi thưởng không hợp lệ?",
"Delete logs": "Xóa nhật ký",
"Delete mapping": "Xóa ánh xạ",
"Delete Model": "Xóa Mô hình",
"Delete Models?": "Xóa mô hình?",
"Delete Provider": "Xóa nhà cung cấp",
@ -1249,6 +1257,8 @@
"Drawing task records": "Lịch sử tác vụ vẽ",
"Duplicate": "Nhân bản",
"Duplicate group names: {{names}}": "Tên nhóm bị trùng: {{names}}",
"Duplicate source model mappings are not allowed": "Không cho phép ánh xạ mô hình nguồn trùng lặp",
"Duplicate source model(s): {{models}}": "Mô hình nguồn trùng lặp: {{models}}",
"Duration": "Thời lượng",
"Duration (hours)": "Thời lượng (giờ)",
"Duration Settings": "Cài đặt thời lượng",
@ -1935,7 +1945,7 @@
"How client credentials are sent to the token endpoint": "Cách thông tin xác thực client được gửi đến endpoint token",
"How frequently the system tests all channels": "Tần suất hệ thống kiểm tra tất cả các kênh là bao nhiêu?",
"How It Works": "Cách hoạt động",
"How model mapping works": "Cách ánh xạ mô hình hoạt động",
"How model mapping works": "Cách hoạt động của ánh xạ mô hình",
"How much to charge for each US dollar of balance (Epay)": "Tính phí bao nhiêu cho mỗi đô la Mỹ số dư (Epay)",
"How this model name should match requests": "Tên mô hình này nên khớp với các yêu cầu như thế nào",
"How to deliver the resulting image": "Cách trả về ảnh kết quả",
@ -2177,6 +2187,7 @@
"Load template...": "Tải mẫu...",
"Loader": "Trình tải",
"Loading": "Đang tải",
"Loading channel details": "Đang tải chi tiết kênh",
"Loading configuration": "Đang tải cấu hình",
"Loading content settings...": "Đang tải cài đặt nội dung...",
"Loading current models...": "Đang tải các mô hình hiện tại...",
@ -2293,7 +2304,6 @@
"Minimum:": "Tối thiểu:",
"Minor blips in the last 30 days": "Vài gián đoạn nhỏ trong 30 ngày qua",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Tạo một cặp mới bên dưới, hoặc chọn một cặp hiện có ở phía dưới. Nhấn Lưu khi đã sẵn sàng.",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Tạo một sản phẩm Pancake trong cửa hàng đã lưu bằng tiêu đề và giá của gói này. Trước tiên cần cấu hình đầy đủ Waffo Pancake trong cài đặt Thanh toán.",
"Minute": "Phút",
"minutes": "phút",
"Missing code": "Thiếu mã",
@ -2324,7 +2334,9 @@
"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ư",
"Model mapping must be a JSON object with string values": "Ánh xạ mô hình phải là đối tượng JSON với giá trị chuỗi",
"Model mapping must be valid JSON": "Ánh xạ mô hình phải là JSON hợp lệ",
"Model mapping values must be strings": "Giá trị ánh xạ mô hình phải là chuỗi",
"Model name": "Tên mẫu",
"Model Name": "Tên mẫu",
"Model Name *": "Tên mẫu *",
@ -2333,8 +2345,8 @@
"Model not found": "Không tìm thấy mô hình",
"Model performance metrics": "Chỉ số hiệu năng mô hình",
"Model Price": "Giá mô hình",
"Model Price Not Configured": "Giá mô hình chưa được cấu hình",
"Model price is not configured. Please complete model pricing in settings.": "Giá mô hình chưa được cấu hình. Vui lòng hoàn tất định giá mô hình trong cài đặt.",
"Model Price Not Configured": "Giá mô hình chưa được cấu hình",
"Model prices": "Giá mô hình",
"Model prices reset successfully": "Đã đặt lại giá mô hình thành công",
"Model Pricing": "Định giá mô hình",
@ -2532,6 +2544,7 @@
"No Logs Found": "Không tìm thấy nhật ký",
"No mappings configured. Click \"Add Row\" to get started.": "Chưa có ánh xạ nào được cấu hình. Nhấp vào \"Thêm hàng\" để bắt đầu.",
"No matches found": "Không tìm thấy kết quả nào",
"No matching items": "Không có mục phù hợp",
"No matching results": "Không có kết quả phù hợp",
"No matching rules": "Không có quy tắc phù hợp",
"No messages yet": "Chưa có tin nhắn",
@ -2950,6 +2963,7 @@
"Please upload key file(s)": "Vui lòng tải lên (các) tệp khóa",
"Please wait a moment before trying again.": "Vui lòng chờ một lát rồi thử lại.",
"Please wait a moment, human check is initializing...": "Vui lòng đợi một chút, kiểm tra con người đang khởi tạo...",
"Please wait before editing to avoid overwriting saved values.": "Vui lòng chờ trước khi chỉnh sửa để tránh ghi đè các giá trị đã lưu.",
"Policy JSON": "JSON chính sách",
"Polling": "Thăm dò",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "Chế độ thăm dò yêu cầu Redis và bộ nhớ đệm, nếu không hiệu suất sẽ bị suy giảm đáng kể.",
@ -2983,6 +2997,7 @@
"Prepend to Start": "Thêm vào đầu",
"Prepend value to array / string / object start": "Thêm giá trị vào đầu mảng / chuỗi / đối tượng",
"Preserve the original field when applying this rule": "Giữ trường gốc khi áp dụng quy tắc này",
"Preset groups": "Nhóm đặt sẵn",
"Preset recharge amounts (JSON array)": "Số tiền nạp đặt trước (mảng JSON)",
"Preset recharge amounts displayed to users": "Các mức nạp tiền đặt trước hiển thị cho người dùng",
"Preset Template": "Mẫu cài sẵn",
@ -3098,6 +3113,7 @@
"Querying...": "Đang truy vấn...",
"Question": "Câu hỏi",
"Queued": "Trong hàng đợi",
"Quick actions": "Thao tác nhanh",
"Quick insert common payment methods": "Chèn nhanh các phương thức thanh toán phổ biến",
"Quick Range": "Phạm vi nhanh",
"Quick Setup from Preset": "Thiết lập nhanh từ cấu hình sẵn",
@ -3219,6 +3235,7 @@
"Remaining:": "Còn lại:",
"Remark": "Nhận xét",
"Remove": "Xóa",
"Remove {{value}}": "Xóa {{value}}",
"Remove ${{amount}}": "Xóa ${{amount}}",
"Remove all log entries created before the selected timestamp.": "Xóa tất cả các mục nhật ký được tạo trước mốc thời gian đã chọn.",
"Remove attachment": "Xóa tệp đính kèm",
@ -3226,6 +3243,7 @@
"Remove Duplicates": "Xóa trùng lặp",
"Remove filter": "Xóa bộ lọc",
"Remove functionResponse.id field": "Loại bỏ trường functionResponse.id",
"Remove mapped targets": "Xóa đích đã ánh xạ",
"Remove Models": "Xóa mô hình",
"Remove Passkey": "Xóa Khóa truy cập",
"Remove Passkey?": "Xóa khóa truy cập?",
@ -3329,7 +3347,6 @@
"Retain last N files": "Giữ lại N tệp gần nhất",
"Retention days": "Số ngày lưu giữ",
"Retry": "Thử lại",
"auth.resetPasswordConfirm.retry": "Thử lại ({{seconds}} giây)",
"Retry Chain": "Chuỗi thử lại",
"Retry Suggestion": "Gợi ý thử lại",
"Retry Times": "Số lần thử lại",
@ -3339,7 +3356,6 @@
"Return Error": "Trả về lỗi",
"Return per-token log probabilities": "Trả về log probabilities cho từng token",
"Return to dashboard": "Quay lại bảng điều khiển",
"auth.resetPasswordConfirm.backToLogin": "Quay lại đăng nhập",
"Return vector embeddings for inputs": "Trả về vector embedding cho đầu vào",
"Reveal API key": "Hiển thị khóa API",
"Reveal key": "Display key",
@ -3491,7 +3507,6 @@
"Select all (filtered)": "Chọn tất cả (đã lọc)",
"Select all models": "Chọn tất cả mô hình",
"Select All Visible": "Chọn tất cả hiển thị",
"Select model {{model}}": "Chọn mô hình {{model}}",
"Select an operation mode and enter the amount": "Chọn chế độ thao tác và nhập số tiền",
"Select announcement type": "Select notification type",
"Select at least one field to overwrite.": "Chọn ít nhất một trường để ghi đè.",
@ -3518,6 +3533,7 @@
"Select layout style": "Chọn kiểu bố cục",
"Select locations": "Chọn vị trí",
"Select Model": "Chọn mẫu",
"Select model {{model}}": "Chọn mô hình {{model}}",
"Select models (empty for allow all)": "Chọn model (để trống nếu muốn cho",
"Select models and apply to channel models list.": "Chọn mô hình và áp dụng cho danh sách mô hình kênh.",
"Select models or add custom ones": "Chọn các mô hình hoặc thêm các mô hình tùy chỉnh",
@ -3547,6 +3563,7 @@
"Select vendor": "Chọn nhà cung cấp",
"Selectable groups": "Nhóm có thể chọn",
"selected": "đã chọn",
"Selected {{count}}": "Đã chọn {{count}}",
"selected channel(s). Leave empty to remove tag.": "Kênh đã chọn. Để trống để xóa thẻ.",
"Selected conflicts were overwritten successfully.": "Các xung đột được chọn đã được ghi đè thành công.",
"Selected when creating a token and used as the default billing group for API calls.": "Được chọn khi tạo token và dùng làm nhóm tính phí mặc định cho các lệnh gọi API.",
@ -3690,6 +3707,7 @@
"Status & Sync": "Trạng thái & Đồng bộ",
"Status Code": "Mã trạng thái",
"Status Code Mapping": "Ánh xạ mã trạng thái",
"Status code mapping must use valid HTTP status codes": "Ánh xạ mã trạng thái phải dùng mã trạng thái HTTP hợp lệ",
"Status Page Slug": "Đường dẫn phụ trang trạng thái",
"Status short": "TT",
"Status:": "Trạng thái:",
@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "Hành động này sẽ vĩnh viễn gỡ bỏ tính năng bảo vệ",
"This channel is not an Ollama channel.": "Kênh này không phải là kênh Ollama.",
"This channel type does not support fetching models": "Loại kênh này không hỗ trợ lấy mô hình",
"This channel type requires additional configuration": "Loại kênh này yêu cầu cấu hình bổ sung",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Xác nhận này mở khóa các tính năng thanh toán, mã đổi thưởng, gói đăng ký và phần thưởng mời. Vui lòng đọc kỹ các tuyên bố.",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Thiết lập này kiểm soát giới hạn tốc độ yêu cầu mô hình. Giới hạn tuyến Web/API được cấu hình bằng biến môi trường và vẫn có thể trả về 429.",
"This data may be unreliable, use with caution": "Dữ liệu này có thể không đáng tin cậy, sử dụng thận trọng",
@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "Sử dụng công cụ ngoài để mở rộng khả năng",
"Use our unified OpenAI-compatible endpoint in your applications": "Sử dụng endpoint thống nhất tương thích OpenAI trong ứng dụng của bạn",
"Use Passkey to sign in without entering your password.": "Sử dụng Khóa truy cập để đăng nhập mà không cần nhập mật khẩu của bạn.",
"Use presets or upstream discovery to populate the model list faster.": "Dùng mẫu đặt sẵn hoặc phát hiện từ upstream để điền danh sách mô hình nhanh hơn.",
"Use secure connection when sending emails": "Sử dụng kết nối an toàn khi gửi email",
"Use sidebar shortcut": "Sử dụng phím tắt thanh bên",
"Use the full-width table to scan prices, then select a row to edit it here.": "Duyệt giá trong bảng, rồi chọn một hàng để chỉnh sửa tại đây.",
@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "Đang xác minh thông tin xác thực và lấy cửa hàng từ tài khoản Pancake của bạn...",
"Version Overrides": "Ghi đè phiên bản",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Chế độ API Key của Vertex AI không hỗ trợ tạo hàng loạt",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI không hỗ trợ functionResponse.id. Bật để tự động loại bỏ trường này.",
"Vertex AI Key Format": "Định dạng khóa Vertex AI",
"Vertex AI service account key must be valid JSON": "Khóa tài khoản dịch vụ Vertex AI phải là JSON hợp lệ",
"Video": "Video",
"Video length in seconds": "Độ dài video (giây)",
"Video Remix": "Remix video",
@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "Client ID OAuth GitHub của bạn",
"Your GitHub OAuth Client Secret": "Bí mật ứng dụng OAuth của GitHub của bạn",
"Your new backup codes are ready": "Mã dự phòng mới của bạn đã sẵn sàng",
"auth.resetPasswordConfirm.success": "Mật khẩu của bạn đã được đặt lại thành công",
"Your Referral Link": "Liên kết giới thiệu của bạn",
"Your setup guide is collapsed so usage stays in focus.": "Hướng dẫn thiết lập đã thu gọn để giữ phần sử dụng ở vị trí nổi bật.",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "Mã truy cập hệ thống của bạn để xác thực API. Hãy giữ nó an toàn và đừng chia sẻ nó với người khác.",

View File

@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} 个 IP",
"{{count}} log entries removed.": "已删除 {{count}} 条日志。",
"{{count}} minutes ago": "{{count}} 分钟前",
"{{count}} model(s)": "{{count}} 个模型",
"{{count}} models": "{{count}} 个模型",
"{{count}} months ago": "{{count}} 个月前",
"{{count}} override": "{{count}} 个覆盖",
@ -135,6 +134,7 @@
"Actual Model": "实际模型",
"Actual Model:": "实际模型:",
"Add": "添加",
"Add \"{{value}}\"": "添加 \"{{value}}\"",
"Add {{title}}": "添加{{title}}",
"Add a group identifier to the auto assignment list.": "将分组标识符添加到自动分配列表。",
"Add a new API key by providing necessary info.": "通过提供必要信息添加新的 API 密钥。",
@ -152,7 +152,7 @@
"Add condition": "新增条件",
"Add Condition": "添加条件",
"Add credits": "添加额度",
"Add custom model(s), comma-separated": "添加自定义模型(多个以逗号分隔)",
"Add custom model \"{{value}}\"": "添加自定义模型“{{value}}”",
"Add discount tier": "添加折扣等级",
"Add each model or tag you want to include.": "添加您想要包含的每个模型或标签。",
"Add FAQ": "添加问答",
@ -164,6 +164,7 @@
"Add group rules": "添加分组规则",
"Add Mapping": "添加映射",
"Add method": "添加方法",
"Add missing models": "添加缺失模型",
"Add Mode": "添加模式",
"Add model": "添加模型",
"Add Model": "添加模型",
@ -192,7 +193,6 @@
"Add User": "添加用户",
"Add user group": "添加用户分组",
"Add your API keys, set up channels and configure access permissions": "添加 API 密钥,设置渠道并配置访问权限",
"Added {{count}} custom model(s)": "已添加 {{count}} 个自定义模型",
"Added {{count}} model(s)": "已添加 {{count}} 个模型",
"Added successfully": "新增成功",
"Additional Conditions": "附加条件",
@ -201,8 +201,8 @@
"Additional Limit": "附加额度",
"Additional Limits": "附加额度",
"Additional metered capability": "附加计费能力",
"Adjust Quota": "调整额度",
"Adjust filters, then search to refresh the logs.": "调整筛选条件,然后搜索以刷新日志。",
"Adjust Quota": "调整额度",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "调整响应格式、提示词行为、代理和上游自动化。",
"Adjust the appearance and layout to suit your preferences.": "调整外观和布局以适应您的偏好。",
"Admin": "管理员",
@ -424,6 +424,11 @@
"Audio Tokens": "语音 Token",
"Auth configured": "认证已配置",
"Auth Style": "认证方式",
"auth.resetPasswordConfirm.backToLogin": "返回登录",
"auth.resetPasswordConfirm.confirm": "确认重置密码",
"auth.resetPasswordConfirm.description": "确认重置请求以生成新密码。",
"auth.resetPasswordConfirm.retry": "重试 ({{seconds}}s)",
"auth.resetPasswordConfirm.success": "您的密码已成功重置",
"Authentication": "身份验证",
"Authenticator code": "身份验证器代码",
"Authorization Endpoint": "授权端点",
@ -502,6 +507,7 @@
"Base Price": "基础价格",
"Base rate limit windows for this account.": "当前账号的基础额度窗口。",
"Base URL": "API 地址",
"Base URL is required for this channel type": "此渠道类型需要填写 Base URL",
"Base URL of your Uptime Kuma instance": "您的 Uptime Kuma 实例的基础 URL",
"Basic Authentication": "基本身份验证",
"Basic Configuration": "基本配置",
@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Codex 账户和用量",
"Codex Authorization": "Codex 授权",
"Codex channels do not support batch creation": "Codex 渠道不支持批量创建",
"Codex channels use an OAuth JSON credential as the key.": "Codex 频道使用 OAuth JSON 凭据作为密钥。",
"Codex CLI Header Passthrough": "Codex CLI 请求头透传",
"Codex credential must be a JSON object with access_token and account_id": "Codex 凭据必须是包含 access_token 和 account_id 的 JSON 对象",
"Cohere": "Cohere",
"Collapse": "收起",
"Collapse All": "全部收起",
@ -867,8 +875,6 @@
"Confirm New Password": "确认新密码",
"Confirm password": "确认密码",
"Confirm Payment": "确认付款",
"auth.resetPasswordConfirm.confirm": "确认重置密码",
"auth.resetPasswordConfirm.description": "确认重置请求以生成新密码。",
"Confirm Selection": "确认选择",
"Confirm settings and finish setup": "确认设置并完成安装",
"confirm that I bear legal responsibility arising from deployment": "确认我承担因部署产生的法律责任",
@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "创建、撤销和审计 API 令牌。",
"Created": "创建时间",
"Created At": "创建时间",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "使用此套餐的标题和价格,在已保存的店铺中创建 Pancake 产品。需要先在支付设置中完整配置 Waffo Pancake。",
"Creating...": "创建中...",
"Creation failed": "创建失败",
"Credential generated": "凭据已生成",
@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "删除无效兑换码",
"Delete Invalid Redemption Codes?": "删除无效的兑换码?",
"Delete logs": "删除日志",
"Delete mapping": "删除映射",
"Delete Model": "删除模型",
"Delete Models?": "删除模型?",
"Delete Provider": "删除提供商",
@ -1249,6 +1257,8 @@
"Drawing task records": "绘图任务记录",
"Duplicate": "重复",
"Duplicate group names: {{names}}": "存在重复的分组名称:{{names}}",
"Duplicate source model mappings are not allowed": "不允许重复的源模型映射",
"Duplicate source model(s): {{models}}": "重复的源模型:{{models}}",
"Duration": "耗时",
"Duration (hours)": "时长 (小时)",
"Duration Settings": "有效期设置",
@ -1935,7 +1945,7 @@
"How client credentials are sent to the token endpoint": "客户端凭据如何发送至令牌端点",
"How frequently the system tests all channels": "系统测试所有渠道的频率",
"How It Works": "工作流程",
"How model mapping works": "模型映射的工作原理",
"How model mapping works": "模型映射如何工作",
"How much to charge for each US dollar of balance (Epay)": "每美元余额Epay的收费金额",
"How this model name should match requests": "此模型名称应如何匹配请求",
"How to deliver the resulting image": "图像结果的返回方式",
@ -2177,6 +2187,7 @@
"Load template...": "加载模板...",
"Loader": "加载器",
"Loading": "加载中",
"Loading channel details": "正在加载渠道详情",
"Loading configuration": "正在加载配置",
"Loading content settings...": "正在加载内容设置...",
"Loading current models...": "正在加载当前模型...",
@ -2293,7 +2304,6 @@
"Minimum:": "最低:",
"Minor blips in the last 30 days": "近 30 天内有轻微抖动",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "在下方创建新的配对,或继续向下选择已有配对。准备好后点击保存。",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "使用此套餐的标题和价格,在已保存的店铺中创建 Pancake 产品。需要先在支付设置中完整配置 Waffo Pancake。",
"Minute": "分钟",
"minutes": "分钟",
"Missing code": "缺少代码",
@ -2324,7 +2334,9 @@
"Model Mapping": "模型映射",
"Model Mapping (JSON)": "模型映射 (JSON)",
"Model Mapping must be a JSON object like": "模型映射必须是如下所示的 JSON 对象",
"Model mapping must be a JSON object with string values": "模型映射必须是值为字符串的 JSON 对象",
"Model mapping must be valid JSON": "模型映射必须是有效的 JSON",
"Model mapping values must be strings": "模型映射的值必须是字符串",
"Model name": "模型名称",
"Model Name": "模型名称",
"Model Name *": "模型名称 *",
@ -2333,8 +2345,8 @@
"Model not found": "模型未找到",
"Model performance metrics": "模型性能指标",
"Model Price": "模型价格",
"Model Price Not Configured": "模型价格未配置",
"Model price is not configured. Please complete model pricing in settings.": "模型价格未配置,请前往设置补充模型价格。",
"Model Price Not Configured": "模型价格未配置",
"Model prices": "模型价格",
"Model prices reset successfully": "模型价格重置成功",
"Model Pricing": "模型定价",
@ -2532,6 +2544,7 @@
"No Logs Found": "未找到日志",
"No mappings configured. Click \"Add Row\" to get started.": "未配置映射。点击 \"添加行\" 开始。",
"No matches found": "未找到匹配项",
"No matching items": "没有匹配项",
"No matching results": "无匹配结果",
"No matching rules": "没有匹配的规则",
"No messages yet": "暂无消息",
@ -2950,6 +2963,7 @@
"Please upload key file(s)": "请上传密钥文件",
"Please wait a moment before trying again.": "请稍候再试。",
"Please wait a moment, human check is initializing...": "请稍等,人机验证正在初始化...",
"Please wait before editing to avoid overwriting saved values.": "请等待加载完成后再编辑,以免覆盖已保存的值。",
"Policy JSON": "策略 JSON",
"Polling": "轮询",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "轮询模式需要 Redis 和内存缓存,否则性能将显著下降",
@ -2983,6 +2997,7 @@
"Prepend to Start": "追加到开头",
"Prepend value to array / string / object start": "把值追加到数组/字符串/对象开头",
"Preserve the original field when applying this rule": "应用此规则时保留原始字段",
"Preset groups": "预设分组",
"Preset recharge amounts (JSON array)": "预设充值金额JSON 数组)",
"Preset recharge amounts displayed to users": "向用户显示的预设充值金额",
"Preset Template": "预设模板",
@ -3098,6 +3113,7 @@
"Querying...": "正在查询...",
"Question": "问题",
"Queued": "排队中",
"Quick actions": "快捷操作",
"Quick insert common payment methods": "快速插入常用支付方式",
"Quick Range": "快速范围",
"Quick Setup from Preset": "从预设快速设置",
@ -3219,6 +3235,7 @@
"Remaining:": "剩余:",
"Remark": "备注",
"Remove": "移除",
"Remove {{value}}": "移除 {{value}}",
"Remove ${{amount}}": "移除 ${{amount}}",
"Remove all log entries created before the selected timestamp.": "移除所选时间戳之前创建的所有日志条目。",
"Remove attachment": "移除附件",
@ -3226,6 +3243,7 @@
"Remove Duplicates": "移除重复项",
"Remove filter": "移除筛选",
"Remove functionResponse.id field": "移除 functionResponse.id 字段",
"Remove mapped targets": "移除映射目标",
"Remove Models": "删除模型",
"Remove Passkey": "解绑 Passkey",
"Remove Passkey?": "移除通行密钥?",
@ -3329,7 +3347,6 @@
"Retain last N files": "保留最近 N 个文件",
"Retention days": "保留天数",
"Retry": "重试",
"auth.resetPasswordConfirm.retry": "重试 ({{seconds}}s)",
"Retry Chain": "重试链路",
"Retry Suggestion": "重试建议",
"Retry Times": "重试次数",
@ -3339,7 +3356,6 @@
"Return Error": "返回错误",
"Return per-token log probabilities": "返回每个 token 的对数概率",
"Return to dashboard": "返回仪表盘",
"auth.resetPasswordConfirm.backToLogin": "返回登录",
"Return vector embeddings for inputs": "为输入返回向量嵌入",
"Reveal API key": "显示 API 密钥",
"Reveal key": "显示密钥",
@ -3491,7 +3507,6 @@
"Select all (filtered)": "全选(筛选结果)",
"Select all models": "选择所有模型",
"Select All Visible": "全选当前",
"Select model {{model}}": "选择模型 {{model}}",
"Select an operation mode and enter the amount": "选择操作模式并输入金额",
"Select announcement type": "选择公告类型",
"Select at least one field to overwrite.": "请选择至少一个要覆盖的字段。",
@ -3518,6 +3533,7 @@
"Select layout style": "选择布局样式",
"Select locations": "选择位置",
"Select Model": "选择模型",
"Select model {{model}}": "选择模型 {{model}}",
"Select models (empty for allow all)": "选择模型(留空表示允许所有)",
"Select models and apply to channel models list.": "选择模型并应用到渠道模型列表。",
"Select models or add custom ones": "选择模型或添加自定义模型",
@ -3547,6 +3563,7 @@
"Select vendor": "选择供应商",
"Selectable groups": "可选分组",
"selected": "已选择",
"Selected {{count}}": "已选 {{count}} 个",
"selected channel(s). Leave empty to remove tag.": "选定的渠道。留空以移除标签。",
"Selected conflicts were overwritten successfully.": "选中的冲突已成功覆盖。",
"Selected when creating a token and used as the default billing group for API calls.": "创建令牌时选择,用作 API 调用的默认计费分组。",
@ -3690,6 +3707,7 @@
"Status & Sync": "状态与同步",
"Status Code": "状态码",
"Status Code Mapping": "状态码映射",
"Status code mapping must use valid HTTP status codes": "状态码映射必须使用有效的 HTTP 状态码",
"Status Page Slug": "状态页面 Slug",
"Status short": "状态",
"Status:": "状态:",
@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "此操作将永久移除您账户的 2FA 保护。",
"This channel is not an Ollama channel.": "该渠道不是 Ollama 渠道。",
"This channel type does not support fetching models": "此通道类型不支持获取模型",
"This channel type requires additional configuration": "此渠道类型需要填写额外配置",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "此确认会解锁支付、兑换码、订阅套餐和邀请奖励功能。请仔细阅读相关声明。",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "此处仅控制模型请求速率限制。Web/API 路由限流由环境变量配置,仍可能返回 429。",
"This data may be unreliable, use with caution": "此数据可能不可靠,请谨慎使用",
@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "通过外部工具扩展能力",
"Use our unified OpenAI-compatible endpoint in your applications": "在应用中使用我们兼容 OpenAI 的统一接口",
"Use Passkey to sign in without entering your password.": "使用通行密钥登录,无需输入密码。",
"Use presets or upstream discovery to populate the model list faster.": "使用预设或上游发现来更快填充模型列表。",
"Use secure connection when sending emails": "发送电子邮件时使用安全连接",
"Use sidebar shortcut": "使用侧边栏快捷方式",
"Use the full-width table to scan prices, then select a row to edit it here.": "先在表格中快速浏览价格,然后选择一行在这里编辑。",
@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "正在验证凭证并从你的 Pancake 账户拉取店铺...",
"Version Overrides": "版本覆盖",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Vertex AI API Key 模式不支持批量创建",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段",
"Vertex AI Key Format": "Vertex AI 密钥格式",
"Vertex AI service account key must be valid JSON": "Vertex AI 服务账号密钥必须是有效 JSON",
"Video": "视频",
"Video length in seconds": "视频时长(秒)",
"Video Remix": "视频 Remix",
@ -4334,7 +4356,7 @@
"Visit Settings → General and adjust quota options...": "访问设置 → 通用并调整配额选项...",
"Visitors must authenticate before accessing the pricing directory.": "访客必须先进行身份验证才能访问定价目录。",
"Visitors must authenticate before accessing the rankings page.": "访客必须先进行身份验证才能访问排行榜页面。",
"Visual": "可视",
"Visual": "可视",
"Visual edit": "可视化编辑",
"Visual editor": "可视化编辑器",
"Visual Editor": "可视编辑器",
@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "您的 GitHub OAuth 客户端 ID",
"Your GitHub OAuth Client Secret": "您的 GitHub OAuth 客户端密钥",
"Your new backup codes are ready": "您的新备份代码已准备就绪",
"auth.resetPasswordConfirm.success": "您的密码已成功重置",
"Your Referral Link": "您的推荐链接",
"Your setup guide is collapsed so usage stays in focus.": "设置引导已收起,让用量信息保持在焦点位置。",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "您的系统访问令牌,用于 API 认证。请妥善保管,不要与他人分享。",