From a08bf2ab9125c648e0ef141fe60e87104feb822c Mon Sep 17 00:00:00 2001 From: Gi99lin <74502520+Gi99lin@users.noreply.github.com> Date: Sat, 23 May 2026 00:17:30 +0300 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20configurable=20sidebar=20?= =?UTF-8?q?=E2=80=94=20presets,=20DnD=20ordering,=20smart-grouping=20(#258?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrated into release/v3.8.2 --- package-lock.json | 56 + package.json | 3 + .../settings/components/AppearanceTab.tsx | 192 +- .../settings/components/SidebarTab.tsx | 613 +++ .../dashboard/settings/sidebar/page.tsx | 7 + src/i18n/messages/en.json | 235 +- src/i18n/messages/ru.json | 3827 ++++++++--------- src/lib/db/settings.ts | 3 + src/shared/components/Sidebar.tsx | 45 +- src/shared/constants/sidebarVisibility.ts | 162 +- src/shared/validation/settingsSchemas.ts | 520 ++- src/types/settings.ts | 10 +- tests/unit/sidebar-customization.test.ts | 152 + 13 files changed, 3241 insertions(+), 2584 deletions(-) create mode 100644 src/app/(dashboard)/dashboard/settings/components/SidebarTab.tsx create mode 100644 src/app/(dashboard)/dashboard/settings/sidebar/page.tsx create mode 100644 tests/unit/sidebar-customization.test.ts diff --git a/package-lock.json b/package-lock.json index 59d05e2f13..346f26249c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ "open-sse" ], "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@lobehub/icons": "^5.8.0", "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "^4.7.0", @@ -664,6 +667,59 @@ "node": ">=20.19.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", diff --git a/package.json b/package.json index 95d9c0c95b..3dd425c524 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,9 @@ "build:cli-api": "node --import tsx/esm scripts/cli/generate-api-commands.mjs" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@lobehub/icons": "^5.8.0", "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "^4.7.0", diff --git a/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx b/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx index 1784b764d9..2221f2da8e 100644 --- a/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect } from "react"; +import Link from "next/link"; import { Button, Card, Toggle } from "@/shared/components"; import { useTheme } from "@/shared/hooks/useTheme"; import useThemeStore, { COLOR_THEMES } from "@/store/themeStore"; @@ -11,21 +12,11 @@ import { normalizeComboConfigMode, type ComboConfigMode, } from "@/shared/constants/comboConfigMode"; -import { - HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, - SIDEBAR_SECTIONS, - SIDEBAR_SETTINGS_UPDATED_EVENT, - getSectionItems, - normalizeHiddenSidebarItems, - type HideableSidebarItemId, -} from "@/shared/constants/sidebarVisibility"; -import { PIN_PROVIDER_QUOTA_TO_HOME_KEY } from "@/shared/constants/homeWidgets"; export default function AppearanceTab() { const { theme, setTheme, isDark } = useTheme(); const { colorTheme, customColor, setColorTheme, setCustomColorTheme } = useThemeStore(); const t = useTranslations("settings"); - const tSidebar = useTranslations("sidebar"); const [settings, setSettings] = useState>({}); const [loading, setLoading] = useState(true); const [uploadError, setUploadError] = useState(null); @@ -33,22 +24,13 @@ export default function AppearanceTab() { const isValidHex = /^#([0-9a-fA-F]{6})$/.test( customThemeColor.startsWith("#") ? customThemeColor : `#${customThemeColor}` ); - const hiddenSidebarItems = normalizeHiddenSidebarItems( - settings[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY] - ); - const hiddenSidebarSet = new Set(hiddenSidebarItems); const comboConfigMode = normalizeComboConfigMode(settings[COMBO_CONFIG_MODE_SETTING_KEY]); const showCloudflaredTunnel = settings.hideEndpointCloudflaredTunnel !== true; const showTailscaleFunnel = settings.hideEndpointTailscaleFunnel !== true; const showNgrokTunnel = settings.hideEndpointNgrokTunnel !== true; - const pinProviderQuotaToHome = settings[PIN_PROVIDER_QUOTA_TO_HOME_KEY] === true; - const showQuickStartOnHome = settings.showQuickStartOnHome !== false; // default on - const showProviderTopologyOnHome = settings.showProviderTopologyOnHome !== false; // default on const getSettingsLabel = (key: string, fallback: string) => typeof t.has === "function" && t.has(key) ? t(key) : fallback; - const getSidebarLabel = (key: string, fallback: string) => - typeof tSidebar.has === "function" && tSidebar.has(key) ? tSidebar(key) : fallback; useEffect(() => { const unsubscribe = useThemeStore.subscribe((state) => { @@ -74,12 +56,7 @@ export default function AppearanceTab() { return res.json(); }) .then((data) => { - setSettings({ - ...data, - [HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]: normalizeHiddenSidebarItems( - data[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY] - ), - }); + setSettings(data); setLoading(false); }) .catch(() => setLoading(false)); @@ -93,18 +70,7 @@ export default function AppearanceTab() { body: JSON.stringify({ [key]: value }), }); if (res.ok) { - setSettings((prev) => ({ - ...prev, - [key]: - key === HIDDEN_SIDEBAR_ITEMS_SETTING_KEY ? normalizeHiddenSidebarItems(value) : value, - })); - if (typeof window !== "undefined") { - window.dispatchEvent( - new CustomEvent(SIDEBAR_SETTINGS_UPDATED_EVENT, { - detail: { [key]: value }, - }) - ); - } + setSettings((prev) => ({ ...prev, [key]: value })); } } catch (err) { console.error("Failed to update", key, err); @@ -147,23 +113,6 @@ export default function AppearanceTab() { }, ]; - const showDebug = settings.debugMode === true; - const sidebarSections = SIDEBAR_SECTIONS.filter( - (section) => section.visibility !== "debug" || showDebug - ).map((section) => ({ - ...section, - title: getSidebarLabel(section.titleKey, section.titleFallback), - items: getSectionItems(section).map((item) => ({ ...item, label: tSidebar(item.i18nKey) })), - })); - - const toggleSidebarItem = (itemId: HideableSidebarItemId) => { - const nextHiddenItems = hiddenSidebarSet.has(itemId) - ? hiddenSidebarItems.filter((id) => id !== itemId) - : [...hiddenSidebarItems, itemId]; - - updateSetting(HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, nextHiddenItems); - }; - return (
@@ -389,130 +338,27 @@ export default function AppearanceTab() {
- {/* Pin Information to Home Page section */}
-
-

- {getSettingsLabel("pinProviderQuotaToHome", "Pin Information to Home Page")} -

-

- Choose which sections to pin to the top of the Home page. -

-
- - {/* Pinned options as a rounded table/list */} -
-
- {/* Provider Quota Limits */} -
-
-

- {getSettingsLabel("providerQuotaLimits", "Provider Quota Limits")} -

-

- {getSettingsLabel( - "providerQuotaLimitsDesc", - "Pin the Provider Quota status container (with Refresh All button) to the top of the Home page." - )} -

-
- updateSetting(PIN_PROVIDER_QUOTA_TO_HOME_KEY, !pinProviderQuotaToHome)} - disabled={loading} - /> -
- - {/* Quick Start */} -
-
-

- {getSettingsLabel("quickStart", "Quick Start")} -

-

- {getSettingsLabel( - "quickStartDesc", - "Show the Quick Start panel on the Home page." - )} -

-
- updateSetting("showQuickStartOnHome", !showQuickStartOnHome)} - disabled={loading} - /> -
- - {/* Provider Topology */} -
-
-

- {getSettingsLabel("providerTopology", "Provider Topology")} -

-

- {getSettingsLabel( - "providerTopologyDesc", - "Show the Provider Topology on the Home page." - )} -

-
- updateSetting("showProviderTopologyOnHome", !showProviderTopologyOnHome)} - disabled={loading} - /> -
+
+
+

{t("sidebarVisibilityToggle")}

+

+ {getSettingsLabel( + "sidebarCustomizeLink", + "Customize which items appear in the sidebar, their order, and apply role presets." + )} +

+ + view_sidebar + {getSettingsLabel("sidebarCustomizeLinkBtn", "Customize")} +
-
-
-

{t("sidebarVisibilityToggle")}

-

- {getSettingsLabel( - "sidebarVisibilityDesc", - "Hide any sidebar navigation entry to reduce visual clutter without disabling any features" - )} -

-
- -
- {sidebarSections.map((section) => ( -
-
-

- {section.title} -

-
- -
- {section.items.map((item) => ( -
-

{item.label}

- toggleSidebarItem(item.id)} - disabled={loading} - /> -
- ))} -
-
- ))} -
- -

- {getSettingsLabel( - "sidebarVisibilityHint", - "Any sidebar section is hidden automatically when all of its entries are hidden" - )} -

-
-
diff --git a/src/app/(dashboard)/dashboard/settings/components/SidebarTab.tsx b/src/app/(dashboard)/dashboard/settings/components/SidebarTab.tsx new file mode 100644 index 0000000000..0478b495db --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/SidebarTab.tsx @@ -0,0 +1,613 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, + type DragEndEvent, +} from "@dnd-kit/core"; +import { + SortableContext, + useSortable, + verticalListSortingStrategy, + arrayMove, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { Card, Toggle } from "@/shared/components"; +import { cn } from "@/shared/utils/cn"; +import { useTranslations } from "next-intl"; +import { + HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, + SIDEBAR_SECTION_ORDER_KEY, + SIDEBAR_ITEM_ORDER_KEY, + SIDEBAR_PRESET_KEY, + SIDEBAR_SETTINGS_UPDATED_EVENT, + SIDEBAR_SECTIONS, + SIDEBAR_PRESETS, + applySectionOrder, + applyItemOrder, + getSectionItems, + normalizeHiddenSidebarItems, + type HideableSidebarItemId, + type SidebarSectionId, + type SidebarItemOrder, + type SidebarPresetId, + type SidebarSectionDefinition, + type SidebarSectionChild, + type SidebarItemDefinition, + type SidebarItemGroup, +} from "@/shared/constants/sidebarVisibility"; + +// ─── Sortable section row ────────────────────────────────────────────────────── + +interface SortableSectionProps { + section: SidebarSectionDefinition & { title: string }; + hiddenSet: Set; + itemOrder: string[]; + onToggleItem: (id: HideableSidebarItemId) => void; + onItemReorder: (sectionId: SidebarSectionId, newOrder: string[]) => void; + getLabel: (key: string, fallback: string) => string; +} + +function SortableSection({ + section, + hiddenSet, + itemOrder, + onToggleItem, + onItemReorder, + getLabel, +}: SortableSectionProps) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: section.id, + }); + const style = { transform: CSS.Transform.toString(transform), transition }; + + const [expanded, setExpanded] = useState(true); + + const allChildren = section.children as SidebarSectionChild[]; + const getChildId = (c: SidebarSectionChild) => + "type" in c && c.type === "group" ? c.id : (c as SidebarItemDefinition).id; + + const orderedChildren = applyItemOrder(allChildren, itemOrder); + const childIds = orderedChildren.map(getChildId); + const sensors = useSensors(useSensor(PointerSensor)); + + const handleItemDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + const oldIdx = childIds.indexOf(active.id as string); + const newIdx = childIds.indexOf(over.id as string); + if (oldIdx === -1 || newIdx === -1) return; + onItemReorder(section.id as SidebarSectionId, arrayMove(childIds, oldIdx, newIdx)); + }; + + return ( +
+ {/* Section header */} +
+ + +
+ + {/* Section children with inner DnD */} + {expanded && ( + + +
+ {orderedChildren.map((child) => { + if ("type" in child && child.type === "group") { + const group = child as SidebarItemGroup; + return ( + + + + ); + } + const item = child as SidebarItemDefinition; + return ( + + + + ); + })} +
+
+
+ )} +
+ ); +} + +// ─── Sortable child row wrapper ──────────────────────────────────────────────── + +function SortableChildRow({ id, children }: { id: string; children: React.ReactNode }) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id, + }); + const style = { transform: CSS.Transform.toString(transform), transition }; + return ( +
+ +
{children}
+
+ ); +} + +// ─── Item row ───────────────────────────────────────────────────────────────── + +interface ItemRowProps { + item: SidebarItemDefinition; + hiddenSet: Set; + onToggleItem: (id: HideableSidebarItemId) => void; + getLabel: (key: string, fallback: string) => string; +} + +// Items that must always remain visible (safety guard) +const PROTECTED_ITEM_IDS = new Set(["settings-sidebar"]); + +function ItemRow({ item, hiddenSet, onToggleItem, getLabel }: ItemRowProps) { + const isProtected = PROTECTED_ITEM_IDS.has(item.id); + return ( +
+
+ + {item.icon} + +

{getLabel(item.i18nKey, item.id)}

+
+ {isProtected ? ( + + lock + + ) : ( + onToggleItem(item.id)} /> + )} +
+ ); +} + +// ─── Group row (items inside group, no sub-DnD) ─────────────────────────────── + +interface GroupRowProps { + group: SidebarItemGroup; + hiddenSet: Set; + onToggleItem: (id: HideableSidebarItemId) => void; + getLabel: (key: string, fallback: string) => string; +} + +function GroupRow({ group, hiddenSet, onToggleItem, getLabel }: GroupRowProps) { + const [open, setOpen] = useState(true); + return ( +
+ + {open && ( +
+ {group.items.map((item) => ( +
+
+ + {item.icon} + +

{getLabel(item.i18nKey, item.id)}

+
+ onToggleItem(item.id)} + /> +
+ ))} +
+ )} +
+ ); +} + +// ─── Main component ─────────────────────────────────────────────────────────── + +export default function SidebarTab() { + const t = useTranslations("settings"); + const tSidebar = useTranslations("sidebar"); + + const getLabel = useCallback( + (key: string, fallback: string) => + typeof tSidebar.has === "function" && tSidebar.has(key) ? tSidebar(key) : fallback, + [tSidebar] + ); + const getSettingsLabel = useCallback( + (key: string, fallback: string) => + typeof t.has === "function" && t.has(key) ? t(key) : fallback, + [t] + ); + + const [loading, setLoading] = useState(true); + const [hiddenSidebarItems, setHiddenSidebarItems] = useState([]); + const [sectionOrder, setSectionOrder] = useState([]); + const [itemOrder, setItemOrder] = useState({}); + const [activePreset, setActivePreset] = useState(null); + const [confirmPreset, setConfirmPreset] = useState(null); + const [showDebug, setShowDebug] = useState(false); + + useEffect(() => { + fetch("/api/settings") + .then((r) => r.json()) + .then((data) => { + setHiddenSidebarItems( + normalizeHiddenSidebarItems(data?.[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]) + ); + setSectionOrder( + Array.isArray(data?.[SIDEBAR_SECTION_ORDER_KEY]) ? data[SIDEBAR_SECTION_ORDER_KEY] : [] + ); + setItemOrder( + data?.[SIDEBAR_ITEM_ORDER_KEY] && typeof data[SIDEBAR_ITEM_ORDER_KEY] === "object" + ? data[SIDEBAR_ITEM_ORDER_KEY] + : {} + ); + setActivePreset(data?.[SIDEBAR_PRESET_KEY] ?? null); + setShowDebug(data?.debugMode === true); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, []); + + const patch = async (updates: Record) => { + try { + const res = await fetch("/api/settings", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(updates), + }); + if (res.ok) { + window.dispatchEvent(new CustomEvent(SIDEBAR_SETTINGS_UPDATED_EVENT, { detail: updates })); + } else { + console.error("Failed to update sidebar settings:", res.statusText); + } + } catch (err) { + console.error("Error updating sidebar settings:", err); + } + }; + + const toggleItem = (id: HideableSidebarItemId) => { + // Protected items can never be hidden + if (PROTECTED_ITEM_IDS.has(id)) return; + const next = hiddenSidebarItems.includes(id) + ? hiddenSidebarItems.filter((x) => x !== id) + : [...hiddenSidebarItems, id]; + setHiddenSidebarItems(next); + // Any manual change → custom mode + setActivePreset(null); + patch({ [HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]: next, [SIDEBAR_PRESET_KEY]: null }); + }; + + const hiddenSet = new Set(hiddenSidebarItems); + + const visibleSections = SIDEBAR_SECTIONS.filter((s) => s.visibility !== "debug" || showDebug).map( + (s) => ({ ...s, title: getLabel(s.titleKey, s.titleFallback) }) + ); + + const orderedSections = applySectionOrder(visibleSections, sectionOrder); + + const sectionIds = orderedSections.map((s) => s.id); + + const sensors = useSensors(useSensor(PointerSensor)); + + const handleSectionDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + const oldIdx = sectionIds.indexOf(active.id as SidebarSectionId); + const newIdx = sectionIds.indexOf(over.id as SidebarSectionId); + if (oldIdx === -1 || newIdx === -1) return; + const newOrder = arrayMove(sectionIds, oldIdx, newIdx) as SidebarSectionId[]; + setSectionOrder(newOrder); + setActivePreset(null); + patch({ [SIDEBAR_SECTION_ORDER_KEY]: newOrder, [SIDEBAR_PRESET_KEY]: null }); + }; + + const handleItemReorder = (sectionId: SidebarSectionId, newOrder: string[]) => { + const next = { ...itemOrder, [sectionId]: newOrder }; + setItemOrder(next); + setActivePreset(null); + patch({ [SIDEBAR_ITEM_ORDER_KEY]: next, [SIDEBAR_PRESET_KEY]: null }); + }; + + const applyPreset = (presetId: SidebarPresetId) => { + const preset = SIDEBAR_PRESETS.find((p) => p.id === presetId); + if (!preset) return; + // Ensure protected items are never hidden, even if a preset includes them + const safeHidden = preset.hiddenItems.filter((id) => !PROTECTED_ITEM_IDS.has(id)); + setHiddenSidebarItems(safeHidden); + setSectionOrder([]); + setItemOrder({}); + setActivePreset(presetId); + setConfirmPreset(null); + patch({ + [HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]: safeHidden, + [SIDEBAR_SECTION_ORDER_KEY]: [], + [SIDEBAR_ITEM_ORDER_KEY]: {}, + [SIDEBAR_PRESET_KEY]: presetId, + }); + }; + + const resetToDefault = () => applyPreset("all"); + + const presetLabels: Record = { + all: getSettingsLabel("presetAll", "All"), + minimal: getSettingsLabel("presetMinimal", "Minimal"), + developer: getSettingsLabel("presetDeveloper", "Developer"), + admin: getSettingsLabel("presetAdmin", "Admin"), + }; + + const presetDescriptions: Record = { + all: getSettingsLabel("presetAllDesc", "Show everything"), + minimal: getSettingsLabel("presetMinimalDesc", "Core pages only"), + developer: getSettingsLabel("presetDeveloperDesc", "Dev & proxy tools"), + admin: getSettingsLabel("presetAdminDesc", "Monitoring & audit"), + }; + + return ( + +
+
+ +
+
+

+ {getSettingsLabel("settingsSidebarTitle", "Sidebar Customization")} +

+

+ {getSettingsLabel( + "settingsSidebarDesc", + "Control which items appear in the sidebar and their order" + )} +

+
+
+ +
+ {/* Presets */} +
+
+

{getSettingsLabel("sidebarPresets", "Presets")}

+

+ {getSettingsLabel( + "sidebarPresetsDesc", + "Start from a role-based layout. Any change after applying a preset switches to Custom." + )} +

+
+ + {/* Active preset badge */} +
+ + {getSettingsLabel("activePresetLabel", "Active:")} + + + {activePreset + ? presetLabels[activePreset] + : getSettingsLabel("presetCustom", "Custom")} + +
+ +
+ {SIDEBAR_PRESETS.map((preset) => { + const isActive = activePreset === preset.id; + return ( + + ); + })} +
+ + {/* Confirm preset dialog */} + {confirmPreset && ( +
+ + warning + +

+ {getSettingsLabel( + "presetConfirmWarning", + `Applying "${presetLabels[confirmPreset]}" will replace your current visibility and order settings.` + )} +

+
+ + +
+
+ )} +
+ + {/* Visibility & order */} +
+
+
+

+ {getSettingsLabel("sidebarOrder", "Visibility & Order")} +

+

+ {getSettingsLabel( + "sidebarOrderDesc", + "Toggle items on/off and drag to reorder sections and their entries." + )} +

+
+ +
+ +
+ + + {orderedSections.map((section) => ( + + ))} + + +
+ +

+ {getSettingsLabel( + "sidebarVisibilityHint", + "A sidebar section hides automatically when all of its entries are hidden" + )} +

+
+
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/sidebar/page.tsx b/src/app/(dashboard)/dashboard/settings/sidebar/page.tsx new file mode 100644 index 0000000000..24b60f9c76 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/sidebar/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +import SidebarTab from "../components/SidebarTab"; + +export default function SettingsSidebarPage() { + return ; +} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index f2cbc93e1a..a33d9b091e 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -842,7 +842,6 @@ "settingsAi": "AI Settings", "settingsSecurity": "Security", "settingsFeatureFlags": "Feature Flags", - "settingsAuthz": "Authz", "settingsRouting": "Routing", "settingsResilience": "Resilience", "settingsAdvanced": "Advanced", @@ -914,16 +913,12 @@ "settingsAdvancedSubtitle": "Power user options", "settingsSecuritySubtitle": "Auth and encryption", "settingsFeatureFlagsSubtitle": "Toggle system capabilities", - "settingsAuthzSubtitle": "Route inventory and bypass policy", "docsSubtitle": "Documentation", "issuesSubtitle": "Report a bug", "changelogSubtitle": "Release notes", - "leaderboard": "Leaderboard", - "leaderboardSubtitle": "Top contributors and usage rankings", - "profile": "Profile", - "profileSubtitle": "Your account profile", - "tokens": "Tokens", - "tokensSubtitle": "Token balance and history" + "settingsSidebar": "Sidebar", + "settingsSidebarSubtitle": "Sidebar customization", + "gamificationGroup": "Gamification" }, "webhooks": { "title": "Webhooks", @@ -1296,7 +1291,6 @@ "keyName": "Key Name", "keyNamePlaceholder": "e.g. Production Key", "keyNameDesc": "Choose a descriptive name to identify this key's purpose", - "managementAccessDesc": "Allow this API key to manage OmniRoute configuration.", "keyCreated": "API Key Created", "keyCreatedSuccess": "Key created successfully!", "keyCreatedNote": "Copy and store this key now — it won't be shown again.", @@ -1634,7 +1628,6 @@ "amp": "Use when you want Amp shorthand workflows but still need OmniRoute alias and routing rules enforcement.", "qwen": "Use when you need Alibaba Qwen Code CLI for coding tasks.", "hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.", - "hermes-agent": "Use when you need Hermes Agent (by Nousresearch) with default, delegation, vision and auxiliary models routed through OmniRoute.", "custom": "Use for custom tool implementations or generic OpenAI-compatible configurations." }, "toolDescriptions": { @@ -1654,7 +1647,6 @@ "qwen": "Alibaba Qwen Code CLI", "amp": "Sourcegraph Amp coding assistant CLI", "hermes": "Hermes AI Terminal Assistant", - "hermes-agent": "Hermes Agent (by Nousresearch) - Advanced terminal AI with multi-model support (delegation, vision, compression, etc.)", "custom": "Generic OpenAI-compatible CLI or SDK configuration generator" }, "guides": { @@ -2041,17 +2033,6 @@ "recommendationsApplied": "Recommendations applied to this combo.", "intelligentPanelTitle": "Intelligent Routing Dashboard", "intelligentPanelDesc": "Real-time scoring and health status for this auto-routing combo.", - "configOnlyStatus": "Configuration View", - "configOnlyHint": "This panel shows routing inputs only. Live breaker state is available on the Health page.", - "routingInputs": "Routing Inputs", - "routingInputsHint": "Mode pack and weighting stay here; breaker runtime state stays on the Health page.", - "emailVisibilityHint": "Account emails here follow the global privacy toggle.", - "emailVisibilityTooltip": "Use the eye icon to reveal or hide account emails globally across combos, providers and quota screens.", - "manualModel": "Manual model", - "manualModelInvalid": "Enter a model as provider/model.", - "manualModelUnknownProvider": "Unknown provider prefix.", - "builderDynamicAccountShort": "Dynamic account", - "builderNeedValidName": "Define a valid combo name before continuing.", "statusOverview": "Status Overview", "normalOperation": "Normal Operation", "allProvidersHealthy": "Providers are reporting healthy routing conditions.", @@ -2361,8 +2342,7 @@ "previousPeriod": "Previous Half", "currentPeriod": "Current Half", "exportCSV": "Export as CSV", - "exportJSON": "Export as JSON", - "legacyFreeLabel": "Legacy / Free" + "exportJSON": "Export as JSON" }, "endpoint": { "title": "API Endpoint", @@ -3645,12 +3625,6 @@ "geminiCliProjectIdLabel": "Google Cloud Project ID", "geminiCliProjectIdPlaceholder": "my-gcp-project-id", "antigravityProjectIdHint": "Optional override for Antigravity Cloud Code requests. Leave blank to use the project discovered during Google OAuth.", - "antigravityClientProfileLabel": "Client profile", - "antigravityClientProfileHint": "Choose which Antigravity client identity OmniRoute presents to the API.", - "antigravityClientProfileIde": "IDE", - "antigravityClientProfileHarness": "Harness / CLI", - "codexFastTierActiveChip": "Codex Fast tier is active", - "tierFast": "Fast", "antigravityProjectIdLabel": "Google Cloud Project ID", "antigravityProjectIdPlaceholder": "my-gcp-project-id", "grokWebCookieHint": "Grok Web Cookie Hint", @@ -3758,65 +3732,6 @@ "cache": "Cache", "resilience": "Resilience", "routingSettingsIntro": "Controls how your requests are routed, transformed, and sent to AI providers.", - "routingOpDropParagraphContainsLabel": "Drop paragraph (contains)", - "routingOpDropParagraphStartsWithLabel": "Drop paragraph (starts with)", - "routingOpReplaceTextLabel": "Replace text", - "routingOpReplaceRegexLabel": "Replace regex", - "routingOpDropBlockContainsLabel": "Drop block (contains)", - "routingOpPrependSystemBlockLabel": "Prepend system block", - "routingOpAppendSystemBlockLabel": "Append system block", - "routingOpInjectBillingHeaderLabel": "Inject billing header", - "routingOpObfuscateWordsLabel": "Obfuscate words (ZWJ)", - "routingOpDropParagraphContainsDesc": "Removes any paragraph (text block split on blank lines) inside the system prompt whose text contains ANY of the listed substrings. Use to strip third-party client fingerprints like 'github.com/anomalyco/opencode' or 'docs.openwebui.com' that Anthropic's classifier flags.", - "routingOpDropParagraphStartsWithDesc": "Removes any paragraph that STARTS WITH one of the listed prefixes. Use for identity lines like 'You are OpenCode' or 'You are Open WebUI' that announce the calling client.", - "routingOpReplaceTextDesc": "Replaces a literal substring with another literal substring. Use for known trigger phrases — e.g. rewrite 'Here is some useful information about the environment you are running in:' to 'Environment context you are running in:' (an empirically-validated trigger phrase).", - "routingOpReplaceRegexDesc": "Replaces text matching a regular expression. Use when you need patterns (character classes, optional whitespace, anchors) instead of literal substrings. A malformed pattern is caught at runtime when the op runs.", - "routingOpDropBlockContainsDesc": "Removes ENTIRE system blocks (not just paragraphs) whose text contains any of the listed substrings. Use when a whole block is fingerprint-bearing and you want it gone — e.g. an injected MCP-server description.", - "routingOpPrependSystemBlockDesc": "Inserts a new text block at the FRONT of the system array. Use to add the SDK identity 'You are a Claude agent, built on Anthropic's Claude Agent SDK.' that Anthropic's classifier expects.", - "routingOpAppendSystemBlockDesc": "Inserts a new text block at the END of the system array. Use for cosmetic additions that don't need to be at position [0].", - "routingOpInjectBillingHeaderDesc": "Prepends the special 'x-anthropic-billing-header: cc_version=...; cc_entrypoint=...; cch=...;' text block that Anthropic's classifier validates. Required for CC bridge relay endpoints; for the native claude provider OmniRoute already injects its own billing line so this op is usually redundant there.", - "routingOpObfuscateWordsDesc": "Inserts a Zero-Width-Joiner character after the first letter of each listed word, so 'opencode' becomes 'o\u200dpencode'. Reads identical to humans but bypasses classifier word matches. Targets system blocks, user/assistant messages, and tool descriptions.", - "routingNeedlesHint": "List of substrings. A paragraph matches if it contains ANY one of them. Add one per line via 'Add entry'.", - "routingPrefixesHint": "List of strings. A paragraph matches if it starts with any one of them (leading whitespace is trimmed before matching).", - "routingCaseSensitiveHint": "When ON, 'OpenCode' and 'opencode' are different strings. When OFF (default), the comparison ignores case.", - "routingMatchLiteralHint": "Exact literal substring to find. No regex syntax — special chars like . * ? are treated as themselves.", - "routingReplacementTextHint": "Replacement string. Leave blank to delete the match. The output preserves surrounding text.", - "routingAllOccurrencesHint": "When ON (default), every instance is replaced. When OFF, only the first match is replaced.", - "routingPatternHint": "JavaScript regex source. Don't wrap in slashes — just 'foo(.*)bar'. Server rejects patterns that fail to compile.", - "routingRegexFlagsHint": "JavaScript regex flags (g = all matches, i = case-insensitive, s = dot matches newline, m = multiline). Default 'g'.", - "routingBlockTextHint": "Full text of the new system block. Use a literal string; the system block stores text only.", - "routingIdempotencyKeyHint": "Optional. If set, the op skips when a block whose text starts with this key is already present. Prevents double-prepend on retries.", - "routingBillingEntrypointHint": "Value injected as 'cc_entrypoint='. Anthropic accepts 'sdk-cli' (Agent SDK), 'cli' (Claude Code CLI), or other documented values.", - "routingBillingVersionFormatHint": "How the 3-char build hash after cc_version= is computed. 'ex-machina' = sha256 of CCH_SALT+chars-from-first-user-msg+version (per-message). 'omniroute-daystamp' = sha256 of YYYY-MM-DD+version (stable per-day).", - "routingBillingCchAlgoHint": "How the 5-char cch= token is computed. 'sha256-first-user' = sha256 of first user message text. 'xxhash64-body' = body-level signing fills it later. 'static-zero' = literal '00000' placeholder.", - "routingObfuscateWordsHint": "Lowercase words to obfuscate. ZWJ insertion is applied case-insensitively, so 'opencode' also matches 'OpenCode' and 'OPENCODE'.", - "routingObfuscateTargetsHint": "Which body regions to scan for the words: system blocks, user/assistant messages, and/or tool descriptions.", - "routingObfuscateTargetsLabel": "Targets", - "routingSummarizeDropParagraphContains": "drop paragraphs containing: {items}", - "routingSummarizeDropParagraphStartsWith": "drop paragraphs starting with: {items}", - "routingSummarizeReplaceText": "replace \"{match}\" → \"{replacement}\"", - "routingSummarizeReplaceRegex": "regex /{pattern}/{flags} → \"{replacement}\"", - "routingSummarizeDropBlockContains": "drop blocks containing: {items}", - "routingSummarizePrependSystemBlock": "prepend block: \"{text}\"", - "routingSummarizeAppendSystemBlock": "append block: \"{text}\"", - "routingSummarizeInjectBillingHeader": "inject billing header (entrypoint={entrypoint}, version={versionFormat}, cch={cchAlgo})", - "routingSummarizeObfuscateWords": "obfuscate {count} word(s) via ZWJ in {targets}", - "routingDefaultAutoVariantLKGP": "Last Known Good Provider", - "routingDefaultAutoVariantLKGPDesc": "Last Known Good Provider", - "routingDefaultAutoVariantCoding": "Quality-first for code", - "routingDefaultAutoVariantCodingDesc": "Quality-first for code", - "routingDefaultAutoVariantFast": "Low-latency routing", - "routingDefaultAutoVariantFastDesc": "Low-latency routing", - "routingDefaultAutoVariantCheap": "Cost-optimized", - "routingDefaultAutoVariantCheapDesc": "Cost-optimized", - "routingDefaultAutoVariantOffline": "High availability", - "routingDefaultAutoVariantOfflineDesc": "High availability", - "routingDefaultAutoVariantSmart": "Best discovery (10% explore)", - "routingDefaultAutoVariantSmartDesc": "Best discovery (10% explore)", - "routingOpSummaryCount": "{count, plural, =0 {no ops} one {# op} other {# ops}}", - "routingOpEnabled": "enabled", - "routingOpDisabled": "disabled", - "routingOpStatusSeparator": " · ", "resilienceSettingsIntro": "Automatic retry, cooldown, and fallback when providers fail.", "aiSettingsIntro": "AI-specific settings for thinking budget, model behavior, and compression.", "systemPrompt": "System Prompt", @@ -3892,13 +3807,6 @@ "systemTheme": "System Theme", "debugToggle": "Enable Debug Mode", "sidebarVisibilityToggle": "Show Sidebar Items", - "pinProviderQuotaToHome": "Pin Information to Home Page", - "providerQuotaLimits": "Provider Quota Limits", - "providerQuotaLimitsDesc": "Pin the Provider Quota status container (with Refresh All button) to the top of the Home page.", - "quickStart": "Quick Start", - "quickStartDesc": "Show the Quick Start panel on the Home page.", - "providerTopology": "Provider Topology", - "providerTopologyDesc": "Show the Provider Topology on the Home page.", "enableCache": "Enable Cache", "cacheTTL": "Cache TTL", "maxCacheSize": "Max Cache Size", @@ -4578,27 +4486,13 @@ "oneproxySuccess": "Success", "oneproxyFailed": "Failed", "routingAntigravitySignatureTitle": "Antigravity Signature Cache Mode", - "routingAntigravitySignatureDesc": "Control whether OmniRoute reuses only stored Gemini thought signatures or accepts validated client-provided signatures in Antigravity-compatible tool-call flows.", - "routingAntigravitySignatureEnabledLabel": "Enabled", - "routingAntigravitySignatureEnabledDesc": "Current behavior. Ignore client-provided signatures and keep using the stored OmniRoute flow.", - "routingAntigravitySignatureBypassLabel": "Bypass", - "routingAntigravitySignatureBypassDesc": "Accept client-provided signatures after lightweight validation and fall back to the stored signature when invalid.", - "routingAntigravitySignatureBypassStrictLabel": "Bypass Strict", - "routingAntigravitySignatureBypassStrictDesc": "Require full protobuf validation before accepting a client-provided signature.", "routingHeaderFingerprintTitle": "Header fingerprint (per provider)", "routingServerRejectedSave": "⚠ Server rejected save:", "routingAddTransformOp": "Add a transform op", "routingClientCacheControlTitle": "Client Cache Control", - "routingClientCacheControlDesc": "Configure whether OmniRoute preserves client-provided cache_control markers", - "routingClientCacheControlAutoDesc": "For deterministic Claude-compatible flows, preserve client-provided cache_control as-is. If the request has no cache_control, OmniRoute does not inject any bridge-owned markers for CC-compatible third-party proxy compatibility.", - "routingClientCacheControlAlwaysLabel": "Always Preserve", - "routingClientCacheControlAlwaysDesc": "Always forward client-provided cache_control headers to upstream providers as-is.", - "routingClientCacheControlNeverLabel": "Never Preserve", - "routingClientCacheControlNeverDesc": "Always remove client cache_control headers and let OmniRoute manage caching where native provider flows support it.", - "routingZeroConfigTitle": "Zero-Config Auto-Routing", - "routingZeroConfigDesc": "Enable automatic provider selection using the auto/ prefix. When enabled, requests to auto, auto/coding, auto/fast, etc. will dynamically route across all connected providers.", - "routingDefaultAutoVariant": "Default Auto Variant", "visionBridge": "Vision Bridge", + "routingZeroConfigTitle": "Zero-Config Auto-Routing", + "routingDefaultAutoVariant": "Default Auto Variant", "visionBridgeModel": "Bridge Model", "resilienceMaxBackoffSteps": "Max backoff steps", "resilienceBaseCooldownLabel": "Base cooldown", @@ -4666,74 +4560,36 @@ "codexFastTierDesc": "Globally inject service_tier=priority for OpenAI Codex requests.", "codexFastTierHint": "When enabled, OmniRoute adds service_tier=priority to outbound Codex requests for connections that don't already specify a tier. Priority tier requires an OpenAI Enterprise API key or the ChatGPT-auth Codex path; other key types will receive a tier-related error from OpenAI. Per-connection settings on the Codex provider page take precedence.", "codexFastTierSaveError": "Failed to update Codex Fast Tier setting", - "codexFastTierTierLabel": "Service tier", - "codexFastTierTierPriority": "Priority", - "codexFastTierTierFlex": "Flex", - "codexFastTierTierDefault": "Default", - "codexFastTierModelsLabel": "Fast-tier models", - "codexFastTierModelsHint": "Only checked models receive service_tier when Fast Tier is enabled.", - "codexFastTierModelCheckbox": "Enable Fast Tier for {model}", "claudeFastModeTitle": "Claude Fast Mode", "claudeFastModeDesc": "Opt selected Claude requests into Anthropic Fast Mode (speed:\"fast\").", "claudeFastModeHint": "Anthropic does not officially support Fast Mode for SDK-style clients. When enabled, OmniRoute forwards an X-CPA-Force-Fast-Mode header so a paired CLIProxyAPI build can opt-in spoof the entrypoint. Only the listed Opus models are gated by Anthropic's client-side check. Subscription tier, Max plan, and Fast Mode credit balance are still enforced server-side — Anthropic may return out_of_credits even when the toggle is on.", "claudeFastModeModelsLabel": "Applied to models ({count})", "claudeFastModeModelCheckbox": "Enable Fast Mode for {model}", "claudeFastModeSaveError": "Failed to update Claude Fast Mode setting", - "authz": { - "title": "Authz Inventory", - "description": "5-tier route classification with live bypass policy. Read shows the full taxonomy; mutations re-prompt for the management password.", - "loading": "Loading inventory…", - "loadError": "Failed to load authz inventory", - "tier": { - "LOCAL_ONLY": "Local only", - "ALWAYS_PROTECTED": "Always protected", - "MANAGEMENT": "Management", - "CLIENT_API": "Client API", - "PUBLIC": "Public" - }, - "bypass": { - "section": "Manage-scope bypass", - "kill_switch": { - "label": "Bypass kill-switch", - "desc": "Master toggle. When off, no LOCAL_ONLY prefix is reachable from non-loopback regardless of the per-prefix list." - }, - "prefix": { - "label": "Bypassable prefixes", - "desc": "LOCAL_ONLY prefixes that manage-scope API keys (or dashboard sessions) may reach from non-loopback hosts.", - "add": "Add prefix", - "placeholder": "/api/mcp/v2/", - "empty": "No prefixes configured. Bypass effectively off." - }, - "cli_tools_runtime_note": "Spawn-capable prefix. Compile-time deny; cannot be made bypassable. Shown read-only." - }, - "password": { - "prompt": { - "label": "Current password", - "desc": "Re-confirm to apply security-impacting changes." - }, - "placeholder": "Current management password", - "cancel": "Cancel", - "submit": "Apply" - }, - "save": "Save changes", - "saved": "Authz settings updated", - "pending": "Unsaved changes", - "badge": { - "bypassable": "Bypassable via manage scope", - "strict": "Strict loopback", - "auth_required": "Auth required", - "public": "Public", - "always_protected": "Always protected", - "spawn_capable": "Spawn-capable" - }, - "error": { - "PASSWORD_REQUIRED": "Current password required to apply these changes.", - "PASSWORD_MISMATCH": "Current password is incorrect.", - "INSUFFICIENT_SCOPE": "API key lacks the manage scope.", - "BYPASS_PREFIX_NOT_ALLOWED": "One or more prefixes target spawn-capable routes and cannot be bypassed.", - "GENERIC": "Failed to update authz settings." - } - } + "settingsSidebarTitle": "Sidebar Customization", + "settingsSidebarDesc": "Control which items appear in the sidebar and their order", + "sidebarPresets": "Presets", + "sidebarPresetsDesc": "Start from a role-based layout. Any change after applying a preset switches to Custom.", + "presetAll": "All", + "presetAllDesc": "Show everything", + "presetMinimal": "Minimal", + "presetMinimalDesc": "Core pages only", + "presetDeveloper": "Developer", + "presetDeveloperDesc": "Dev & proxy tools", + "presetAdmin": "Admin", + "presetAdminDesc": "Monitoring & audit", + "presetCustom": "Custom", + "activePresetLabel": "Active:", + "presetConfirmWarning": "Applying this preset will replace your current visibility and order settings.", + "cancelLabel": "Cancel", + "applyLabel": "Apply", + "sidebarOrder": "Visibility & Order", + "sidebarOrderDesc": "Toggle items on/off and drag to reorder sections and their entries.", + "sidebarCustomizeLink": "Customize which items appear in the sidebar, their order, and apply role presets.", + "sidebarCustomizeLinkBtn": "Customize", + "resetDefault": "Reset to default", + "settingsSidebar": "Sidebar", + "settingsSidebarSubtitle": "Visibility and order" }, "contextRtk": { "title": "RTK Engine", @@ -4828,8 +4684,6 @@ "enabled": "Enabled", "autoDetect": "Auto-detect language", "rulesCount": "{count} rules", - "inputCompressionTitle": "Input compression", - "inputCompressionDesc": "Rewrite chat history with shorter wording. Reduces input tokens by ~50%.", "analyticsTitle": "Compression Analytics", "noAnalytics": "No compression analytics yet.", "outputMode": "Output Mode", @@ -5231,25 +5085,7 @@ "tierPro": "Pro", "tierPlus": "Plus", "tierFree": "Free", - "tierLite": "Lite", "tierUnknown": "Unknown", - "statTotal": "Total", - "statCritical": "Critical", - "statAlert": "Alert", - "statHealthy": "Healthy", - "filterPurchaseTypeLabel": "Type", - "filterTierLabel": "Tier", - "purchaseAll": "All", - "purchaseOauthSub": "Subscription", - "purchaseOauthFree": "OAuth Free", - "purchaseApiKey": "API Key", - "creditsLabel": "Credits", - "creditBalanceHint": "Remaining balance", - "unlimitedLabel": "Unlimited", - "refreshing": "Refreshing", - "resetsIn": "Resets in", - "editCutoffs": "Edit cutoffs", - "forceRefresh": "Refresh now", "suiteBuilderSaveFailed": "Failed to save suite", "clone": "Clone", "exportSuite": "Export", @@ -5395,9 +5231,7 @@ "budgetWarnAtPct": "Warn at %", "quotaAlerts": "Quota alerts", "quotaTableRefreshing": "⟳ Refreshing...", - "noSpendLast30Days": "No spend in last 30 days", - "updatedShort": "Updated", - "lastRefreshed": "Last refreshed" + "noSpendLast30Days": "No spend in last 30 days" }, "modals": { "waitingAuth": "Waiting for Authorization", @@ -6072,7 +5906,6 @@ "clearError": "Failed to clear cache.", "unavailable": "Cache unavailable", "unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.", - "loadingCacheAria": "Loading cache", "promptCache": "Prompt Cache (Provider-Side)", "semanticCache": "Semantic Cache", "promptCacheSectionDesc": "Shows provider-side prompt caching activity from usage history so you can see where cache control is active and how much input reuse you are getting.", @@ -6412,8 +6245,6 @@ "combo": "Combo", "keys": "Keys", "shown": "Shown", - "loadMore": "Load more", - "loadingMore": "Loading more...", "sortNewest": "Newest", "sortOldest": "Oldest", "sortTokensDesc": "Tokens ↓", @@ -6633,8 +6464,8 @@ "equalSplit": "Equal split", "save": "Save allocations", "betaPreviewLabel": "Beta — UI preview.", - "betaConfigSavedPrefix": "Configuration is saved in", - "betaConfigSavedSuffix": "(not yet persisted on the server). Per-request cap enforcement is not yet wired into the proxy pipeline. This screen lets you design and visualize the quota split;", + "betaConfigSavedPrefix": "A configuração é salva em", + "betaConfigSavedSuffix": "(não persiste no servidor ainda). A aplicação dos caps por request ainda não está conectada ao pipeline da proxy. Esta tela permite desenhar e visualizar a divisão de cota;", "policyLabel": "Policy:" } } diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 9b7640d48f..2407dd11f0 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -22,34 +22,34 @@ "disabled": "Отключено", "active": "Активный", "inactive": "Неактивный", - "noData": "Нет данных", - "nothingHere": "Здесь пока ничего нет", + "noData": "Нет доступных данных", + "nothingHere": "Nothing here yet", "configure": "Настроить", - "manage": "Управлять", + "manage": "Управление", "name": "Имя", "actions": "Действия", "status": "Статус", "type": "Тип", "model": "Модель", "models": "модели", - "provider": "Провайдер", + "provider": "Поставщик", "account": "Аккаунт", "time": "Время", - "details": "Подробнее", + "details": "Подробности", "created": "Создано", "lastUsed": "Последнее обновление", - "loadMore": "Загрузить ещё", - "noResults": "Ничего не найдено", + "loadMore": "Загрузить больше", + "noResults": "Результаты не найдены", "reloadPage": "Обновить страницу", "connected": "Подключено", "disconnected": "Отключено", "notConfigured": "Не настроено", - "testConnection": "Проверить подключение", + "testConnection": "Тестовое соединение", "enable": "Включить", "disable": "Отключить", - "columns": "Колонки", - "newest": "Новые", - "oldest": "Старые", + "columns": "Столбцы", + "newest": "Новейший", + "oldest": "Самый старый", "all": "Все", "none": "Нет", "yes": "Да", @@ -57,7 +57,7 @@ "warning": "Предупреждение", "note": "Примечание", "free": "Бесплатно", - "skipToContent": "Перейти к содержанию", + "skipToContent": "Перейти к содержимому", "maintenanceServerIssues": "На сервере возникли проблемы. Некоторые функции могут быть недоступны.", "maintenanceServerUnreachable": "Сервер недоступен. Повторное подключение...", "accept": "accept", @@ -110,7 +110,7 @@ "oauth": "oauth", "auth_token": "auth_token", "crypto": "crypto", - "hours": "часы", + "hours": "hours", "selfsigned": "selfsigned", "proxy_id": "proxy_id", "proxyId": "proxyId", @@ -138,51 +138,51 @@ "Failed to reset pricing": "Не удалось сбросить цены.", "apikey": "API-ключ", "http": "HTTP", - "goToDashboard": "Перейти на главную", - "checkSystemStatus": "Проверить состояние системы", - "selectModel": "Выберите модель", + "goToDashboard": "Go to Dashboard", + "checkSystemStatus": "Check System Status", + "selectModel": "Select Model", "combos": "Combos", - "noModelsFound": "Модели не найдены", - "clear": "Очистить", - "done": "Готово", - "errorOccurred": "Произошла ошибка", + "noModelsFound": "No models found", + "clear": "Clear", + "done": "Done", + "errorOccurred": "Error Occurred", "comboDeleted": "Combo Deleted", - "hide": "Скрыть", - "creating": "Создание...", + "hide": "Hide", + "creating": "Creating", "comboCreated": "Combo Created", - "swapFormats": "Поменять форматы", - "daysAgo": "дней назад", - "retries": "Повторы", + "swapFormats": "Swap Formats", + "daysAgo": "Days Ago", + "retries": "Retries", "errorDuringRestore": "Error During Restore", "eventsAppearHint": "Events Appear Hint", "noLockouts": "No Lockouts", "webSearchDesc": "Web Search Desc", "audioProvidersHeading": "Audio Providers Heading", - "cloudAgentProviders": "Провайдеры облачных агентов", - "minutesAgo": "минут назад", + "cloudAgentProviders": "__MISSING__:Cloud Agent Providers", + "minutesAgo": "Minutes Ago", "a": "A", - "liveAutoRefreshing": "Автообновление", - "webSearch": "Поиск в интернете", + "liveAutoRefreshing": "Live Auto Refreshing", + "webSearch": "Web Search", "anthropicPrefixPlaceholder": "Anthropic Prefix Placeholder", - "addModelToCombo": "Добавить модель в комбо", - "failedToLoad": "Не удалось загрузить", + "addModelToCombo": "Add Model To Combo", + "failedToLoad": "Failed To Load", "categoryMedia": "Category Media", "enableCloud": "Enable Cloud", "expirationBannerExpiringSoon": "Expiration Banner Expiring Soon", - "retry": "Повторить", + "retry": "Retry", "embeddings": "Embeddings", - "errorCount": "Количество ошибок", + "errorCount": "Error Count", "backupReasonManual": "Backup Reason Manual", "safeSearchModerate": "Safe Search Moderate", - "activeLimiters": "Активные лимитеры", + "activeLimiters": "Active Limiters", "a2aCardTitle": "A2A Card Title", "cloudWorkerUnreachable": "Cloud Worker Unreachable", - "testFailed": "Тест не пройден", + "testFailed": "Test Failed", "compatibleBaseUrlHint": "Compatible Base Url Hint", - "usageTracking": "Отслеживание использования", + "usageTracking": "Usage Tracking", "disableCloudTitle": "Disable Cloud Title", - "noConnections": "Нет подключений", - "providerHealth": "Здоровье провайдера", + "noConnections": "No Connections", + "providerHealth": "Provider Health", "confirmDbImport": "Confirm Db Import", "notAvailableSymbol": "Not Available Symbol", "backupsAvailable": "Backups Available", @@ -190,30 +190,30 @@ "newProviderNamePlaceholder": "New Provider Name Placeholder", "a2aQuickStartStep2": "A2A Quick Start Step2", "ok": "Ok", - "available": "Доступно", - "noBackupYet": "Резервных копий пока нет", - "more": "Ещё", + "available": "Available", + "noBackupYet": "No Backup Yet", + "more": "More", "noCompatibleYet": "No Compatible Yet", "multiProvider": "Multi Provider", "repairEnvHint": "Repair Env Hint", "disablingCloud": "Disabling Cloud", "testBench": "Test Bench", - "valid": "Действителен", + "valid": "Valid", "cloudBenefitShare": "Cloud Benefit Share", "mcpCardTitle": "Mcp Card Title", "disableConfirm": "Disable Confirm", - "filters": "Фильтры", + "filters": "Filters", "expirationBannerExpiredDesc": "Expiration Banner Expired Desc", "saveComboDefaults": "Save Combo Defaults", "cloudConnectedVerified": "Cloud Connected Verified", - "uptime": "Время работы", + "uptime": "Uptime", "compatibleProdPlaceholder": "Compatible Prod Placeholder", "fallbackChainsTitle": "Fallback Chains Title", "oauthLabel": "Oauth Label", "a2aCardDescription": "A2A Card Description", "okShort": "Ok Short", - "maintenance": "Обслуживание", - "formatConverter": "Конвертер форматов", + "maintenance": "Maintenance", + "formatConverter": "Format Converter", "zedImportNetworkError": "Zed Import Network Error", "apiKeyLabel": "Api Key Label", "noModelsForProvider": "No Models For Provider", @@ -221,26 +221,26 @@ "apiTypeLabel": "Api Type Label", "configuredProvidersLabel": "Configured Providers Label", "maxRetriesLabel": "Max Retries Label", - "title": "Название", - "output": "Вывод", + "title": "Title", + "output": "Output", "prefixHint": "Prefix Hint", "skipWizard": "Skip Wizard", - "failedCount": "Неудачных", + "failedCount": "Failed Count", "confirmDbImportDesc": "Confirm Db Import Desc", - "entries": "Записей", - "until": "До", + "entries": "Entries", + "until": "Until", "disableCombo": "Disable Combo", "liveMonitorDescriptionPrefix": "Live Monitor Description Prefix", - "runningCount": "Активных", + "runningCount": "Running Count", "noActiveConnectionsInGroup": "No Active Connections In Group", - "savedSuccessfully": "Успешно сохранено", + "savedSuccessfully": "Saved Successfully", "systemStorage": "System Storage", "videoDesc": "Video Desc", - "maxResults": "Макс. результатов", + "maxResults": "Max Results", "timeRangeMonth": "Time Range Month", - "testSummary": "Сводка теста", + "testSummary": "Test Summary", "failedSetPassword": "Failed Set Password", - "audio": "Аудио", + "audio": "Audio", "restore": "Restore", "disableWarning": "Disable Warning", "moderationsDesc": "Moderations Desc", @@ -309,7 +309,7 @@ "testDesc": "Test Desc", "chatDesc": "Chat Desc", "importSuccess": "Import Success", - "chat": "Чат", + "chat": "Chat", "a2aQuickStartStep1": "A2A Quick Start Step1", "importFailed": "Import Failed", "inputPlaceholder": "Input Placeholder", @@ -365,10 +365,10 @@ "apiKeyForCheck": "Api Key For Check", "cloudRequestTimeout": "Cloud Request Timeout", "showConfiguredOnly": "Show Configured Only", - "showFreeOnly": "Только бесплатные", - "addFirstProvider": "Добавьте первого провайдера", - "addFirstProviderDesc": "Подключите AI-провайдера, чтобы начать маршрутизировать запросы через OmniRoute", - "learnMore": "Узнать больше", + "showFreeOnly": "__MISSING__:Free only", + "addFirstProvider": "__MISSING__:Add your first provider", + "addFirstProviderDesc": "__MISSING__:Connect an AI provider to start routing requests through OmniRoute. You can use free providers, API keys, or OAuth accounts.", + "learnMore": "__MISSING__:Learn more", "includeDomains": "Include Domains", "promptCache": "Prompt Cache", "cloudConnected": "Cloud Connected", @@ -452,7 +452,7 @@ "chatPathHint": "Chat Path Hint", "defaultStrategyDesc": "Default Strategy Desc", "latencyP95": "Latency P95", - "textToSpeech": "Озвучивание текста", + "textToSpeech": "Text To Speech", "searchType": "Search Type", "messages": "Messages", "aggregatorsGateways": "Aggregators Gateways", @@ -598,9 +598,9 @@ "syncingData": "Syncing Data", "cloudBenefitPorts": "Cloud Benefit Ports", "compatibleProviders": "Compatible Providers", - "freeTierProviders": "Бесплатные провайдеры", - "freeTierLabel": "Бесплатно", - "freeTierProvidersDesc": "Провайдеры с бесплатным доступом", + "freeTierProviders": "__MISSING__:Free Tier Providers", + "freeTierLabel": "__MISSING__:Free tier available", + "freeTierProvidersDesc": "__MISSING__:Providers with free tiers — some require an API key signup, others need no credentials at all.", "clearCache": "Clear Cache", "reqs": "Reqs", "addAnthropicCompatible": "Add Anthropic Compatible", @@ -622,7 +622,7 @@ "whatYouGet": "What You Get", "signatureSession": "Signature Session", "errorCountNoCode": "Error Count No Code", - "testing": "Тестирование...", + "testing": "Testing", "providersCommaSeparated": "Providers Comma Separated", "exportDatabase": "Export Database", "hitRate": "Hit Rate", @@ -657,95 +657,55 @@ "learnedFromHeaders": "Learned From Headers", "totalRequests": "Total Requests", "cloudUnstableNote": "Cloud Unstable Note", - "gamificationAdmin": "Геймификация", - "monitorAnomaliesAndHealth": "Monitor anomalies and system health", - "flaggedAnomalies": "Flagged Anomalies", - "noAnomaliesDetected": "No anomalies detected", - "apiKey": "Ключ API", - "xpLastHour": "Опыт за час", - "zScore": "Z-оценка", - "tokensCommunityServers": "Серверы сообщества", - "tokensServerNamePlaceholder": "Название сервера", - "tokensApiKeyPlaceholder": "ID ключа API", - "tokensTokenBalance": "Баланс токенов", - "tokensSendTokens": "Отправить токены", - "tokensRecipientApiKeyId": "ID ключа получателя", - "tokensRecipientApiKeyIdPlaceholder": "Вставьте ID ключа API получателя", - "tokensReasonOptional": "Причина (необязательно)", - "tokensReasonPlaceholder": "Назначение перевода", - "tokensTransactionHistory": "История транзакций", - "tokensNoTransactionsYet": "Транзакций пока нет", - "tokensInviteCodes": "Коды приглашений", - "tokensMaxUses": "Макс. использований", - "tokensRedeemCode": "Активировать код", - "tokensRedeemCodePlaceholder": "Введите код", - "tokensYourActiveInvites": "Ваши активные приглашения", - "tierCoverageTitle": "Покрытие тарифов", - "tierCoverageSubtitle": "Распределение провайдеров по тарифам", - "batchDetailCopyId": "Копировать ID", - "batchDetailClose": "Закрыть", - "batchDetailEndpoint": "Эндпоинт", - "batchDetailModel": "Модель", - "batchDetailWindow": "Window", - "batchDetailCreated": "Создано", - "providerTopologyEmpty": "Провайдеры не настроены", - "badgeToastUnlocked": "Badge Unlocked!", - "batchListSearchPlaceholder": "Поиск пакетов...", - "batchListDeleteAllCompletedTitle": "Удалить все завершённые", - "batchListBatchesTable": "Таблица пакетов", - "changelogViewerLoading": "Загрузка изменений...", - "profileLoading": "Loading profile...", - "profileHowToEarn": "How to earn", - "bootstrapBannerDismiss": "Dismiss", - "batchListDeleteBatchTitle": "Delete batch and its files", - "leaderboardYourRank": "Ваше место", - "leaderboardLoading": "Загрузка рейтинга...", - "batchFileDetailCopyId": "Копировать ID", - "batchFileDetailClose": "Закрыть", - "batchFileDetailFailedToLoad": "Не удалось загрузить файл", - "batchFilesListSearchPlaceholder": "Поиск файлов...", - "batchFilesListFilesTable": "Таблица файлов", - "batchPageLoadingMore": "Загрузка...", - "default": "По умолчанию", - "video": "Видео", - "speech": "Речь", - "fastest": "Самый быстрый", - "record": "Запись", - "searchEngine": "Поисковая система", - "userAgent": "User-Agent", - "sync": "Синхронизация", - "healthCheck": "Проверка здоровья", - "mcpServer": "MCP-сервер", - "timeout": "Тайм-аут", - "parameters": "Параметры", - "codeInterpreter": "Интерпретатор кода", - "fileSearch": "Поиск файлов", - "functionCall": "Вызов функции", - "enableAutoRouting": "Включить автоматическую маршрутизацию", - "autoRouting": "Автомаршрутизация", - "autoRoutingDesc": "Автоматически выбирает лучшего провайдера для каждого запроса", - "objectDetection": "Обнаружение объектов", - "backgroundRemoval": "Удаление фона", - "temporaryChat": "Временный чат", - "tokenConsumption": "Потребление токенов", - "avg": "Среднее", - "total": "Всего", - "p50": "p50", - "p95": "p95", - "p99": "p99", - "minutes": "минуты", - "days": "дни", - "enableStreaming": "Включить потоковую передачу", - "download": "Скачать", - "upload": "Загрузить", - "preview": "Предпросмотр", - "incoming": "Входящие", - "outgoing": "Исходящие", - "requestMethod": "Метод запроса", - "requestUrl": "URL запроса", - "responseTime": "Время ответа", - "responseStatus": "Статус ответа", - "charset": "Кодировка" + "gamificationAdmin": "__MISSING__:Gamification Admin", + "monitorAnomaliesAndHealth": "__MISSING__:Monitor anomalies and system health", + "flaggedAnomalies": "__MISSING__:Flagged Anomalies", + "noAnomaliesDetected": "__MISSING__:No anomalies detected", + "apiKey": "__MISSING__:API Key", + "xpLastHour": "__MISSING__:XP (1h)", + "zScore": "__MISSING__:Z-Score", + "tokensCommunityServers": "__MISSING__:Community Servers", + "tokensServerNamePlaceholder": "__MISSING__:Server name", + "tokensApiKeyPlaceholder": "__MISSING__:API key", + "tokensTokenBalance": "__MISSING__:Token Balance", + "tokensSendTokens": "__MISSING__:Send Tokens", + "tokensRecipientApiKeyId": "__MISSING__:Recipient API Key ID", + "tokensRecipientApiKeyIdPlaceholder": "__MISSING__:Enter recipient API key ID", + "tokensReasonOptional": "__MISSING__:Reason (optional)", + "tokensReasonPlaceholder": "__MISSING__:e.g. bonus, reward", + "tokensTransactionHistory": "__MISSING__:Transaction History", + "tokensNoTransactionsYet": "__MISSING__:No transactions yet", + "tokensInviteCodes": "__MISSING__:Invite Codes", + "tokensMaxUses": "__MISSING__:Max Uses", + "tokensRedeemCode": "__MISSING__:Redeem Code", + "tokensRedeemCodePlaceholder": "__MISSING__:Enter invite code", + "tokensYourActiveInvites": "__MISSING__:Your Active Invites", + "tierCoverageTitle": "__MISSING__:Tier coverage", + "tierCoverageSubtitle": "__MISSING__:Providers configured per fallback tier", + "batchDetailCopyId": "__MISSING__:Copy ID", + "batchDetailClose": "__MISSING__:Close", + "batchDetailEndpoint": "__MISSING__:Endpoint", + "batchDetailModel": "__MISSING__:Model", + "batchDetailWindow": "__MISSING__:Window", + "batchDetailCreated": "__MISSING__:Created", + "providerTopologyEmpty": "__MISSING__:No providers connected yet", + "badgeToastUnlocked": "__MISSING__:Badge Unlocked!", + "batchListSearchPlaceholder": "__MISSING__:Search by ID, endpoint, model…", + "batchListDeleteAllCompletedTitle": "__MISSING__:Delete all completed batches", + "batchListBatchesTable": "__MISSING__:Batches", + "changelogViewerLoading": "__MISSING__:Loading changelog from GitHub...", + "profileLoading": "__MISSING__:Loading profile...", + "profileHowToEarn": "__MISSING__:How to earn", + "bootstrapBannerDismiss": "__MISSING__:Dismiss", + "batchListDeleteBatchTitle": "__MISSING__:Delete batch and its files", + "leaderboardYourRank": "__MISSING__:Your Rank", + "leaderboardLoading": "__MISSING__:Loading leaderboard...", + "batchFileDetailCopyId": "__MISSING__:Copy ID", + "batchFileDetailClose": "__MISSING__:Close", + "batchFileDetailFailedToLoad": "__MISSING__:Failed to load file contents", + "batchFilesListSearchPlaceholder": "__MISSING__:Search by ID or filename…", + "batchFilesListFilesTable": "__MISSING__:Files", + "batchPageLoadingMore": "__MISSING__:Loading more…" }, "sidebar": { "home": "Главная", @@ -765,24 +725,24 @@ "playground": "Площадка", "searchTools": "Инструменты поиска", "agents": "Агенты", - "cloudAgents": "Cloud Agents", + "cloudAgents": "__MISSING__:Cloud Agents", "memory": "Память", "skills": "Навыки", - "omniSkills": "OmniSkills", - "agentSkills": "AgentSkills", + "omniSkills": "__MISSING__:OmniSkills", + "agentSkills": "__MISSING__:AgentSkills", "docs": "Документы", "issues": "Проблемы", "endpoints": "Конечные точки", - "endpointsSubtitle": "URL-адреса для подключения к AI", + "endpointsSubtitle": "__MISSING__:Your AI connection URLs", "apiManager": "Менеджер API", - "apiManagerSubtitle": "Управление ключами API и доступом", + "apiManagerSubtitle": "__MISSING__:Manage API keys and access", "logs": "Журналы", - "webhooks": "Вебхуки", - "webhooksSubtitle": "Уведомления о событиях системы", - "combosSubtitle": "Группировка провайдеров для отказоустойчивости", - "batchSubtitle": "Пакетная обработка запросов", - "contextCavemanSubtitle": "Сжатие промптов", - "contextRtkSubtitle": "Фильтрация вывода", + "webhooks": "__MISSING__:Webhooks", + "webhooksSubtitle": "__MISSING__:Get notified of events", + "combosSubtitle": "__MISSING__:Group providers for failover", + "batchSubtitle": "__MISSING__:Process multiple requests", + "contextCavemanSubtitle": "__MISSING__:Prompt compression", + "contextRtkSubtitle": "__MISSING__:Output filtering", "auditLog": "Журнал аудита", "shutdown": "Выключить", "restart": "Перезапуск", @@ -815,261 +775,264 @@ "cliToolsShort": "Инструменты", "cache": "Кэш", "cacheShort": "Кэш", - "batch": "Пакетные задания", - "themeSystem": "Системная тема", - "whitelabelingDesc": "Настройка брендирования", - "switchThemes": "Переключить тему", - "themeAccentDesc": "Цвет акцента интерфейса", - "uploadFavicon": "Загрузить иконку", - "themeDark": "Тёмная", - "customLogoDesc": "Загрузите свой логотип", - "sidebarVisibilityToggle": "Показать/скрыть боковую панель", - "themeAccent": "Акцентный цвет", - "resetFavicon": "Сбросить иконку", - "whitelabeling": "Белый лейбл", - "darkMode": "Тёмный режим", - "uploadLogo": "Загрузить логотип", - "themeLight": "Светлая", - "appName": "Название приложения", - "appNameDesc": "Отображаемое название приложения", - "resetLogo": "Сбросить логотип", - "customFavicon": "Своя иконка", - "hideHealthLogs": "Скрыть логи здоровья", - "customLogo": "Свой логотип", - "appearance": "Оформление", - "themeSelectionAria": "Выбор темы оформления", - "themeCreate": "Создать тему", - "customFaviconDesc": "Загрузите свою иконку сайта", - "logoPreview": "Предпросмотр логотипа", - "themeCustom": "Пользовательская", - "hideHealthLogsDesc": "Скрыть сообщения о проверке здоровья провайдеров", - "faviconPreview": "Предпросмотр иконки", - "changelog": "Что нового", - "contextSection": "Контекст и кэш", + "batch": "Batch Jobs", + "themeSystem": "Theme System", + "whitelabelingDesc": "Whitelabeling Desc", + "switchThemes": "Switch Themes", + "themeAccentDesc": "Theme Accent Desc", + "uploadFavicon": "Upload Favicon", + "themeDark": "Theme Dark", + "customLogoDesc": "Custom Logo Desc", + "sidebarVisibilityToggle": "Sidebar Visibility Toggle", + "themeAccent": "Theme Accent", + "resetFavicon": "Reset Favicon", + "whitelabeling": "Whitelabeling", + "darkMode": "Dark Mode", + "uploadLogo": "Upload Logo", + "themeLight": "Theme Light", + "appName": "App Name", + "appNameDesc": "App Name Desc", + "resetLogo": "Reset Logo", + "customFavicon": "Custom Favicon", + "hideHealthLogs": "Hide Health Logs", + "customLogo": "Custom Logo", + "appearance": "Appearance", + "themeSelectionAria": "Theme Selection Aria", + "themeCreate": "Theme Create", + "customFaviconDesc": "Custom Favicon Desc", + "logoPreview": "Logo Preview", + "themeCustom": "Theme Custom", + "hideHealthLogsDesc": "Hide Health Logs Desc", + "faviconPreview": "Favicon Preview", + "changelog": "Changelog", + "contextSection": "Context & Cache", "contextCaveman": "Caveman", "contextRtk": "RTK", - "contextCombos": "Комбинации движков", - "routingSection": "Маршрутизация", - "protocolsSection": "Протоколы", - "agentsAiSection": "Агенты и AI", - "cacheContextSection": "Кэш и контекст", - "analyticsSection": "Аналитика", - "costsSection": "Затраты", - "monitoringSection": "Мониторинг", - "auditSecuritySection": "Аудит и безопасность", - "devtoolsSection": "Инструменты разработчика", - "configurationSection": "Конфигурация", - "aiFeaturesSection": "AI-возможности", + "contextCombos": "Engine Combos", + "routingSection": "Routing", + "protocolsSection": "Protocols", + "agentsAiSection": "Agents & AI", + "cacheContextSection": "Cache & Context", + "analyticsSection": "Analytics", + "costsSection": "Costs", + "monitoringSection": "Monitoring", + "auditSecuritySection": "Audit & Security", + "devtoolsSection": "Dev Tools", + "configurationSection": "Configuration", + "aiFeaturesSection": "AI Features", "mcp": "MCP", "a2a": "A2A", - "apiEndpoints": "API-эндпоинты", - "batchFiles": "Файлы", - "analyticsEvals": "Тестирование", - "analyticsSearch": "Поиск", - "analyticsUtilization": "Загрузка", - "analyticsComboHealth": "Здоровье комбо", - "analyticsCompression": "Сжатие", - "costsBudget": "Бюджет", - "costsQuotaShare": "Общие квоты", - "costsPricing": "Цены", - "logsProxy": "Логи прокси", - "logsConsole": "Консоль", - "logsActivity": "Активность", - "auditMcp": "MCP аудит", - "auditA2a": "A2A Аудит", - "settingsGeneral": "Основные", - "settingsAppearance": "Оформление", - "settingsAi": "Настройки AI", - "settingsSecurity": "Безопасность", - "settingsFeatureFlags": "Функциональные флаги", - "settingsRouting": "Маршрутизация", - "settingsResilience": "Отказоустойчивость", - "settingsAdvanced": "Продвинутые", + "apiEndpoints": "API Endpoints", + "batchFiles": "Files", + "analyticsEvals": "Evals", + "analyticsSearch": "Search", + "analyticsUtilization": "Utilization", + "analyticsComboHealth": "Combo Health", + "analyticsCompression": "Compression", + "costsBudget": "Budget", + "costsQuotaShare": "Quota Sharing", + "costsPricing": "Pricing", + "logsProxy": "Proxy Logs", + "logsConsole": "Console", + "logsActivity": "Activity", + "auditMcp": "MCP Audit", + "auditA2a": "__MISSING__:A2A Audit", + "settingsGeneral": "General", + "settingsAppearance": "Appearance", + "settingsAi": "AI Settings", + "settingsSecurity": "Security", + "settingsFeatureFlags": "__MISSING__:Feature Flags", + "settingsRouting": "Routing", + "settingsResilience": "Resilience", + "settingsAdvanced": "Advanced", "omniProxySection": "OmniProxy", "quotaTracker": "Provider Quota", - "providerQuota": "Квоты провайдеров", + "providerQuota": "Provider Quota", "runtime": "Runtime", - "consoleLogs": "Консоль", - "globalRouting": "Глобальная маршрутизация", - "mitmProxy": "MITM-прокси", + "consoleLogs": "Console Logs", + "globalRouting": "Global Routing", + "mitmProxy": "MITM Proxy", "oneProxy": "1Proxy", - "agenticFeaturesSection": "Агентные возможности", - "otherFeaturesSection": "Прочие возможности", - "compressionContextGroup": "Сжатие контекста", - "toolsGroup": "Инструменты", - "integrationsGroup": "Интеграции", - "proxyGroup": "Прокси", - "costsParametersGroup": "Параметры затрат", - "auditGroup": "Аудит", - "batchGroup": "Пакетная обработка", - "homeSubtitle": "Обзор панели управления", - "providersSubtitle": "Управление провайдерами AI", - "quotaTrackerSubtitle": "Отслеживание лимитов", - "providerQuotaSubtitle": "Квоты провайдеров", - "runtimeSubtitle": "Мониторинг отказоустойчивости в реальном времени", - "contextCombosSubtitle": "Комбинация движков сжатия", - "cliToolsSubtitle": "Настройка CLI-инструментов", - "agentsSubtitle": "Локальные AI-агенты", - "cloudAgentsSubtitle": "Облачные AI-агенты", - "apiEndpointsSubtitle": "Создание собственных эндпоинтов", - "proxySubtitle": "Настройки HTTP-прокси", - "mitmProxySubtitle": "MITM-перехват трафика", - "oneProxySubtitle": "Публичный прокси-шлюз", - "usageSubtitle": "Статистика трафика", - "analyticsComboHealthSubtitle": "Надёжность целей комбо", - "analyticsUtilizationSubtitle": "Загрузка провайдеров", - "costsSubtitle": "Детализация расходов", - "cacheSubtitle": "Попадания в кэш", - "analyticsCompressionSubtitle": "Экономия токенов", - "analyticsSearchSubtitle": "Аналитика поисковых инструментов", - "analyticsEvalsSubtitle": "Результаты тестов", - "logsSubtitle": "Логи приложения", - "logsProxySubtitle": "Логи прокси-трафика", - "consoleLogsSubtitle": "Вывод консоли", - "logsActivitySubtitle": "Журнал действий пользователей", - "healthSubtitle": "Проверка состояния системы", - "costsPricingSubtitle": "Цены на модели", - "costsBudgetSubtitle": "Бюджетные лимиты", - "costsQuotaShareSubtitle": "Общие квоты между ключами", - "auditLogSubtitle": "Аудит авторизации", - "auditMcpSubtitle": "Аудит MCP-сервера", - "auditA2aSubtitle": "Аудит A2A-протокола", - "translatorSubtitle": "Конвертация форматов", - "playgroundSubtitle": "Тестирование промптов в реальном времени", - "searchToolsSubtitle": "Реестр поисковых инструментов", - "memorySubtitle": "Постоянная память агента", - "omniSkillsSubtitle": "Песочница навыков", - "agentSkillsSubtitle": "Реестр A2A-навыков", - "mcpSubtitle": "Управление MCP-сервером", - "a2aSubtitle": "A2A-протокол", - "mediaSubtitle": "Кэшированные медиафайлы", - "batchFilesSubtitle": "Входные и выходные файлы", - "settingsSubtitle": "Все настройки", - "settingsGeneralSubtitle": "Базовые настройки", - "settingsAppearanceSubtitle": "Тема и расположение", - "settingsAiSubtitle": "Поведение AI по умолчанию", - "globalRoutingSubtitle": "Глобальные правила маршрутизации", - "settingsResilienceSubtitle": "Повторы и автоматические выключатели", - "settingsAdvancedSubtitle": "Настройки для опытных пользователей", - "settingsSecuritySubtitle": "Аутентификация и шифрование", - "settingsFeatureFlagsSubtitle": "Включение и отключение возможностей системы", - "docsSubtitle": "Документация", - "issuesSubtitle": "Сообщить об ошибке", - "changelogSubtitle": "Заметки о релизах" + "agenticFeaturesSection": "Agentic Features", + "otherFeaturesSection": "Other Features", + "compressionContextGroup": "Compression Context", + "toolsGroup": "Tools", + "integrationsGroup": "Integrations", + "proxyGroup": "Proxy", + "costsParametersGroup": "Costs Parameters", + "auditGroup": "Audit", + "batchGroup": "Batch", + "homeSubtitle": "Dashboard overview", + "providersSubtitle": "Manage AI providers", + "quotaTrackerSubtitle": "Track usage limits", + "providerQuotaSubtitle": "Track provider usage limits", + "runtimeSubtitle": "Realtime resilience & sessions", + "contextCombosSubtitle": "Combine compression engines", + "cliToolsSubtitle": "Configure CLI runtimes", + "agentsSubtitle": "Manage local agents", + "cloudAgentsSubtitle": "Manage cloud-based agents", + "apiEndpointsSubtitle": "Expose custom endpoints", + "proxySubtitle": "HTTP proxy settings", + "mitmProxySubtitle": "MITM interception", + "oneProxySubtitle": "Public proxy gateway", + "usageSubtitle": "Traffic and usage stats", + "analyticsComboHealthSubtitle": "Combo target reliability", + "analyticsUtilizationSubtitle": "Provider utilization", + "costsSubtitle": "Spend breakdown", + "cacheSubtitle": "Cache hit rates", + "analyticsCompressionSubtitle": "Token savings stats", + "analyticsSearchSubtitle": "Search tool analytics", + "analyticsEvalsSubtitle": "Eval suite results", + "logsSubtitle": "Application logs", + "logsProxySubtitle": "Proxy traffic logs", + "consoleLogsSubtitle": "Console output", + "logsActivitySubtitle": "User activity log", + "healthSubtitle": "System health check", + "costsPricingSubtitle": "Per-model pricing rules", + "costsBudgetSubtitle": "Budget limits", + "costsQuotaShareSubtitle": "Share provider quotas across keys", + "auditLogSubtitle": "Authorization audit", + "auditMcpSubtitle": "MCP server audit", + "auditA2aSubtitle": "A2A protocol audit", + "translatorSubtitle": "Format conversion", + "playgroundSubtitle": "Test prompts live", + "searchToolsSubtitle": "Search tool registry", + "memorySubtitle": "Persistent agent memory", + "omniSkillsSubtitle": "Sandbox skill registry", + "agentSkillsSubtitle": "A2A skill registry", + "mcpSubtitle": "MCP server controls", + "a2aSubtitle": "A2A protocol server", + "mediaSubtitle": "Cached media files", + "batchFilesSubtitle": "Batch input/output files", + "settingsSubtitle": "All settings", + "settingsGeneralSubtitle": "App basics", + "settingsAppearanceSubtitle": "Theme and layout", + "settingsAiSubtitle": "AI behavior defaults", + "globalRoutingSubtitle": "Global routing rules", + "settingsResilienceSubtitle": "Retries and breakers", + "settingsAdvancedSubtitle": "Power user options", + "settingsSecuritySubtitle": "Auth and encryption", + "settingsFeatureFlagsSubtitle": "__MISSING__:Toggle system capabilities", + "docsSubtitle": "Documentation", + "issuesSubtitle": "Report a bug", + "changelogSubtitle": "Release notes", + "settingsSidebar": "Боковая панель", + "settingsSidebarSubtitle": "Настройка боковой панели", + "gamificationGroup": "Геймификация" }, "webhooks": { - "title": "Вебхуки", - "description": "HTTP-уведомления о событиях системы", - "configuredWebhooks": "Настроенные вебхуки", - "configuredWebhooksDesc": "Управление эндпоинтами, подписками на события и статусом доставки", - "addWebhook": "Добавить вебхук", - "editWebhook": "Редактировать вебхук", - "name": "Название", - "namePlaceholder": "Мой вебхук", - "unnamedWebhook": "Безымянный вебхук", - "url": "URL", - "events": "События", - "allEvents": "Все события", - "secret": "Секрет", - "secretPlaceholder": "Оставьте пустым для автогенерации", - "secretEditPlaceholder": "Оставьте пустым, чтобы не менять", - "status": "Статус", - "active": "Активен", - "inactive": "Неактивен", - "errored": "Ошибка", - "total": "Всего", - "lastTriggered": "Последний вызов", - "actions": "Действия", - "enabled": "Включён", - "enabledDesc": "Включить отправку уведомлений на этот эндпоинт", - "refresh": "Обновить", - "loading": "Загрузка...", - "never": "Никогда", - "failureCount": "Количество ошибок", - "testWebhook": "Тестовый запрос", - "testSuccess": "Тестовый запрос отправлен", - "testFailed": "Тестовый запрос не удался", - "saveSuccess": "Вебхук сохранён", - "saveFailed": "Не удалось сохранить вебхук", - "loadFailed": "Не удалось загрузить вебхуки", - "delete": "Удалить", - "deleteConfirm": "Удалить этот вебхук?", - "deleteSuccess": "Вебхук удалён", - "deleteFailed": "Не удалось удалить вебхук", - "edit": "Редактировать", - "enable": "Включить", - "disable": "Отключить", - "noWebhooks": "Вебхуки не настроены", - "signatureTitle": "Подпись", - "signatureDescription": "OmniRoute подписывает каждый запрос заголовком X-OmniRoute-Signature" + "title": "__MISSING__:Webhooks", + "description": "__MISSING__:Configure HTTP callbacks for system events.", + "configuredWebhooks": "__MISSING__:Configured Webhooks", + "configuredWebhooksDesc": "__MISSING__:Manage delivery endpoints, subscribed events, status, and test sends.", + "addWebhook": "__MISSING__:Add Webhook", + "editWebhook": "__MISSING__:Edit Webhook", + "name": "__MISSING__:Name", + "namePlaceholder": "__MISSING__:Production monitoring", + "unnamedWebhook": "__MISSING__:Unnamed webhook", + "url": "__MISSING__:Endpoint URL", + "events": "__MISSING__:Events", + "allEvents": "__MISSING__:All events", + "secret": "__MISSING__:Secret", + "secretPlaceholder": "__MISSING__:Leave blank to auto-generate a secret", + "secretEditPlaceholder": "__MISSING__:Leave blank to keep the current secret", + "status": "__MISSING__:Status", + "active": "__MISSING__:Active", + "inactive": "__MISSING__:Inactive", + "errored": "__MISSING__:Errored", + "total": "__MISSING__:Total", + "lastTriggered": "__MISSING__:Last Triggered", + "actions": "__MISSING__:Actions", + "enabled": "__MISSING__:Enabled", + "enabledDesc": "__MISSING__:Disabled webhooks remain saved but do not receive deliveries.", + "refresh": "__MISSING__:Refresh", + "loading": "__MISSING__:Loading webhooks...", + "never": "__MISSING__:Never", + "failureCount": "__MISSING__:{count, plural, =0 {no failures} one {# failure} other {# failures}}", + "testWebhook": "__MISSING__:Send Test", + "testSuccess": "__MISSING__:Test webhook sent successfully.", + "testFailed": "__MISSING__:Test webhook failed.", + "saveSuccess": "__MISSING__:Webhook saved successfully.", + "saveFailed": "__MISSING__:Failed to save webhook.", + "loadFailed": "__MISSING__:Failed to load webhooks.", + "delete": "__MISSING__:Delete", + "deleteConfirm": "__MISSING__:Are you sure you want to delete this webhook?", + "deleteSuccess": "__MISSING__:Webhook deleted successfully.", + "deleteFailed": "__MISSING__:Failed to delete webhook.", + "edit": "__MISSING__:Edit", + "enable": "__MISSING__:Enable", + "disable": "__MISSING__:Disable", + "noWebhooks": "__MISSING__:No webhooks configured yet.", + "signatureTitle": "__MISSING__:Webhook Signatures", + "signatureDescription": "__MISSING__:Each delivery includes an X-Webhook-Signature header signed with HMAC-SHA256 using the webhook secret. Verify the signature before trusting the payload." }, "compliance": { - "auditTitle": "Аудит", - "auditDescription": "Просмотр событий безопасности и вызовов MCP-инструментов", - "complianceTab": "Комплаенс", - "mcpTab": "MCP аудит", - "title": "Аудит соответствия", - "description": "Журнал событий безопасности и MCP-инструментов", - "eventType": "Тип события", - "eventTypePlaceholder": "Все типы", - "severity": "Серьёзность", - "allSeverities": "Все", - "info": "Информация", - "warning": "Предупреждение", - "critical": "Критично", - "sourceIp": "IP-адрес", - "userOrKey": "Пользователь / Ключ", - "action": "Действие", - "result": "Результат", - "details": "Подробности", - "timestamp": "Время", - "from": "От", - "to": "До", - "refresh": "Обновить", - "export": "Экспорт", - "clearFilters": "Очистить фильтры", - "loading": "Загрузка...", - "showing": "Showing {count} of {total} events", - "policyViolation": "Нарушение политики", - "accessDenied": "Доступ запрещён", - "injectionBlocked": "Инъекция заблокирована", - "noEvents": "Событий не найдено", - "failedFetch": "Не удалось загрузить события", - "viewDetails": "Подробнее", - "closeDetails": "Закрыть", - "notAvailable": "Н/Д", - "system": "Система", - "previous": "Назад", - "next": "Вперёд", - "mcpAudit": "MCP аудит", - "mcpAuditDesc": "Журнал вызовов MCP-инструментов", - "failedFetchMcpAudit": "Не удалось загрузить MCP-аудит", - "tool": "Инструмент", - "toolPlaceholder": "Все инструменты", - "duration": "Длительность", - "apiKey": "Ключ API", - "output": "Вывод", - "allResults": "Все результаты", - "success": "Успешно", - "failure": "Ошибка", - "noMcpEvents": "MCP-событий не найдено", - "a2aAudit": "A2A аудит", - "a2aAuditDesc": "Журнал A2A-протокола", - "a2aShowingTasks": "Показано {count} задач", - "a2aSkill": "Навык", - "a2aSkillPlaceholder": "Все навыки", - "a2aState": "Состояние", - "a2aAllStates": "Все состояния", - "a2aStateSubmitted": "Отправлено", - "a2aStateWorking": "Выполняется", - "a2aStateCompleted": "Завершено", - "a2aStateFailed": "Ошибка", - "a2aStateCancelled": "Отменено", - "a2aTaskId": "ID задачи", - "a2aEvents": "События", - "a2aArtifacts": "Артефакты", - "a2aNoTasks": "Задач не найдено", - "a2aLoadingTasks": "Загрузка задач..." + "auditTitle": "__MISSING__:Audit", + "auditDescription": "__MISSING__:Review compliance events and MCP tool calls from one operational view.", + "complianceTab": "__MISSING__:Compliance", + "mcpTab": "__MISSING__:MCP Audit", + "title": "__MISSING__:Compliance Audit", + "description": "__MISSING__:Policy, access, provider, and security events recorded by the compliance audit log.", + "eventType": "__MISSING__:Event Type", + "eventTypePlaceholder": "__MISSING__:Filter by action or event type", + "severity": "__MISSING__:Severity", + "allSeverities": "__MISSING__:All severities", + "info": "__MISSING__:Info", + "warning": "__MISSING__:Warning", + "critical": "__MISSING__:Critical", + "sourceIp": "__MISSING__:Source IP", + "userOrKey": "__MISSING__:User / Key", + "action": "__MISSING__:Action", + "result": "__MISSING__:Result", + "details": "__MISSING__:Details", + "timestamp": "__MISSING__:Timestamp", + "from": "__MISSING__:From", + "to": "__MISSING__:To", + "refresh": "__MISSING__:Refresh", + "export": "__MISSING__:Export", + "clearFilters": "__MISSING__:Clear filters", + "loading": "__MISSING__:Loading audit events...", + "showing": "__MISSING__:Showing {count} of {total} events", + "policyViolation": "__MISSING__:Policy Violation", + "accessDenied": "__MISSING__:Access Denied", + "injectionBlocked": "__MISSING__:Injection Blocked", + "noEvents": "__MISSING__:No compliance events recorded.", + "failedFetch": "__MISSING__:Failed to fetch compliance audit log.", + "viewDetails": "__MISSING__:View details", + "closeDetails": "__MISSING__:Close details", + "notAvailable": "__MISSING__:—", + "system": "__MISSING__:system", + "previous": "__MISSING__:Previous", + "next": "__MISSING__:Next", + "mcpAudit": "__MISSING__:MCP Audit", + "mcpAuditDesc": "__MISSING__:Tool call audit entries recorded by the MCP server.", + "failedFetchMcpAudit": "__MISSING__:Failed to fetch MCP audit log.", + "tool": "__MISSING__:Tool", + "toolPlaceholder": "__MISSING__:Filter by tool name", + "duration": "__MISSING__:Duration", + "apiKey": "__MISSING__:API Key", + "output": "__MISSING__:Output", + "allResults": "__MISSING__:All results", + "success": "__MISSING__:Success", + "failure": "__MISSING__:Failure", + "noMcpEvents": "__MISSING__:No MCP audit events recorded.", + "a2aAudit": "__MISSING__:A2A Audit", + "a2aAuditDesc": "__MISSING__:Task execution audit trail recorded by the A2A server.", + "a2aShowingTasks": "__MISSING__:Showing {count} of {total} tasks", + "a2aSkill": "__MISSING__:Skill", + "a2aSkillPlaceholder": "__MISSING__:Filter by skill name", + "a2aState": "__MISSING__:State", + "a2aAllStates": "__MISSING__:All states", + "a2aStateSubmitted": "__MISSING__:Submitted", + "a2aStateWorking": "__MISSING__:Working", + "a2aStateCompleted": "__MISSING__:Completed", + "a2aStateFailed": "__MISSING__:Failed", + "a2aStateCancelled": "__MISSING__:Cancelled", + "a2aTaskId": "__MISSING__:Task ID", + "a2aEvents": "__MISSING__:Events", + "a2aArtifacts": "__MISSING__:Artifacts", + "a2aNoTasks": "__MISSING__:No A2A tasks recorded.", + "a2aLoadingTasks": "__MISSING__:Loading A2A tasks..." }, "themesPage": { "title": "Темы", @@ -1109,9 +1072,9 @@ "mediaDescription": "Создание изображений, видео и музыки", "themes": "Темы", "themesDescription": "Выберите цветовую тему для всей панели", - "costsDescription": "Отслеживание расходов и управление бюджетом AI", - "cacheDescription": "Мониторинг эффективности кэша провайдеров", - "limitsDescription": "Настройка лимитов и квот для ключей API и провайдеров", + "costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers", + "cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.", + "limitsDescription": "Configure rate limits and quotas per API key and provider", "runtimeDescription": "Live runtime observability — circuit breakers, cooldowns, model lockouts, sessions and quota alerts", "apiManagerDescription": "Manage API keys and access control for your OmniRoute instance", "batchDescription": "Process large volumes of requests asynchronously with batched API calls", @@ -1145,35 +1108,35 @@ "logsConsoleDescription": "Application console output and debug logs", "logsActivityDescription": "Audit trail of user actions and system events", "auditMcpDescription": "MCP tool invocation audit trail and compliance records", - "auditA2a": "A2A аудит", - "auditA2aDescription": "Просмотр событий A2A-протокола", + "auditA2a": "__MISSING__:A2A Audit", + "auditA2aDescription": "__MISSING__:A2A task execution audit trail, state transitions, and skill invocation records", "settingsGeneralDescription": "Storage, database, and general instance configuration", "settingsAppearanceDescription": "Theme, branding, and visual customization", "settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings", "settingsSecurityDescription": "Authentication, authorization, and access control settings", - "featureFlags": "Функциональные флаги", - "featureFlagsDescription": "Включение и отключение возможностей системы", - "featureFlagsActive": "Активные", - "featureFlagsInactive": "Неактивные", - "featureFlagsOverrides": "Переопределения", - "featureFlagsSearch": "Поиск флагов...", - "featureFlagsCategoryAll": "Все категории", - "featureFlagsCategorySecurity": "Безопасность", - "featureFlagsCategoryNetwork": "Сеть", - "featureFlagsCategoryPolicies": "Политики", - "featureFlagsCategoryRuntime": "Среда выполнения", - "featureFlagsCategoryCli": "CLI", - "featureFlagsCategoryHealth": "Здоровье", - "featureFlagsSourceDb": "База данных", - "featureFlagsSourceEnv": "Переменные окружения", - "featureFlagsSourceDefault": "По умолчанию", - "featureFlagsReset": "Сбросить", - "featureFlagsResetAll": "Сбросить все", - "featureFlagsResetAllConfirm": "Сбросить все флаги? Это действие нельзя отменить.", - "featureFlagsRestartRequired": "Требуется перезапуск", - "featureFlagsNoResults": "Флаги не найдены", - "featureFlagsSaved": "Флаги сохранены", - "featureFlagsError": "Ошибка сохранения флагов", + "featureFlags": "__MISSING__:Feature Flags", + "featureFlagsDescription": "__MISSING__:Control system capabilities and experimental features", + "featureFlagsActive": "__MISSING__:{count} active", + "featureFlagsInactive": "__MISSING__:{count} inactive", + "featureFlagsOverrides": "__MISSING__:{count} DB overrides", + "featureFlagsSearch": "__MISSING__:Search flags...", + "featureFlagsCategoryAll": "__MISSING__:All", + "featureFlagsCategorySecurity": "__MISSING__:Security", + "featureFlagsCategoryNetwork": "__MISSING__:Network", + "featureFlagsCategoryPolicies": "__MISSING__:Policies", + "featureFlagsCategoryRuntime": "__MISSING__:Runtime", + "featureFlagsCategoryCli": "__MISSING__:CLI", + "featureFlagsCategoryHealth": "__MISSING__:Health", + "featureFlagsSourceDb": "__MISSING__:DB", + "featureFlagsSourceEnv": "__MISSING__:ENV", + "featureFlagsSourceDefault": "__MISSING__:DEF", + "featureFlagsReset": "__MISSING__:Reset", + "featureFlagsResetAll": "__MISSING__:Reset All Overrides", + "featureFlagsResetAllConfirm": "__MISSING__:Are you sure you want to reset all feature flag overrides? This will revert all flags to their ENV or default values.", + "featureFlagsRestartRequired": "__MISSING__:Restart required to apply", + "featureFlagsNoResults": "__MISSING__:No flags match your search", + "featureFlagsSaved": "__MISSING__:Flag updated", + "featureFlagsError": "__MISSING__:Failed to update flag", "settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings", "settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration", "settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings", @@ -1195,8 +1158,8 @@ "providersOverview": "Обзор провайдеров", "configuredOf": "{configured} настроен из {total} доступных поставщиков", "noModelsAvailable": "Нет доступных моделей для этого поставщика.", - "noProvidersConfigured": "Провайдеры ещё не настроены", - "addProvider": "Добавить провайдера", + "noProvidersConfigured": "__MISSING__:No providers configured yet", + "addProvider": "__MISSING__:Add a provider", "configureFirst": "Сначала настройте соединение в {providers}.", "configureProvider": "Настроить поставщика", "modelAvailable": "Доступна модель {count}", @@ -1218,8 +1181,8 @@ "updating": "Обновление...", "updateAvailableDesc": "Доступна новая версия. Нажмите для обновления.", "updateStarted": "Обновление начато...", - "reloadingPageAutomatically": "Страница автоматически обновляется...", - "providerTopology": "Топология провайдеров" + "reloadingPageAutomatically": "__MISSING__:Reloading page automatically...", + "providerTopology": "__MISSING__:Provider Topology" }, "analytics": { "title": "Аналитика", @@ -1229,58 +1192,58 @@ "evals": "Оценки", "utilization": "Использование", "utilizationDescription": "Тенденции использования квоты поставщика и отслеживание ограничений скорости", - "modelStatus": "Статус модели", - "modelStatusCooldown": "Остывание", - "modelStatusUnavailable": "Недоступна", - "modelStatusError": "Ошибка", + "modelStatus": "__MISSING__:Model Status", + "modelStatusCooldown": "__MISSING__:Cooldown", + "modelStatusUnavailable": "__MISSING__:Unavailable", + "modelStatusError": "__MISSING__:Error", "comboHealth": "Здоровье комбо", "comboHealthDescription": "Квота на уровне комбо, распределение использования и метрики производительности", "compressionAnalyticsTitle": "Compression Analytics", "compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats.", - "autoRoutingTotalAutoRequests": "Всего автоматических запросов", - "autoRoutingAvgSelectionScore": "Средний балл выбора", - "autoRoutingExplorationRate": "Доля разведывательных запросов", - "autoRoutingLkgpHitRate": "Доля LKGP попаданий", - "autoRoutingRequestsByVariant": "Запросы по вариантам", - "autoRoutingTopRoutedProviders": "Топ провайдеров по маршрутизации", - "comboHealthWorstQuotaLeft": "Худший остаток квоты", - "comboHealthUsageSkew": "Перекос использования", - "comboHealthSuccessRate": "Доля успеха", - "comboHealthQuotaHealth": "Здоровье квоты", - "comboHealthRequests": "Запросы", - "comboHealthTokens": "Токены", - "comboHealthAvgLatency": "Средняя задержка", - "comboHealthTotalRequests": "Всего запросов", - "comboHealthExecutionTargets": "Цели выполнения", - "comboHealthSuccess": "Успешно", - "comboHealthLatency": "Задержка", - "comboHealthQuota": "Квота", - "comboHealthTitle": "Здоровье комбо", - "comboHealthUnableToLoad": "Не удалось загрузить", - "comboHealthGettingStarted": "Начните с создания комбо", - "compressionAnalyticsTotalRequests": "Всего запросов", - "compressionAnalyticsTokensSaved": "Сэкономлено токенов", - "compressionAnalyticsAvgSavings": "Средняя экономия", - "compressionAnalyticsAvgDuration": "Средняя длительность", - "compressionAnalyticsReceipts": "Чеки", - "compressionAnalyticsFallbacks": "Fallback'и", - "compressionAnalyticsPromptTokens": "Токены промпта", - "compressionAnalyticsCompletionTokens": "Токены завершения", - "compressionAnalyticsTotalTokens": "Всего токенов", - "compressionAnalyticsCacheTokens": "Токены кэша", - "compressionAnalyticsNoDataYet": "Данных пока нет", - "searchAnalyticsTotalSearches": "Всего поисков", - "searchAnalyticsCacheHitRate": "Доля попаданий в кэш", - "searchAnalyticsTotalCost": "Общая стоимость", - "searchAnalyticsAvgResponse": "Среднее время ответа", - "searchAnalyticsNoSearchesYet": "Поисков пока нет", - "providerUtilizationTitle": "Загрузка провайдеров", - "providerUtilizationFailedToLoad": "Не удалось загрузить", - "providerUtilizationNoData": "Нет данных", - "providerUtilizationGettingStarted": "Начните с подключения провайдеров", - "providerUtilizationLatestSnapshot": "Последний снимок", - "providerUtilizationRemainingCapacity": "Оставшаяся ёмкость", - "diversityScoreTitle": "Оценка разнообразия" + "autoRoutingTotalAutoRequests": "__MISSING__:Total Auto Requests", + "autoRoutingAvgSelectionScore": "__MISSING__:Avg Selection Score", + "autoRoutingExplorationRate": "__MISSING__:Exploration Rate", + "autoRoutingLkgpHitRate": "__MISSING__:LKGP Hit Rate", + "autoRoutingRequestsByVariant": "__MISSING__:Requests by Variant", + "autoRoutingTopRoutedProviders": "__MISSING__:Top Routed Providers", + "comboHealthWorstQuotaLeft": "__MISSING__:Worst quota left", + "comboHealthUsageSkew": "__MISSING__:Usage skew", + "comboHealthSuccessRate": "__MISSING__:Success rate", + "comboHealthQuotaHealth": "__MISSING__:Quota health", + "comboHealthRequests": "__MISSING__:Requests", + "comboHealthTokens": "__MISSING__:Tokens", + "comboHealthAvgLatency": "__MISSING__:Avg latency", + "comboHealthTotalRequests": "__MISSING__:Total requests", + "comboHealthExecutionTargets": "__MISSING__:Execution targets", + "comboHealthSuccess": "__MISSING__:Success", + "comboHealthLatency": "__MISSING__:Latency", + "comboHealthQuota": "__MISSING__:Quota", + "comboHealthTitle": "__MISSING__:Combo health", + "comboHealthUnableToLoad": "__MISSING__:Unable to load combo health", + "comboHealthGettingStarted": "__MISSING__:Getting started", + "compressionAnalyticsTotalRequests": "__MISSING__:Total Requests", + "compressionAnalyticsTokensSaved": "__MISSING__:Tokens Saved", + "compressionAnalyticsAvgSavings": "__MISSING__:Avg Savings", + "compressionAnalyticsAvgDuration": "__MISSING__:Avg Duration", + "compressionAnalyticsReceipts": "__MISSING__:Receipts", + "compressionAnalyticsFallbacks": "__MISSING__:Fallbacks", + "compressionAnalyticsPromptTokens": "__MISSING__:Prompt tokens", + "compressionAnalyticsCompletionTokens": "__MISSING__:Completion tokens", + "compressionAnalyticsTotalTokens": "__MISSING__:Total tokens", + "compressionAnalyticsCacheTokens": "__MISSING__:Cache tokens", + "compressionAnalyticsNoDataYet": "__MISSING__:No compression data yet", + "searchAnalyticsTotalSearches": "__MISSING__:Total Searches", + "searchAnalyticsCacheHitRate": "__MISSING__:Cache Hit Rate", + "searchAnalyticsTotalCost": "__MISSING__:Total Cost", + "searchAnalyticsAvgResponse": "__MISSING__:Avg Response", + "searchAnalyticsNoSearchesYet": "__MISSING__:No searches yet", + "providerUtilizationTitle": "__MISSING__:Provider utilization", + "providerUtilizationFailedToLoad": "__MISSING__:Failed to load utilization data", + "providerUtilizationNoData": "__MISSING__:No utilization data available", + "providerUtilizationGettingStarted": "__MISSING__:Getting started", + "providerUtilizationLatestSnapshot": "__MISSING__:Latest quota snapshot", + "providerUtilizationRemainingCapacity": "__MISSING__:Remaining capacity", + "diversityScoreTitle": "__MISSING__:Provider Diversity" }, "apiManager": { "title": "API-ключи", @@ -1382,30 +1345,30 @@ "lastUsedOn": "Последний: {date}", "editPermissions": "Редактировать разрешения", "deleteKey": "Удалить ключ", - "regenerateKey": "Перевыпустить ключ", - "regenerateConfirm": "Перевыпустить ключ? Старый ключ будет немедленно аннулирован.", - "failedRegenerateKey": "Не удалось перевыпустить ключ", - "failedRegenerateKeyRetry": "Не удалось перевыпустить ключ. Попробуйте снова.", + "regenerateKey": "__MISSING__:Regenerate key", + "regenerateConfirm": "__MISSING__:Are you sure you want to regenerate this API key? The old key will be immediately invalidated.", + "failedRegenerateKey": "__MISSING__:Failed to regenerate API key", + "failedRegenerateKeyRetry": "__MISSING__:Failed to regenerate API key. Please try again.", "model": "{count} модель", "models": "{count} модели", "permissionsTitle": "Разрешения: {name}", "allowAllDesc": "Этот ключ дает доступ ко всем доступным моделям.", "restrictDesc": "Этот ключ может получить доступ к {selectedCount} из моделей {totalModels}.", "selectedCount": "{count} выбрано", - "maxActiveSessions": "Макс. активных сессий", - "apiManagerCustomRateLimits": "Свои лимиты", - "apiManagerCustomRateLimitsDesc": "Индивидуальные лимиты запросов для этого ключа", - "apiManagerRateLimitRequestsPlaceholder": "Количество запросов", - "apiManagerRateLimitReqPer": "запросов на", - "apiManagerRateLimitSecondsPlaceholder": "секунд", - "apiManagerRemoveLimitTitle": "Удалить лимит?", - "apiManagerTimezonePlaceholder": "Часовой пояс", - "noLogPayloadPrivacy": "Не логировать содержимое", - "bannedStatus": "Забанен", - "managementApiAccess": "Доступ к API управления", - "expirationDate": "Срок действия", - "managementAccess": "Управляющий доступ", - "allowedConnections": "Разрешённые подключения" + "maxActiveSessions": "__MISSING__:Max Active Sessions", + "apiManagerCustomRateLimits": "__MISSING__:Custom Rate Limits", + "apiManagerCustomRateLimitsDesc": "__MISSING__:Override global default limits. Leave empty to use defaults.", + "apiManagerRateLimitRequestsPlaceholder": "__MISSING__:Requests", + "apiManagerRateLimitReqPer": "__MISSING__:req /", + "apiManagerRateLimitSecondsPlaceholder": "__MISSING__:Seconds", + "apiManagerRemoveLimitTitle": "__MISSING__:Remove limit", + "apiManagerTimezonePlaceholder": "__MISSING__:America/Sao_Paulo", + "noLogPayloadPrivacy": "__MISSING__:No-Log Payload Privacy", + "bannedStatus": "__MISSING__:Banned Status", + "managementApiAccess": "__MISSING__:Management API Access", + "expirationDate": "__MISSING__:Expiration Date", + "managementAccess": "__MISSING__:Management Access", + "allowedConnections": "__MISSING__:Allowed Connections" }, "auditLog": { "title": "Журнал аудита", @@ -1494,8 +1457,8 @@ "rerankModel": "Модель rerank", "positionDelta": "Изменение позиции", "emptyState": "Отправьте поисковый запрос, чтобы увидеть результаты", - "copy": "Копировать", - "resetToDefault": "Сбросить на умолчания" + "copy": "__MISSING__:Copy", + "resetToDefault": "__MISSING__:Reset to default" }, "cliTools": { "title": "Инструменты интерфейса командной строки", @@ -1556,7 +1519,7 @@ "inactive": "Неактивный", "startMitm": "Запустить МИТМ", "stopMitm": "Остановить МИТМ", - "mitmStarted": "MITM запущен успешно!", + "mitmStarted": "MITM стартовал успешно!", "mitmStopped": "MITM успешно остановлен!", "failedStart": "Не удалось запустить MITM", "failedStop": "Не удалось остановить MITM", @@ -1664,8 +1627,8 @@ "copilot": "Используйте его, если вам нужен пользовательский интерфейс в стиле чата Copilot, одновременно обеспечивая соблюдение ключей OmniRoute и правил маршрутизации.", "amp": "Use when you want Amp CLI routed through OmniRoute with stable shorthand aliases and OpenAI-compatible setup.", "qwen": "Use when you want Qwen Code CLI with OmniRoute-managed provider settings and multi-provider model access.", - "hermes": "Use when you need a lightweight terminal-native AI assistant for quick tasks.", - "custom": "Use for custom tool implementations or generic OpenAI-compatible configurations." + "hermes": "__MISSING__:Use when you need a lightweight terminal-native AI assistant for quick tasks.", + "custom": "__MISSING__:Use for custom tool implementations or generic OpenAI-compatible configurations." }, "toolDescriptions": { "antigravity": "Google Antigravity IDE с MITM", @@ -1683,8 +1646,8 @@ "copilot": "Ассистент GitHub Copilot с ИИ", "qwen": "Alibaba Qwen Code CLI", "amp": "Sourcegraph Amp coding assistant CLI", - "hermes": "Hermes AI Terminal Assistant", - "custom": "Generic OpenAI-compatible CLI or SDK configuration generator" + "hermes": "__MISSING__:Hermes AI Terminal Assistant", + "custom": "__MISSING__:Generic OpenAI-compatible CLI or SDK configuration generator" }, "guides": { "cursor": { @@ -1814,45 +1777,45 @@ "allToolsTab": "All Tools Tab", "guidedClientsTab": "Guided Clients Tab", "mitmClientsTab": "Mitm Clients Tab", - "customCliTab": "Custom CLI", + "customCliTab": "__MISSING__:Custom CLI", "toolCategories": "Tool Categories", "visibleToolsCount": "Visible Tools Count", - "customCliBuilderTitle": "OpenAI-compatible CLI builder", - "customCliBuilderDescription": "Generate env vars and JSON snippets for any CLI or SDK that accepts an OpenAI-compatible base URL, API key, and model ID.", - "customCliNoModels": "Connect at least one provider to populate the model selectors.", - "customCliNameLabel": "CLI name", - "customCliNamePlaceholder": "e.g. My Team CLI", - "customCliDefaultModelLabel": "Default model", - "customCliDefaultModelHelp": "Use any OmniRoute model ID or combo. Most OpenAI-compatible CLIs only need the /v1 base URL plus a model string.", - "customCliKeyHelper": "For local installs OmniRoute can use sk_omniroute. In cloud mode, pick one of your management API keys.", - "customCliAliasMappingsLabel": "Alias mappings", - "customCliAliasMappingsHelp": "Optional helper aliases for wrapper scripts or config files that want stable shorthand names.", - "customCliAddAlias": "Add alias", - "customCliNoMappings": "No alias mappings yet. Add one if your wrapper or team scripts use stable short names.", - "customCliAliasPlaceholder": "e.g. review", - "customCliTargetModelLabel": "Target model", - "customCliEndpointHintLabel": "How to wire the endpoint", - "customCliEndpointHint": "Point any OpenAI-compatible client to the OmniRoute /v1 base URL. The raw chat completions endpoint is {endpoint}. Use the JSON block when the tool wants a provider object, or the env script when it reads OPENAI_* variables.", - "customCliEnvBlockTitle": "Env / shell snippet", - "customCliJsonBlockTitle": "Provider JSON block", - "copilotConfigGenerator": "Генератор конфигурации Copilot", - "copilotApiKey": "Ключ API Copilot", - "copilotFilterModelsPlaceholder": "Filter models...", - "copilotMaxInputTokens": "Max Input Tokens", - "copilotMaxOutputTokens": "Max Output Tokens", - "copilotToolCalling": "Tool Calling", - "copilotPasteInto": "Paste into: ", - "wireApiChatCompletions": "Chat Completions (/chat/completions)", - "wireApiResponses": "Responses API (/responses)" + "customCliBuilderTitle": "__MISSING__:OpenAI-compatible CLI builder", + "customCliBuilderDescription": "__MISSING__:Generate env vars and JSON snippets for any CLI or SDK that accepts an OpenAI-compatible base URL, API key, and model ID.", + "customCliNoModels": "__MISSING__:Connect at least one provider to populate the model selectors.", + "customCliNameLabel": "__MISSING__:CLI name", + "customCliNamePlaceholder": "__MISSING__:e.g. My Team CLI", + "customCliDefaultModelLabel": "__MISSING__:Default model", + "customCliDefaultModelHelp": "__MISSING__:Use any OmniRoute model ID or combo. Most OpenAI-compatible CLIs only need the /v1 base URL plus a model string.", + "customCliKeyHelper": "__MISSING__:For local installs OmniRoute can use sk_omniroute. In cloud mode, pick one of your management API keys.", + "customCliAliasMappingsLabel": "__MISSING__:Alias mappings", + "customCliAliasMappingsHelp": "__MISSING__:Optional helper aliases for wrapper scripts or config files that want stable shorthand names.", + "customCliAddAlias": "__MISSING__:Add alias", + "customCliNoMappings": "__MISSING__:No alias mappings yet. Add one if your wrapper or team scripts use stable short names.", + "customCliAliasPlaceholder": "__MISSING__:e.g. review", + "customCliTargetModelLabel": "__MISSING__:Target model", + "customCliEndpointHintLabel": "__MISSING__:How to wire the endpoint", + "customCliEndpointHint": "__MISSING__:Point any OpenAI-compatible client to the OmniRoute /v1 base URL. The raw chat completions endpoint is {endpoint}. Use the JSON block when the tool wants a provider object, or the env script when it reads OPENAI_* variables.", + "customCliEnvBlockTitle": "__MISSING__:Env / shell snippet", + "customCliJsonBlockTitle": "__MISSING__:Provider JSON block", + "copilotConfigGenerator": "__MISSING__:GitHub Copilot Config Generator", + "copilotApiKey": "__MISSING__:API Key", + "copilotFilterModelsPlaceholder": "__MISSING__:Filter models...", + "copilotMaxInputTokens": "__MISSING__:Max Input Tokens", + "copilotMaxOutputTokens": "__MISSING__:Max Output Tokens", + "copilotToolCalling": "__MISSING__:Tool Calling", + "copilotPasteInto": "__MISSING__:Paste into: ", + "wireApiChatCompletions": "__MISSING__:Chat Completions (/chat/completions)", + "wireApiResponses": "__MISSING__:Responses API (/responses)" }, "combos": { "title": "Комбо", "description": "Создавайте комбинации моделей с взвешенной маршрутизацией и поддержкой резервного варианта.", - "autoCatalogTitle": "Каталог авто-маршрутизации", - "autoCatalogTemplateCount": "{count} templates", - "autoCatalogDescription": "Встроенные auto/* комбо, динамически собираемые из подключённых провайдеров", - "autoCatalogExpand": "Развернуть", - "autoCatalogCollapse": "Свернуть", + "autoCatalogTitle": "__MISSING__:Auto-routing catalog", + "autoCatalogTemplateCount": "__MISSING__:{count} templates", + "autoCatalogDescription": "__MISSING__:Built-in auto/* combos resolved dynamically from connected providers. Use any of these IDs as the model field — no setup needed.", + "autoCatalogExpand": "__MISSING__:Expand auto-routing catalog", + "autoCatalogCollapse": "__MISSING__:Collapse auto-routing catalog", "createCombo": "Создать комбо", "editCombo": "Редактировать комбо", "deleteCombo": "Удалить комбо", @@ -1906,8 +1869,8 @@ "randomDesc": "Равномерный случайный выбор, затем возврат к оставшимся моделям", "leastUsedDesc": "Выбирает модель с наименьшим количеством запросов, балансируя нагрузку с течением времени.", "costOptimizedDesc": "Маршруты к самой дешевой модели в первую очередь на основе цены", - "resetAware": "Сброс-ориентированная", - "resetAwareDesc": "Учитывает время сброса квоты", + "resetAware": "__MISSING__:Reset-Aware RR", + "resetAwareDesc": "__MISSING__:Balances remaining quota against 5h and weekly resets, then round-robins similar scores", "strictRandom": "Строгий случайный", "strictRandomDesc": "Перемешивание колоды - каждая модель используется по одному разу перед новым перемешиванием", "models": "Модели", @@ -1924,9 +1887,9 @@ "contextRelaySummaryModelHelp": "Optional override model used only for generating the handoff summary. Leave empty to reuse the active combo model.", "contextRelayProviderNote": "Context Relay currently generates handoffs for Codex account rotation. Pair it with multiple accounts of the same provider for the best continuity.", "advancedHint": "Оставьте пустым, чтобы использовать глобальные значения по умолчанию. Они переопределяют настройки каждого провайдера.", - "failoverBeforeRetry": "Failback до повтора", - "maxSetRetries": "Макс. повторов набора", - "setRetryDelayMs": "Задержка повтора (мс)", + "failoverBeforeRetry": "__MISSING__:Failover Before Retry", + "maxSetRetries": "__MISSING__:Max Set Retries", + "setRetryDelayMs": "__MISSING__:Set Retry Delay (ms)", "moveUp": "Вверх", "moveDown": "Двигаться вниз", "removeModel": "Удалить", @@ -1970,9 +1933,9 @@ "example": "Фоновые или пакетные задачи, где важнее низкая стоимость." }, "reset-aware": { - "when": "You route across multiple accounts with quota telemetry and different reset windows.", - "avoid": "Quota telemetry is unavailable for most accounts.", - "example": "Prefer a 60% weekly account resetting tomorrow over an 80% account resetting later." + "when": "__MISSING__:You route across multiple accounts with quota telemetry and different reset windows.", + "avoid": "__MISSING__:Quota telemetry is unavailable for most accounts.", + "example": "__MISSING__:Prefer a 60% weekly account resetting tomorrow over an 80% account resetting later." }, "strict-random": { "when": "Используйте, когда нужен идеально ровный spread - каждая модель используется один раз перед повтором.", @@ -1990,24 +1953,24 @@ "example": "Example: Codex sessions that rotate across multiple accounts near quota exhaustion." }, "fill-first": { - "when": "Use when you want to fully exhaust one provider's quota before moving to the next.", - "avoid": "Avoid when you need request-level load balancing across providers.", - "example": "Example: Use all $200 Deepgram credits before falling back to Groq." + "when": "__MISSING__:Use when you want to fully exhaust one provider's quota before moving to the next.", + "avoid": "__MISSING__:Avoid when you need request-level load balancing across providers.", + "example": "__MISSING__:Example: Use all $200 Deepgram credits before falling back to Groq." }, "auto": { - "when": "Use when you need multi-factor scoring routing based on cost, latency, and quality.", - "avoid": "Avoid when you need strict priority ordering or historical persistence.", - "example": "Example: Balance requests across models with different strengths." + "when": "__MISSING__:Use when you need multi-factor scoring routing based on cost, latency, and quality.", + "avoid": "__MISSING__:Avoid when you need strict priority ordering or historical persistence.", + "example": "__MISSING__:Example: Balance requests across models with different strengths." }, "lkgp": { - "when": "Use when you want to route based on historical success rates and performance.", - "avoid": "Avoid when historical data is limited or unreliable.", - "example": "Example: Route to models with a good track record on specific tasks." + "when": "__MISSING__:Use when you want to route based on historical success rates and performance.", + "avoid": "__MISSING__:Avoid when historical data is limited or unreliable.", + "example": "__MISSING__:Example: Route to models with a good track record on specific tasks." }, "context-optimized": { - "when": "Use when you need to optimize context window usage across models.", - "avoid": "Avoid when models have similar context lengths or tasks are simple.", - "example": "Example: Distribute long conversations across models with larger context windows." + "when": "__MISSING__:Use when you need to optimize context window usage across models.", + "avoid": "__MISSING__:Avoid when models have similar context lengths or tasks are simple.", + "example": "__MISSING__:Example: Distribute long conversations across models with larger context windows." } }, "advancedHelp": { @@ -2017,9 +1980,9 @@ "healthcheck": "Пропускает нездоровые модели и провайдеров при принятии решений маршрутизации.", "concurrencyPerModel": "Максимальное количество одновременных запросов, разрешенных для каждой модели в циклическом переборе.", "queueTimeout": "Как долго запрос может находиться в очереди до истечения времени ожидания.", - "failoverBeforeRetry": "When enabled, any upstream error triggers immediate failover to the next combo target, skipping all retries and fallback URLs.", - "maxSetRetries": "Number of times to retry the full target set when every target fails. 0 = no set-level retry.", - "setRetryDelayMs": "Delay between set-level retry attempts, giving transient issues time to resolve." + "failoverBeforeRetry": "__MISSING__:When enabled, any upstream error triggers immediate failover to the next combo target, skipping all retries and fallback URLs.", + "maxSetRetries": "__MISSING__:Number of times to retry the full target set when every target fails. 0 = no set-level retry.", + "setRetryDelayMs": "__MISSING__:Delay between set-level retry attempts, giving transient issues time to resolve." }, "templatesTitle": "Быстрые шаблоны", "templatesDescription": "Примените стартовый профиль, затем настройте модели и конфигурации.", @@ -2150,11 +2113,11 @@ "tip3": "Подходит для пакетных и фоновых задач, где стоимость - главный KPI." }, "reset-aware": { - "title": "Reset-aware account rotation", - "description": "Balances remaining provider quota against reset timing.", - "tip1": "Use explicit account steps or account-tag routing for providers with quota telemetry.", - "tip2": "Tune session vs weekly weights when short-term exhaustion is more risky.", - "tip3": "Keep the tie band small so equivalent accounts still rotate fairly." + "title": "__MISSING__:Reset-aware account rotation", + "description": "__MISSING__:Balances remaining provider quota against reset timing.", + "tip1": "__MISSING__:Use explicit account steps or account-tag routing for providers with quota telemetry.", + "tip2": "__MISSING__:Tune session vs weekly weights when short-term exhaustion is more risky.", + "tip3": "__MISSING__:Keep the tie band small so equivalent accounts still rotate fairly." }, "strict-random": { "title": "Распределение по перемешиванию", @@ -2164,46 +2127,46 @@ "tip3": "Идеально для балансировки нагрузки между несколькими API-аккаунтами." }, "fill-first": { - "description": "Exhaust provider quotas sequentially before falling back.", - "tip1": "Use for quota-based routing with clear fallback chains.", - "tip2": "Set quotas accurately to avoid premature fallback.", - "tip3": "Works best with providers offering free tiers or credit buckets.", - "title": "Quota Exhaustion" + "description": "__MISSING__:Exhaust provider quotas sequentially before falling back.", + "tip1": "__MISSING__:Use for quota-based routing with clear fallback chains.", + "tip2": "__MISSING__:Set quotas accurately to avoid premature fallback.", + "tip3": "__MISSING__:Works best with providers offering free tiers or credit buckets.", + "title": "__MISSING__:Quota Exhaustion" }, "auto": { - "description": "Route based on real-time scoring for cost, latency, quality, and health.", - "tip1": "Let the engine balance multiple factors automatically.", - "tip2": "Monitor which factors drive routing decisions in logs.", - "tip3": "Use for complex workloads where no single factor dominates.", - "title": "Multi-Factor Optimization" + "description": "__MISSING__:Route based on real-time scoring for cost, latency, quality, and health.", + "tip1": "__MISSING__:Let the engine balance multiple factors automatically.", + "tip2": "__MISSING__:Monitor which factors drive routing decisions in logs.", + "tip3": "__MISSING__:Use for complex workloads where no single factor dominates.", + "title": "__MISSING__:Multi-Factor Optimization" }, "lkgp": { - "description": "Route based on historical performance and success patterns.", - "tip1": "Requires sufficient request history for accurate predictions.", - "tip2": "Good for workloads with consistent model performance patterns.", - "tip3": "Monitor prediction accuracy and adjust weights as needed.", - "title": "History-Based Routing" + "description": "__MISSING__:Route based on historical performance and success patterns.", + "tip1": "__MISSING__:Requires sufficient request history for accurate predictions.", + "tip2": "__MISSING__:Good for workloads with consistent model performance patterns.", + "tip3": "__MISSING__:Monitor prediction accuracy and adjust weights as needed.", + "title": "__MISSING__:History-Based Routing" }, "context-optimized": { - "description": "Optimize routing based on context window usage and token efficiency.", - "tip1": "Route long conversations to models with larger context windows.", - "tip2": "Monitor context utilization to avoid token waste.", - "tip3": "Best for conversational AI requiring extensive context retention.", - "title": "Context Optimization" + "description": "__MISSING__:Optimize routing based on context window usage and token efficiency.", + "tip1": "__MISSING__:Route long conversations to models with larger context windows.", + "tip2": "__MISSING__:Monitor context utilization to avoid token waste.", + "tip3": "__MISSING__:Best for conversational AI requiring extensive context retention.", + "title": "__MISSING__:Context Optimization" }, "context-relay": { - "description": "Best when account rotation is expected and the next account must inherit a simplified task summary.", - "tip1": "Use with providers rotating accounts for the same model family.", - "tip2": "Set handoff threshold below hard quota cutoffs for summary generation time.", - "tip3": "Only set a dedicated summary model if the primary is too expensive or unstable.", - "title": "Session Continuity Priority" + "description": "__MISSING__:Best when account rotation is expected and the next account must inherit a simplified task summary.", + "tip1": "__MISSING__:Use with providers rotating accounts for the same model family.", + "tip2": "__MISSING__:Set handoff threshold below hard quota cutoffs for summary generation time.", + "tip3": "__MISSING__:Only set a dedicated summary model if the primary is too expensive or unstable.", + "title": "__MISSING__:Session Continuity Priority" }, "p2c": { - "description": "Route to the provider with the lowest current load based on real-time metrics.", - "tip1": "Monitor provider health metrics for accurate load assessment.", - "tip2": "Works best with homogeneous provider pools.", - "tip3": "Adjust sensitivity to avoid oscillation during traffic spikes.", - "title": "Power of Two Choices" + "description": "__MISSING__:Route to the provider with the lowest current load based on real-time metrics.", + "tip1": "__MISSING__:Monitor provider health metrics for accurate load assessment.", + "tip2": "__MISSING__:Works best with homogeneous provider pools.", + "tip3": "__MISSING__:Adjust sensitivity to avoid oscillation during traffic spikes.", + "title": "__MISSING__:Power of Two Choices" } }, "templateFreeStack": "Бесплатный стек ($0)", @@ -2266,7 +2229,7 @@ "builderAccount": "Account", "builderPreview": "Preview", "builderAddStep": "Add step", - "builderDuplicateExact": "This exact provider/model/account step is already in the combo.", + "builderDuplicateExact": "__MISSING__:This exact provider/model/account step is already in the combo.", "builderComboRef": "Combo Ref", "builderAddComboRef": "Add combo reference", "builderComboRefStep": "Add combo reference", @@ -2282,55 +2245,55 @@ "reviewAgentFlags": "Agent Flags", "reviewSequence": "Model Sequence", "reviewNoSteps": "No steps configured", - "builderStagesDescription": "Complete each stage in order to define combos, build steps, select routing strategy, and review results.", - "builderStepsDescription": "Build each combo step in order: provider, model, then account. This allows reusing the same provider and model across different accounts.", - "selectProvider": "Выберите провайдера", - "selectProviderPlaceholder": "Провайдер...", - "selectModel": "Выберите модель", - "selectModelPlaceholder": "Модель...", - "selectAccount": "Выберите аккаунт", - "selectComboToReference": "Выберите комбо для ссылки", - "comboReference": "Ссылка на комбо", - "addComboReference": "Добавить ссылку", - "addStepBeforeContinue": "Добавьте шаг перед продолжением", - "previewNextStep": "Предпросмотр следующего шага", - "autoSelectAccount": "Авто-выбор аккаунта", - "modePackBalanced": "Сбалансированный", - "modePackBudget": "Бюджетный", - "modePackPerformance": "Производительный", - "modePackCustom": "Пользовательский", - "browseLegacyCatalog": "Каталог (устаревший)", - "agentFeaturesTitle": "Agent Features", - "agentFeaturesDescription": "Enable advanced features for agents using this combo", - "agentFeaturesSystemMessageOverride": "Override system message", - "agentFeaturesSystemMessagePlaceholder": "You are an expert assistant...", - "agentFeaturesSystemMessageHint": "System message override for agents", - "agentFeaturesToolFilterRegex": "/regex-pattern/", - "agentFeaturesToolFilterHint": "Tool filter regex for agents", - "agentFeaturesContextCacheHint": "Enable in-context cache for agent tools", - "agentFeaturesContextCacheProtection": "Protect cache from agent tool mutations", - "agentFeaturesContextLength": "Context length", - "agentFeaturesContextLengthPlaceholder": "e.g. 128000", - "agentFeaturesContextLengthHint": "Defines the context window for this combo in /v1/models.", - "agentFeaturesContextLengthErrorInteger": "Context length must be a valid integer", - "agentFeaturesContextLengthErrorRange": "Context length must be between 1000 and 2000000", - "compressionOverride": "Compression Override", - "modePack": "Mode Pack" + "builderStagesDescription": "__MISSING__:Complete each stage in order to define combos, build steps, select routing strategy, and review results.", + "builderStepsDescription": "__MISSING__:Build each combo step in order: provider, model, then account. This allows reusing the same provider and model across different accounts.", + "selectProvider": "__MISSING__:Select Provider", + "selectProviderPlaceholder": "__MISSING__:Select a provider", + "selectModel": "__MISSING__:Select Model", + "selectModelPlaceholder": "__MISSING__:Select a provider first", + "selectAccount": "__MISSING__:Select Account", + "selectComboToReference": "__MISSING__:Select combo to reference", + "comboReference": "__MISSING__:Combo Reference", + "addComboReference": "__MISSING__:Add Combo Reference", + "addStepBeforeContinue": "__MISSING__:Please add at least one step before proceeding to the next stage.", + "previewNextStep": "__MISSING__:Preview next step", + "autoSelectAccount": "__MISSING__:Automatically select account at runtime", + "modePackBalanced": "__MISSING__:Balanced", + "modePackBudget": "__MISSING__:Budget", + "modePackPerformance": "__MISSING__:Performance", + "modePackCustom": "__MISSING__:Custom", + "browseLegacyCatalog": "__MISSING__:Browse legacy combo catalog", + "agentFeaturesTitle": "__MISSING__:Agent Features", + "agentFeaturesDescription": "__MISSING__:Enable advanced features for agents using this combo", + "agentFeaturesSystemMessageOverride": "__MISSING__:Override system message", + "agentFeaturesSystemMessagePlaceholder": "__MISSING__:You are an expert assistant...", + "agentFeaturesSystemMessageHint": "__MISSING__:System message override for agents", + "agentFeaturesToolFilterRegex": "__MISSING__:/regex-pattern/", + "agentFeaturesToolFilterHint": "__MISSING__:Tool filter regex for agents", + "agentFeaturesContextCacheHint": "__MISSING__:Enable in-context cache for agent tools", + "agentFeaturesContextCacheProtection": "__MISSING__:Protect cache from agent tool mutations", + "agentFeaturesContextLength": "__MISSING__:Context length", + "agentFeaturesContextLengthPlaceholder": "__MISSING__:e.g. 128000", + "agentFeaturesContextLengthHint": "__MISSING__:Defines the context window for this combo in /v1/models.", + "agentFeaturesContextLengthErrorInteger": "__MISSING__:Context length must be a valid integer", + "agentFeaturesContextLengthErrorRange": "__MISSING__:Context length must be between 1000 and 2000000", + "compressionOverride": "__MISSING__:Compression Override", + "modePack": "__MISSING__:Mode Pack" }, "costs": { "title": "Затраты", - "pageDescription": "Отслеживание расходов, анализ трендов и управление AI-бюджетом", - "overview": "Обзор", + "pageDescription": "__MISSING__:Track spending, analyze trends, and manage your AI budget across all providers", + "overview": "__MISSING__:Overview", "budget": "Бюджет", "totalCost": "Общая стоимость", "breakdown": "Распределение затрат", - "noData": "Нет данных", + "noData": "Нет данных о стоимости", "byModel": "По модели", "byProvider": "По поставщику", - "range7d": "7 дней", - "range30d": "30 дней", - "range90d": "90 дней", - "rangeAll": "За всё время", + "range7d": "__MISSING__:7 Days", + "range30d": "__MISSING__:30 Days", + "range90d": "__MISSING__:90 Days", + "rangeAll": "__MISSING__:All Time", "spend30d": "Spend30D", "activeModels": "Active Models", "selectedWindow": "Selected Window", @@ -2348,38 +2311,38 @@ "noCostDataTitle": "No Cost Data Title", "topModels": "Top Models", "requestsInWindow": "Requests In Window", - "tokenUsage": "Использование токенов", - "totalTokens": "Всего токенов", - "inputTokens": "Входящие токены", - "outputTokens": "Исходящие токены", - "inputOutputRatio": "Соотношение ввода/вывода", - "tokens": "Токены", - "routingEfficiency": "Эффективность маршрутизации", - "fallbackCount": "Количество fallback'ов", - "fallbackRate": "Доля fallback'ов", - "modelCoverage": "Покрытие моделей", - "modelCoverageDesc": "Распределение запросов по моделям", - "outOfRequests": "Не хватает данных", - "costByApiKey": "Расходы по ключам API", - "costByAccount": "Расходы по аккаунтам", - "apiKeyName": "Имя ключа", - "account": "Аккаунт", - "requests": "Запросы", - "cost": "Стоимость", - "dayStreak": "Дни подряд", - "weeklyUsagePattern": "Недельный паттерн", - "activityHeatmap": "Тепловая карта активности", - "less": "Меньше", - "more": "Больше", - "monthlyForecast": "Прогноз на месяц", - "forecastBasis": "Основание прогноза", - "avgDailyCost": "Средний дневной расход", - "daysRemaining": "Дней до конца периода", - "periodComparison": "Сравнение периодов", - "previousPeriod": "Предыдущий период", - "currentPeriod": "Текущий период", - "exportCSV": "Экспорт CSV", - "exportJSON": "Экспорт JSON" + "tokenUsage": "__MISSING__:Token Usage", + "totalTokens": "__MISSING__:Total Tokens", + "inputTokens": "__MISSING__:Input Tokens", + "outputTokens": "__MISSING__:Output Tokens", + "inputOutputRatio": "__MISSING__:Input/Output Ratio", + "tokens": "__MISSING__:tokens", + "routingEfficiency": "__MISSING__:Routing Efficiency", + "fallbackCount": "__MISSING__:Fallback Requests", + "fallbackRate": "__MISSING__:Fallback Rate", + "modelCoverage": "__MISSING__:Model Coverage", + "modelCoverageDesc": "__MISSING__:% of requests with explicit model", + "outOfRequests": "__MISSING__:out of {total} requests", + "costByApiKey": "__MISSING__:Cost by API Key", + "costByAccount": "__MISSING__:Cost by Account", + "apiKeyName": "__MISSING__:API Key", + "account": "__MISSING__:Account", + "requests": "__MISSING__:Requests", + "cost": "__MISSING__:Cost", + "dayStreak": "__MISSING__:day streak", + "weeklyUsagePattern": "__MISSING__:Weekly Usage Pattern", + "activityHeatmap": "__MISSING__:Activity (365 days)", + "less": "__MISSING__:Less", + "more": "__MISSING__:More", + "monthlyForecast": "__MISSING__:Monthly Forecast", + "forecastBasis": "__MISSING__:Based on last {days} days", + "avgDailyCost": "__MISSING__:Avg. daily cost", + "daysRemaining": "__MISSING__:{days} days remaining", + "periodComparison": "__MISSING__:Period Comparison", + "previousPeriod": "__MISSING__:Previous Half", + "currentPeriod": "__MISSING__:Current Half", + "exportCSV": "__MISSING__:Export as CSV", + "exportJSON": "__MISSING__:Export as JSON" }, "endpoint": { "title": "Конечная точка API", @@ -2512,14 +2475,14 @@ "a2aQuickStartStep3": "Отслеживайте и управляйте задачами через `tasks/get` и `tasks/cancel`.", "completionsLegacy": "Завершения (устар.)", "completionsLegacyDesc": "Устаревшие текстовые завершения OpenAI — поддерживают и строку prompt, и массив messages", - "messagesApi": "Messages API", - "messagesApiDesc": "API сообщений Anthropic-совместимых клиентов", - "imageEdits": "Редактирование изображений", - "imageEditsDesc": "Изменение изображений с помощью AI", - "batchApi": "Пакетный API", - "batchApiDesc": "Массовая обработка запросов в пакетном режиме", - "filesApi": "API файлов", - "filesApiDesc": "Загрузка и управление файлами", + "messagesApi": "__MISSING__:Messages", + "messagesApiDesc": "__MISSING__:Native Anthropic Messages API format for Claude-compatible providers", + "imageEdits": "__MISSING__:Image Edits", + "imageEditsDesc": "__MISSING__:Edit and modify existing images with AI (inpainting, outpainting, variations)", + "batchApi": "__MISSING__:Batch API", + "batchApiDesc": "__MISSING__:Process large batches of requests asynchronously (OpenAI-compatible)", + "filesApi": "__MISSING__:Files API", + "filesApiDesc": "__MISSING__:Upload and manage files for batch processing", "videoGeneration": "Video Generation", "videoDesc": "Generate videos using AI models like ComfyUI and Stable Video Diffusion.", "tailscaleRequestFailed": "Failed to load Tailscale status", @@ -2554,35 +2517,35 @@ "tailscaleInstallPasswordHint": "On macOS and Linux, sudo may be required for the package install and daemon start.", "tailscaleSudoPlaceholder": "Optional sudo password", "tailscaleInstalling": "Installing", - "tailscaleSudoLabel": "Требуется sudo", - "ngrokTitle": "ngrok", - "ngrokRunning": "Запущен", - "ngrokStarting": "Запуск...", - "ngrokStoppedState": "Остановлен", - "ngrokNeedsAuth": "Требуется авторизация", - "ngrokNotInstalled": "Не установлен", - "ngrokUnsupported": "Не поддерживается", - "ngrokError": "Ошибка", - "ngrokEnable": "Включить", - "ngrokDisable": "Отключить", - "ngrokUrlNotice": "Публичный URL", - "ngrokAuthTokenLabel": "Токен аутентификации", - "ngrokAuthTokenPlaceholder": "Ваш ngrok auth token", - "ngrokLastError": "Последняя ошибка", - "ngrokStarted": "ngrok запущен", - "ngrokStopped": "ngrok остановлен", - "ngrokRequestFailed": "Запрос не удался", - "tokenSaverSubtitle": "Экономия токенов", - "tokenSaverToolOutput": "Вывод инструментов", - "tokenSaverLlmOutput": "Вывод LLM", - "tokenSaverInputCompression": "Сжатие ввода", - "apiEndpointsCatalogUnavailable": "Каталог недоступен", - "apiEndpointsSearchPlaceholder": "Поиск эндпоинтов...", - "apiEndpointsRequiresAuth": "Требуется аутентификация", - "apiEndpointsNoMatch": "Ничего не найдено", - "localServer": "Локальный сервер", - "cloudOmniroute": "OmniRoute Cloud", - "copyUrl": "Копировать URL" + "tailscaleSudoLabel": "__MISSING__:Sudo Password (required on macOS/Linux)", + "ngrokTitle": "__MISSING__:ngrok Tunnel", + "ngrokRunning": "__MISSING__:Running", + "ngrokStarting": "__MISSING__:Starting", + "ngrokStoppedState": "__MISSING__:Stopped", + "ngrokNeedsAuth": "__MISSING__:Needs Auth", + "ngrokNotInstalled": "__MISSING__:Not installed", + "ngrokUnsupported": "__MISSING__:Unsupported", + "ngrokError": "__MISSING__:Error", + "ngrokEnable": "__MISSING__:Enable Tunnel", + "ngrokDisable": "__MISSING__:Stop Tunnel", + "ngrokUrlNotice": "__MISSING__:Creates a public ngrok tunnel.", + "ngrokAuthTokenLabel": "__MISSING__:Authtoken (Required if NGROK_AUTHTOKEN not set)", + "ngrokAuthTokenPlaceholder": "__MISSING__:Enter your ngrok authtoken", + "ngrokLastError": "__MISSING__:Last error: {error}", + "ngrokStarted": "__MISSING__:ngrok tunnel started", + "ngrokStopped": "__MISSING__:ngrok tunnel stopped", + "ngrokRequestFailed": "__MISSING__:Failed to update ngrok tunnel", + "tokenSaverSubtitle": "__MISSING__:Spend less tokens on every request.", + "tokenSaverToolOutput": "__MISSING__:Tool output", + "tokenSaverLlmOutput": "__MISSING__:LLM output", + "tokenSaverInputCompression": "__MISSING__:Input compression", + "apiEndpointsCatalogUnavailable": "__MISSING__:API catalog unavailable", + "apiEndpointsSearchPlaceholder": "__MISSING__:Search endpoints...", + "apiEndpointsRequiresAuth": "__MISSING__:Requires auth", + "apiEndpointsNoMatch": "__MISSING__:No endpoints match your filter", + "localServer": "__MISSING__:Local Server", + "cloudOmniroute": "__MISSING__:Cloud OmniRoute", + "copyUrl": "__MISSING__:Copy URL" }, "endpoints": { "tabProxy": "Прокси конечных точек", @@ -2663,11 +2626,11 @@ "failed": "сбой", "previous": "Предыдущая", "next": "Следующая", - "apiKeyId": "ID ключа API", - "offset": "Смещение", - "limit": "Лимит", - "tool": "Инструмент", - "mcpDashboardCopyUrl": "Копировать URL" + "apiKeyId": "Api Key Id", + "offset": "Offset", + "limit": "Limit", + "tool": "Tool", + "mcpDashboardCopyUrl": "__MISSING__:Copy URL" }, "a2aDashboard": { "loading": "Загрузка панели A2A...", @@ -2722,16 +2685,16 @@ "metadata": "Метаданные", "events": "События", "artifacts": "Артефакты", - "tablePhase": "Фаза", - "offset": "Смещение", - "limit": "Лимит", - "skill": "Навык", - "rpcEndpoint": "RPC-эндпоинт", - "rpcMethodSend": "Отправить задачу", - "rpcMethodStream": "Потоковая передача", - "rpcMethodGet": "Получить задачу", - "rpcMethodCancel": "Отменить задачу", - "serviceLabel": "Сервис" + "tablePhase": "Table Phase", + "offset": "Offset", + "limit": "Limit", + "skill": "Skill", + "rpcEndpoint": "__MISSING__:POST /a2a", + "rpcMethodSend": "__MISSING__:message/send", + "rpcMethodStream": "__MISSING__:message/stream", + "rpcMethodGet": "__MISSING__:tasks/get", + "rpcMethodCancel": "__MISSING__:tasks/cancel", + "serviceLabel": "__MISSING__:A2A" }, "memory": { "title": "Память", @@ -2752,24 +2715,24 @@ "content": "Содержимое", "created": "Создано", "actions": "Действия", - "delete": "Удалить", + "delete": "Delete", "factual": "Фактическая", "episodic": "Эпизодическая", "procedural": "Процедурная", "semantic": "Семантическая", "a": "A", - "pipelineOk": "Конвейер в порядке ({latencyMs}мс)", - "pipelineError": "Ошибка конвейера", - "healthUnknown": "Состояние неизвестно", - "checkingHealth": "Проверка состояния...", - "checkHealth": "Проверить состояние", - "pageInfo": "Страница {current} из {total}", - "previous": "Назад", - "next": "Вперёд", - "cancel": "Отмена", - "save": "Сохранить", - "keyPlaceholder": "Ключ", - "contentPlaceholder": "Содержимое" + "pipelineOk": "__MISSING__:Pipeline OK ({latencyMs}ms)", + "pipelineError": "__MISSING__:Pipeline error", + "healthUnknown": "__MISSING__:Health unknown", + "checkingHealth": "__MISSING__:Checking…", + "checkHealth": "__MISSING__:Check health", + "pageInfo": "__MISSING__:Page {page} of {totalPages} ({total} total)", + "previous": "__MISSING__:Previous", + "next": "__MISSING__:Next", + "cancel": "__MISSING__:Cancel", + "save": "__MISSING__:Save", + "keyPlaceholder": "__MISSING__:e.g. user.preferences.theme", + "contentPlaceholder": "__MISSING__:Value or JSON content to remember" }, "skills": { "title": "Навыки", @@ -2797,12 +2760,12 @@ "timeoutDesc": "Максимальная длительность выполнения до принудительного завершения.", "networkAccess": "Доступ к сети", "networkAccessDesc": "Разрешить навыку сетевые запросы.", - "mode": "Режим", + "mode": "Mode", "q": "Q", - "filterSkillsPlaceholder": "Фильтр навыков по названию, описанию или тегу", - "allModes": "Все режимы", - "skillsMarketplace": "Маркетплейс навыков", - "installSkill": "Установить навык" + "filterSkillsPlaceholder": "__MISSING__:Filter skills by name, description, or tag", + "allModes": "__MISSING__:All modes", + "skillsMarketplace": "__MISSING__:Skills Marketplace", + "installSkill": "__MISSING__:Install Skill" }, "health": { "title": "Здоровье системы", @@ -2877,83 +2840,83 @@ "resetting": "Сброс...", "resetAll": "Сбросить все", "until": "До {time}", - "limitExhausted": "Исчерпан", - "learnedFromHeaders": "Из заголовков", + "limitExhausted": "Exhausted", + "learnedFromHeaders": "Learned from headers", "remainingOfLimit": "{remaining}/{limit} remaining", - "throttleStatus": "Троттлинг: {value}", - "lastHeaderUpdate": "Обновление заголовка: {age}", - "databaseHealth": "Здоровье базы данных", - "stickyBoundSessions": "Привязанные сессии", - "sessionsByApiKey": "Сессии по ключам API", - "noActiveSessionsTracked": "Активные сессии не отслеживаются", - "noSessionQuotaMonitorsActive": "Мониторы квот сессий не активны", - "gracefulDegradationStatus": "Состояние плавной деградации" + "throttleStatus": "Throttle: {value}", + "lastHeaderUpdate": "Header update: {age}", + "databaseHealth": "__MISSING__:Database Health", + "stickyBoundSessions": "__MISSING__:Sticky-bound sessions", + "sessionsByApiKey": "__MISSING__:Sessions by API key", + "noActiveSessionsTracked": "__MISSING__:No active sessions tracked yet.", + "noSessionQuotaMonitorsActive": "__MISSING__:No session quota monitors active.", + "gracefulDegradationStatus": "__MISSING__:Graceful Degradation Status" }, "telemetry": { - "title": "Телеметрия системы", - "description": "Показатели запросов, времени работы, сессий и памяти в реальном времени", - "uptime": "Время работы", - "totalRequests": "Всего запросов", - "avgLatency": "Средняя задержка", - "errorRate": "Доля ошибок", - "activeConnections": "Активные подключения", - "memoryUsage": "Использование памяти", - "latencyTrend": "Тренд задержки", - "throughputTrend": "Тренд пропускной способности", - "memoryTrend": "Тренд памяти", - "refresh": "Обновить", - "updatedAt": "Обновлено", - "loadFailed": "Не удалось загрузить", - "partialData": "Частичные данные" + "title": "__MISSING__:System Telemetry", + "description": "__MISSING__:Rolling request, runtime, session, and memory signals from this OmniRoute process.", + "uptime": "__MISSING__:Uptime", + "totalRequests": "__MISSING__:Total Requests", + "avgLatency": "__MISSING__:Avg Latency", + "errorRate": "__MISSING__:Error Rate", + "activeConnections": "__MISSING__:Active Connections", + "memoryUsage": "__MISSING__:Memory Usage", + "latencyTrend": "__MISSING__:Latency trend", + "throughputTrend": "__MISSING__:Throughput trend", + "memoryTrend": "__MISSING__:Memory trend", + "refresh": "__MISSING__:Refresh", + "updatedAt": "__MISSING__:Updated {time}", + "loadFailed": "__MISSING__:Failed to load telemetry.", + "partialData": "__MISSING__:Telemetry is partially available: {error}" }, "mitm": { - "title": "MITM-прокси", - "description": "Прозрачный прокси для перехвата и маршрутизации запросов клиентов", - "enable": "Включить MITM", - "enableDesc": "Запуск и остановка локального процесса перехвата", - "status": "Статус", - "running": "Запущен", - "stopped": "Остановлен", - "start": "Запустить", - "stop": "Остановить", - "refresh": "Обновить", - "port": "Порт", - "apiKey": "Ключ API", - "apiKeyPlaceholder": "Ключ для подключений", - "sudoPassword": "Пароль sudo", - "cachedPassword": "Пароль сохранён в памяти", - "saveSettings": "Сохранить настройки", - "settingsSaved": "Настройки сохранены", - "startedSuccess": "MITM запущен", - "stoppedSuccess": "MITM остановлен", - "saveFailed": "Не удалось сохранить", - "loadFailed": "Не удалось загрузить", - "invalidPort": "Некорректный порт", - "certificate": "Сертификат", - "certificateReady": "Сертификат готов", - "certificateMissing": "Сертификат не найден", - "available": "Доступен", - "missing": "Отсутствует", - "downloadCert": "Скачать сертификат", - "regenerateCert": "Пересоздать сертификат", - "regenerateConfirm": "Пересоздать сертификат? Это может потребовать переустановки на клиентах.", - "regenerateSuccess": "Сертификат пересоздан", - "regenerateFailed": "Не удалось пересоздать сертификат", - "targetRoutes": "Целевые маршруты", - "interceptedRequests": "Перехваченные запросы", - "activeConnections": "Активные подключения", - "dnsConfigured": "DNS настроен", - "pid": "PID", - "lastIntercept": "Последний перехват", - "target": "Цель", - "host": "Хост", - "localPort": "Локальный порт", - "endpoints": "Эндпоинты", - "enabled": "Включён", - "configured": "Настроен", - "yes": "Да", - "no": "Нет", - "noTargets": "Целей не задано" + "title": "__MISSING__:MITM Proxy", + "description": "__MISSING__:Transparent proxy for intercepting and routing client requests.", + "enable": "__MISSING__:Enable MITM Proxy", + "enableDesc": "__MISSING__:Start or stop the local interception process and DNS override.", + "status": "__MISSING__:Status", + "running": "__MISSING__:Running", + "stopped": "__MISSING__:Stopped", + "start": "__MISSING__:Start", + "stop": "__MISSING__:Stop", + "refresh": "__MISSING__:Refresh", + "port": "__MISSING__:Proxy Port", + "apiKey": "__MISSING__:Router API Key", + "apiKeyPlaceholder": "__MISSING__:Optional; falls back to a local key", + "sudoPassword": "__MISSING__:Sudo Password", + "cachedPassword": "__MISSING__:Cached for this process", + "saveSettings": "__MISSING__:Save Settings", + "settingsSaved": "__MISSING__:MITM settings saved.", + "startedSuccess": "__MISSING__:MITM proxy started.", + "stoppedSuccess": "__MISSING__:MITM proxy stopped.", + "saveFailed": "__MISSING__:Failed to update MITM settings.", + "loadFailed": "__MISSING__:Failed to load MITM settings.", + "invalidPort": "__MISSING__:Transparent MITM interception currently requires port 443.", + "certificate": "__MISSING__:CA Certificate", + "certificateReady": "__MISSING__:Certificate is available for client trust installation.", + "certificateMissing": "__MISSING__:Certificate has not been generated yet.", + "available": "__MISSING__:Available", + "missing": "__MISSING__:Missing", + "downloadCert": "__MISSING__:Download CA Certificate", + "regenerateCert": "__MISSING__:Regenerate Certificate", + "regenerateConfirm": "__MISSING__:This will invalidate existing client trust. Continue?", + "regenerateSuccess": "__MISSING__:MITM certificate regenerated.", + "regenerateFailed": "__MISSING__:Failed to regenerate MITM certificate.", + "targetRoutes": "__MISSING__:Target Routes", + "interceptedRequests": "__MISSING__:Intercepted Requests", + "activeConnections": "__MISSING__:Active Connections", + "dnsConfigured": "__MISSING__:DNS Configured", + "pid": "__MISSING__:PID", + "lastIntercept": "__MISSING__:Last Intercept", + "target": "__MISSING__:Target", + "host": "__MISSING__:Host", + "localPort": "__MISSING__:Local Port", + "endpoints": "__MISSING__:Endpoints", + "enabled": "__MISSING__:Enabled", + "configured": "__MISSING__:Configured", + "yes": "__MISSING__:Yes", + "no": "__MISSING__:No", + "noTargets": "__MISSING__:No target routes configured." }, "limits": { "title": "Лимиты и квоты", @@ -2992,11 +2955,11 @@ "noEntries": "Записи журнала аудита не найдены", "previous": "Предыдущий", "next": "Далее", - "providerWarningTitle": "Предупреждение провайдера", - "viewDetails": "Подробнее", - "eventMetadata": "Метаданные события", - "eventPayload": "Полезная нагрузка", - "requestId": "ID запроса", + "providerWarningTitle": "Provider Warning Title", + "viewDetails": "View Details", + "eventMetadata": "Event Metadata", + "eventPayload": "Event Payload", + "requestId": "Request Id", "providerWarningDesc": "Provider Warning Desc", "a": "A", "offset": "Offset", @@ -3009,8 +2972,8 @@ "close": "Close", "runningRequests": "Active Requests", "runningRequestsDesc": "Real-time view of currently running upstream requests", - "clearAll": "Очистить все", - "confirmClearActiveRequests": "Очистить все активные запросы?", + "clearAll": "__MISSING__:Clear All", + "confirmClearActiveRequests": "__MISSING__:Clear all active requests?", "model": "Model", "provider": "Provider", "account": "Account", @@ -3076,35 +3039,34 @@ "failedAddProvider": "Не удалось добавить поставщика. Попробуйте еще раз.", "connectionError": "Ошибка подключения. Пожалуйста, попробуйте еще раз.", "provider": "Поставщик", - "apiKeyHelp": "Ключ API — это пароль для AI-сервисов. Получите его у своего провайдера.", + "apiKeyHelp": "__MISSING__:An API key is a password for AI services. Get one from your provider's website (e.g., platform.openai.com, console.anthropic.com).", "tier": { - "subtitle": "OmniRoute организует провайдеров по трём уровням для удобной маршрутизации.", + "subtitle": "__MISSING__:OmniRoute organises providers into three tiers so routing prefers the most reliable, lowest-cost path first.", "tier1": { - "label": "Премиум-клиенты", - "description": "CLI первого класса с нативной аутентификацией и моделями рассуждений" + "label": "__MISSING__:Premium clients", + "description": "__MISSING__:First-class CLIs with native auth flows and reasoning models." }, "tier2": { - "label": "Оптимизировано по цене", - "description": "Недорогие высокопроизводительные провайдеры для повседневного трафика." + "label": "__MISSING__:Cost-optimised", + "description": "__MISSING__:Cheap, high-throughput providers used for everyday traffic." }, "tier3": { - "label": "Резерв и специализированные", - "description": "Локальные или специализированные эндпоинты для резерва." + "label": "__MISSING__:Fallback & specialty", + "description": "__MISSING__:Locally-hosted or specialty endpoints used as fallbacks." }, - "configure": "Настроить", - "flowDiagramAlt": "Схема уровней провайдеров" + "configure": "__MISSING__:Configure providers" }, - "tierFlowDiagramAlt": "OmniRoute 3-tier fallback diagram" + "tierFlowDiagramAlt": "__MISSING__:OmniRoute 3-tier fallback diagram" }, "providers": { "title": "Провайдеры", - "allProviders": "Все провайдеры", - "audioProviders": "Аудиопровайдеры", - "showFreeOnly": "Только бесплатные", + "allProviders": "__MISSING__:All Providers", + "audioProviders": "__MISSING__:Audio Providers", + "showFreeOnly": "__MISSING__:Free only", "addProvider": "Добавить поставщика", - "addFirstProvider": "Добавьте первого провайдера", - "addFirstProviderDesc": "Подключите AI-провайдера для маршрутизации запросов через OmniRoute", - "learnMore": "Узнать больше", + "addFirstProvider": "__MISSING__:Add your first provider", + "addFirstProviderDesc": "__MISSING__:Connect an AI provider to start routing requests through OmniRoute. You can use free providers, API keys, or OAuth accounts.", + "learnMore": "__MISSING__:Learn more", "editProvider": "Изменить поставщика", "deleteProvider": "Удалить поставщика", "noProviders": "Поставщики не настроены", @@ -3137,14 +3099,14 @@ "connected": "{count} подключено", "errorCount": "{count} Ошибка ({code})", "errorCountNoCode": "{count} Ошибка", - "warningCount": "{count} предупреждений", + "warningCount": "__MISSING__:{count} Warning", "noConnections": "Нет соединений", "expiredBadge": "Expired", "expiringSoonBadge": "Expiring Soon", - "freeTier": "Бесплатный тариф", - "freeTierAvailable": "Доступен бесплатный тариф", - "deprecated": "Устарел", - "deprecatedProvider": "Устаревший провайдер", + "freeTier": "__MISSING__:Free Tier", + "freeTierAvailable": "__MISSING__:Free tier available", + "deprecated": "__MISSING__:Deprecated", + "deprecatedProvider": "__MISSING__:This provider has been deprecated", "disabled": "Отключено", "enableProvider": "Включить провайдера", "disableProvider": "Отключить провайдера", @@ -3274,19 +3236,19 @@ "disableRateLimitProtection": "Нажмите, чтобы отключить защиту ограничения скорости", "productionKey": "Производственный ключ", "enterNewApiKey": "Введите новый ключ API", - "codexApplyModalTitle": "Apply to Local Codex", - "codexApplyTargetLabel": "Target path", - "codexApplyBackupLabel": "Backups", - "codexApplyWarning": "This will replace the existing auth.json. Continue?", - "codexApplyConfirmCheckbox": "I confirm I want to replace the existing auth.json", - "codexApply": "Apply", - "bulkTabSingle": "По одному", - "bulkTabBulkAdd": "Массовое добавление", - "bulkAddFormatHint": "Формат: key=value, по одному на строку", - "bulkValidateKeys": "Проверить ключи", - "bulkAddAllKeys": "Добавить все ключи", - "bulkAddedCount": "Добавлено: {count}", - "bulkFailedCount": "С ошибками: {count}", + "codexApplyModalTitle": "__MISSING__:Apply to Local Codex", + "codexApplyTargetLabel": "__MISSING__:Target path", + "codexApplyBackupLabel": "__MISSING__:Backups", + "codexApplyWarning": "__MISSING__:This will replace the existing auth.json. Continue?", + "codexApplyConfirmCheckbox": "__MISSING__:I confirm I want to replace the existing auth.json", + "codexApply": "__MISSING__:Apply", + "bulkTabSingle": "__MISSING__:Single", + "bulkTabBulkAdd": "__MISSING__:Bulk Add", + "bulkAddFormatHint": "__MISSING__:One key per line. Format: name|apiKey or just apiKey (auto-named by index).", + "bulkValidateKeys": "__MISSING__:Validate each key before saving (slower)", + "bulkAddAllKeys": "__MISSING__:Add All Keys", + "bulkAddedCount": "__MISSING__:{count, plural, one {# key added} other {# keys added}}", + "bulkFailedCount": "__MISSING__:{count, plural, one {# failed} other {# failed}}", "optional": "Необязательно", "anthropicCompatibleName": "Антропная совместимость", "openaiCompatibleName": "Совместимость с OpenAI", @@ -3387,7 +3349,7 @@ "compatBadgeUpstreamHeaders": "Заголовки", "perModelQuotaLabel": "Per Model Quota Label", "perModelQuotaDescription": "Per Model Quota Description", - "perModelQuotaToggle": "Квота на модель", + "perModelQuotaToggle": "__MISSING__:Per-Model Quota Toggle", "modelId": "Идентификатор модели", "customModelPlaceholder": "например gpt-4.5-турбо", "loading": "Загрузка...", @@ -3439,127 +3401,127 @@ "tokenRefreshFailed": "Не удалось обновить токен", "applyCodexAuthLocal": "Применить авторизацию", "exportCodexAuthFile": "Экспортировать авторизацию", - "applyClaudeAuthLocal": "Применить к Claude", - "exportClaudeAuthFile": "Экспортировать файл", - "importClaudeAuth": "Импорт из Claude", - "claudeApplyModalTitle": "Apply to Local Claude Code", - "claudeApplyTargetLabel": "Target path", - "claudeApplyBackupLabel": "Backups", - "claudeApplyMcpHint": "Existing MCP OAuth state will be preserved.", - "claudeApplyWarning": "This will replace the existing claudeAiOauth section. Continue?", - "claudeApplyConfirmCheckbox": "I confirm I want to replace the existing claudeAiOauth section", - "claudeApply": "Apply", - "claudeAuthAppliedLocal": "Claude auth applied locally", - "claudeAuthApplyFailed": "Failed to apply Claude auth locally", - "claudeAuthExported": "Claude auth file exported", - "claudeAuthExportFailed": "Failed to export Claude auth file", - "claudeImportModalTitle": "Import Claude Auth", - "claudeImportTabSingle": "Single", - "claudeImportTabBulk": "Bulk", - "claudeImportTabUpload": "Upload file", - "claudeImportTabPaste": "Paste JSON", - "claudeImportFileLabel": "Choose .credentials.json", - "claudeImportPasteLabel": "Paste the JSON content", - "claudeImportEmailLabel": "Account email", - "claudeImportNameLabel": "Connection name (optional)", - "claudeImportOverwriteLabel": "Replace existing connection if account already exists", - "claudeImportSubmit": "Import", - "claudeImportSuccess": "Claude connection imported successfully", - "claudeImportInvalidJson": "Could not parse the file as JSON", - "claudeImportInvalidShape": "The file is not a valid .credentials.json", - "claudeImportDuplicate": "Account already exists — enable \"Replace existing\" to overwrite", - "claudeImportIdentityUnverified": "Bootstrap could not verify the account. Enable \"Replace existing\" or provide an email.", - "claudeImportFailed": "Failed to import Claude auth", - "claudeImportBulkModeUpload": "Upload files", - "claudeImportBulkModePaste": "Paste JSON array", - "claudeImportBulkModeZip": "Upload ZIP", - "claudeImportBulkUploadHint": "Drop or pick up to 50 .credentials.json files (256KB each, 10MB total).", - "claudeImportBulkPasteHint": "Paste an array of objects: [{ json, name?, email? }, ...]", - "claudeImportBulkZipHint": "ZIP containing .json entries. Max 50 entries, 10MB unpacked.", - "claudeImportBulkSubmit": "Import all", - "claudeImportBulkSuccess": "Imported {count} Claude connections", - "claudeImportBulkFailed": "Some entries failed to import", - "claudeImportBulkZipExtracting": "Extracting ZIP…", - "claudeImportBulkZipError": "Failed to extract ZIP", - "applyGeminiAuthLocal": "Применить к Gemini", - "exportGeminiAuthFile": "Экспортировать файл", - "importGeminiAuth": "Импорт из Gemini", - "geminiApplyModalTitle": "Apply to Local Gemini CLI", - "geminiApplyTargetLabel": "Target path", - "geminiApplyBackupLabel": "Backups", - "geminiApplyAccountsHint": "The google_accounts.json active account will be updated to match this connection.", - "geminiApplyWarning": "This will replace the existing oauth_creds.json and update google_accounts.json. Continue?", - "geminiApplyConfirmCheckbox": "I confirm I want to replace the existing oauth_creds.json", - "geminiApply": "Apply", - "geminiAuthAppliedLocal": "Gemini auth applied locally", - "geminiAuthApplyFailed": "Failed to apply Gemini auth locally", - "geminiAuthExported": "Gemini auth file exported", - "geminiAuthExportFailed": "Failed to export Gemini auth file", - "geminiImportModalTitle": "Import Gemini Auth", - "geminiImportTabSingle": "Single", - "geminiImportTabBulk": "Bulk", - "geminiImportTabUpload": "Upload file", - "geminiImportTabPaste": "Paste JSON", - "geminiImportFileLabel": "Choose oauth_creds.json", - "geminiImportPasteLabel": "Paste the JSON content", - "geminiImportEmailLabel": "Account email", - "geminiImportNameLabel": "Connection name (optional)", - "geminiImportOverwriteLabel": "Replace existing connection if account already exists", - "geminiImportSubmit": "Import", - "geminiImportSuccess": "Gemini connection imported successfully", - "geminiImportInvalidJson": "Could not parse the file as JSON", - "geminiImportInvalidShape": "The file is not a valid oauth_creds.json", - "geminiImportDuplicate": "Account already exists — enable \"Replace existing\" to overwrite", - "geminiImportIdentityUnverified": "Could not verify identity from id_token. Enable \"Replace existing\" or provide an email.", - "geminiImportFailed": "Failed to import Gemini auth", - "geminiImportBulkModeUpload": "Upload files", - "geminiImportBulkModePaste": "Paste JSON array", - "geminiImportBulkModeZip": "Upload ZIP", - "geminiImportBulkUploadHint": "Drop or pick up to 50 oauth_creds.json files (256KB each, 10MB total).", - "geminiImportBulkPasteHint": "Paste an array of objects: [{ json, name?, email? }, ...]", - "geminiImportBulkZipHint": "ZIP containing oauth_creds.json entries. Max 50 entries, 10MB unpacked.", - "geminiImportBulkSubmit": "Import all", - "geminiImportBulkSuccess": "Imported {count} Gemini connections", - "geminiImportBulkFailed": "Some entries failed to import", - "geminiImportBulkZipExtracting": "Extracting ZIP…", - "geminiImportBulkZipError": "Failed to extract ZIP", + "applyClaudeAuthLocal": "__MISSING__:Apply auth", + "exportClaudeAuthFile": "__MISSING__:Export auth", + "importClaudeAuth": "__MISSING__:Import auth", + "claudeApplyModalTitle": "__MISSING__:Apply to Local Claude Code", + "claudeApplyTargetLabel": "__MISSING__:Target path", + "claudeApplyBackupLabel": "__MISSING__:Backups", + "claudeApplyMcpHint": "__MISSING__:Existing MCP OAuth state will be preserved.", + "claudeApplyWarning": "__MISSING__:This will replace the existing claudeAiOauth section. Continue?", + "claudeApplyConfirmCheckbox": "__MISSING__:I confirm I want to replace the existing claudeAiOauth section", + "claudeApply": "__MISSING__:Apply", + "claudeAuthAppliedLocal": "__MISSING__:Claude auth applied locally", + "claudeAuthApplyFailed": "__MISSING__:Failed to apply Claude auth locally", + "claudeAuthExported": "__MISSING__:Claude auth file exported", + "claudeAuthExportFailed": "__MISSING__:Failed to export Claude auth file", + "claudeImportModalTitle": "__MISSING__:Import Claude Auth", + "claudeImportTabSingle": "__MISSING__:Single", + "claudeImportTabBulk": "__MISSING__:Bulk", + "claudeImportTabUpload": "__MISSING__:Upload file", + "claudeImportTabPaste": "__MISSING__:Paste JSON", + "claudeImportFileLabel": "__MISSING__:Choose .credentials.json", + "claudeImportPasteLabel": "__MISSING__:Paste the JSON content", + "claudeImportEmailLabel": "__MISSING__:Account email", + "claudeImportNameLabel": "__MISSING__:Connection name (optional)", + "claudeImportOverwriteLabel": "__MISSING__:Replace existing connection if account already exists", + "claudeImportSubmit": "__MISSING__:Import", + "claudeImportSuccess": "__MISSING__:Claude connection imported successfully", + "claudeImportInvalidJson": "__MISSING__:Could not parse the file as JSON", + "claudeImportInvalidShape": "__MISSING__:The file is not a valid .credentials.json", + "claudeImportDuplicate": "__MISSING__:Account already exists — enable \"Replace existing\" to overwrite", + "claudeImportIdentityUnverified": "__MISSING__:Bootstrap could not verify the account. Enable \"Replace existing\" or provide an email.", + "claudeImportFailed": "__MISSING__:Failed to import Claude auth", + "claudeImportBulkModeUpload": "__MISSING__:Upload files", + "claudeImportBulkModePaste": "__MISSING__:Paste JSON array", + "claudeImportBulkModeZip": "__MISSING__:Upload ZIP", + "claudeImportBulkUploadHint": "__MISSING__:Drop or pick up to 50 .credentials.json files (256KB each, 10MB total).", + "claudeImportBulkPasteHint": "__MISSING__:Paste an array of objects: [{ json, name?, email? }, ...]", + "claudeImportBulkZipHint": "__MISSING__:ZIP containing .json entries. Max 50 entries, 10MB unpacked.", + "claudeImportBulkSubmit": "__MISSING__:Import all", + "claudeImportBulkSuccess": "__MISSING__:Imported {count} Claude connections", + "claudeImportBulkFailed": "__MISSING__:Some entries failed to import", + "claudeImportBulkZipExtracting": "__MISSING__:Extracting ZIP…", + "claudeImportBulkZipError": "__MISSING__:Failed to extract ZIP", + "applyGeminiAuthLocal": "__MISSING__:Apply auth", + "exportGeminiAuthFile": "__MISSING__:Export auth", + "importGeminiAuth": "__MISSING__:Import auth", + "geminiApplyModalTitle": "__MISSING__:Apply to Local Gemini CLI", + "geminiApplyTargetLabel": "__MISSING__:Target path", + "geminiApplyBackupLabel": "__MISSING__:Backups", + "geminiApplyAccountsHint": "__MISSING__:The google_accounts.json active account will be updated to match this connection.", + "geminiApplyWarning": "__MISSING__:This will replace the existing oauth_creds.json and update google_accounts.json. Continue?", + "geminiApplyConfirmCheckbox": "__MISSING__:I confirm I want to replace the existing oauth_creds.json", + "geminiApply": "__MISSING__:Apply", + "geminiAuthAppliedLocal": "__MISSING__:Gemini auth applied locally", + "geminiAuthApplyFailed": "__MISSING__:Failed to apply Gemini auth locally", + "geminiAuthExported": "__MISSING__:Gemini auth file exported", + "geminiAuthExportFailed": "__MISSING__:Failed to export Gemini auth file", + "geminiImportModalTitle": "__MISSING__:Import Gemini Auth", + "geminiImportTabSingle": "__MISSING__:Single", + "geminiImportTabBulk": "__MISSING__:Bulk", + "geminiImportTabUpload": "__MISSING__:Upload file", + "geminiImportTabPaste": "__MISSING__:Paste JSON", + "geminiImportFileLabel": "__MISSING__:Choose oauth_creds.json", + "geminiImportPasteLabel": "__MISSING__:Paste the JSON content", + "geminiImportEmailLabel": "__MISSING__:Account email", + "geminiImportNameLabel": "__MISSING__:Connection name (optional)", + "geminiImportOverwriteLabel": "__MISSING__:Replace existing connection if account already exists", + "geminiImportSubmit": "__MISSING__:Import", + "geminiImportSuccess": "__MISSING__:Gemini connection imported successfully", + "geminiImportInvalidJson": "__MISSING__:Could not parse the file as JSON", + "geminiImportInvalidShape": "__MISSING__:The file is not a valid oauth_creds.json", + "geminiImportDuplicate": "__MISSING__:Account already exists — enable \"Replace existing\" to overwrite", + "geminiImportIdentityUnverified": "__MISSING__:Could not verify identity from id_token. Enable \"Replace existing\" or provide an email.", + "geminiImportFailed": "__MISSING__:Failed to import Gemini auth", + "geminiImportBulkModeUpload": "__MISSING__:Upload files", + "geminiImportBulkModePaste": "__MISSING__:Paste JSON array", + "geminiImportBulkModeZip": "__MISSING__:Upload ZIP", + "geminiImportBulkUploadHint": "__MISSING__:Drop or pick up to 50 oauth_creds.json files (256KB each, 10MB total).", + "geminiImportBulkPasteHint": "__MISSING__:Paste an array of objects: [{ json, name?, email? }, ...]", + "geminiImportBulkZipHint": "__MISSING__:ZIP containing oauth_creds.json entries. Max 50 entries, 10MB unpacked.", + "geminiImportBulkSubmit": "__MISSING__:Import all", + "geminiImportBulkSuccess": "__MISSING__:Imported {count} Gemini connections", + "geminiImportBulkFailed": "__MISSING__:Some entries failed to import", + "geminiImportBulkZipExtracting": "__MISSING__:Extracting ZIP…", + "geminiImportBulkZipError": "__MISSING__:Failed to extract ZIP", "codexAuthAppliedLocal": "Codex auth.json применён локально", "codexAuthApplyFailed": "Не удалось применить Codex auth.json локально", "codexAuthExported": "Codex auth.json экспортирован", "codexAuthExportFailed": "Не удалось экспортировать Codex auth.json", - "importCodexAuth": "Импорт из Codex", - "codexImportModalTitle": "Import Codex Auth", - "codexImportTabSingle": "Single", - "codexImportTabBulk": "Bulk", - "codexImportTabUpload": "Upload file", - "codexImportTabPaste": "Paste JSON", - "codexImportFileLabel": "Choose auth.json", - "codexImportFileHint": "Select the auth.json file exported from Codex or from OmniRoute.", - "codexImportPasteLabel": "Paste the JSON content", - "codexImportEmailLabel": "Account email", - "codexImportEmailHint": "Auto-detected from the file; edit if needed.", - "codexImportNameLabel": "Connection name (optional)", - "codexImportOverwriteLabel": "Replace existing connection if account already exists", - "codexImportSubmit": "Import", - "codexImportSuccess": "Codex connection imported successfully", - "codexImportInvalidJson": "Could not parse the file as JSON", - "codexImportInvalidShape": "The file is not a valid Codex auth.json", - "codexImportDuplicate": "Account already exists — enable \"Replace existing\" to overwrite", - "codexImportFailed": "Failed to import Codex auth", - "codexImportDetectedEmail": "Detected: {email}", - "codexImportNoEmailDetected": "No email detected in file", - "codexImportBulkModeUpload": "Upload files", - "codexImportBulkModePaste": "Paste list", - "codexImportBulkModeZip": "ZIP archive", - "codexImportBulkUploadHint": "Select multiple .json files or drag and drop", - "codexImportBulkPasteHint": "JSON array [ {...}, {...} ] or multiple JSONs separated by --- on its own line", - "codexImportBulkZipHint": "Upload a .zip containing auth.json files (max 50 files, 10 MB)", - "codexImportBulkSubmit": "Import {count} accounts", - "codexImportBulkLimit": "Max 50 files per import", - "codexImportBulkSuccess": "{count} imported", - "codexImportBulkFailed": "{count} failed", - "codexImportBulkZipExtracting": "Extracting ZIP…", - "codexImportBulkZipError": "Failed to extract ZIP", + "importCodexAuth": "__MISSING__:Import auth", + "codexImportModalTitle": "__MISSING__:Import Codex Auth", + "codexImportTabSingle": "__MISSING__:Single", + "codexImportTabBulk": "__MISSING__:Bulk", + "codexImportTabUpload": "__MISSING__:Upload file", + "codexImportTabPaste": "__MISSING__:Paste JSON", + "codexImportFileLabel": "__MISSING__:Choose auth.json", + "codexImportFileHint": "__MISSING__:Select the auth.json file exported from Codex or from OmniRoute.", + "codexImportPasteLabel": "__MISSING__:Paste the JSON content", + "codexImportEmailLabel": "__MISSING__:Account email", + "codexImportEmailHint": "__MISSING__:Auto-detected from the file; edit if needed.", + "codexImportNameLabel": "__MISSING__:Connection name (optional)", + "codexImportOverwriteLabel": "__MISSING__:Replace existing connection if account already exists", + "codexImportSubmit": "__MISSING__:Import", + "codexImportSuccess": "__MISSING__:Codex connection imported successfully", + "codexImportInvalidJson": "__MISSING__:Could not parse the file as JSON", + "codexImportInvalidShape": "__MISSING__:The file is not a valid Codex auth.json", + "codexImportDuplicate": "__MISSING__:Account already exists — enable \"Replace existing\" to overwrite", + "codexImportFailed": "__MISSING__:Failed to import Codex auth", + "codexImportDetectedEmail": "__MISSING__:Detected: {email}", + "codexImportNoEmailDetected": "__MISSING__:No email detected in file", + "codexImportBulkModeUpload": "__MISSING__:Upload files", + "codexImportBulkModePaste": "__MISSING__:Paste list", + "codexImportBulkModeZip": "__MISSING__:ZIP archive", + "codexImportBulkUploadHint": "__MISSING__:Select multiple .json files or drag and drop", + "codexImportBulkPasteHint": "__MISSING__:JSON array [ {...}, {...} ] or multiple JSONs separated by --- on its own line", + "codexImportBulkZipHint": "__MISSING__:Upload a .zip containing auth.json files (max 50 files, 10 MB)", + "codexImportBulkSubmit": "__MISSING__:Import {count} accounts", + "codexImportBulkLimit": "__MISSING__:Max 50 files per import", + "codexImportBulkSuccess": "__MISSING__:{count} imported", + "codexImportBulkFailed": "__MISSING__:{count} failed", + "codexImportBulkZipExtracting": "__MISSING__:Extracting ZIP…", + "codexImportBulkZipError": "__MISSING__:Failed to extract ZIP", "advancedSettings": "Расширенные настройки", "chatPathLabel": "Путь чат-эндпоинта", "chatPathPlaceholder": "/chat/completions", @@ -3581,7 +3543,7 @@ "addAnotherApiKey": "Add another API key or paste multiple keys", "addCcCompatible": "Add Cc Compatible", "aggregatorsGateways": "Aggregators Gateways", - "enterpriseCloud": "Облако для Enterprise", + "enterpriseCloud": "__MISSING__:Enterprise & Cloud", "apiFormatLabel": "Api Format Label", "apiKeyOptionalHint": "Api Key Optional Hint", "apiKeyOptionalLabel": "Api Key Optional Label", @@ -3592,14 +3554,14 @@ "apikey": "Apikey", "audio": "Audio", "audioProvidersHeading": "Audio Providers Heading", - "cloudAgentProviders": "Провайдеры облачных агентов", + "cloudAgentProviders": "__MISSING__:Cloud Agent Providers", "audioShortLabel": "Audio Short Label", "azureOpenAiBaseUrlHint": "Azure Open Ai Base Url Hint", "bailianBaseUrlHint": "Bailian Base Url Hint", "blackboxWebCookieHint": "Blackbox Web Cookie Hint", "blackboxWebCookiePlaceholder": "Blackbox Web Cookie Placeholder", - "t3ChatWebCookieHint": "Open t3.chat → DevTools → Application → Local Storage → https://t3.chat, copy 'convex-session-id'. Then open DevTools → Network, copy the full Cookie header from any chat request. Paste both values in the fields below.", - "t3ChatWebCookiePlaceholder": "convex-session-id=abc123...", + "t3ChatWebCookieHint": "__MISSING__:Open t3.chat → DevTools → Application → Local Storage → https://t3.chat, copy 'convex-session-id'. Then open DevTools → Network, copy the full Cookie header from any chat request. Paste both values in the fields below.", + "t3ChatWebCookiePlaceholder": "__MISSING__:convex-session-id=abc123...", "blockClaudeExtraUsageDescription": "Block Claude Extra Usage Description", "blockClaudeExtraUsageLabel": "Block Claude Extra Usage Label", "bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}", @@ -3659,19 +3621,19 @@ "apiKeyWarningAlert": "{count} API key(s) in warning state due to elevated failure rate in connections: {connections}. Review to prevent rotation issues.", "apiKeyWarningAlertTitle": "API Key Warning", "googlePseInfo": "Google Pse Info", - "geminiCliProjectIdHint": "Your Google Cloud Project ID. Required for accounts with exceptions. Enter your GCP Project ID to use with Gemini CLI.", - "geminiCliProjectIdLabel": "Google Cloud Project ID", - "geminiCliProjectIdPlaceholder": "my-gcp-project-id", - "antigravityProjectIdHint": "Optional override for Antigravity Cloud Code requests. Leave blank to use the project discovered during Google OAuth.", - "antigravityProjectIdLabel": "Google Cloud Project ID", - "antigravityProjectIdPlaceholder": "my-gcp-project-id", + "geminiCliProjectIdHint": "__MISSING__:Your Google Cloud Project ID. Required for accounts with exceptions. Enter your GCP Project ID to use with Gemini CLI.", + "geminiCliProjectIdLabel": "__MISSING__:Google Cloud Project ID", + "geminiCliProjectIdPlaceholder": "__MISSING__:my-gcp-project-id", + "antigravityProjectIdHint": "__MISSING__:Optional override for Antigravity Cloud Code requests. Leave blank to use the project discovered during Google OAuth.", + "antigravityProjectIdLabel": "__MISSING__:Google Cloud Project ID", + "antigravityProjectIdPlaceholder": "__MISSING__:my-gcp-project-id", "grokWebCookieHint": "Grok Web Cookie Hint", "grokWebCookiePlaceholder": "Grok Web Cookie Placeholder", "herokuBaseUrlHint": "Heroku Base Url Hint", "hideEmail": "Hide Email", "imageProviders": "Image Providers", - "videoProviders": "Видеопровайдеры", - "embeddingRerankProviders": "Эмбеддинги и переранжирование", + "videoProviders": "__MISSING__:Video Generation", + "embeddingRerankProviders": "__MISSING__:Embeddings & Rerank", "imagesShortLabel": "Images Short Label", "llmProviders": "Llm Providers", "localProviderApiKeyOptionalHint": "Local Provider Api Key Optional Hint", @@ -3744,22 +3706,22 @@ "zedImportNone": "Zed Import None", "zedImportSuccess": "Zed Import Success", "zedImporting": "Zed Importing", - "freeTierProviders": "Бесплатные провайдеры", - "freeTierLabel": "Бесплатно", - "freeTierProvidersDesc": "Провайдеры с бесплатным доступом", - "providerSummaryAll": "Сводка по всем провайдерам", - "ideProviders": "IDE-провайдеры", - "ideProvidersDesc": "Провайдеры для сред разработки", - "noIdeProviders": "Нет IDE-провайдеров", - "providerDetailFastTierTooltip": "Apply Codex Fast tier to all Codex connections by default", - "providerDetailFastDefaultLabel": "Fast default", - "providerDetailBrowserManualConnect": "Browser/manual connect", - "providerDetailAuthUrl": "Auth URL", - "providerDetailCallbackUrl": "Callback URL", - "providerDetailValidClaudeCredentialsFile": "Valid Claude credentials file", - "providerDetailPathAutoDetectedAllOs": "Path is auto-detected per OS (Linux/Mac/Windows).", - "providerDetailMyClaudeAccountPlaceholder": "My Claude account", - "providerDetailPathAutoDetected": "Path is auto-detected per OS (Linux/Mac)." + "freeTierProviders": "__MISSING__:Free Tier Providers", + "freeTierLabel": "__MISSING__:Free tier available", + "freeTierProvidersDesc": "__MISSING__:Providers with free tiers — some require an API key signup, others need no credentials at all.", + "providerSummaryAll": "__MISSING__:Total", + "ideProviders": "__MISSING__:IDE Providers", + "ideProvidersDesc": "__MISSING__:Editors with built-in AI subscription. Use the provider page to import credentials directly from the IDE keychain.", + "noIdeProviders": "__MISSING__:No IDE providers match the current filters.", + "providerDetailFastTierTooltip": "__MISSING__:Apply Codex Fast tier to all Codex connections by default", + "providerDetailFastDefaultLabel": "__MISSING__:Fast default", + "providerDetailBrowserManualConnect": "__MISSING__:Browser/manual connect", + "providerDetailAuthUrl": "__MISSING__:Auth URL", + "providerDetailCallbackUrl": "__MISSING__:Callback URL", + "providerDetailValidClaudeCredentialsFile": "__MISSING__:Valid Claude credentials file", + "providerDetailPathAutoDetectedAllOs": "__MISSING__:Path is auto-detected per OS (Linux/Mac/Windows).", + "providerDetailMyClaudeAccountPlaceholder": "__MISSING__:My Claude account", + "providerDetailPathAutoDetected": "__MISSING__:Path is auto-detected per OS (Linux/Mac)." }, "settings": { "title": "Настройки", @@ -3769,15 +3731,15 @@ "routing": "Маршрутизация", "cache": "Кэш", "resilience": "Устойчивость", - "routingSettingsIntro": "Управление маршрутизацией, трансформацией и отправкой запросов", - "resilienceSettingsIntro": "Автоматические повторы, остывание и fallback при сбоях провайдеров", - "aiSettingsIntro": "Настройки AI: бюджет мышления, поведение моделей и агентные возможности", + "routingSettingsIntro": "__MISSING__:Controls how your requests are routed, transformed, and sent to AI providers.", + "resilienceSettingsIntro": "__MISSING__:Automatic retry, cooldown, and fallback when providers fail.", + "aiSettingsIntro": "__MISSING__:AI-specific settings for thinking budget, model behavior, and compression.", "systemPrompt": "Системная подсказка", "thinkingBudget": "Думая о бюджете", "proxy": "Прокси", - "httpProxy": "HTTP-прокси", + "httpProxy": "HTTP Proxy", "1proxy": "1proxy", - "proxySubTabsAria": "Разделы прокси", + "proxySubTabsAria": "Proxy sections", "requestBodyLimitTitle": "Request Body Limit", "requestBodyLimitDescription": "Maximum API payload size accepted before a request body is parsed. Dedicated upload routes keep at least their built-in 100 MB limit.", "requestBodyLimitInputLabel": "Request body limit in MB", @@ -3791,7 +3753,7 @@ "requestBodyLimitSaving": "Saving...", "requestBodyLimitSave": "Save", "requestBodyLimitCurrent": "Current: {value}", - "mitmProxy": "MITM-прокси", + "mitmProxy": "__MISSING__:MITM Proxy", "pricing": "Цены", "storage": "Хранение", "policies": "Политика", @@ -3877,8 +3839,8 @@ "autoDisableDescription": "Навсегда помечать соединения провайдера как отключённые, если они возвращают сигнал окончательной блокировки (например, HTTP 403 'verify your account'). Это убирает их из ротации комбо.", "autoDisableThreshold": "Порог блокировки", "autoDisableThresholdDesc": "Количество подряд идущих сигналов блокировки, необходимых перед постоянным отключением.", - "resilienceStructureTitle": "Структура отказоустойчивости", - "resilienceStructureDesc": "Три уровня: выключатели провайдеров → остывание подключений → блокировки моделей", + "resilienceStructureTitle": "__MISSING__:Resilience Structure", + "resilienceStructureDesc": "__MISSING__:This page only configures behavior. Live breaker state is shown on the Health page. Combo-specific retry and round-robin slot control remain on combo settings.", "enableThinking": "Включить мышление", "maxThinkingTokens": "Максимальное количество жетонов мышления", "enableProxy": "Включить прокси", @@ -3915,14 +3877,14 @@ "themeLight": "Свет", "themeDark": "Темный", "themeSystem": "Система", - "endpointTunnelVisibility": "Видимость туннелей", - "endpointTunnelVisibilityDesc": "Показывать/скрывать опции туннелирования", - "showCloudflareTunnel": "Cloudflare Tunnel", - "showCloudflareTunnelDesc": "Показать опцию Cloudflare Tunnel на странице эндпоинтов", - "showTailscaleFunnel": "Tailscale Funnel", - "showTailscaleFunnelDesc": "Показать опцию Tailscale Funnel на странице эндпоинтов", - "showNgrokTunnel": "ngrok", - "showNgrokTunnelDesc": "Показать опцию ngrok на странице эндпоинтов", + "endpointTunnelVisibility": "__MISSING__:Endpoint tunnel visibility", + "endpointTunnelVisibilityDesc": "__MISSING__:Hide tunnel controls from the Endpoint page without changing tunnel state.", + "showCloudflareTunnel": "__MISSING__:Cloudflare Quick Tunnel", + "showCloudflareTunnelDesc": "__MISSING__:Show Cloudflare Quick Tunnel controls on the Endpoint page.", + "showTailscaleFunnel": "__MISSING__:Tailscale Funnel", + "showTailscaleFunnelDesc": "__MISSING__:Show Tailscale Funnel controls on the Endpoint page.", + "showNgrokTunnel": "__MISSING__:ngrok Tunnel", + "showNgrokTunnelDesc": "__MISSING__:Show ngrok Tunnel controls on the Endpoint page.", "sidebarVisibility": "Скрывать элементы боковой панели", "sidebarVisibilityDesc": "Скрывайте любые элементы навигации боковой панели, чтобы уменьшить визуальный шум.", "sidebarVisibilityHint": "Любой раздел боковой панели скрывается автоматически, когда...", @@ -4004,14 +3966,14 @@ "apiEndpointProtection": "Защита конечных точек API", "requireAuthModels": "Требовать ключ API для /models", "requireAuthModelsDesc": "Если включено, конечная точка /v1/models возвращает 404 для неаутентифицированных запросов. Предотвращает обнаружение модели неавторизованными пользователями.", - "authModelHeading": "Модель аутентификации", - "authModelClient": "Клиентская", - "authModelManagement": "Управляющая", - "authModelPublic": "Публичная", - "bruteForceProtection": "Защита от перебора", - "bruteForceProtectionDesc": "Блокировка после нескольких неудачных попыток входа", - "corsAllowedOrigins": "Разрешённые CORS-источники", - "corsAllowedOriginsDesc": "Список источников, которым разрешены кросс-доменные запросы", + "authModelHeading": "__MISSING__:Active authorization model", + "authModelClient": "__MISSING__:Client API endpoints (/v1/*, /chat/*, /responses/*, /codex/*, /messages/*) require a Bearer API key.", + "authModelManagement": "__MISSING__:Management endpoints (/dashboard, /api/*) require a dashboard session or management credential.", + "authModelPublic": "__MISSING__:Only login, health, and bootstrap routes are public.", + "bruteForceProtection": "__MISSING__:Login brute-force protection", + "bruteForceProtectionDesc": "__MISSING__:Throttle and lock /api/auth/login after repeated failures from the same IP.", + "corsAllowedOrigins": "__MISSING__:CORS allowed origins", + "corsAllowedOriginsDesc": "__MISSING__:Comma-separated list of browser origins permitted to call this server. Empty list = no browser CORS access (server-to-server still works). Use CORS_ALLOW_ALL=true env var only for development.", "blockedProviders": "Заблокированные провайдеры", "blockedProvidersDesc": "Скройте конкретных поставщиков из ответа /v1/models. Заблокированные поставщики не будут отображаться в списках моделей.", "providersBlocked": "Поставщик(и) {count} заблокирован в /models", @@ -4022,16 +3984,16 @@ "cliFingerprintEnabled": "{count} провайдер(ов) с активным CLI-отпечатком", "enableFingerprintTitle": "Включить отпечаток для {provider}", "disableFingerprintTitle": "Отключить отпечаток для {provider}", - "systemTransforms": "Системные трансформации", - "systemTransformsDesc": "Автоматические преобразования запросов для выбранных провайдеров", - "systemTransformsAddProvider": "Add provider", - "systemTransformsAddProviderPlaceholder": "Select a provider…", - "systemTransformsAddProviderAllConfigured": "All providers already configured", - "systemTransformsRemoveProvider": "Remove provider", - "systemTransformsNoProviders": "No providers configured. Add a provider to get started.", - "systemTransformsOpMoveUp": "Move up", - "systemTransformsOpMoveDown": "Move down", - "systemTransformsOpDelete": "Delete op", + "systemTransforms": "__MISSING__:System-block Transform Pipeline", + "systemTransformsDesc": "__MISSING__:Per-provider ordered pipeline of transforms applied to the request body before forwarding. Supports any provider ID.", + "systemTransformsAddProvider": "__MISSING__:Add provider", + "systemTransformsAddProviderPlaceholder": "__MISSING__:Select a provider…", + "systemTransformsAddProviderAllConfigured": "__MISSING__:All providers already configured", + "systemTransformsRemoveProvider": "__MISSING__:Remove provider", + "systemTransformsNoProviders": "__MISSING__:No providers configured. Add a provider to get started.", + "systemTransformsOpMoveUp": "__MISSING__:Move up", + "systemTransformsOpMoveDown": "__MISSING__:Move down", + "systemTransformsOpDelete": "__MISSING__:Delete op", "routingStrategy": "Стратегия маршрутизации", "routingAdvancedGuideTitle": "Расширенное руководство по маршрутизации", "routingAdvancedGuideHint1": "Используйте «Fill First» для предсказуемого приоритета, Round Robin для обеспечения справедливости и P2C для устойчивости к задержкам.", @@ -4048,8 +4010,8 @@ "leastUsedDesc": "Выберите наименее использованную учетную запись", "costOpt": "Опция стоимости", "costOptDesc": "Предпочитаю самый дешевый доступный аккаунт", - "resetAware": "Сброс-ориентированная маршрутизация", - "resetAwareDesc": "Учитывает время сброса квоты провайдера для оптимального распределения", + "resetAware": "__MISSING__:Reset-Aware RR", + "resetAwareDesc": "__MISSING__:Prefer accounts with healthy remaining quota and nearer resets", "strictRandom": "Строгий случайный", "strictRandomDesc": "Перемешивание колоды — каждый аккаунт используется один раз перед повторным перемешиванием", "stickyLimit": "Липкий лимит", @@ -4376,21 +4338,21 @@ "compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings", "compressionModeUltra": "Ultra", "compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication", - "compressionAggressiveConfig": "Агрессивное сжатие", - "compressionAggressiveConfigDesc": "Настройки агрессивного режима сжатия", - "compressionUltraConfig": "Ультра-сжатие", - "compressionUltraConfigDesc": "Максимальное сжатие с использованием SLM", - "compressionUltraRate": "Keep Rate", - "compressionUltraMinScore": "Minimum Score Threshold", - "compressionUltraSlmFallback": "Fallback to Aggressive", - "compressionUltraModelPath": "SLM Model Path", - "compressionSummarizerEnabled": "Enable Summarizer", - "compressionMaxTokensPerMessage": "Max Tokens Per Message", - "compressionMinSavings": "Min Savings Threshold", - "compressionAgingThresholds": "Aging Thresholds", - "compressionAgingThresholdsDesc": "Number of recent messages kept at each aging tier (higher = more preserved)", - "compressionToolStrategies": "Tool Result Strategies", - "compressionToolStrategiesDesc": "Toggle compression strategies for different tool result types", + "compressionAggressiveConfig": "__MISSING__:Aggressive Engine Configuration", + "compressionAggressiveConfigDesc": "__MISSING__:Fine-tune summarization, tool compression, and aging thresholds", + "compressionUltraConfig": "__MISSING__:Ultra Engine Configuration", + "compressionUltraConfigDesc": "__MISSING__:Fine-tune heuristic pruning, SLM fallback, and per-message thresholds", + "compressionUltraRate": "__MISSING__:Keep Rate", + "compressionUltraMinScore": "__MISSING__:Minimum Score Threshold", + "compressionUltraSlmFallback": "__MISSING__:Fallback to Aggressive", + "compressionUltraModelPath": "__MISSING__:SLM Model Path", + "compressionSummarizerEnabled": "__MISSING__:Enable Summarizer", + "compressionMaxTokensPerMessage": "__MISSING__:Max Tokens Per Message", + "compressionMinSavings": "__MISSING__:Min Savings Threshold", + "compressionAgingThresholds": "__MISSING__:Aging Thresholds", + "compressionAgingThresholdsDesc": "__MISSING__:Number of recent messages kept at each aging tier (higher = more preserved)", + "compressionToolStrategies": "__MISSING__:Tool Result Strategies", + "compressionToolStrategiesDesc": "__MISSING__:Toggle compression strategies for different tool result types", "compressionGeneral": "General Settings", "compressionAutoTrigger": "Auto-Trigger Threshold", "compressionCacheTTL": "Cache TTL", @@ -4408,7 +4370,7 @@ "compressionPreservePatternsDesc": "Regex patterns that are never compressed (one per line)", "compressionLogTitle": "Compression Log", "compressionLogEmpty": "No compressed requests yet. Compression stats will appear here when requests are processed with compression enabled.", - "minutes": "минуты", + "minutes": "__MISSING__:minutes", "compressionModeRtk": "RTK", "compressionModeRtkDesc": "Command-aware tool output filtering", "compressionModeStacked": "Stacked", @@ -4450,199 +4412,220 @@ "searching": "Searching...", "cleaning": "Cleaning...", "cleanNow": "Clean now", - "optional": "необязательно", - "current": "текущее", - "remove": "Удалить", - "search": "Поиск", - "oneproxyTitle": "1Proxy", - "oneproxyTotalProxies": "Всего прокси", - "oneproxyAvgQuality": "Среднее качество", - "resilienceScope": "Область", - "resilienceTrigger": "Триггер", - "resilienceEffect": "Эффект", - "resilienceRequestQueueTitle": "Очередь запросов", - "resilienceAutoEnableApiKeyProviders": "Auto-enable for API-key providers", - "resilienceRequestsPerMinute": "Requests per minute", - "resilienceMinTimeBetweenRequests": "Minimum time between requests", - "resilienceConcurrentRequests": "Concurrent requests", - "resilienceMaxQueueWaitTime": "Maximum queue wait time", - "resilienceBaseCooldown": "Base cooldown", - "resilienceUseUpstreamRetryHints": "Use upstream retry hints", - "resilienceDefaultPerProvider": "Default (per provider)", - "resilienceAlwaysOn": "Always on", - "resilienceAlwaysOff": "Always off", - "routingRemoveEntry": "Remove entry", - "routingNeedlesSubstrings": "Needles (substrings to match)", - "routingCaseSensitive": "Case sensitive", - "routingPrefixes": "Prefixes", - "routingMatch": "Match", - "routingReplacement": "Replacement", - "routingReplaceAllOccurrences": "Replace all occurrences", - "routingPatternRegex": "Pattern (regex)", - "routingFlags": "Flags", - "routingNeedles": "Needles", - "routingBlockText": "Block text", - "routingIdempotencyKey": "Idempotency key", - "routingEntrypoint": "Entrypoint", - "routingVersionFormat": "Version format", - "routingCchAlgorithm": "CCH algorithm", - "routingWordsToObfuscate": "Words to obfuscate (ZWJ inserted after first char)", - "logsSettingsTitle": "Logs Settings", - "detailedLogsLabel": "Детальные логи", - "detailedLogsDesc": "Включить подробное логирование запросов", - "callLogPipelineLabel": "Call Log Pipeline", - "callLogPipelineDesc": "Enable call log processing pipeline", - "maxDetailSizeLabel": "Max Detail Size (KB)", - "maxDetailSizeDesc": "Maximum size for detailed log entries", - "ringBufferSizeLabel": "Ring Buffer Size", - "ringBufferSizeDesc": "Size of the ring buffer for logs", - "semanticCacheEnabledLabel": "Семантический кэш", - "semanticCacheMaxSizeLabel": "Макс. размер кэша", - "semanticCacheMaxSizeDesc": "Maximum number of semantic cache entries", - "semanticCacheTTLLabel": "TTL кэша", - "promptCacheEnabledLabel": "Кэш промптов", - "promptCacheEnabledDesc": "Кэширование промптов на стороне провайдера", - "promptCacheStrategyLabel": "Prompt Cache Strategy", - "promptCacheStrategyDesc": "Strategy for prompt caching", - "alwaysPreserveClientCacheLabel": "Always Preserve Client Cache", - "alwaysPreserveClientCacheDesc": "Client cache preservation policy", - "logRetentionPolicyTitle": "Log retention policy", - "resilienceUseUpstream429HintsForBreaker": "Учитывать 429 от провайдера", - "appearanceLogoPreviewAlt": "Предпросмотр логотипа", - "appearanceFaviconPreviewAlt": "Предпросмотр иконки", - "oneproxyLastSync": "Last Sync", - "oneproxyAllProtocols": "All Protocols", - "oneproxyCountryCodePlaceholder": "Country code (e.g. US)", - "oneproxyMinQualityPlaceholder": "Min quality", - "oneproxyLoadingProxies": "Loading proxies...", - "oneproxyLastSyncLabel": "Last sync:", - "oneproxyProxiesFetched": "Proxies fetched:", - "oneproxyConsecutiveFailures": "Consecutive failures:", - "oneproxyErrorLabel": "Error:", - "oneproxyNever": "Never", - "oneproxySyncStatusTitle": "Sync Status", - "oneproxySuccess": "Success", - "oneproxyFailed": "Failed", - "routingAntigravitySignatureTitle": "Подпись Antigravity", - "routingHeaderFingerprintTitle": "Отпечаток заголовка", - "routingServerRejectedSave": "⚠ Server rejected save:", - "routingAddTransformOp": "Add a transform op", - "routingClientCacheControlTitle": "Кэш-контроль клиента", - "visionBridge": "Мост Vision", - "routingZeroConfigTitle": "Нулевая конфигурация", - "routingDefaultAutoVariant": "Вариант авто по умолчанию", - "visionBridgeModel": "Модель Vision", - "resilienceMaxBackoffSteps": "Макс. шагов отсрочки", - "resilienceBaseCooldownLabel": "Базовая задержка остывания", - "resilienceUseUpstreamRetryHintsLabel": "Учитывать подсказки о повторах", - "resilienceYes": "Да", - "resilienceNo": "Нет", - "resilienceUseUpstream429BreakerLabel": "Use upstream 429 hints (breaker)", - "resilienceDefault": "По умолчанию", - "resilienceMaxBackoffStepsLabel": "Максимум шагов отсрочки", - "visionBridgePrompt": "Промпт Vision", - "visionBridgeTimeoutMs": "Тайм-аут Vision (мс)", - "resilienceConnectionCooldownTitle": "Остывание подключений", - "visionBridgeMaxImagesPerRequest": "Макс. изображений на запрос", - "resilienceFailureThreshold": "Порог отказов", - "resilienceResetTimeout": "Тайм-аут сброса", - "resilienceFailureThresholdLabel": "Порог ошибок до срабатывания", - "resilienceResetTimeoutLabel": "Время до сброса состояния", - "visionBridgeModelPlaceholder": "Выберите модель", - "visionBridgePromptPlaceholder": "Опишите, как обрабатывать изображения", - "resilienceProviderBreakerTitle": "Выключатели провайдеров", - "storageDatabaseBackupRetention": "Хранение резервных копий", - "storagePurgeData": "Очистить данные", - "retentionQuotaSnapshots": "Снимки квот", - "retentionMcpAudit": "MCP аудит", - "retentionA2aEvents": "A2A события", - "retentionCallLogs": "Логи запросов", - "retentionUsageHistory": "История использования", - "retentionMemoryEntries": "Записи памяти", - "storageAutoVacuumMode": "Авто-VACUUM", - "storageScheduledVacuum": "VACUUM по расписанию", - "storageVacuumHour": "Час VACUUM", - "storagePageSize": "Размер страницы", - "storageDatabaseSize": "Размер БД", - "storagePageCount": "Страниц", - "storageFreelistCount": "Freelist", - "storageLastVacuum": "Последний VACUUM", - "storageLastOptimization": "Последняя оптимизация", - "storageIntegrityCheck": "Проверка целостности", - "storageIntegrityOk": "Целостность БД в порядке", - "storageIntegrityError": "Нарушение целостности", - "storageUsageTokenBuffer": "Буфер токенов", - "compressionSettingsAutoTriggerMode": "Auto trigger mode", - "compressionSettingsMcpDescriptionCompression": "MCP description compression", - "compressionSettingsCavemanIntensity": "Caveman intensity", - "compressionSettingsCavemanOutputMode": "Caveman output mode", - "compressionSettingsOutputIntensity": "Output intensity", - "compressionSettingsAutoClarityBypass": "Auto clarity bypass", - "resilienceWaitForCooldown": "Ждать остывания", - "resilienceEnableServerSideWait": "Ожидание на стороне сервера", - "resilienceMaximumRetries": "Максимум повторов", - "resilienceMaximumWaitPerRetry": "Макс. ожидание на повтор", - "memorySkillsSkillsmpMarketplace": "SkillsMP Marketplace", - "memorySkillsFailedToSave": "Failed to save", - "memorySkillsApiKey": "API Key", - "memorySkillsActiveSkillsProvider": "Active Skills Provider", - "cliproxyapiFallback": "CLIProxyAPI Fallback", - "cliproxyapiEnableFallback": "Enable CLIProxyAPI Fallback", - "cliproxyapiUrl": "CLIProxyAPI URL", - "cliproxyapiStatus": "CLIProxyAPI Status", - "cliproxyapiNotDetected": "Not detected", - "payloadRulesTitle": "Правила полезной нагрузки", - "modelCooldownsTitle": "Остывание моделей", - "modelCooldownsEmpty": "Нет активных остываний", - "codexFastTierTitle": "Codex Fast Tier", - "codexFastTierDesc": "Быстрый тариф для Codex", - "codexFastTierHint": "When enabled, OmniRoute adds service_tier=priority to outbound Codex requests for connections that don't already specify a tier. Priority tier requires an OpenAI Enterprise API key or the ChatGPT-auth Codex path; other key types will receive a tier-related error from OpenAI. Per-connection settings on the Codex provider page take precedence.", - "codexFastTierSaveError": "Failed to update Codex Fast Tier setting", - "claudeFastModeTitle": "Claude Fast Mode", - "claudeFastModeDesc": "Быстрый режим для Claude", - "claudeFastModeHint": "Anthropic does not officially support Fast Mode for SDK-style clients. When enabled, OmniRoute forwards an X-CPA-Force-Fast-Mode header so a paired CLIProxyAPI build can opt-in spoof the entrypoint. Only the listed Opus models are gated by Anthropic's client-side check. Subscription tier, Max plan, and Fast Mode credit balance are still enforced server-side — Anthropic may return out_of_credits even when the toggle is on.", - "claudeFastModeModelsLabel": "Applied to models ({count})", - "claudeFastModeModelCheckbox": "Enable Fast Mode for {model}", - "claudeFastModeSaveError": "Failed to update Claude Fast Mode setting", - "routingSettingsTitle": "Маршрутизация", - "logSettingsTitle": "Настройки логов", - "resilienceModelLockoutTitle": "Блокировки моделей" + "optional": "__MISSING__:Optional", + "current": "__MISSING__:Current", + "remove": "__MISSING__:Remove", + "search": "__MISSING__:Search", + "oneproxyTitle": "__MISSING__:1proxy Free Proxy Marketplace", + "oneproxyTotalProxies": "__MISSING__:Total Proxies", + "oneproxyAvgQuality": "__MISSING__:Avg Quality", + "resilienceScope": "__MISSING__:Scope:", + "resilienceTrigger": "__MISSING__:Trigger:", + "resilienceEffect": "__MISSING__:Effect:", + "resilienceRequestQueueTitle": "__MISSING__:Request Queue & Rate", + "resilienceAutoEnableApiKeyProviders": "__MISSING__:Auto-enable for API-key providers", + "resilienceRequestsPerMinute": "__MISSING__:Requests per minute", + "resilienceMinTimeBetweenRequests": "__MISSING__:Minimum time between requests", + "resilienceConcurrentRequests": "__MISSING__:Concurrent requests", + "resilienceMaxQueueWaitTime": "__MISSING__:Maximum queue wait time", + "resilienceBaseCooldown": "__MISSING__:Base cooldown", + "resilienceUseUpstreamRetryHints": "__MISSING__:Use upstream retry hints", + "resilienceDefaultPerProvider": "__MISSING__:Default (per provider)", + "resilienceAlwaysOn": "__MISSING__:Always on", + "resilienceAlwaysOff": "__MISSING__:Always off", + "routingRemoveEntry": "__MISSING__:Remove entry", + "routingNeedlesSubstrings": "__MISSING__:Needles (substrings to match)", + "routingCaseSensitive": "__MISSING__:Case sensitive", + "routingPrefixes": "__MISSING__:Prefixes", + "routingMatch": "__MISSING__:Match", + "routingReplacement": "__MISSING__:Replacement", + "routingReplaceAllOccurrences": "__MISSING__:Replace all occurrences", + "routingPatternRegex": "__MISSING__:Pattern (regex)", + "routingFlags": "__MISSING__:Flags", + "routingNeedles": "__MISSING__:Needles", + "routingBlockText": "__MISSING__:Block text", + "routingIdempotencyKey": "__MISSING__:Idempotency key", + "routingEntrypoint": "__MISSING__:Entrypoint", + "routingVersionFormat": "__MISSING__:Version format", + "routingCchAlgorithm": "__MISSING__:CCH algorithm", + "routingWordsToObfuscate": "__MISSING__:Words to obfuscate (ZWJ inserted after first char)", + "logsSettingsTitle": "__MISSING__:Logs Settings", + "detailedLogsLabel": "__MISSING__:Detailed Logs Enabled", + "detailedLogsDesc": "__MISSING__:Enable detailed request/response logging", + "callLogPipelineLabel": "__MISSING__:Call Log Pipeline", + "callLogPipelineDesc": "__MISSING__:Enable call log processing pipeline", + "maxDetailSizeLabel": "__MISSING__:Max Detail Size (KB)", + "maxDetailSizeDesc": "__MISSING__:Maximum size for detailed log entries", + "ringBufferSizeLabel": "__MISSING__:Ring Buffer Size", + "ringBufferSizeDesc": "__MISSING__:Size of the ring buffer for logs", + "semanticCacheEnabledLabel": "__MISSING__:Semantic Cache Enabled", + "semanticCacheMaxSizeLabel": "__MISSING__:Semantic Cache Max Size", + "semanticCacheMaxSizeDesc": "__MISSING__:Maximum number of semantic cache entries", + "semanticCacheTTLLabel": "__MISSING__:Semantic Cache TTL", + "promptCacheEnabledLabel": "__MISSING__:Prompt Cache Enabled", + "promptCacheEnabledDesc": "__MISSING__:Enable prompt caching", + "promptCacheStrategyLabel": "__MISSING__:Prompt Cache Strategy", + "promptCacheStrategyDesc": "__MISSING__:Strategy for prompt caching", + "alwaysPreserveClientCacheLabel": "__MISSING__:Always Preserve Client Cache", + "alwaysPreserveClientCacheDesc": "__MISSING__:Client cache preservation policy", + "logRetentionPolicyTitle": "__MISSING__:Log retention policy", + "resilienceUseUpstream429HintsForBreaker": "__MISSING__:Use upstream 429 hints (breaker)", + "appearanceLogoPreviewAlt": "__MISSING__:Logo preview", + "appearanceFaviconPreviewAlt": "__MISSING__:Favicon preview", + "oneproxyLastSync": "__MISSING__:Last Sync", + "oneproxyAllProtocols": "__MISSING__:All Protocols", + "oneproxyCountryCodePlaceholder": "__MISSING__:Country code (e.g. US)", + "oneproxyMinQualityPlaceholder": "__MISSING__:Min quality", + "oneproxyLoadingProxies": "__MISSING__:Loading proxies...", + "oneproxyLastSyncLabel": "__MISSING__:Last sync:", + "oneproxyProxiesFetched": "__MISSING__:Proxies fetched:", + "oneproxyConsecutiveFailures": "__MISSING__:Consecutive failures:", + "oneproxyErrorLabel": "__MISSING__:Error:", + "oneproxyNever": "__MISSING__:Never", + "oneproxySyncStatusTitle": "__MISSING__:Sync Status", + "oneproxySuccess": "__MISSING__:Success", + "oneproxyFailed": "__MISSING__:Failed", + "routingAntigravitySignatureTitle": "__MISSING__:Antigravity Signature Cache Mode", + "routingHeaderFingerprintTitle": "__MISSING__:Header fingerprint (per provider)", + "routingServerRejectedSave": "__MISSING__:⚠ Server rejected save:", + "routingAddTransformOp": "__MISSING__:Add a transform op", + "routingClientCacheControlTitle": "__MISSING__:Client Cache Control", + "visionBridge": "__MISSING__:Vision Bridge", + "routingZeroConfigTitle": "__MISSING__:Zero-Config Auto-Routing", + "routingDefaultAutoVariant": "__MISSING__:Default Auto Variant", + "visionBridgeModel": "__MISSING__:Bridge Model", + "resilienceMaxBackoffSteps": "__MISSING__:Max backoff steps", + "resilienceBaseCooldownLabel": "__MISSING__:Base cooldown", + "resilienceUseUpstreamRetryHintsLabel": "__MISSING__:Use upstream retry hints", + "resilienceYes": "__MISSING__:Yes", + "resilienceNo": "__MISSING__:No", + "resilienceUseUpstream429BreakerLabel": "__MISSING__:Use upstream 429 hints (breaker)", + "resilienceDefault": "__MISSING__:Default", + "resilienceMaxBackoffStepsLabel": "__MISSING__:Max backoff steps", + "visionBridgePrompt": "__MISSING__:Bridge Prompt", + "visionBridgeTimeoutMs": "__MISSING__:Timeout (ms)", + "resilienceConnectionCooldownTitle": "__MISSING__:Connection Cooldown", + "visionBridgeMaxImagesPerRequest": "__MISSING__:Max Images Per Request", + "resilienceFailureThreshold": "__MISSING__:Failure threshold", + "resilienceResetTimeout": "__MISSING__:Reset timeout", + "resilienceFailureThresholdLabel": "__MISSING__:Failure threshold", + "resilienceResetTimeoutLabel": "__MISSING__:Reset timeout", + "visionBridgeModelPlaceholder": "__MISSING__:openai/gpt-4o-mini", + "visionBridgePromptPlaceholder": "__MISSING__:Describe this image concisely.", + "resilienceProviderBreakerTitle": "__MISSING__:Circuit Breaker per Provider", + "storageDatabaseBackupRetention": "__MISSING__:Database backup retention", + "storagePurgeData": "__MISSING__:Purge Data", + "retentionQuotaSnapshots": "__MISSING__:Quota Snapshots (days)", + "retentionMcpAudit": "__MISSING__:MCP Audit (days)", + "retentionA2aEvents": "__MISSING__:A2A Events (days)", + "retentionCallLogs": "__MISSING__:Call Logs (days)", + "retentionUsageHistory": "__MISSING__:Usage History (days)", + "retentionMemoryEntries": "__MISSING__:Memory Entries (days)", + "storageAutoVacuumMode": "__MISSING__:Auto Vacuum Mode", + "storageScheduledVacuum": "__MISSING__:Scheduled Vacuum", + "storageVacuumHour": "__MISSING__:Vacuum Hour (0-23)", + "storagePageSize": "__MISSING__:Page Size (bytes)", + "storageDatabaseSize": "__MISSING__:Database Size", + "storagePageCount": "__MISSING__:Page Count", + "storageFreelistCount": "__MISSING__:Freelist Count", + "storageLastVacuum": "__MISSING__:Last Vacuum", + "storageLastOptimization": "__MISSING__:Last Optimization", + "storageIntegrityCheck": "__MISSING__:Integrity Check", + "storageIntegrityOk": "__MISSING__:✓ OK", + "storageIntegrityError": "__MISSING__:✗ Error", + "storageUsageTokenBuffer": "__MISSING__:Usage Token Buffer", + "compressionSettingsAutoTriggerMode": "__MISSING__:Auto trigger mode", + "compressionSettingsMcpDescriptionCompression": "__MISSING__:MCP description compression", + "compressionSettingsCavemanIntensity": "__MISSING__:Caveman intensity", + "compressionSettingsCavemanOutputMode": "__MISSING__:Caveman output mode", + "compressionSettingsOutputIntensity": "__MISSING__:Output intensity", + "compressionSettingsAutoClarityBypass": "__MISSING__:Auto clarity bypass", + "resilienceWaitForCooldown": "__MISSING__:Wait for Cooldown", + "resilienceEnableServerSideWait": "__MISSING__:Enable server-side wait", + "resilienceMaximumRetries": "__MISSING__:Maximum retries", + "resilienceMaximumWaitPerRetry": "__MISSING__:Maximum wait per retry", + "memorySkillsSkillsmpMarketplace": "__MISSING__:SkillsMP Marketplace", + "memorySkillsFailedToSave": "__MISSING__:Failed to save", + "memorySkillsApiKey": "__MISSING__:API Key", + "memorySkillsActiveSkillsProvider": "__MISSING__:Active Skills Provider", + "cliproxyapiFallback": "__MISSING__:CLIProxyAPI Fallback", + "cliproxyapiEnableFallback": "__MISSING__:Enable CLIProxyAPI Fallback", + "cliproxyapiUrl": "__MISSING__:CLIProxyAPI URL", + "cliproxyapiStatus": "__MISSING__:CLIProxyAPI Status", + "cliproxyapiNotDetected": "__MISSING__:Not detected", + "payloadRulesTitle": "__MISSING__:Payload Rules", + "modelCooldownsTitle": "__MISSING__:Models in cooldown", + "modelCooldownsEmpty": "__MISSING__:No models in cooldown right now.", + "codexFastTierTitle": "__MISSING__:Codex Fast Tier", + "codexFastTierDesc": "__MISSING__:Globally inject service_tier=priority for OpenAI Codex requests.", + "codexFastTierHint": "__MISSING__:When enabled, OmniRoute adds service_tier=priority to outbound Codex requests for connections that don't already specify a tier. Priority tier requires an OpenAI Enterprise API key or the ChatGPT-auth Codex path; other key types will receive a tier-related error from OpenAI. Per-connection settings on the Codex provider page take precedence.", + "codexFastTierSaveError": "__MISSING__:Failed to update Codex Fast Tier setting", + "claudeFastModeTitle": "__MISSING__:Claude Fast Mode", + "claudeFastModeDesc": "__MISSING__:Opt selected Claude requests into Anthropic Fast Mode (speed:\"fast\").", + "claudeFastModeHint": "__MISSING__:Anthropic does not officially support Fast Mode for SDK-style clients. When enabled, OmniRoute forwards an X-CPA-Force-Fast-Mode header so a paired CLIProxyAPI build can opt-in spoof the entrypoint. Only the listed Opus models are gated by Anthropic's client-side check. Subscription tier, Max plan, and Fast Mode credit balance are still enforced server-side — Anthropic may return out_of_credits even when the toggle is on.", + "claudeFastModeModelsLabel": "__MISSING__:Applied to models ({count})", + "claudeFastModeModelCheckbox": "__MISSING__:Enable Fast Mode for {model}", + "claudeFastModeSaveError": "__MISSING__:Failed to update Claude Fast Mode setting", + "settingsSidebarTitle": "Настройка боковой панели", + "settingsSidebarDesc": "Управляйте видимостью пунктов и их порядком", + "sidebarPresets": "Пресеты", + "sidebarPresetsDesc": "Начните с ролевого набора. Любое изменение после применения пресета переключает в режим Custom.", + "presetAll": "Все", + "presetAllDesc": "Показать всё", + "presetMinimal": "Минимум", + "presetMinimalDesc": "Только основные", + "presetDeveloper": "Разработчик", + "presetDeveloperDesc": "Инструменты разработки", + "presetAdmin": "Администратор", + "presetAdminDesc": "Мониторинг и аудит", + "presetCustom": "Кастом", + "activePresetLabel": "Активен:", + "presetConfirmWarning": "Применение пресета заменит ваши текущие настройки видимости и порядка.", + "cancelLabel": "Отмена", + "applyLabel": "Применить", + "sidebarOrder": "Видимость и порядок", + "sidebarOrderDesc": "Включайте/выключайте пункты и перетаскивайте для изменения порядка.", + "sidebarCustomizeLink": "Настройте видимость, порядок и ролевые пресеты для боковой панели.", + "sidebarCustomizeLinkBtn": "Настроить", + "resetDefault": "Сбросить к настройкам по умолчанию", + "settingsSidebar": "Боковая панель", + "settingsSidebarSubtitle": "Видимость и порядок" }, "contextRtk": { - "title": "RTK", - "description": "Сжатие с учётом контекста для вывода инструментов, логов терминала и команд", - "enabled": "Включено", + "title": "RTK Engine", + "description": "Command-aware compression for tool output, terminal logs and build results.", + "enabled": "Enabled", "intensity": "Intensity", - "intensityMinimal": "Минимальная", + "intensityMinimal": "Minimal", "intensityStandard": "Standard", "intensityAggressive": "Aggressive", "toolResults": "Tool results", "assistantMessages": "Assistant messages", "codeBlocks": "Code blocks", - "filterCatalog": "Каталог фильтров", - "filterCatalogDesc": "Предустановленные фильтры для распространённых инструментов", - "guidedConfig": "Настроить", - "guidedConfigDesc": "Пошаговая настройка RTK", + "filterCatalog": "__MISSING__:Filter Catalog", + "filterCatalogDesc": "__MISSING__:Available output filters by category", + "guidedConfig": "__MISSING__:Configuration", + "guidedConfigDesc": "__MISSING__:Adjust how RTK filters your terminal output", "maxLines": "Max lines", "maxChars": "Max chars", - "deduplicateThreshold": "Порог дедупликации", - "customFilters": "Пользовательские фильтры", - "detectedType": "Обнаруженный тип", - "confidence": "Уверенность", - "beforeAfter": "До/После", - "trustProjectFilters": "Доверять фильтрам проекта", - "rawOutputRetention": "Хранение сырого вывода", - "rawOutputNever": "Никогда", - "rawOutputFailures": "При ошибках", - "rawOutputAlways": "Всегда", - "rawOutputMaxBytes": "Макс. байт", + "deduplicateThreshold": "__MISSING__:Deduplicate threshold", + "customFilters": "__MISSING__:Custom filters", + "detectedType": "__MISSING__:Detected type", + "confidence": "__MISSING__:Confidence", + "beforeAfter": "__MISSING__:Before / After", + "trustProjectFilters": "__MISSING__:Trust project filters", + "rawOutputRetention": "__MISSING__:Raw output retention", + "rawOutputNever": "__MISSING__:Never", + "rawOutputFailures": "__MISSING__:Failures", + "rawOutputAlways": "__MISSING__:Always", + "rawOutputMaxBytes": "__MISSING__:Raw output max bytes", "filterTesting": "Filter Testing", "pasteOutput": "Paste tool output to test filtering...", - "presetHigh": "Высокий", - "presetLow": "Низкий", - "presetMaxChars": "Макс. символов", - "presetMaxLines": "Макс. строк", - "presetMedium": "Средний", + "presetHigh": "__MISSING__:Aggressive", + "presetLow": "__MISSING__:Light", + "presetMaxChars": "__MISSING__:Max output characters", + "presetMaxLines": "__MISSING__:Max output lines", + "presetMedium": "__MISSING__:Standard", "run": "Run", "result": "Result", "previewEmpty": "Run a sample to preview RTK output.", @@ -4651,19 +4634,19 @@ "filtersActive": "Filters active", "requests": "Requests", "avgSavings": "Avg savings", - "simpleMode": "Простой режим", - "advancedMode": "Расширенный режим", - "searchFilters": "Поиск фильтров...", - "tooltipDedup": "How aggressively to remove repeated lines.", - "tooltipMaxChars": "Maximum characters per output block.", - "tooltipMaxLines": "Maximum lines to keep from command output. Excess is truncated." + "simpleMode": "__MISSING__:Simple", + "advancedMode": "__MISSING__:Advanced", + "searchFilters": "__MISSING__:Search filters...", + "tooltipDedup": "__MISSING__:How aggressively to remove repeated lines.", + "tooltipMaxChars": "__MISSING__:Maximum characters per output block.", + "tooltipMaxLines": "__MISSING__:Maximum lines to keep from command output. Excess is truncated." }, "contextCombos": { - "title": "Комбинации сжатия", - "description": "Определение комбинаций движков для разных сценариев маршрутизации", - "createCombo": "Создать комбинацию", - "editCombo": "Редактировать", - "deleteCombo": "Удалить", + "title": "Compression Combos", + "description": "Define how engines are combined for different routing scenarios.", + "createCombo": "Create Compression Combo", + "editCombo": "Edit", + "deleteCombo": "Delete", "deleteConfirm": "Delete this compression combo?", "pipeline": "Pipeline", "addStep": "Add Step", @@ -4681,52 +4664,50 @@ "enabled": "Enabled" }, "contextCaveman": { - "title": "Caveman", - "description": "Правила сжатия сообщений, языковые пакеты, аналитика и мониторинг", - "advancedConfig": "Расширенная конфигурация", - "advancedConfigDesc": "Тонкая настройка сжатия", - "aggressiveSettings": "Агрессивные настройки", - "aggressiveSettingsDesc": "Настройки агрессивного сжатия", + "title": "Caveman Engine", + "description": "Rule-based message compression, language packs, analytics and output mode controls.", + "advancedConfig": "__MISSING__:Advanced Configuration", + "advancedConfigDesc": "__MISSING__:Fine-tune compression behavior", + "aggressiveSettings": "__MISSING__:Aggressive Settings", + "aggressiveSettingsDesc": "__MISSING__:Maximum compression with potential quality trade-offs", "requests": "Requests", "tokensSaved": "Tokens saved", "savingsPercent": "Savings %", "avgLatency": "Avg latency", "languagePacks": "Language Packs", "languagePacksDesc": "Enable compression rules for specific languages.", - "labelAutoTrigger": "Автосрабатывание", - "labelCompressionRate": "Коэффициент сжатия", - "labelMaxTokens": "Макс. токенов", - "labelMinLength": "Мин. длина", - "labelMinSavings": "Мин. экономия", - "enabled": "Включено", + "labelAutoTrigger": "__MISSING__:Auto-compress when context exceeds", + "labelCompressionRate": "__MISSING__:Compression strength", + "labelMaxTokens": "__MISSING__:Target tokens per message", + "labelMinLength": "__MISSING__:Minimum message length", + "labelMinSavings": "__MISSING__:Minimum tokens to save", + "enabled": "Enabled", "autoDetect": "Auto-detect language", "rulesCount": "{count} rules", "analyticsTitle": "Compression Analytics", "noAnalytics": "No compression analytics yet.", - "outputMode": "Режим вывода", + "outputMode": "__MISSING__:Output Mode", "outputModeDesc": "Instructs the LLM to respond in a terse, compact format.", "outputModeTitle": "Output Mode", - "quickSettings": "Быстрые настройки", - "quickSettingsDesc": "Быстрая настройка сжатия", + "quickSettings": "__MISSING__:Quick Settings", + "quickSettingsDesc": "__MISSING__:Basic compression settings to get started", "autoClarity": "Auto-Clarity Bypass", "bypassConditions": "Bypass conditions", - "bypassConditionsList": "security, irreversible, clarification, order-sensitive", - "simpleMode": "Простой режим", - "advancedMode": "Расширенный режим", - "tooltipAutoTrigger": "Автоматически применять сжатие", - "tooltipCompressionRate": "Целевой коэффициент сжатия", - "tooltipMaxTokens": "Максимальное количество токенов после сжатия", - "tooltipMinLength": "Минимальная длина сообщения для сжатия", - "tooltipMinSavings": "Минимальная экономия для применения сжатия", - "ultraSettings": "Ультра-настройки", - "ultraSettingsDesc": "Экстремальное сжатие", + "bypassConditionsList": "__MISSING__:security, irreversible, clarification, order-sensitive", + "simpleMode": "__MISSING__:Simple", + "advancedMode": "__MISSING__:Advanced", + "tooltipAutoTrigger": "__MISSING__:Automatically compress when context exceeds this size.", + "tooltipCompressionRate": "__MISSING__:0.0 = none, 1.0 = maximum. 0.5 = balanced.", + "tooltipMaxTokens": "__MISSING__:Target token count after compression. Lower = more aggressive.", + "tooltipMinLength": "__MISSING__:Messages shorter than this are not compressed. Lower = more compression.", + "tooltipMinSavings": "__MISSING__:Only compress if it saves at least this many tokens.", + "ultraSettings": "__MISSING__:Ultra Settings", + "ultraSettingsDesc": "__MISSING__:SLM-powered semantic compression", "preview": { "lite": "Respond concise. Preserve technical terms, code, errors, URLs, and identifiers.", "full": "Respond terse and compact. Preserve all technical substance.", "ultra": "Respond ultra terse with common technical abbreviations. Preserve exact symbols." - }, - "intensity": "Интенсивность", - "intensityMinimal": "Минимальная" + } }, "translator": { "title": "Переводчик", @@ -4769,28 +4750,28 @@ "errorShort": "ОШИБКА", "formatConverter": "Конвертер форматов", "formatConverterDescription": "Вставьте или введите тело запроса JSON. Переводчик автоматически определит исходный формат и преобразует его в целевой формат. Используйте это для отладки того, как OmniRoute преобразует запросы между форматами (OpenAI ↔ Claude ↔ Gemini ↔ API ответов).", - "translationPathHubSpoke": "{source} → OpenAI (intermediate) → {target}", - "translationPathDirect": "{source} → {target} (direct translator)", - "translationPathPassthrough": "Тот же формат — перевод не нужен", - "openaiIntermediatePanel": "Промежуточный OpenAI", - "autoFeaturesTitle": "Что OmniRoute делает автоматически", - "autoFeaturesCount": "8 features", - "featureReasoningCache": "Кэш рассуждений", - "featureReasoningCacheDesc": "Повторно использует кэшированный reasoning_content для моделей с режимом мышления", - "featureSchemaCoercion": "Приведение схем", - "featureSchemaCoercionDesc": "Автоматическое приведение JSON-схемы при трансляции между форматами", - "featureRoleNormalization": "Нормализация ролей", - "featureRoleNormalizationDesc": "Приведение названий ролей к стандарту целевого API", - "featureToolCallIds": "ID вызовов инструментов", - "featureToolCallIdsDesc": "Перезапись/сохранение ID вызовов инструментов между разными форматами", - "featureMissingToolResponse": "Пустые ответы инструментов", - "featureMissingToolResponseDesc": "Автозаполнение пустых ответов инструментов", - "featureThinkingBudget": "Бюджет мышления", - "featureThinkingBudgetDesc": "Настройка лимита токенов для reasoning/thinking", - "featureDirectPaths": "Прямые пути", - "featureDirectPathsDesc": "Проксирование запросов напрямую без трансляции, где это возможно", - "featureImageMapping": "Маппинг изображений", - "featureImageMappingDesc": "Преобразование форматов изображений между API (base64 ↔ URL)", + "translationPathHubSpoke": "__MISSING__:{source} → OpenAI (intermediate) → {target}", + "translationPathDirect": "__MISSING__:{source} → {target} (direct translator)", + "translationPathPassthrough": "__MISSING__:Same format — no translation needed", + "openaiIntermediatePanel": "__MISSING__:OpenAI Intermediate", + "autoFeaturesTitle": "__MISSING__:What OmniRoute does automatically", + "autoFeaturesCount": "__MISSING__:8 features", + "featureReasoningCache": "__MISSING__:Reasoning Cache", + "featureReasoningCacheDesc": "__MISSING__:Re-injects cached reasoning_content for thinking-mode models (DeepSeek V4, Kimi K2, Qwen) when clients omit it from conversation history.", + "featureSchemaCoercion": "__MISSING__:Schema Coercion", + "featureSchemaCoercionDesc": "__MISSING__:Fixes broken tool schemas: adds missing additionalProperties, sanitizes long descriptions, normalizes nested objects.", + "featureRoleNormalization": "__MISSING__:Role Normalization", + "featureRoleNormalizationDesc": "__MISSING__:Maps developer→system for non-OpenAI targets. Maps system→user for models that don't support system role.", + "featureToolCallIds": "__MISSING__:Tool Call ID Normalization", + "featureToolCallIdsDesc": "__MISSING__:Generates unique tool_call IDs when missing. Normalizes to 9-char format for providers like Mistral.", + "featureMissingToolResponse": "__MISSING__:Tool Response Injection", + "featureMissingToolResponseDesc": "__MISSING__:Injects empty tool_result messages when clients send tool_calls without corresponding responses.", + "featureThinkingBudget": "__MISSING__:Thinking Budget", + "featureThinkingBudgetDesc": "__MISSING__:Automatically manages thinking config. Removes thinking parameters when the last message is not from the user.", + "featureDirectPaths": "__MISSING__:Direct Translation Paths", + "featureDirectPathsDesc": "__MISSING__:Some format pairs (Claude→Gemini) have direct translators that bypass the OpenAI hub, producing more accurate output.", + "featureImageMapping": "__MISSING__:Image Size Mapping", + "featureImageMappingDesc": "__MISSING__:Translates image dimension conventions between API formats (e.g., OpenAI detail levels → Gemini dimensions).", "input": "Ввод", "output": "Выход", "auto": "Авто", @@ -4826,8 +4807,8 @@ "thinking": "мышление", "system-prompt": "Системная подсказка", "streaming": "Потоковое вещание", - "vision": "Vision", - "schema-coercion": "Schema Coercion" + "vision": "__MISSING__:Vision", + "schema-coercion": "__MISSING__:Schema Coercion" }, "templateDescriptions": { "simple-chat": "Основное текстовое сообщение", @@ -4836,8 +4817,8 @@ "thinking": "Расширенное мышление/рассуждение", "system-prompt": "Сложные системные инструкции", "streaming": "Запрос потоковой передачи SSE", - "vision": "Multimodal request with image input", - "schema-coercion": "Structured-output / JSON schema enforcement" + "vision": "__MISSING__:Multimodal request with image input", + "schema-coercion": "__MISSING__:Structured-output / JSON schema enforcement" }, "templatePayloads": { "simpleChat": { @@ -4866,14 +4847,14 @@ "prompt": "Расскажите мне короткую историю о роботе, который учится рисовать." }, "vision": { - "system": "You are an assistant that describes images precisely.", - "userPrompt": "What is shown in this image?", - "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/600px-PNG_transparency_demonstration_1.png" + "system": "__MISSING__:You are an assistant that describes images precisely.", + "userPrompt": "__MISSING__:What is shown in this image?", + "imageUrl": "__MISSING__:https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/600px-PNG_transparency_demonstration_1.png" }, "schemaCoercion": { - "userPrompt": "Look up the weather for Tokyo using metric units and include the hourly breakdown.", - "toolDescription": "Fetch weather for a city with structured options.", - "cityDescription": "The city to query, e.g. 'Tokyo' or 'New York'." + "userPrompt": "__MISSING__:Look up the weather for Tokyo using metric units and include the hourly breakdown.", + "toolDescription": "__MISSING__:Fetch weather for a city with structured options.", + "cityDescription": "__MISSING__:The city to query, e.g. 'Tokyo' or 'New York'." } }, "openaiCompatibleLabel": "Совместимость с OpenAI", @@ -4913,40 +4894,40 @@ "errorMessage": "Ошибка: {message}", "requestFailed": "Запрос не выполнен", "noTextExtracted": "(текст не извлечен)", - "liveMonitorMemoryNote": "Монитор показывает последние события в реальном времени. Данные хранятся в памяти.", - "liveMonitorMemoryCapNote": "Монитор буферизирует до {max} событий.", - "eventSourcesLabel": "Источники событий", - "eventSourceTranslatorPage": "Страница транслятора", - "eventSourceMainPipeline": "Основной конвейер", + "liveMonitorMemoryNote": "__MISSING__:Events are stored in memory and lost on restart.", + "liveMonitorMemoryCapNote": "__MISSING__:Max 200 events retained.", + "eventSourcesLabel": "__MISSING__:Event sources:", + "eventSourceTranslatorPage": "__MISSING__:• Translator page (Chat Tester, Test Bench)", + "eventSourceMainPipeline": "__MISSING__:• Main request pipeline (CLI/IDE/API traffic)", "liveMonitorDescriptionPrefix": "Показывает события перевода, когда вызовы API проходят через OmniRoute. События поступают из буфера в памяти (сбрасывается при перезапуске). Использование", "liveMonitorDescriptionSuffix": "или внешние вызовы API для генерации событий.", - "streamTransformer": "Трансформер потока", - "modeDescriptionStreamTransformer": "Преобразование потоковых SSE-событий Chat Completions в Responses API", - "streamTransformerTitle": "Трансформер потока", - "streamTransformerDescription": "Преобразование SSE-событий Chat Completions в Responses API", - "loadTextSample": "Загрузить текстовый пример", - "loadToolSample": "Загрузить пример с инструментами", - "transformToResponses": "Преобразовать в Responses", - "rawChatSseInput": "Ввод Chat SSE", - "transformedResponsesSse": "Вывод Responses SSE", - "noResultsYet": "Результатов пока нет", - "transformedEvents": "Преобразованные события", - "uniqueEventTypes": "Уникальные типы событий", - "inputLines": "Строк ввода", - "outputLines": "Строк вывода", - "transformedEventTimeline": "Временная шкала событий", - "transformerTimelineHint": "Зелёные — Chat события, синие — Responses события", - "eventType": "Тип события", - "eventPreview": "Предпросмотр события", - "comboRouted": "Маршрутизировано через комбо", - "uniqueEndpoints": "Уникальные эндпоинты", - "routeDetails": "Детали маршрутизации", - "comboBadge": "Комбо", - "routeEndpointLabel": "Эндпоинт", - "routeConnectionLabel": "Подключение", - "scenarioVision": "Vision (image understanding)", - "scenarioSchemaCoercion": "Schema coercion (structured output)", - "techniques": "Техники" + "streamTransformer": "__MISSING__:Stream Transformer", + "modeDescriptionStreamTransformer": "__MISSING__:Run chat completions SSE streams through the Responses transformer.", + "streamTransformerTitle": "__MISSING__:Responses Stream Transformer", + "streamTransformerDescription": "__MISSING__:Paste a chat completions SSE stream, run it through OmniRoute's Responses transformer, and inspect the emitted response.* events before wiring a client.", + "loadTextSample": "__MISSING__:Load text sample", + "loadToolSample": "__MISSING__:Load tool-call sample", + "transformToResponses": "__MISSING__:Transform to Responses", + "rawChatSseInput": "__MISSING__:Raw chat completions SSE", + "transformedResponsesSse": "__MISSING__:Transformed Responses API SSE", + "noResultsYet": "__MISSING__:No results yet", + "transformedEvents": "__MISSING__:Transformed events", + "uniqueEventTypes": "__MISSING__:Unique event types", + "inputLines": "__MISSING__:Input lines", + "outputLines": "__MISSING__:Output lines", + "transformedEventTimeline": "__MISSING__:Transformed event timeline", + "transformerTimelineHint": "__MISSING__:Run the transformer to inspect emitted response.output_* events in order.", + "eventType": "__MISSING__:Event type", + "eventPreview": "__MISSING__:Preview", + "comboRouted": "__MISSING__:Combo routed", + "uniqueEndpoints": "__MISSING__:Unique endpoints", + "routeDetails": "__MISSING__:Route details", + "comboBadge": "__MISSING__:Combo", + "routeEndpointLabel": "__MISSING__:Endpoint", + "routeConnectionLabel": "__MISSING__:Connection", + "scenarioVision": "__MISSING__:Vision (image understanding)", + "scenarioSchemaCoercion": "__MISSING__:Schema coercion (structured output)", + "techniques": "__MISSING__:Techniques:" }, "usage": { "title": "Использование", @@ -5020,25 +5001,25 @@ "passSuffix": "успеха", "casesCount": "{count, plural, one {# случай} few {# случая} many {# случаев} other {# случая}}", "runEval": "Запустить оценку", - "runAllSuites": "Run All", - "runAllRunning": "Running all...", - "runAllProgress": "Running {current}/{total}: {name}", - "runAllFailedSuites": "{count, plural, one {# suite failed} other {# suites failed}}", - "runAllCompleted": "Ran {suites} suites — {passed} passed, {failed} failed", - "runAllCompletedWithFailures": "Ran {completed} suites; {failedSuites} failed to complete", + "runAllSuites": "__MISSING__:Run All", + "runAllRunning": "__MISSING__:Running all...", + "runAllProgress": "__MISSING__:Running {current}/{total}: {name}", + "runAllFailedSuites": "__MISSING__:{count, plural, one {# suite failed} other {# suites failed}}", + "runAllCompleted": "__MISSING__:Ran {suites} suites — {passed} passed, {failed} failed", + "runAllCompletedWithFailures": "__MISSING__:Ran {completed} suites; {failedSuites} failed to complete", "runningProgress": "Запуск {current}/{total}...", "passRate": "проходной балл", "summaryBreakdown": "{passed} пройден · {failed} не пройден · всего {total}", "passedIconLabel": "✅ Пройдено", "failedIconLabel": "❌ Не удалось", - "resultPassed": "Пройдено", - "resultFailed": "Не пройдено", - "expandResult": "Развернуть", - "collapseResult": "Свернуть", + "resultPassed": "__MISSING__:Passed", + "resultFailed": "__MISSING__:Failed", + "expandResult": "__MISSING__:Expand result details", + "collapseResult": "__MISSING__:Collapse result details", "detailsContains": "Содержит: «{term}»", "detailsRegex": "Регулярное выражение: {pattern}", "detailsExpected": "Ожидается: \"{expected}\"", - "expectedOutputLabel": "Ожидаемый вывод", + "expectedOutputLabel": "__MISSING__:Expected Output", "noResultsYet": "Пока нет результатов", "testCasesCount": "Тестовые примеры ({count})", "noTestCasesDefined": "Тестовые примеры не определены", @@ -5106,16 +5087,16 @@ "tierFree": "Бесплатно", "tierUnknown": "Неизвестно", "suiteBuilderSaveFailed": "Failed to save suite", - "clone": "Клонировать", - "exportSuite": "Экспорт набора", - "importSuite": "Импорт набора", - "suiteExported": "Suite exported", - "suiteExportFailed": "Failed to export suite", - "suiteImportReady": "Suite import loaded for review", - "suiteImportFailed": "Failed to import suite", - "suiteImportInvalid": "Invalid eval suite JSON", - "suiteBuilderCloneSuffix": "copy", - "suiteBuilderImportedSuite": "Imported Suite", + "clone": "__MISSING__:Clone", + "exportSuite": "__MISSING__:Export", + "importSuite": "__MISSING__:Import", + "suiteExported": "__MISSING__:Suite exported", + "suiteExportFailed": "__MISSING__:Failed to export suite", + "suiteImportReady": "__MISSING__:Suite import loaded for review", + "suiteImportFailed": "__MISSING__:Failed to import suite", + "suiteImportInvalid": "__MISSING__:Invalid eval suite JSON", + "suiteBuilderCloneSuffix": "__MISSING__:copy", + "suiteBuilderImportedSuite": "__MISSING__:Imported Suite", "scorecardTitle": "Scorecard", "evalApiKey": "API Key", "scorecardPassRate": "Pass Rate", @@ -5158,10 +5139,10 @@ "recentRunsTitle": "Recent Runs", "suiteBuilderCaseSystemPromptLabel": "System Prompt", "suiteBuilderCaseExpectedPlaceholder": "e.g. def fibonacci", - "suiteBuilderCaseExpectedPlaceholderContains": "e.g. def fibonacci", - "suiteBuilderCaseExpectedPlaceholderExact": "Paste the exact expected response", - "suiteBuilderCaseExpectedPlaceholderRegex": "e.g. ^\\\\s*\\\\{.*\\\\}\\\\s*$", - "suiteBuilderCaseExpectedHintRegex": "Use a JavaScript regular expression without wrapping slashes.", + "suiteBuilderCaseExpectedPlaceholderContains": "__MISSING__:e.g. def fibonacci", + "suiteBuilderCaseExpectedPlaceholderExact": "__MISSING__:Paste the exact expected response", + "suiteBuilderCaseExpectedPlaceholderRegex": "__MISSING__:e.g. ^\\\\s*\\\\{.*\\\\}\\\\s*$", + "suiteBuilderCaseExpectedHintRegex": "__MISSING__:Use a JavaScript regular expression without wrapping slashes.", "suiteBuilderNamePlaceholder": "e.g. Coding Quality Suite", "suiteBuilderAddCase": "Add Case", "cancel": "Cancel", @@ -5175,7 +5156,7 @@ "resultErrorLabel": "Error", "suiteBuilderBuiltInBadge": "Built-in", "suiteBuilderCaseCardTitle": "Test Case", - "suiteBuilderDuplicateCase": "Duplicate", + "suiteBuilderDuplicateCase": "__MISSING__:Duplicate", "weeklyLimitPlaceholder": "e.g. 50.00", "suiteBuilderCaseTagsHint": "Comma-separated tags for organizing test cases.", "notifyEvalRunFailedWithReason": "Evaluation failed: {reason}", @@ -5213,44 +5194,44 @@ "targetComparisonHint": "Side-by-side comparison of evaluation results between targets.", "compareCompletedWithScore": "Comparison completed — {score}% pass rate", "staleQuotaTooltip": "Last refresh failed — showing cached data", - "quotaThresholdLabel": "Порог квоты", - "quotaCutoffsColumnHelp": "Stop requests when remaining quota falls to this percentage or below.", - "quotaCutoffsButtonDefault": "Default", - "quotaCutoffsButtonHelp": "Edit minimum remaining quota cutoffs for this account.", - "quotaCutoffsButtonDisabled": "No quota windows are available for this account yet.", - "quotaCutoffsTitle": "Quota cutoffs for {name} ({provider})", - "quotaCutoffsExplainer": "Override the minimum remaining quota percentage where this account stops being selected for each quota window. Leave blank to inherit the provider default.", - "quotaCutoffsDefaultHint": "Default min remaining: {default}%", - "quotaCutoffsResetAll": "Reset all", - "quotaCutoffsNoWindows": "No quota windows are available for this account yet.", - "quotaThresholdInvalid": "Enter a whole number from 0 to 100.", - "budgetKpiToday": "Сегодня", - "budgetKpiThisMonth": "Этот месяц", - "budgetKpiProjEom": "Прогноз на конец месяца", - "budgetKpiBlocked": "Заблокировано", - "budgetKpiAtRisk": "Под риском", - "budgetKpiActiveKeys": "Активных ключей", - "budgetSearchKeysPlaceholder": "Поиск ключей...", - "budgetSortPctUsed": "% использования", - "budgetSortTodayDollar": "Сегодня ($)", - "budgetSortMonthDollar": "Месяц ($)", - "budgetSortNameAZ": "Имя А-Я", - "budgetColDailyLim": "Дневной лимит", - "budgetColMonthlyLim": "Месячный лимит", - "budgetColUsedPct": "Использовано", - "budgetLoading": "Загрузка...", - "budgetNoKeysMatch": "Нет ключей по запросу", - "budgetLinearExtrapolation": "Линейная экстраполяция", - "budgetThisMonthSoFar": "За текущий месяц", - "budgetProjectedEndOfMonth": "Прогноз на конец месяца", - "budgetByProvider": "По провайдерам", - "budgetDailyDollar": "В день ($)", - "budgetWeeklyDollar": "В неделю ($)", - "budgetMonthlyDollar": "В месяц ($)", - "budgetWarnAtPct": "Предупредить при %", - "quotaAlerts": "Оповещения о квотах", - "quotaTableRefreshing": "Обновление таблицы квот...", - "noSpendLast30Days": "Нет расходов за последние 30 дней" + "quotaThresholdLabel": "__MISSING__:Min left", + "quotaCutoffsColumnHelp": "__MISSING__:Stop requests when remaining quota falls to this percentage or below.", + "quotaCutoffsButtonDefault": "__MISSING__:Default", + "quotaCutoffsButtonHelp": "__MISSING__:Edit minimum remaining quota cutoffs for this account.", + "quotaCutoffsButtonDisabled": "__MISSING__:No quota windows are available for this account yet.", + "quotaCutoffsTitle": "__MISSING__:Quota cutoffs for {name} ({provider})", + "quotaCutoffsExplainer": "__MISSING__:Override the minimum remaining quota percentage where this account stops being selected for each quota window. Leave blank to inherit the provider default.", + "quotaCutoffsDefaultHint": "__MISSING__:Default min remaining: {default}%", + "quotaCutoffsResetAll": "__MISSING__:Reset all", + "quotaCutoffsNoWindows": "__MISSING__:No quota windows are available for this account yet.", + "quotaThresholdInvalid": "__MISSING__:Enter a whole number from 0 to 100.", + "budgetKpiToday": "__MISSING__:Today", + "budgetKpiThisMonth": "__MISSING__:This month", + "budgetKpiProjEom": "__MISSING__:Proj EOM", + "budgetKpiBlocked": "__MISSING__:Blocked", + "budgetKpiAtRisk": "__MISSING__:At risk", + "budgetKpiActiveKeys": "__MISSING__:Active keys", + "budgetSearchKeysPlaceholder": "__MISSING__:Search keys...", + "budgetSortPctUsed": "__MISSING__:Sort: % Used ↓", + "budgetSortTodayDollar": "__MISSING__:Sort: Today $ ↓", + "budgetSortMonthDollar": "__MISSING__:Sort: Month $ ↓", + "budgetSortNameAZ": "__MISSING__:Sort: Name (A–Z)", + "budgetColDailyLim": "__MISSING__:Daily lim", + "budgetColMonthlyLim": "__MISSING__:Monthly lim", + "budgetColUsedPct": "__MISSING__:Used %", + "budgetLoading": "__MISSING__:Loading…", + "budgetNoKeysMatch": "__MISSING__:No keys match filters", + "budgetLinearExtrapolation": "__MISSING__:linear extrapolation", + "budgetThisMonthSoFar": "__MISSING__:This month so far", + "budgetProjectedEndOfMonth": "__MISSING__:Projected end of month", + "budgetByProvider": "__MISSING__:by provider", + "budgetDailyDollar": "__MISSING__:Daily $", + "budgetWeeklyDollar": "__MISSING__:Weekly $", + "budgetMonthlyDollar": "__MISSING__:Monthly $", + "budgetWarnAtPct": "__MISSING__:Warn at %", + "quotaAlerts": "__MISSING__:Quota alerts", + "quotaTableRefreshing": "__MISSING__:⟳ Refreshing...", + "noSpendLast30Days": "__MISSING__:No spend in last 30 days" }, "modals": { "waitingAuth": "Ожидание авторизации", @@ -5468,7 +5449,7 @@ "docs": { "title": "Документация", "quickStart": "Быстрый старт", - "deploymentGuides": "Руководства по развёртыванию", + "deploymentGuides": "__MISSING__:Deployment Guides", "features": "Особенности", "supportedProviders": "Поддерживаемые провайдеры", "supportedProvidersToc": "Провайдеры", @@ -5505,18 +5486,18 @@ "quickStartStep4Title": "4. Установите URL-адрес клиентской базы.", "quickStartStep4Prefix": "Направьте свой клиент IDE или API на", "quickStartStep4Suffix": "Используйте префикс провайдера, например", - "deploySetupTitle": "Setup Guide", - "deploySetupText": "Step-by-step installation, environment configuration, and first-run walkthrough for OmniRoute.", - "deployElectronTitle": "Electron Desktop", - "deployElectronText": "Run OmniRoute as a native desktop application on Windows, macOS, and Linux.", - "deployDockerTitle": "Docker", - "deployDockerText": "Containerized deployment with Docker Compose; ready for production stacks and Kubernetes.", - "deployVmTitle": "Virtual Machine", - "deployVmText": "Self-host on any Linux VM. Includes systemd unit, log rotation, and backup tooling.", - "deployFlyTitle": "Fly.io", - "deployFlyText": "Deploy to Fly.io edge runtime with one fly.toml and a single deploy command.", - "deployPwaTitle": "PWA", - "deployPwaText": "Install OmniRoute as a Progressive Web App on Android, iOS, and desktop browsers.", + "deploySetupTitle": "__MISSING__:Setup Guide", + "deploySetupText": "__MISSING__:Step-by-step installation, environment configuration, and first-run walkthrough for OmniRoute.", + "deployElectronTitle": "__MISSING__:Electron Desktop", + "deployElectronText": "__MISSING__:Run OmniRoute as a native desktop application on Windows, macOS, and Linux.", + "deployDockerTitle": "__MISSING__:Docker", + "deployDockerText": "__MISSING__:Containerized deployment with Docker Compose; ready for production stacks and Kubernetes.", + "deployVmTitle": "__MISSING__:Virtual Machine", + "deployVmText": "__MISSING__:Self-host on any Linux VM. Includes systemd unit, log rotation, and backup tooling.", + "deployFlyTitle": "__MISSING__:Fly.io", + "deployFlyText": "__MISSING__:Deploy to Fly.io edge runtime with one fly.toml and a single deploy command.", + "deployPwaTitle": "__MISSING__:PWA", + "deployPwaText": "__MISSING__:Install OmniRoute as a Progressive Web App on Android, iOS, and desktop browsers.", "deployTermuxTitle": "Termux (Android)", "deployTermuxText": "Run OmniRoute headless on Android via Termux. Access the dashboard from your mobile browser.", "featureRoutingTitle": "Маршрутизация между несколькими провайдерами", @@ -5563,18 +5544,18 @@ "clientClaudeBullet1Prefix": "Использование", "clientClaudeBullet1Middle": "(Клод) или", "clientClaudeBullet1Suffix": "(Антигравитация) приставка.", - "clientWindsurfTitle": "Windsurf", - "clientWindsurfBullet1": "Use OmniRoute as an OpenAI-compatible base URL and keep explicit provider prefixes for deterministic routing.", - "clientWindsurfBullet2": "Point models to `/v1/chat/completions` for general traffic and preserve `/v1/responses` for Codex-style flows.", - "clientWindsurfBullet3": "Use Dashboard -> CLI Tools for a ready-made Windsurf configuration guide.", - "clientClineTitle": "Cline", - "clientClineBullet1": "Cline works best with explicit provider/model prefixes so the router never has to guess the backend.", - "clientClineBullet2": "Use `/v1/chat/completions` for general models and reuse the same OmniRoute base URL across different accounts.", - "clientClineBullet3": "Use the Providers dashboard to validate OAuth/API key before debugging Cline runtime issues.", - "clientKimiTitle": "Kimi", - "clientKimiBullet1": "Use OmniRoute as a stable base URL while rotating accounts or provider combos underneath.", - "clientKimiBullet2": "Prefer prefixed models in coding flows so fallback and audit trail remain explicit.", - "clientKimiBullet3": "Use `/v1/responses` when you want native Responses-style routing for tool-using clients.", + "clientWindsurfTitle": "__MISSING__:Windsurf", + "clientWindsurfBullet1": "__MISSING__:Use OmniRoute as an OpenAI-compatible base URL and keep explicit provider prefixes for deterministic routing.", + "clientWindsurfBullet2": "__MISSING__:Point models to `/v1/chat/completions` for general traffic and preserve `/v1/responses` for Codex-style flows.", + "clientWindsurfBullet3": "__MISSING__:Use Dashboard -> CLI Tools for a ready-made Windsurf configuration guide.", + "clientClineTitle": "__MISSING__:Cline", + "clientClineBullet1": "__MISSING__:Cline works best with explicit provider/model prefixes so the router never has to guess the backend.", + "clientClineBullet2": "__MISSING__:Use `/v1/chat/completions` for general models and reuse the same OmniRoute base URL across different accounts.", + "clientClineBullet3": "__MISSING__:Use the Providers dashboard to validate OAuth/API key before debugging Cline runtime issues.", + "clientKimiTitle": "__MISSING__:Kimi Coding", + "clientKimiBullet1": "__MISSING__:Use OmniRoute as a stable base URL while rotating accounts or provider combos underneath.", + "clientKimiBullet2": "__MISSING__:Prefer prefixed models in coding flows so fallback and audit trail remain explicit.", + "clientKimiBullet3": "__MISSING__:Use `/v1/responses` when you want native Responses-style routing for tool-using clients.", "protocolsTitle": "Протоколы: MCP и A2A", "protocolsDescription": "OmniRoute предоставляет два операционных протокола помимо OpenAI-совместимых API: MCP для выполнения инструментов и A2A для рабочих процессов агент-к-агенту.", "protocolMcpTitle": "MCP (Model Context Protocol)", @@ -5618,61 +5599,61 @@ "troubleshootingTestConnection": "Используйте «Панель мониторинга» > «Поставщики» > «Проверить соединение» перед тестированием из IDE или внешних клиентов.", "troubleshootingCircuitBreaker": "Если поставщик услуг показывает, что автоматический выключатель разомкнут, дождитесь охлаждения или посетите страницу «Здоровье» для получения подробной информации.", "troubleshootingOAuth": "Для поставщиков OAuth выполните повторную аутентификацию, если срок действия токенов истечет. Проверьте индикатор состояния карты провайдера.", - "endpointCompletionsNote": "Эндпоинт Chat Completions", - "endpointModerationsNote": "Content moderation and safety classification.", - "endpointRerankNote": "Document reranking for retrieval-augmented generation (Cohere, Jina).", - "endpointSearchNote": "Web search with 5 providers (Serper, Brave, Exa, Tavily, Perplexity).", - "endpointSearchAnalyticsNote": "Analytics and metrics for search requests.", - "endpointVideoNote": "Video generation (ComfyUI, SD WebUI workflows).", - "endpointMusicNote": "Music generation via ComfyUI workflows.", - "endpointMessagesNote": "Anthropic-native messages endpoint.", - "endpointCountTokensNote": "Count tokens for a given message payload.", - "endpointFilesNote": "File upload for multimodal inputs.", - "endpointBatchesNote": "Batch processing for bulk API requests.", - "endpointWsNote": "WebSocket endpoint for real-time streaming.", - "mgmtProvidersListNote": "List all registered provider connections.", - "mgmtProvidersCreateNote": "Create a new provider connection.", - "mgmtProvidersUpdateNote": "Update an existing provider connection.", - "mgmtProvidersDeleteNote": "Delete a provider connection.", - "mgmtProvidersTestNote": "Test connectivity and authentication for a provider.", - "mgmtProvidersModelsNote": "List available models for a specific provider.", - "mgmtSettingsGetNote": "Retrieve current application settings.", - "mgmtSettingsUpdateNote": "Update application settings.", - "mgmtPayloadRulesGetNote": "Get payload transformation rules.", - "mgmtPayloadRulesUpdateNote": "Update payload transformation rules.", - "mcpToolsTitle": "MCP-инструменты", - "mcpToolsDescription": "OmniRoute предоставляет 37+ MCP-инструментов", - "mcpToolsCount": "{count} инструментов", - "mcpToolsToc": "Содержание", - "mcpToolsRoutingTitle": "Маршрутизация", - "mcpToolsRoutingDesc": "Health checks, combo management, quota monitoring, cost reporting, and model catalog access.", - "mcpToolsOperationsTitle": "Operations & Strategy", - "mcpToolsOperationsDesc": "Route simulation, budget guards, strategy switching, resilience profiles, and provider metrics.", - "mcpToolsCacheTitle": "Кэш", - "mcpToolsCacheDesc": "View cache statistics and flush semantic or signature caches.", - "mcpToolsCompressionTitle": "Сжатие", - "mcpToolsCompressionDesc": "Configure RTK/Brotli compression, swap engines, and inspect compression analytics by combo.", - "mcpToolsOneProxyTitle": "1Proxy / Tunnels", - "mcpToolsOneProxyDesc": "Manage outbound proxies, rotate residential IPs, and inspect proxy health.", - "mcpToolsMemoryTitle": "Память", - "mcpToolsMemoryDesc": "Search, add, and clear persistent conversational memory entries.", - "mcpToolsSkillsTitle": "Навыки", - "mcpToolsSkillsDesc": "List, enable, execute, and monitor custom skill executions.", - "featureAutoComboTitle": "Автоматические комбо", - "featureAutoComboText": "Automatically create optimized combos based on your connected providers, usage patterns, and model capabilities.", - "featureSearchTitle": "Поиск", - "featureSearchText": "Integrated web search with 5 providers (Serper, Brave, Exa, Tavily, Perplexity) including analytics and cost tracking.", - "featureMemoryTitle": "Память", - "featureMemoryText": "Persistent conversational memory with extraction, injection, retrieval, and summarization across sessions.", - "featureSkillsTitle": "Навыки", - "featureSkillsText": "Extensible skill system with built-in and custom skills, sandbox execution, request interception, and context injection.", - "featureAcpTitle": "ACP-протокол", - "featureAcpText": "Agent Communication Protocol (ACP) registry for managing agent-to-agent workflows and tool orchestration.", - "protocolAcpTitle": "ACP", - "protocolAcpDesc": "Agent Communication Protocol", - "protocolAcpStep1": "Navigate to Dashboard → Agents to view registered ACP agents.", - "protocolAcpStep2": "Register new agents with capabilities and endpoint configuration.", - "protocolAcpStep3": "Use CLI Tools to configure agent communication channels." + "endpointCompletionsNote": "__MISSING__:Legacy completions endpoint for text generation.", + "endpointModerationsNote": "__MISSING__:Content moderation and safety classification.", + "endpointRerankNote": "__MISSING__:Document reranking for retrieval-augmented generation (Cohere, Jina).", + "endpointSearchNote": "__MISSING__:Web search with 5 providers (Serper, Brave, Exa, Tavily, Perplexity).", + "endpointSearchAnalyticsNote": "__MISSING__:Analytics and metrics for search requests.", + "endpointVideoNote": "__MISSING__:Video generation (ComfyUI, SD WebUI workflows).", + "endpointMusicNote": "__MISSING__:Music generation via ComfyUI workflows.", + "endpointMessagesNote": "__MISSING__:Anthropic-native messages endpoint.", + "endpointCountTokensNote": "__MISSING__:Count tokens for a given message payload.", + "endpointFilesNote": "__MISSING__:File upload for multimodal inputs.", + "endpointBatchesNote": "__MISSING__:Batch processing for bulk API requests.", + "endpointWsNote": "__MISSING__:WebSocket endpoint for real-time streaming.", + "mgmtProvidersListNote": "__MISSING__:List all registered provider connections.", + "mgmtProvidersCreateNote": "__MISSING__:Create a new provider connection.", + "mgmtProvidersUpdateNote": "__MISSING__:Update an existing provider connection.", + "mgmtProvidersDeleteNote": "__MISSING__:Delete a provider connection.", + "mgmtProvidersTestNote": "__MISSING__:Test connectivity and authentication for a provider.", + "mgmtProvidersModelsNote": "__MISSING__:List available models for a specific provider.", + "mgmtSettingsGetNote": "__MISSING__:Retrieve current application settings.", + "mgmtSettingsUpdateNote": "__MISSING__:Update application settings.", + "mgmtPayloadRulesGetNote": "__MISSING__:Get payload transformation rules.", + "mgmtPayloadRulesUpdateNote": "__MISSING__:Update payload transformation rules.", + "mcpToolsTitle": "__MISSING__:MCP Tools", + "mcpToolsDescription": "__MISSING__:OmniRoute exposes {count} tools via Model Context Protocol for agent orchestration.", + "mcpToolsCount": "__MISSING__:{count} tools", + "mcpToolsToc": "__MISSING__:MCP Tools", + "mcpToolsRoutingTitle": "__MISSING__:Routing & Discovery", + "mcpToolsRoutingDesc": "__MISSING__:Health checks, combo management, quota monitoring, cost reporting, and model catalog access.", + "mcpToolsOperationsTitle": "__MISSING__:Operations & Strategy", + "mcpToolsOperationsDesc": "__MISSING__:Route simulation, budget guards, strategy switching, resilience profiles, and provider metrics.", + "mcpToolsCacheTitle": "__MISSING__:Cache Management", + "mcpToolsCacheDesc": "__MISSING__:View cache statistics and flush semantic or signature caches.", + "mcpToolsCompressionTitle": "__MISSING__:Compression Engines", + "mcpToolsCompressionDesc": "__MISSING__:Configure RTK/Brotli compression, swap engines, and inspect compression analytics by combo.", + "mcpToolsOneProxyTitle": "__MISSING__:1Proxy / Tunnels", + "mcpToolsOneProxyDesc": "__MISSING__:Manage outbound proxies, rotate residential IPs, and inspect proxy health.", + "mcpToolsMemoryTitle": "__MISSING__:Memory", + "mcpToolsMemoryDesc": "__MISSING__:Search, add, and clear persistent conversational memory entries.", + "mcpToolsSkillsTitle": "__MISSING__:Skills", + "mcpToolsSkillsDesc": "__MISSING__:List, enable, execute, and monitor custom skill executions.", + "featureAutoComboTitle": "__MISSING__:Auto-Combo", + "featureAutoComboText": "__MISSING__:Automatically create optimized combos based on your connected providers, usage patterns, and model capabilities.", + "featureSearchTitle": "__MISSING__:Web Search", + "featureSearchText": "__MISSING__:Integrated web search with 5 providers (Serper, Brave, Exa, Tavily, Perplexity) including analytics and cost tracking.", + "featureMemoryTitle": "__MISSING__:Memory System", + "featureMemoryText": "__MISSING__:Persistent conversational memory with extraction, injection, retrieval, and summarization across sessions.", + "featureSkillsTitle": "__MISSING__:Skills Framework", + "featureSkillsText": "__MISSING__:Extensible skill system with built-in and custom skills, sandbox execution, request interception, and context injection.", + "featureAcpTitle": "__MISSING__:Agent Communication", + "featureAcpText": "__MISSING__:Agent Communication Protocol (ACP) registry for managing agent-to-agent workflows and tool orchestration.", + "protocolAcpTitle": "__MISSING__:ACP (Agent Communication)", + "protocolAcpDesc": "__MISSING__:Register and manage agents via the ACP registry for inter-agent communication and tool sharing.", + "protocolAcpStep1": "__MISSING__:Navigate to Dashboard → Agents to view registered ACP agents.", + "protocolAcpStep2": "__MISSING__:Register new agents with capabilities and endpoint configuration.", + "protocolAcpStep3": "__MISSING__:Use CLI Tools to configure agent communication channels." }, "legal": { "privacyPolicy": "Политика конфиденциальности", @@ -5777,75 +5758,75 @@ "flowExecute": "Flow Execute", "flowSpawn": "Flow Spawn", "cliToolsRedirectCta": "Cli Tools Redirect Cta", - "comparisonTitle": "Сравнение", - "comparisonCliToolsLabel": "CLI Tools page", - "comparisonCliToolsTitle": "Your IDE sends requests through OmniRoute", - "comparisonCliToolsDesc": "Configure Claude Code, Codex, Cursor, and other IDEs to use OmniRoute as their API base URL. OmniRoute acts as a proxy, routing requests to your configured providers.", - "comparisonAgentsLabel": "This page (Agent Targets)", - "comparisonAgentsTitle": "OmniRoute sends requests into local CLI tools", - "comparisonAgentsDesc": "OmniRoute can spawn local CLI binaries (claude, codex, goose) as execution backends. The CLI tool processes the request using its own authentication and returns the result.", - "comparisonSummary": "In short: CLI Tools = you configure tools to point at OmniRoute. Agent Targets = OmniRoute uses tools as its endpoints.", - "agentUseCaseHint": "Пример использования агента", - "flowDiagramClient": "Клиент", - "flowDiagramClientDesc": "IDE или терминал, где работает агент", - "flowDiagramOmniRoute": "OmniRoute", - "flowDiagramOmniRouteDesc": "Маршрутизация, прокси, балансировка, квоты", - "flowDiagramSpawn": "Запуск", - "flowDiagramSpawnDesc": "CLI или API-запрос к агенту", - "flowDiagramCli": "CLI", - "flowDiagramCliDesc": "Локальный AI-агент в терминале", - "fingerprintSettingsHint": "Настройки отпечатка", - "settingsRoutingLink": "Маршрутизация", - "openSettings": "Настройки", - "copyRawUrlTitle": "Скопировать URL", - "copied": "Скопировано!", - "copyUrl": "Копировать URL", - "startHere": "Начать здесь", - "badgeNew": "Новое", - "viewOnGithub": "Смотреть на GitHub", - "howToUse": "Как использовать", - "browseAllSkillsOnGithub": "Все навыки на GitHub", - "apiSkills": "API-навыки", - "cliSkills": "CLI-навыки" + "comparisonTitle": "__MISSING__:CLI Tools vs Agent Targets — What's the Difference?", + "comparisonCliToolsLabel": "__MISSING__:CLI Tools page", + "comparisonCliToolsTitle": "__MISSING__:Your IDE sends requests through OmniRoute", + "comparisonCliToolsDesc": "__MISSING__:Configure Claude Code, Codex, Cursor, and other IDEs to use OmniRoute as their API base URL. OmniRoute acts as a proxy, routing requests to your configured providers.", + "comparisonAgentsLabel": "__MISSING__:This page (Agent Targets)", + "comparisonAgentsTitle": "__MISSING__:OmniRoute sends requests into local CLI tools", + "comparisonAgentsDesc": "__MISSING__:OmniRoute can spawn local CLI binaries (claude, codex, goose) as execution backends. The CLI tool processes the request using its own authentication and returns the result.", + "comparisonSummary": "__MISSING__:In short: CLI Tools = you configure tools to point at OmniRoute. Agent Targets = OmniRoute uses tools as its endpoints.", + "agentUseCaseHint": "__MISSING__:Can be used as an execution target via ACP protocol", + "flowDiagramClient": "__MISSING__:Client App", + "flowDiagramClientDesc": "__MISSING__:SDK, API, or upstream service", + "flowDiagramOmniRoute": "__MISSING__:OmniRoute", + "flowDiagramOmniRouteDesc": "__MISSING__:Receives request and selects target", + "flowDiagramSpawn": "__MISSING__:Spawn Process", + "flowDiagramSpawnDesc": "__MISSING__:Launches CLI binary via stdio", + "flowDiagramCli": "__MISSING__:CLI Agent", + "flowDiagramCliDesc": "__MISSING__:Processes with own auth/model", + "fingerprintSettingsHint": "__MISSING__:CLI Fingerprint matching (disguise requests as specific CLI tools) can be configured in", + "settingsRoutingLink": "__MISSING__:Settings/Routing", + "openSettings": "__MISSING__:Settings", + "copyRawUrlTitle": "__MISSING__:Copy raw URL to clipboard", + "copied": "__MISSING__:Copied!", + "copyUrl": "__MISSING__:Copy URL", + "startHere": "__MISSING__:Start Here", + "badgeNew": "__MISSING__:New", + "viewOnGithub": "__MISSING__:View on GitHub", + "howToUse": "__MISSING__:How to use", + "browseAllSkillsOnGithub": "__MISSING__:Browse all skills on GitHub", + "apiSkills": "__MISSING__:API Skills", + "cliSkills": "__MISSING__:CLI Skills" }, "cloudAgents": { - "title": "Облачные агенты", - "description": "Управление автономными агентами кодирования (Jules, Devin, Codex Cloud)", - "loading": "Загрузка задач...", - "aboutTitle": "Об облачных агентах", - "aboutDescription": "Облачные агенты — это удалённые AI-помощники, которые могут выполнять задачи автономно.", - "howItWorksTitle": "Как это работает:", - "howItWorksDesc": "Создайте задачу → Агент анализирует и предлагает план → Вы утверждаете → Агент выполняет → Возвращает результат", - "newTaskTitle": "Новая задача", - "newTaskDescription": "Запустите новую задачу в облачном агенте", - "selectAgent": "Выберите агента", - "taskDescription": "Описание задачи", - "taskDescriptionPlaceholder": "Опишите, что должен сделать агент...", - "startTask": "Запустить", - "tasks": "Задачи", - "taskDetail": "Детали задачи", - "noTasks": "Задач пока нет.", - "untitledTask": "Задача без названия", - "created": "Создано", - "conversation": "Диалог", - "result": "Результат", - "error": "Ошибка", - "planReady": "План готов к утверждению", - "approvePlan": "Утвердить план", - "rejectPlan": "Отклонить и отменить", - "sendMessagePlaceholder": "Отправить сообщение агенту...", - "cancel": "Отмена", - "delete": "Удалить", - "selectTaskPrompt": "Выберите задачу для просмотра деталей", - "statusPending": "Ожидание", - "statusRunning": "Выполняется", - "statusWaitingApproval": "Ожидает утверждения", - "statusCompleted": "Завершено", - "statusFailed": "Ошибка", - "statusCancelled": "Отменено", - "repositoryName": "Название репозитория", - "repositoryUrl": "URL репозитория", - "branch": "Ветка" + "title": "__MISSING__:Cloud Agents", + "description": "__MISSING__:Manage autonomous coding agents (Jules, Devin, Codex Cloud)", + "loading": "__MISSING__:Loading tasks...", + "aboutTitle": "__MISSING__:About Cloud Agents", + "aboutDescription": "__MISSING__:Cloud agents are remote AI coding assistants that can execute tasks autonomously. They work differently from local CLI agents - you interact with them through OmniRoute's API.", + "howItWorksTitle": "__MISSING__:How it works:", + "howItWorksDesc": "__MISSING__:Create a task → Agent analyzes and proposes a plan → You approve → Agent executes → Results returned", + "newTaskTitle": "__MISSING__:Create New Task", + "newTaskDescription": "__MISSING__:Start a new task with a cloud agent", + "selectAgent": "__MISSING__:Select Agent", + "taskDescription": "__MISSING__:Task Description", + "taskDescriptionPlaceholder": "__MISSING__:Describe what you want the agent to do...", + "startTask": "__MISSING__:Start Task", + "tasks": "__MISSING__:Tasks", + "taskDetail": "__MISSING__:Task Detail", + "noTasks": "__MISSING__:No tasks yet. Create one to get started.", + "untitledTask": "__MISSING__:Untitled Task", + "created": "__MISSING__:Created", + "conversation": "__MISSING__:Conversation", + "result": "__MISSING__:Result", + "error": "__MISSING__:Error", + "planReady": "__MISSING__:Plan Ready for Approval", + "approvePlan": "__MISSING__:Approve Plan", + "rejectPlan": "__MISSING__:Reject & Cancel", + "sendMessagePlaceholder": "__MISSING__:Send a message to the agent...", + "cancel": "__MISSING__:Cancel", + "delete": "__MISSING__:Delete", + "selectTaskPrompt": "__MISSING__:Select a task to view details", + "statusPending": "__MISSING__:Pending", + "statusRunning": "__MISSING__:Running", + "statusWaitingApproval": "__MISSING__:Waiting Approval", + "statusCompleted": "__MISSING__:Completed", + "statusFailed": "__MISSING__:Failed", + "statusCancelled": "__MISSING__:Cancelled", + "repositoryName": "__MISSING__:Repository name", + "repositoryUrl": "__MISSING__:Repository URL", + "branch": "__MISSING__:Branch" }, "templateNames": { "simple-chat": "Простой чат", @@ -5854,8 +5835,8 @@ "thinking": "Мышление", "tool-calling": "Вызов инструмента", "multi-turn": "Многооборотный", - "vision": "Vision", - "schema-coercion": "Schema Coercion" + "vision": "__MISSING__:Vision", + "schema-coercion": "__MISSING__:Schema Coercion" }, "templateDescriptions": { "simple-chat": "Шаблон простого чата", @@ -5864,8 +5845,8 @@ "thinking": "Шаблон рассуждения", "tool-calling": "Шаблон вызова инструментов", "multi-turn": "Шаблон многоходового диалога", - "vision": "Multimodal template with image input", - "schema-coercion": "Structured-output / JSON schema enforcement" + "vision": "__MISSING__:Multimodal template with image input", + "schema-coercion": "__MISSING__:Structured-output / JSON schema enforcement" }, "templatePayloads": { "simpleChat": { @@ -5987,248 +5968,248 @@ "totalProcessed": "Всего запросов обработано", "disabled": "Disabled", "totalRequests": "Total Requests", - "reasoningCache": "Кэш рассуждений", - "reasoningCacheDesc": "Сохраняет reasoning_content для многошаговых вызовов инструментов", - "reasoningEntries": "Активные записи", - "reasoningReplayRate": "Доля повторного использования", - "reasoningReplays": "Всего повторных использований", - "reasoningCharsCached": "Символов в кэше", - "reasoningMisses": "Промахи кэша", - "reasoningByProvider": "По провайдерам", - "reasoningByModel": "По моделям", - "reasoningRecentEntries": "Последние записи", - "reasoningToolCallId": "ID вызова инструмента", - "reasoningChars": "Символов", - "reasoningAge": "Возраст", - "reasoningView": "Просмотр", - "reasoningDetail": "Содержимое рассуждения", - "reasoningBehavior": "Поведение", - "reasoningBehaviorCapture": "Захватывает reasoning_content из потоковых ответов", - "reasoningBehaviorReplay": "Повторно внедряет на следующем шаге, если клиент пропустил", - "reasoningBehaviorFallback": "Сначала в памяти, с SQLite для восстановления после сбоев", - "reasoningBehaviorTtl": "TTL: 2 часа | Макс. записей: 2,000 (в памяти)", - "reasoningBehaviorModels": "Поддерживается: DeepSeek, Kimi, Qwen-Thinking, GLM", - "reasoningClearAll": "Очистить кэш рассуждений", - "reasoningClearSuccess": "Очищено {count} записей кэша рассуждений", - "reasoningClearError": "Не удалось очистить кэш рассуждений", - "reasoningNoData": "Нет записей в кэше рассуждений.", - "cachePerformanceRetry": "Повторить", - "cachePerformanceHitRate": "Доля попаданий", - "cachePerformanceAvgLatency": "Средняя задержка (мс)", - "cachePerformanceP95Latency": "p95 задержка (мс)", - "retry": "Повторить", - "reasoningAvgChars": "Среднее символов" + "reasoningCache": "__MISSING__:Reasoning Replay", + "reasoningCacheDesc": "__MISSING__:Preserves model thinking for multi-turn tool calling flows", + "reasoningEntries": "__MISSING__:Active Entries", + "reasoningReplayRate": "__MISSING__:Replay Rate", + "reasoningReplays": "__MISSING__:Total Replays", + "reasoningCharsCached": "__MISSING__:Characters Cached", + "reasoningMisses": "__MISSING__:Cache Misses", + "reasoningByProvider": "__MISSING__:By Provider", + "reasoningByModel": "__MISSING__:By Model", + "reasoningRecentEntries": "__MISSING__:Recent Entries", + "reasoningToolCallId": "__MISSING__:Tool Call ID", + "reasoningChars": "__MISSING__:Characters", + "reasoningAge": "__MISSING__:Age", + "reasoningView": "__MISSING__:View", + "reasoningDetail": "__MISSING__:Reasoning Content", + "reasoningBehavior": "__MISSING__:Behavior", + "reasoningBehaviorCapture": "__MISSING__:Captures reasoning_content from streaming responses", + "reasoningBehaviorReplay": "__MISSING__:Re-injects on next turn when client omits it", + "reasoningBehaviorFallback": "__MISSING__:Memory-first with SQLite fallback for crash recovery", + "reasoningBehaviorTtl": "__MISSING__:TTL: 2 hours | Max entries: 2,000 (memory)", + "reasoningBehaviorModels": "__MISSING__:Supported: DeepSeek, Kimi, Qwen-Thinking, GLM", + "reasoningClearAll": "__MISSING__:Clear Reasoning Cache", + "reasoningClearSuccess": "__MISSING__:Cleared {count} reasoning cache entries", + "reasoningClearError": "__MISSING__:Failed to clear reasoning cache", + "reasoningNoData": "__MISSING__:No reasoning entries cached yet. Entries appear when thinking models use tool calling.", + "cachePerformanceRetry": "__MISSING__:Retry", + "cachePerformanceHitRate": "__MISSING__:Hit Rate", + "cachePerformanceAvgLatency": "__MISSING__:Avg Latency (ms)", + "cachePerformanceP95Latency": "__MISSING__:p95 Latency (ms)", + "retry": "__MISSING__:Retry", + "reasoningAvgChars": "__MISSING__:Avg Chars" }, "proxyConfigModal": { - "levelGlobal": "Глобальный", - "levelProvider": "Провайдер", - "levelCombo": "Комбо", - "levelKey": "Ключ", - "levelDirect": "Напрямую (без прокси)", - "titleGlobal": "Глобальная конфигурация прокси", - "titleLevel": "{level} прокси — {label}", - "loading": "Загрузка конфигурации прокси...", - "inheritingFrom": "Наследуется от", - "source": "Источник", - "savedProxy": "Сохранённый прокси", - "custom": "Свой", - "selectSavedProxyPlaceholder": "Выберите сохранённый прокси...", - "proxyType": "Тип прокси", - "host": "Хост", - "hostPlaceholder": "1.2.3.4 или proxy.example.com", - "port": "Порт", - "authOptional": "Аутентификация (необязательно)", - "username": "Имя пользователя", - "usernamePlaceholder": "Имя пользователя", - "password": "Пароль", - "passwordPlaceholder": "Пароль", - "connected": "Подключено", - "ip": "IP:", - "connectionFailed": "Ошибка подключения", - "testConnection": "Проверить подключение", - "clear": "Очистить", - "cancel": "Отмена", - "save": "Сохранить", - "errorSelectSavedProxy": "Выберите сохранённый прокси.", - "errorSelectProxyFirst": "Сначала выберите прокси.", - "errorProxyNotFound": "Прокси не найден.", - "errorClearSavedProxy": "Не удалось очистить прокси", - "errorSaveProxy": "Не удалось сохранить прокси", - "errorClearProxy": "Не удалось очистить конфигурацию прокси", - "errorSocks5Hidden": "SOCKS5 настроен, но скрыт (NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=false)" + "levelGlobal": "__MISSING__:Global", + "levelProvider": "__MISSING__:Provider", + "levelCombo": "__MISSING__:Combo", + "levelKey": "__MISSING__:Key", + "levelDirect": "__MISSING__:Direct (no proxy)", + "titleGlobal": "__MISSING__:Global Proxy Configuration", + "titleLevel": "__MISSING__:{level} Proxy — {label}", + "loading": "__MISSING__:Loading proxy configuration...", + "inheritingFrom": "__MISSING__:Inheriting from", + "source": "__MISSING__:Source", + "savedProxy": "__MISSING__:Saved Proxy", + "custom": "__MISSING__:Custom", + "selectSavedProxyPlaceholder": "__MISSING__:Select saved proxy...", + "proxyType": "__MISSING__:Proxy Type", + "host": "__MISSING__:Host", + "hostPlaceholder": "__MISSING__:1.2.3.4 or proxy.example.com", + "port": "__MISSING__:Port", + "authOptional": "__MISSING__:Authentication (optional)", + "username": "__MISSING__:Username", + "usernamePlaceholder": "__MISSING__:Username", + "password": "__MISSING__:Password", + "passwordPlaceholder": "__MISSING__:Password", + "connected": "__MISSING__:Connected", + "ip": "__MISSING__:IP:", + "connectionFailed": "__MISSING__:Connection failed", + "testConnection": "__MISSING__:Test connection", + "clear": "__MISSING__:Clear", + "cancel": "__MISSING__:Cancel", + "save": "__MISSING__:Save", + "errorSelectSavedProxy": "__MISSING__:Please select a saved proxy first.", + "errorSelectProxyFirst": "__MISSING__:Please select a proxy first.", + "errorProxyNotFound": "__MISSING__:Selected proxy not found.", + "errorClearSavedProxy": "__MISSING__:Failed to clear saved proxy", + "errorSaveProxy": "__MISSING__:Failed to save proxy configuration", + "errorClearProxy": "__MISSING__:Failed to clear proxy configuration", + "errorSocks5Hidden": "__MISSING__:SOCKS5 is configured but hidden because NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=false." }, "oauthModal": { - "title": "Подключить {providerName}", - "waiting": "Ожидание авторизации", - "completeAuthInPopup": "Завершите авторизацию во всплывающем окне.", - "popupClosedHint": "Если окно закрылось без перенаправления, диалог переключится на ручной ввод URL.", - "popupBlocked": "Всплывающее окно заблокировано? Введите URL вручную", - "deviceCodeVisitUrl": "Откройте URL ниже и введите код:", - "deviceCodeVerificationUrl": "URL верификации", - "deviceCodeYourCode": "Ваш код", - "deviceCodeWaiting": "Ожидание авторизации...", - "googleOAuthWarning": "Удалённый доступ + Google OAuth: Учётные данные по умолчанию принимают перенаправления только на localhost. После авторизации скопируйте полный URL из адресной строки и вставьте его ниже.", - "remoteAccessInfo": "Удалённый доступ: после авторизации вы увидите страницу ошибки (localhost не найден). Это нормально — скопируйте URL из адресной строки и вставьте его ниже.", - "step1OpenUrl": "Шаг 1: Откройте этот URL в браузере", - "copy": "Копировать", - "step2PasteCallback": "Шаг 2: Вставьте callback URL или код авторизации", - "step2Hint": "После авторизации вставьте полный callback URL. Для Claude Code и Cline можно вставить код аутентификации напрямую, например code#state.", - "connect": "Подключить", - "cancel": "Отмена", - "success": "Подключение успешно!", - "successMessage": "Ваш {providerName} аккаунт подключён.", - "done": "Готово", - "error": "Ошибка подключения", - "tryAgain": "Попробовать снова" + "title": "__MISSING__:Connect {providerName}", + "waiting": "__MISSING__:Waiting for authorization", + "completeAuthInPopup": "__MISSING__:Complete authorization in popup.", + "popupClosedHint": "__MISSING__:If the popup closed without redirecting back (e.g. Qoder), this dialog will auto-switch to manual URL entry mode.", + "popupBlocked": "__MISSING__:Popup blocked? Enter URL manually", + "deviceCodeVisitUrl": "__MISSING__:Visit the URL below and enter code:", + "deviceCodeVerificationUrl": "__MISSING__:Verification URL", + "deviceCodeYourCode": "__MISSING__:Your code", + "deviceCodeWaiting": "__MISSING__:Waiting for authorization...", + "googleOAuthWarning": "__MISSING__:Remote access + Google OAuth: Default credentials only accept redirects to localhost. After authorizing, your browser will try to open localhost — copy that full URL and paste it below. For fully remote use without this manual step, configure your own OAuth credentials.", + "remoteAccessInfo": "__MISSING__:Remote access: Since you're accessing OmniRoute remotely, after authorization you'll see an error page (localhost not found). This is normal — just copy the full URL from your browser address bar and paste it below.", + "step1OpenUrl": "__MISSING__:Step 1: Open this URL in your browser", + "copy": "__MISSING__:Copy", + "step2PasteCallback": "__MISSING__:Step 2: Paste callback URL or authorization code here", + "step2Hint": "__MISSING__:After authorization, paste the full callback URL. For Claude Code and Cline, you can also paste the authentication code directly, e.g. code#state.", + "connect": "__MISSING__:Connect", + "cancel": "__MISSING__:Cancel", + "success": "__MISSING__:Connection successful!", + "successMessage": "__MISSING__:Your {providerName} account has been connected.", + "done": "__MISSING__:Done", + "error": "__MISSING__:Connection failed", + "tryAgain": "__MISSING__:Try again" }, "cursorAuthModal": { - "title": "Подключить Cursor IDE", - "autoDetecting": "Автоопределение токенов...", - "readingFromCursor": "Чтение из Cursor IDE или cursor-agent", - "tokensAutoDetected": "Токены успешно определены из Cursor IDE!", - "cursorNotDetected": "Cursor IDE не обнаружен. Вставьте токен вручную.", - "accessToken": "Токен доступа", - "required": "*", - "accessTokenPlaceholder": "Токен доступа будет определён автоматически...", - "machineId": "ID машины", - "optional": "(опционально)", - "machineIdPlaceholder": "ID машины будет определён автоматически...", - "importing": "Импорт...", - "importToken": "Импортировать токен", - "cancel": "Отмена", - "errorAutoDetect": "Не удалось определить токены", - "errorAutoDetectFailed": "Автоопределение токенов не удалось", - "errorEnterToken": "Введите токен доступа", - "errorImportFailed": "Импорт не удался" + "title": "__MISSING__:Connect Cursor IDE", + "autoDetecting": "__MISSING__:Auto-detecting tokens...", + "readingFromCursor": "__MISSING__:Reading from Cursor IDE or cursor-agent", + "tokensAutoDetected": "__MISSING__:Tokens successfully auto-detected from Cursor IDE!", + "cursorNotDetected": "__MISSING__:Cursor IDE not detected. Please manually paste your token.", + "accessToken": "__MISSING__:Access Token", + "required": "__MISSING__:*", + "accessTokenPlaceholder": "__MISSING__:Access token will auto-populate...", + "machineId": "__MISSING__:Machine ID", + "optional": "__MISSING__:(optional)", + "machineIdPlaceholder": "__MISSING__:Machine ID will auto-populate...", + "importing": "__MISSING__:Importing...", + "importToken": "__MISSING__:Import Token", + "cancel": "__MISSING__:Cancel", + "errorAutoDetect": "__MISSING__:Unable to auto-detect tokens", + "errorAutoDetectFailed": "__MISSING__:Auto-detect tokens failed", + "errorEnterToken": "__MISSING__:Please enter access token", + "errorImportFailed": "__MISSING__:Import failed" }, "pricingModal": { - "title": "Настройка цен", - "loading": "Загрузка цен...", - "pricingRatesFormat": "Формат ставок", - "ratesDescription": "Все ставки указаны в долларах за миллион токенов ($/1M токенов). Пример: ставка ввода 2.50 означает $2.50 за 1,000,000 входящих токенов.", - "model": "Модель", - "input": "Ввод", - "output": "Вывод", - "cached": "Кэш", - "reasoning": "Рассуждение", - "cacheCreation": "Создание кэша", - "noPricingData": "Нет данных о ценах", - "resetToDefaults": "Сбросить на умолчания", - "cancel": "Отмена", - "saving": "Сохранение...", - "saveChanges": "Сохранить изменения", - "resetConfirm": "Сбросить все цены на умолчания? Это действие нельзя отменить.", - "errorSaveFailed": "Не удалось сохранить цены", - "errorResetFailed": "Не удалось сбросить цены" + "title": "__MISSING__:Pricing Configuration", + "loading": "__MISSING__:Loading pricing data...", + "pricingRatesFormat": "__MISSING__:Pricing Rates Format", + "ratesDescription": "__MISSING__:All rates are in dollars per million tokens ($/1M tokens). Example: Input rate of 2.50 means $2.50 per 1,000,000 input tokens.", + "model": "__MISSING__:Model", + "input": "__MISSING__:Input", + "output": "__MISSING__:Output", + "cached": "__MISSING__:Cached", + "reasoning": "__MISSING__:Reasoning", + "cacheCreation": "__MISSING__:Cache Creation", + "noPricingData": "__MISSING__:No pricing data available", + "resetToDefaults": "__MISSING__:Reset to defaults", + "cancel": "__MISSING__:Cancel", + "saving": "__MISSING__:Saving...", + "saveChanges": "__MISSING__:Save changes", + "resetConfirm": "__MISSING__:Reset all pricing to defaults? This cannot be undone.", + "errorSaveFailed": "__MISSING__:Failed to save pricing", + "errorResetFailed": "__MISSING__:Failed to reset pricing" }, "proxyRegistry": { - "title": "Реестр прокси", - "description": "Храните переиспользуемые прокси и отслеживайте привязки.", - "importLegacy": "Импорт из старой версии", - "bulkAssign": "Массовое назначение", - "addProxy": "Добавить прокси", - "loading": "Загрузка прокси...", - "noProxies": "Прокси пока не сохранены.", - "tableName": "Название", - "tableEndpoint": "Эндпоинт", - "tableStatus": "Статус", - "tableHealth": "Здоровье (24ч)", - "tableUsage": "Использование", - "tableActions": "Действия", - "test": "Проверить", - "edit": "Редактировать", - "delete": "Удалить", - "modalCreateTitle": "Создать прокси", - "modalEditTitle": "Редактировать прокси", - "labelName": "Название", - "labelType": "Тип", - "labelHost": "Хост", - "labelPort": "Порт", - "labelUsername": "Имя пользователя", - "labelPassword": "Пароль", - "labelRegion": "Регион", - "labelStatus": "Статус", - "labelNotes": "Заметки", - "usernamePlaceholderEdit": "Leave blank to keep current username", - "passwordPlaceholderEdit": "Leave blank to keep current password", - "statusActive": "Активен", - "statusInactive": "Неактивен", - "cancel": "Отмена", - "save": "Сохранить", - "bulkModalTitle": "Массовое назначение прокси", - "bulkLabelScope": "Область", - "bulkLabelProxy": "Прокси", - "bulkClearAssignment": "(Очистить привязку)", - "bulkLabelScopeIds": "ID областей (через запятую или с новой строки)", - "bulkScopeIdsPlaceholder": "provider-openai,provider-anthropic", - "bulkApply": "Применить", - "errorLoadFailed": "Не удалось загрузить реестр", - "errorNameHostRequired": "Название и хост обязательны", - "errorSaveFailed": "Не удалось сохранить прокси", - "errorDeleteFailed": "Не удалось удалить прокси", + "title": "Title", + "description": "Description", + "importLegacy": "Import Legacy", + "bulkAssign": "Bulk Assign", + "addProxy": "Add Proxy", + "loading": "Loading", + "noProxies": "No Proxies", + "tableName": "Table Name", + "tableEndpoint": "Table Endpoint", + "tableStatus": "Table Status", + "tableHealth": "Table Health", + "tableUsage": "Table Usage", + "tableActions": "Table Actions", + "test": "Test", + "edit": "Edit", + "delete": "Delete", + "modalCreateTitle": "Modal Create Title", + "modalEditTitle": "Modal Edit Title", + "labelName": "Label Name", + "labelType": "__MISSING__:Type", + "labelHost": "__MISSING__:Host", + "labelPort": "__MISSING__:Port", + "labelUsername": "__MISSING__:Username", + "labelPassword": "__MISSING__:Password", + "labelRegion": "__MISSING__:Region", + "labelStatus": "__MISSING__:Status", + "labelNotes": "__MISSING__:Notes", + "usernamePlaceholderEdit": "__MISSING__:Leave blank to keep current username", + "passwordPlaceholderEdit": "__MISSING__:Leave blank to keep current password", + "statusActive": "__MISSING__:Active", + "statusInactive": "__MISSING__:Inactive", + "cancel": "__MISSING__:Cancel", + "save": "__MISSING__:Save", + "bulkModalTitle": "__MISSING__:Bulk Proxy Assignment", + "bulkLabelScope": "__MISSING__:Scope", + "bulkLabelProxy": "__MISSING__:Proxy", + "bulkClearAssignment": "__MISSING__:(Clear assignment)", + "bulkLabelScopeIds": "__MISSING__:Scope IDs (comma or newline separated)", + "bulkScopeIdsPlaceholder": "__MISSING__:provider-openai,provider-anthropic", + "bulkApply": "__MISSING__:Apply", + "errorLoadFailed": "Error Load Failed", + "errorNameHostRequired": "Error Name Host Required", + "errorSaveFailed": "Error Save Failed", + "errorDeleteFailed": "Error Delete Failed", "errorForceDeleteConfirm": "Error Force Delete Confirm", "errorMigrateFailed": "Error Migrate Failed", "errorBulkFailed": "Error Bulk Failed", - "success": "OK", - "failure": "Ошибка", - "failed": "Ошибка", + "success": "__MISSING__:✓", + "failure": "__MISSING__:✗", + "failed": "Failed", "successRate": "Success Rate", "avgLatency": "Avg Latency", "assignmentsCount": "Assignments Count", "noData": "No Data", - "testSuccess": "OK {ip}", - "testLatency": "{latency}мс", - "testFailure": "Ошибка {error}", - "bulkImport": "Массовый импорт", - "bulkImportTitle": "Массовый импорт прокси", - "bulkImportDescription": "Вставьте профили прокси в формате с разделителем |. Один прокси на строку.", - "bulkImportParse": "Разобрать", - "bulkImportImport": "Импортировать {count} прокси", - "bulkImportImporting": "Импорт...", - "bulkImportParsed": "{count} прокси распознано", - "bulkImportSkipped": "{count} строк пропущено", - "bulkImportParseErrors": "{count} ошибок", - "bulkImportNoValidEntries": "Нет корректных записей.", - "bulkImportSuccess": "Импорт завершён: {created} создано, {updated} обновлено, {failed} с ошибками", - "bulkImportErrorLine": "Строка {line}: {reason}", - "bulkImportMaxExceeded": "Максимум 100 прокси за один импорт", - "bulkImportPreview": "Предпросмотр", - "bulkImportErrorMissingName": "Отсутствует NAME", - "bulkImportErrorMissingHost": "Отсутствует HOST", - "bulkImportErrorInvalidPort": "Некорректный PORT (1-65535)", - "bulkImportErrorInvalidType": "Некорректный TYPE (http, https, socks5)", - "bulkImportErrorInvalidStatus": "Некорректный STATUS (active, inactive)", - "clearAssignment": "(очистить привязку)", - "bulkProxyAssignment": "Массовое назначение прокси" + "testSuccess": "__MISSING__:✓ {ip}", + "testLatency": "__MISSING__:{latency}ms", + "testFailure": "__MISSING__:✗ {error}", + "bulkImport": "__MISSING__:Bulk Import", + "bulkImportTitle": "__MISSING__:Bulk Import Proxies", + "bulkImportDescription": "__MISSING__:Paste proxy profiles using pipe-delimited format. One proxy per line. Existing proxies (same host + port) will be updated.", + "bulkImportParse": "__MISSING__:Parse", + "bulkImportImport": "__MISSING__:Import {count} Proxies", + "bulkImportImporting": "__MISSING__:Importing...", + "bulkImportParsed": "__MISSING__:{count} proxies parsed", + "bulkImportSkipped": "__MISSING__:{count} lines skipped", + "bulkImportParseErrors": "__MISSING__:{count} errors", + "bulkImportNoValidEntries": "__MISSING__:No valid entries found. Check the format and try again.", + "bulkImportSuccess": "__MISSING__:Import complete: {created} created, {updated} updated, {failed} failed", + "bulkImportErrorLine": "__MISSING__:Line {line}: {reason}", + "bulkImportMaxExceeded": "__MISSING__:Maximum 100 proxies per import", + "bulkImportPreview": "__MISSING__:Preview", + "bulkImportErrorMissingName": "__MISSING__:Missing NAME", + "bulkImportErrorMissingHost": "__MISSING__:Missing HOST", + "bulkImportErrorInvalidPort": "__MISSING__:Invalid PORT (must be 1-65535)", + "bulkImportErrorInvalidType": "__MISSING__:Invalid TYPE (use http, https, or socks5)", + "bulkImportErrorInvalidStatus": "__MISSING__:Invalid STATUS (use active or inactive)", + "clearAssignment": "__MISSING__:(clear assignment)", + "bulkProxyAssignment": "__MISSING__:Bulk Proxy Assignment" }, "playground": { - "title": "Песочница", - "description": "Тестируйте модели напрямую из панели управления", - "endpoint": "Эндпоинт", - "provider": "Провайдер", - "model": "Модель", - "accountKey": "Аккаунт / Ключ", - "autoAccounts": "Авто ({count} акк.)", - "noAccounts": "Нет аккаунтов", - "send": "Отправить", - "cancel": "Отмена", - "audioFile": "Аудиофайл", - "attachImages": "Прикрепить изображения (Vision)", - "multipartFormData": "multipart/form-data", - "upToImages": "До 4 изображений", - "selectAudioFile": "Выберите аудиофайл для распознавания (mp3, wav, m4a, ogg, flac...)", - "clearAll": "Очистить всё", - "request": "Запрос", - "response": "Ответ", - "transcription": "Распознавание", - "copy": "Копировать", - "resetToDefault": "Сбросить на умолчания", - "downloadAudio": "Скачать аудио", - "copyText": "Копировать текст", - "transcriptionHint": "Распознавание использует multipart/form-data. Загрузите аудиофайл выше — JSON ниже управляет доп. параметрами (model, language).", - "imagesGenerated": "Сгенерировано {count} изображений", - "generatedImage": "Изображение {index}", - "save": "Сохранить", + "title": "Title", + "description": "Description", + "endpoint": "Endpoint", + "provider": "Provider", + "model": "Model", + "accountKey": "Account Key", + "autoAccounts": "Auto Accounts", + "noAccounts": "No Accounts", + "send": "Send", + "cancel": "Cancel", + "audioFile": "Audio File", + "attachImages": "Attach Images", + "multipartFormData": "Multipart Form Data", + "upToImages": "Up To Images", + "selectAudioFile": "Select Audio File", + "clearAll": "Clear All", + "request": "Request", + "response": "Response", + "transcription": "Transcription", + "copy": "Copy", + "resetToDefault": "Reset To Default", + "downloadAudio": "Download Audio", + "copyText": "Copy Text", + "transcriptionHint": "Transcription Hint", + "imagesGenerated": "Images Generated", + "generatedImage": "Generated Image", + "save": "Save", "endpointOptions": { "chat": "Chat", "responses": "Responses", @@ -6241,49 +6222,49 @@ "rerank": "Rerank", "search": "Search" }, - "conversationalChat": "Диалоговый чат", - "clearChat": "Очистить чат", - "typeMessagePlaceholder": "Введите сообщение... (Shift+Enter для новой строки)" + "conversationalChat": "__MISSING__:Conversational Chat", + "clearChat": "__MISSING__:Clear chat", + "typeMessagePlaceholder": "__MISSING__:Type a message... (Shift+Enter for new line)" }, "requestLogger": { - "recording": "Запись", - "paused": "Пауза", - "pipelineLogsOn": "Логи конвейера вкл.", - "pipelineLogsOff": "Логи конвейера выкл.", - "updatingPipelineLogs": "Обновление логов конвейера...", - "updatePipelineFailed": "Не удалось обновить логи конвейера", - "capturePipeline": "Захват полезной нагрузки конвейера для новых запросов", - "searchPlaceholder": "Поиск по моделям, провайдерам, аккаунтам, ключам API, комбо...", - "allProviders": "Все провайдеры", - "allModels": "Все модели", - "allAccounts": "Все аккаунты", - "allApiKeys": "Все ключи API", - "total": "Всего", - "ok": "OK", - "err": "Ошибка", - "combo": "Комбо", - "keys": "Ключи", - "shown": "Показано", - "sortNewest": "Сначала новые", - "sortOldest": "Сначала старые", - "sortTokensDesc": "Токены ↓", - "sortTokensAsc": "Токены ↑", - "sortDurationDesc": "Длительность ↓", - "sortDurationAsc": "Длительность ↑", - "sortStatusDesc": "Статус ↓", - "sortStatusAsc": "Статус ↑", - "sortModelAsc": "Модель А-Я", - "sortModelDesc": "Модель Я-А", - "sortLogs": "Сортировка", - "refresh": "Обновить", - "columnsLabel": "Колонки", - "cacheSem": "СЕМ", - "cacheUp": "АП", - "semantic": "Семантический", - "upstream": "Провайдер", - "semanticCacheHit": "Попадание в семантический кэш (ответ OmniRoute)", - "upstreamResponse": "Ответ вышестоящего провайдера", - "noApiKey": "Нет ключа API", + "recording": "Recording", + "paused": "Paused", + "pipelineLogsOn": "Pipeline logs on", + "pipelineLogsOff": "Pipeline logs off", + "updatingPipelineLogs": "Updating pipeline logs...", + "updatePipelineFailed": "Failed to update pipeline logging", + "capturePipeline": "Capture pipeline payloads for new requests", + "searchPlaceholder": "Search models, providers, accounts, API keys, combos...", + "allProviders": "All providers", + "allModels": "All models", + "allAccounts": "All accounts", + "allApiKeys": "All API keys", + "total": "Total", + "ok": "Success", + "err": "Error", + "combo": "Combo", + "keys": "Keys", + "shown": "Shown", + "sortNewest": "Newest", + "sortOldest": "Oldest", + "sortTokensDesc": "Tokens ↓", + "sortTokensAsc": "Tokens ↑", + "sortDurationDesc": "Duration ↓", + "sortDurationAsc": "Duration ↑", + "sortStatusDesc": "Status ↓", + "sortStatusAsc": "Status ↑", + "sortModelAsc": "Model A-Z", + "sortModelDesc": "Model Z-A", + "sortLogs": "Sort logs", + "refresh": "Refresh", + "columnsLabel": "Columns", + "cacheSem": "SEM", + "cacheUp": "UP", + "semantic": "Semantic", + "upstream": "Upstream", + "semanticCacheHit": "Semantic cache hit (served by OmniRoute)", + "upstreamResponse": "Upstream provider response", + "noApiKey": "No API key", "requestedRoutedTitle": "Requested {requested}, routed as {routed}", "statusFilters": { "all": "All", @@ -6302,64 +6283,64 @@ "apiKey": "API Key", "combo": "Combo", "tokens": "Tokens", - "compressed": "Compression", + "compressed": "__MISSING__:Compression", "tps": "TPS", "duration": "Duration", "time": "Time" }, - "loadingLogs": "Загрузка логов...", - "noLogs": "Логов пока нет. Сделайте несколько API-запросов, чтобы они появились.", - "noMatchingLogs": "Нет логов по текущим фильтрам.", + "loadingLogs": "Loading logs...", + "noLogs": "No logs yet. Make some API calls to see them here.", + "noMatchingLogs": "No logs matching current filters.", "callLogsInfo": "Call logs are also saved as JSON files to {dataDir} and rotated based on {retentionDays} and {maxEntries}." }, "proxyLogger": { - "filterAll": "Все", - "filterErrors": "Ошибки", - "filterSuccess": "Успешно", - "filterTimeout": "Тайм-аут", - "colStatus": "Статус", - "colProxy": "Прокси", + "filterAll": "All", + "filterErrors": "Errors", + "filterSuccess": "Success", + "filterTimeout": "Timeout", + "colStatus": "Status", + "colProxy": "Proxy", "colTls": "TLS", - "colType": "Тип", - "colLevel": "Уровень", - "colProvider": "Провайдер", - "colTarget": "Цель", - "colLatency": "Задержка", - "colPublicIp": "Публичный IP", - "colTime": "Время", - "recording": "Запись", - "paused": "Пауза", - "searchPlaceholder": "Поиск по хосту, провайдеру, цели, IP...", - "allTypes": "Все типы", - "allLevels": "Все уровни", - "allProviders": "Все провайдеры", - "total": "всего", + "colType": "Type", + "colLevel": "Level", + "colProvider": "Provider", + "colTarget": "Target", + "colLatency": "Latency", + "colPublicIp": "Public IP", + "colTime": "Time", + "recording": "Recording", + "paused": "Paused", + "searchPlaceholder": "Search host, provider, target, IP...", + "allTypes": "All Types", + "allLevels": "All Levels", + "allProviders": "All Providers", + "total": "total", "ok": "OK", - "err": "ОШБ", - "timeoutShort": "ТАО", - "direct": "напрямую", - "newest": "Новые", - "oldest": "Старые", - "latencyDesc": "Задержка ↓", - "latencyAsc": "Задержка ↑", - "refresh": "Обновить", - "columns": "Колонки", - "loadingProxyLogs": "Загрузка логов прокси...", - "noProxyLogs": "Логов прокси пока нет. Настройте прокси и сделайте запросы.", - "noMatchingLogs": "Нет логов по текущим фильтрам.", - "tlsFingerprint": "TLS-отпечаток Chrome 124" + "err": "ERR", + "timeoutShort": "TMO", + "direct": "direct", + "newest": "Newest", + "oldest": "Oldest", + "latencyDesc": "Latency ↓", + "latencyAsc": "Latency ↑", + "refresh": "Refresh", + "columns": "Columns", + "loadingProxyLogs": "Loading proxy logs...", + "noProxyLogs": "No proxy logs yet. Configure proxies and make API calls to see them here.", + "noMatchingLogs": "No logs match the current filters.", + "tlsFingerprint": "Chrome 124 TLS Fingerprint" }, "endpointOptions": { - "speech": "Озвучивание", - "search": "Поиск", - "images": "Изображения", - "chat": "Чат", - "music": "Музыка", + "speech": "Speech", + "search": "Search", + "images": "Images", + "chat": "Chat", + "music": "Music", "responses": "Responses", - "rerank": "Переранжирование", - "video": "Видео", - "embeddings": "Эмбеддинги", - "transcription": "Распознавание" + "rerank": "Rerank", + "video": "Video", + "embeddings": "Embeddings", + "transcription": "Transcription" }, "toolDescriptions": { "cline": "Cline", @@ -6370,31 +6351,31 @@ "kilo": "Kilo" }, "runtime": { - "title": "Среда выполнения", - "description": "Наблюдение в реальном времени — 3 уровня отказоустойчивости + сессии + оповещения о квотах", - "pause": "Пауза", - "resume": "Продолжить", - "refreshNow": "Обновить", - "kpiSessions": "Сессии", - "kpiCircuits": "Выключатели", - "kpiCooldowns": "Остывание", - "kpiLockouts": "Блокировки", + "title": "Runtime", + "description": "Realtime observability — 3-layer resilience + sessions + quota alerts", + "pause": "Pause", + "resume": "Resume", + "refreshNow": "Refresh now", + "kpiSessions": "Sessions", + "kpiCircuits": "Circuits", + "kpiCooldowns": "Cooldowns", + "kpiLockouts": "Lockouts", "hintStickyBound": "{count} sticky-bound", "hintRecovering": "{count} recovering", "hintAllHealthy": "all healthy", "hintOpen": "open", "hintConnsCooling": "connections cooling", "hintModelsBlocked": "models blocked", - "resilienceTitle": "3-уровневая отказоустойчивость", + "resilienceTitle": "3-Layer Resilience", "resilienceSubtitle": "Mirrors the documented resilience model", - "providersHealthy": "{percent}% провайдеров здоровы", - "layer": "Уровень {n}", - "layer1Title": "Автоматические выключатели провайдеров", - "layer1Desc": "Остановка трафика к провайдерам с ошибками на upstream", - "layer2Title": "Остывание подключений", - "layer2Desc": "Пропуск одного сбойного аккаунта/ключа; остальные продолжают работать", - "layer3Title": "Блокировки моделей", - "layer3Desc": "Блокировка по лимиту на модель; то же подключение обслуживает другие модели", + "providersHealthy": "{percent}% providers healthy", + "layer": "Layer {n}", + "layer1Title": "Provider Circuit Breakers", + "layer1Desc": "Stop traffic to providers failing at the upstream level", + "layer2Title": "Connection Cooldowns", + "layer2Desc": "Skip one bad account/key; other connections keep serving", + "layer3Title": "Model Lockouts", + "layer3Desc": "Per-model rate-limit locks; same connection still serves other models", "badgeAffectedOf": "{affected} of {total} affected", "badgeCooling": "{count} cooling", "badgeLocked": "{count} locked", @@ -6403,43 +6384,43 @@ "emptyLockouts": "No model lockouts", "moreCooldowns": "+{count} more cooldowns", "moreLockouts": "+{count} more lockouts", - "feedTitle": "Лента событий", - "feedSubtitle": "Последние {count} событий", - "feedFilterAll": "Все", - "feedFilterCircuits": "Выключатели", - "feedFilterCooldowns": "Остывание", - "feedFilterLockouts": "Блокировки", - "feedFilterSessions": "Сессии", - "feedFilterQuotas": "Квоты", - "feedClear": "Очистить", + "feedTitle": "Live Feed", + "feedSubtitle": "Last {count} events", + "feedFilterAll": "All", + "feedFilterCircuits": "Circuits", + "feedFilterCooldowns": "Cooldowns", + "feedFilterLockouts": "Lockouts", + "feedFilterSessions": "Sessions", + "feedFilterQuotas": "Quotas", + "feedClear": "Clear", "feedEmptyWaiting": "Waiting for events... (polled every 5s)", "feedEmptyFiltered": "No events match this filter", - "sessionsTitle": "Активные сессии", - "sessionsSubtitle": "Привязки запросов (в реальном времени)", + "sessionsTitle": "Active Sessions", + "sessionsSubtitle": "Sticky-bound request fingerprints (live)", "sessionsActive": "{count} active", "sessionsEmptyTitle": "No active sessions", - "sessionsEmptyHint": "Сессии появляются по мере прохождения запросов через прокси", - "tblSession": "Сессия", - "tblAge": "Возраст", - "tblIdle": "Простой", - "tblReqs": "Запросы", - "tblBoundTo": "Привязан к", - "topApiKeys": "Топ ключей API", - "quotaMonitorsTitle": "Мониторы квот", - "quotaMonitorsSubtitle": "Состояние квот в реальном времени", - "openQuota": "Открытая квота", - "allQuotasHealthy": "Все квоты в порядке", - "statusExhausted": "ИСЧЕРПАНО", - "statusAlerting": "ПРЕДУПРЕЖДЕНИЕ", - "statusError": "ОШИБКА", + "sessionsEmptyHint": "Sessions appear as requests flow through the proxy", + "tblSession": "Session", + "tblAge": "Age", + "tblIdle": "Idle", + "tblReqs": "Reqs", + "tblBoundTo": "Bound to", + "topApiKeys": "Top API keys", + "quotaMonitorsTitle": "Quota Monitors", + "quotaMonitorsSubtitle": "Live quota state per account window", + "openQuota": "Open Quota", + "allQuotasHealthy": "All quotas healthy", + "statusExhausted": "EXHAUSTED", + "statusAlerting": "ALERTING", + "statusError": "ERROR", "moreSuffix": "+{count} more" }, "quotaShare": { - "title": "Общие квоты", - "description": "Распределение квот провайдеров между ключами API с процентными лимитами", - "newPool": "Новый пул", - "betaTitle": "Бета — предпросмотр UI.", - "betaDescription": "Конфигурация сохраняется в localStorage (ещё не на сервере). Этот экран позволяет спроектировать разделение квот; реальное применение будет в будущих версиях.", + "title": "Quota Sharing", + "description": "Share provider quotas across API keys with % limits", + "newPool": "New pool", + "betaTitle": "Beta — UI preview.", + "betaDescription": "Configuration is saved in localStorage (not yet persisted on the server). Per-request cap enforcement is not yet wired into the proxy pipeline. This screen lets you design and visualize the quota split; real enforcement will land in a future iteration with DB persistence and upstream call interception.", "kpiActivePools": "Active pools", "kpiKeysAllocated": "Keys allocated", "kpiAvgUnallocated": "Avg unallocated", @@ -6482,9 +6463,9 @@ "addKey": "+ Add key…", "equalSplit": "Equal split", "save": "Save allocations", - "betaPreviewLabel": "Beta — UI preview.", - "betaConfigSavedPrefix": "A configuração é salva em", - "betaConfigSavedSuffix": "(não persiste no servidor ainda). A aplicação dos caps por request ainda não está conectada ao pipeline da proxy. Esta tela permite desenhar e visualizar a divisão de cota;", - "policyLabel": "Policy:" + "betaPreviewLabel": "__MISSING__:Beta — UI preview.", + "betaConfigSavedPrefix": "__MISSING__:A configuração é salva em", + "betaConfigSavedSuffix": "__MISSING__:(não persiste no servidor ainda). A aplicação dos caps por request ainda não está conectada ao pipeline da proxy. Esta tela permite desenhar e visualizar a divisão de cota;", + "policyLabel": "__MISSING__:Policy:" } -} \ No newline at end of file +} diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index ece2968e3a..789974514f 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -93,6 +93,9 @@ export async function getSettings() { mcpEnabled: false, a2aEnabled: false, hiddenSidebarItems: [], + sidebarSectionOrder: [], + sidebarItemOrder: {}, + sidebarActivePreset: null, hideEndpointCloudflaredTunnel: false, hideEndpointTailscaleFunnel: false, hideEndpointNgrokTunnel: false, diff --git a/src/shared/components/Sidebar.tsx b/src/shared/components/Sidebar.tsx index 3b0b68d456..85ec4b833c 100644 --- a/src/shared/components/Sidebar.tsx +++ b/src/shared/components/Sidebar.tsx @@ -14,12 +14,17 @@ import { useTranslations } from "next-intl"; import { HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, SIDEBAR_SETTINGS_UPDATED_EVENT, + SIDEBAR_SECTION_ORDER_KEY, + SIDEBAR_ITEM_ORDER_KEY, SIDEBAR_SECTIONS, getSectionItems, normalizeHiddenSidebarItems, + applySectionOrder, + applyItemOrder, type SidebarSectionId, type SidebarItemDefinition, type SidebarItemGroup, + type SidebarItemOrder, } from "@/shared/constants/sidebarVisibility"; const isE2EMode = process.env.NEXT_PUBLIC_OMNIROUTE_E2E_MODE === "1"; @@ -70,6 +75,8 @@ export default function Sidebar({ const [isDisconnected, setIsDisconnected] = useState(false); const [showDebug, setShowDebug] = useState(false); const [hiddenSidebarItems, setHiddenSidebarItems] = useState([]); + const [sidebarSectionOrder, setSidebarSectionOrder] = useState([]); + const [sidebarItemOrder, setSidebarItemOrder] = useState({}); const [customAppName, setCustomAppName] = useState(null); const [customLogo, setCustomLogo] = useState(null); const [expandedSections, setExpandedSections] = useState>( @@ -116,7 +123,15 @@ export default function Sidebar({ fetch("/api/settings") .then((res) => res.json()) - .then((data) => applySettings(data)) + .then((data) => { + applySettings(data); + if (Array.isArray(data?.[SIDEBAR_SECTION_ORDER_KEY])) { + setSidebarSectionOrder(data[SIDEBAR_SECTION_ORDER_KEY] as SidebarSectionId[]); + } + if (data?.[SIDEBAR_ITEM_ORDER_KEY] && typeof data[SIDEBAR_ITEM_ORDER_KEY] === "object") { + setSidebarItemOrder(data[SIDEBAR_ITEM_ORDER_KEY] as SidebarItemOrder); + } + }) .catch(() => {}); const handleSettingsUpdated = (event: Event) => { @@ -127,6 +142,16 @@ export default function Sidebar({ normalizeHiddenSidebarItems(detail[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]) ); } + if (SIDEBAR_SECTION_ORDER_KEY in detail && Array.isArray(detail[SIDEBAR_SECTION_ORDER_KEY])) { + setSidebarSectionOrder(detail[SIDEBAR_SECTION_ORDER_KEY] as SidebarSectionId[]); + } + if ( + SIDEBAR_ITEM_ORDER_KEY in detail && + detail[SIDEBAR_ITEM_ORDER_KEY] && + typeof detail[SIDEBAR_ITEM_ORDER_KEY] === "object" + ) { + setSidebarItemOrder(detail[SIDEBAR_ITEM_ORDER_KEY] as SidebarItemOrder); + } if ("instanceName" in detail) setCustomAppName((detail.instanceName as string) || null); if ("customLogoBase64" in detail) { setCustomLogo((detail.customLogoBase64 as string) || null); @@ -158,17 +183,27 @@ export default function Sidebar({ const hiddenSidebarSet = new Set(hiddenSidebarItems); - const visibleSections = SIDEBAR_SECTIONS.filter( - (section) => section.visibility !== "debug" || showDebug - ) + const orderedSections = applySectionOrder( + SIDEBAR_SECTIONS.filter((section) => section.visibility !== "debug" || showDebug), + sidebarSectionOrder + ); + + const visibleSections = orderedSections .map((section) => { - const children = section.children + const orderedChildren = applyItemOrder( + section.children, + sidebarItemOrder[section.id as SidebarSectionId] ?? [] + ); + + const children = orderedChildren .map((child) => { if ("type" in child && child.type === "group") { const items = child.items .map((item) => resolveItem(item, hiddenSidebarSet)) .filter(Boolean) as (SidebarItemDefinition & { label: string })[]; if (items.length === 0) return null; + // Smart-grouping: single visible item → inline flat (no group header) + if (items.length === 1) return items[0]; return { ...child, title: getSidebarLabel(child.titleKey, child.titleFallback), diff --git a/src/shared/constants/sidebarVisibility.ts b/src/shared/constants/sidebarVisibility.ts index 8452cc1099..7443df9002 100644 --- a/src/shared/constants/sidebarVisibility.ts +++ b/src/shared/constants/sidebarVisibility.ts @@ -75,7 +75,7 @@ export const HIDEABLE_SIDEBAR_ITEM_IDS = [ "settings-advanced", "settings-security", "settings-feature-flags", - "settings-authz", + "settings-sidebar", // Help "docs", "issues", @@ -661,11 +661,11 @@ const CONFIGURATION_ITEMS: readonly SidebarItemDefinition[] = [ icon: "flag", }, { - id: "settings-authz", - href: "/dashboard/settings/authz", - i18nKey: "settingsAuthz", - subtitleKey: "settingsAuthzSubtitle", - icon: "shield_lock", + id: "settings-sidebar", + href: "/dashboard/settings/sidebar", + i18nKey: "settingsSidebar", + subtitleKey: "settingsSidebarSubtitle", + icon: "view_sidebar", }, ]; @@ -763,11 +763,159 @@ export const SIDEBAR_SECTIONS: readonly SidebarSectionDefinition[] = [ }, ] as const; -// ─── Settings helpers ───────────────────────────────────────────────────────── +// ─── Ordering & preset setting keys ────────────────────────────────────────── export const HIDDEN_SIDEBAR_ITEMS_SETTING_KEY = "hiddenSidebarItems"; +export const SIDEBAR_SECTION_ORDER_KEY = "sidebarSectionOrder"; +export const SIDEBAR_ITEM_ORDER_KEY = "sidebarItemOrder"; +export const SIDEBAR_PRESET_KEY = "sidebarActivePreset"; export const SIDEBAR_SETTINGS_UPDATED_EVENT = "omniroute:settings-updated"; +// ─── Preset types & definitions ─────────────────────────────────────────────── + +export type SidebarPresetId = "all" | "minimal" | "developer" | "admin"; + +export interface SidebarPresetDefinition { + id: SidebarPresetId; + icon: string; + hiddenItems: HideableSidebarItemId[]; +} + +const MINIMAL_SHOWN: ReadonlySet = new Set([ + "home", + "endpoints", + "api-manager", + "providers", + "combos", + "analytics", + "costs", + "logs", + "health", + "settings", + "settings-sidebar", + "docs", + "changelog", +]); + +const DEVELOPER_SHOWN: ReadonlySet = new Set([ + "home", + "endpoints", + "api-manager", + "providers", + "combos", + "quota", + "context-caveman", + "context-rtk", + "context-combos", + "cli-tools", + "agents", + "api-endpoints", + "analytics", + "analytics-combo-health", + "costs", + "cache", + "logs", + "health", + "runtime", + "translator", + "playground", + "memory", + "skills", + "mcp", + "a2a", + "settings", + "settings-routing", + "settings-resilience", + "settings-sidebar", + "docs", + "issues", + "changelog", +]); + +const ADMIN_SHOWN: ReadonlySet = new Set([ + "home", + "endpoints", + "api-manager", + "providers", + "combos", + "quota", + "analytics", + "analytics-combo-health", + "analytics-utilization", + "costs", + "costs-pricing", + "costs-budget", + "costs-quota-share", + "cache", + "logs", + "logs-activity", + "health", + "runtime", + "audit", + "audit-mcp", + "audit-a2a", + "settings", + "settings-general", + "settings-routing", + "settings-resilience", + "settings-security", + "settings-feature-flags", + "settings-sidebar", + "docs", + "changelog", +]); + +function buildHiddenList(shown: ReadonlySet): HideableSidebarItemId[] { + return HIDEABLE_SIDEBAR_ITEM_IDS.filter((id) => !shown.has(id)); +} + +export const SIDEBAR_PRESETS: readonly SidebarPresetDefinition[] = [ + { id: "all", icon: "select_all", hiddenItems: [] }, + { id: "minimal", icon: "minimize", hiddenItems: buildHiddenList(MINIMAL_SHOWN) }, + { id: "developer", icon: "code", hiddenItems: buildHiddenList(DEVELOPER_SHOWN) }, + { id: "admin", icon: "admin_panel_settings", hiddenItems: buildHiddenList(ADMIN_SHOWN) }, +]; + +export type SidebarItemOrder = Partial>; + +// ─── Ordering utilities ─────────────────────────────────────────────────────── + +export function applySectionOrder( + sections: readonly SidebarSectionDefinition[], + order: SidebarSectionId[] +): SidebarSectionDefinition[] { + if (order.length === 0) return [...sections]; + const knownIds = new Set(sections.map((s) => s.id)); + const validOrder = order.filter((id) => knownIds.has(id)); + const orderMap = new Map(validOrder.map((id, i) => [id, i])); + return [...sections].sort((a, b) => { + const ai = orderMap.get(a.id) ?? validOrder.length + sections.indexOf(a); + const bi = orderMap.get(b.id) ?? validOrder.length + sections.indexOf(b); + return ai - bi; + }); +} + +export function applyItemOrder( + children: readonly SidebarSectionChild[], + order: string[] +): SidebarSectionChild[] { + if (order.length === 0) return [...children]; + const getChildId = (c: SidebarSectionChild): string => + "type" in c && c.type === "group" ? c.id : (c as SidebarItemDefinition).id; + const knownIds = new Set(children.map(getChildId)); + const validOrder = order.filter((id) => knownIds.has(id)); + const orderMap = new Map(validOrder.map((id, i) => [id, i])); + return [...children].sort((a, b) => { + const aId = getChildId(a); + const bId = getChildId(b); + const ai = orderMap.get(aId) ?? validOrder.length + children.indexOf(a); + const bi = orderMap.get(bId) ?? validOrder.length + children.indexOf(b); + return ai - bi; + }); +} + +// ─── Settings helpers ───────────────────────────────────────────────────────── + export function normalizeHiddenSidebarItems(value: unknown): HideableSidebarItemId[] { if (!Array.isArray(value)) return []; diff --git a/src/shared/validation/settingsSchemas.ts b/src/shared/validation/settingsSchemas.ts index 601717bb94..993b3a9b90 100644 --- a/src/shared/validation/settingsSchemas.ts +++ b/src/shared/validation/settingsSchemas.ts @@ -8,284 +8,258 @@ import { z } from "zod"; import { COMBO_CONFIG_MODES } from "@/shared/constants/comboConfigMode"; import { MAX_REQUEST_BODY_LIMIT_MB, MIN_REQUEST_BODY_LIMIT_MB } from "@/shared/constants/bodySize"; -import { HIDEABLE_SIDEBAR_ITEM_IDS } from "@/shared/constants/sidebarVisibility"; +import { HIDEABLE_SIDEBAR_ITEM_IDS, SIDEBAR_SECTIONS } from "@/shared/constants/sidebarVisibility"; import { ACCOUNT_FALLBACK_STRATEGY_VALUES } from "@/shared/constants/routingStrategies"; -import { SPAWN_CAPABLE_PREFIXES } from "@/server/authz/routeGuard"; const signatureCacheModeValues = ["enabled", "bypass", "bypass-strict"] as const; -export const updateSettingsSchema = z - .object({ - newPassword: z.string().min(1).max(200).optional(), - currentPassword: z.string().max(200).optional(), - theme: z.string().max(50).optional(), - language: z.string().max(10).optional(), - requireLogin: z.boolean().optional(), - enableSocks5Proxy: z.boolean().optional(), - instanceName: z.string().max(100).optional(), - customLogoUrl: z.string().max(2000).optional(), - customLogoBase64: z.string().max(100000).optional(), - customFaviconUrl: z.string().max(2000).optional(), - customFaviconBase64: z.string().max(50000).optional(), - corsOrigins: z.string().max(500).optional(), - cloudUrl: z.string().max(500).optional(), - baseUrl: z.string().max(500).optional(), - setupComplete: z.boolean().optional(), - blockedProviders: z.array(z.string().max(100)).optional(), - hideHealthCheckLogs: z.boolean().optional(), - hideEndpointCloudflaredTunnel: z.boolean().optional(), - hideEndpointTailscaleFunnel: z.boolean().optional(), - hideEndpointNgrokTunnel: z.boolean().optional(), - debugMode: z.boolean().optional(), - hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(), - comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(), - codexServiceTier: z - .object({ - enabled: z.boolean().optional(), - tier: z.enum(["default", "priority", "flex"]).optional(), - supportedModels: z.array(z.string().max(200)).max(200).optional(), - }) - .optional(), - // Claude Fast Mode: opt-in toggle that asks a paired CLIProxyAPI build - // (claude-fastmode-spoof) to rewrite SDK-shaped entrypoints so requests can - // reach Anthropic Fast Mode (speed:"fast"). Default off; only the listed - // Opus models are gated by the Anthropic binary KT() check. Schema is - // intentionally permissive on supportedModels so additional eligible model - // ids can be enabled without a schema bump. - claudeFastMode: z - .object({ - enabled: z.boolean().optional(), - supportedModels: z.array(z.string().max(200)).max(200).optional(), - }) - .optional(), - // Routing settings (#134) - fallbackStrategy: z.enum(ACCOUNT_FALLBACK_STRATEGY_VALUES).optional(), - wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(), - stickyRoundRobinLimit: z.number().int().min(0).max(1000).optional(), - requestRetry: z.number().int().min(0).max(10).optional(), - maxRetryIntervalSec: z.number().int().min(0).max(300).optional(), - maxBodySizeMb: z - .number() - .int() - .min(MIN_REQUEST_BODY_LIMIT_MB) - .max(MAX_REQUEST_BODY_LIMIT_MB) - .optional(), - // Auto intent classifier settings (multilingual routing) - intentDetectionEnabled: z.boolean().optional(), - intentSimpleMaxWords: z.number().int().min(1).max(500).optional(), - intentExtraCodeKeywords: z.array(z.string().max(100)).optional(), - intentExtraReasoningKeywords: z.array(z.string().max(100)).optional(), - intentExtraSimpleKeywords: z.array(z.string().max(100)).optional(), - // Protocol toggles (default: disabled) - mcpEnabled: z.boolean().optional(), - mcpTransport: z.enum(["stdio", "sse", "streamable-http"]).optional(), - a2aEnabled: z.boolean().optional(), - wsAuth: z.boolean().optional(), - // CLI Fingerprint compatibility (per-provider) - cliCompatProviders: z.array(z.string().max(100)).optional(), - // CC bridge transforms (issue #2260): config-driven pipeline that normalizes - // system blocks at the Claude Code bridge so any client (OpenCode, Cline, - // Cursor, Continue, raw API) ends up with classifier-correct structure. - ccBridgeTransforms: z - .object({ - enabled: z.boolean(), - pipeline: z - .array( - z.discriminatedUnion("kind", [ - z.object({ - kind: z.literal("drop_paragraph_if_contains"), - needles: z.array(z.string().max(500)).max(50), - caseSensitive: z.boolean().optional(), - }), - z.object({ - kind: z.literal("drop_paragraph_if_starts_with"), - prefixes: z.array(z.string().max(500)).max(50), - caseSensitive: z.boolean().optional(), - }), - z.object({ - kind: z.literal("replace_text"), - match: z.string().min(1).max(500), - replacement: z.string().max(500), - allOccurrences: z.boolean().optional(), - }), - z.object({ - kind: z.literal("replace_regex"), - pattern: z.string().min(1).max(500), - flags: z.string().max(10).optional(), - replacement: z.string().max(500), - }), - z.object({ - kind: z.literal("drop_block_if_contains"), - needles: z.array(z.string().max(500)).max(50), - }), - z.object({ - kind: z.literal("prepend_system_block"), - text: z.string().min(1).max(2000), - idempotencyKey: z.string().max(100).optional(), - }), - z.object({ - kind: z.literal("append_system_block"), - text: z.string().min(1).max(2000), - idempotencyKey: z.string().max(100).optional(), - }), - z.object({ - kind: z.literal("inject_billing_header"), - entrypoint: z.string().min(1).max(50), - versionFormat: z.enum(["ex-machina", "omniroute-daystamp"]), - cchAlgo: z.enum(["sha256-first-user", "xxhash64-body", "static-zero"]), - version: z.string().max(50).optional(), - }), - ]) - ) - .max(50), - }) - .optional(), - // System Transforms (issue #2260 v2): generic per-provider DSL covering - // native `claude`, `anthropic-compatible-cc-*` bridge, and any other - // provider key. Adds `obfuscate_words` op kind on top of the base set. - systemTransforms: z - .object({ - providers: z.record( - z.string().max(100), - z.object({ - enabled: z.boolean(), - pipeline: z - .array( - z.discriminatedUnion("kind", [ - z.object({ - kind: z.literal("drop_paragraph_if_contains"), - needles: z.array(z.string().max(500)).max(50), - caseSensitive: z.boolean().optional(), - }), - z.object({ - kind: z.literal("drop_paragraph_if_starts_with"), - prefixes: z.array(z.string().max(500)).max(50), - caseSensitive: z.boolean().optional(), - }), - z.object({ - kind: z.literal("replace_text"), - match: z.string().min(1).max(500), - replacement: z.string().max(500), - allOccurrences: z.boolean().optional(), - }), - z.object({ - kind: z.literal("replace_regex"), - pattern: z.string().min(1).max(500), - flags: z.string().max(10).optional(), - replacement: z.string().max(500), - }), - z.object({ - kind: z.literal("drop_block_if_contains"), - needles: z.array(z.string().max(500)).max(50), - }), - z.object({ - kind: z.literal("prepend_system_block"), - text: z.string().min(1).max(2000), - idempotencyKey: z.string().max(100).optional(), - }), - z.object({ - kind: z.literal("append_system_block"), - text: z.string().min(1).max(2000), - idempotencyKey: z.string().max(100).optional(), - }), - z.object({ - kind: z.literal("inject_billing_header"), - entrypoint: z.string().min(1).max(50), - versionFormat: z.enum(["ex-machina", "omniroute-daystamp"]), - cchAlgo: z.enum(["sha256-first-user", "xxhash64-body", "static-zero"]), - version: z.string().max(50).optional(), - }), - z.object({ - kind: z.literal("obfuscate_words"), - words: z.array(z.string().max(100)).max(200), - targets: z - .array(z.enum(["system", "messages", "tools"])) - .max(3) - .optional(), - }), - ]) - ) - .max(50), - }) - ), - }) - .optional(), - // Strip provider/model prefix at proxy layer (e.g. "openai/gpt-4" → "gpt-4") - stripModelPrefix: z.boolean().optional(), - // Cache control preservation mode - alwaysPreserveClientCache: z.enum(["auto", "always", "never"]).optional(), - antigravitySignatureCacheMode: z.enum(signatureCacheModeValues).optional(), - // Adaptive Volume Routing - adaptiveVolumeRouting: z.boolean().optional(), - // Usage token buffer — safety margin added to reported prompt/input token counts. - // Prevents CLI tools from overrunning context windows. Set to 0 to disable. - usageTokenBuffer: z.number().int().min(0).max(50000).optional(), - // Custom CLI agent definitions for ACP - customAgents: z - .array( - z.object({ - id: z.string().max(50), - name: z.string().max(100), - binary: z.string().max(200), - versionCommand: z.string().max(300), - providerAlias: z.string().max(50), - spawnArgs: z.array(z.string().max(200)), - protocol: z.enum(["stdio", "http"]), - }) - ) - .optional(), - // SkillsMP marketplace API key - skillsmpApiKey: z.string().max(200).optional(), - // Active skills provider (single source of truth for skills page) - skillsProvider: z.enum(["skillsmp", "skillssh"]).optional(), - // models.dev sync settings - modelsDevSyncEnabled: z.boolean().optional(), - modelsDevSyncInterval: z.number().int().min(3600000).max(604800000).optional(), - // Vision Bridge settings - visionBridgeEnabled: z.boolean().optional(), - visionBridgeModel: z.string().max(200).optional(), - visionBridgePrompt: z.string().max(5000).optional(), - visionBridgeTimeout: z.number().int().min(1000).max(300000).optional(), - visionBridgeMaxImages: z.number().int().min(1).max(20).optional(), - // Missing settings - lkgpEnabled: z.boolean().optional(), - backgroundDegradation: z.unknown().optional(), - bruteForceProtection: z.boolean().optional(), - // Auto-routing settings - autoRoutingEnabled: z.boolean().optional(), - autoRoutingDefaultVariant: z - .enum(["lkgp", "coding", "fast", "cheap", "offline", "smart"]) - .optional(), - // LOCAL_ONLY manage-scope bypass policy (T-011). Kill-switch + per-prefix - // list, both DB-stored and hot-reloaded into `getAuthzBypassSnapshot()` on - // each PATCH. The prefix list MUST NOT include any spawn-capable path — - // enforced here and at runtime (GHSA-fhh6-4qxv-rpqj rationale). - localOnlyManageScopeBypassEnabled: z.boolean().optional(), - localOnlyManageScopeBypassPrefixes: z.array(z.string().startsWith("/")).max(20).optional(), - }) - .superRefine((data, ctx) => { - const prefixes = data.localOnlyManageScopeBypassPrefixes; - if (!prefixes) return; - for (let i = 0; i < prefixes.length; i++) { - const prefix = prefixes[i]; - // Reject prefixes that are the same as, child of, or PARENT of any - // spawn-capable prefix. The parent case catches e.g. `/api/cli-tools/` - // — a bypass on the parent would grant non-loopback access to the - // spawn-capable `/api/cli-tools/runtime/*` surface via path.startsWith. - if ( - SPAWN_CAPABLE_PREFIXES.some( - (spawn) => prefix === spawn || prefix.startsWith(spawn) || spawn.startsWith(prefix) +export const updateSettingsSchema = z.object({ + newPassword: z.string().min(1).max(200).optional(), + currentPassword: z.string().max(200).optional(), + theme: z.string().max(50).optional(), + language: z.string().max(10).optional(), + requireLogin: z.boolean().optional(), + enableSocks5Proxy: z.boolean().optional(), + instanceName: z.string().max(100).optional(), + customLogoUrl: z.string().max(2000).optional(), + customLogoBase64: z.string().max(100000).optional(), + customFaviconUrl: z.string().max(2000).optional(), + customFaviconBase64: z.string().max(50000).optional(), + corsOrigins: z.string().max(500).optional(), + cloudUrl: z.string().max(500).optional(), + baseUrl: z.string().max(500).optional(), + setupComplete: z.boolean().optional(), + blockedProviders: z.array(z.string().max(100)).optional(), + hideHealthCheckLogs: z.boolean().optional(), + hideEndpointCloudflaredTunnel: z.boolean().optional(), + hideEndpointTailscaleFunnel: z.boolean().optional(), + hideEndpointNgrokTunnel: z.boolean().optional(), + debugMode: z.boolean().optional(), + hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(), + sidebarSectionOrder: z + .array(z.enum(SIDEBAR_SECTIONS.map((s) => s.id) as [string, ...string[]])) + .optional(), + sidebarItemOrder: z.record(z.string(), z.array(z.string().max(100))).optional(), + sidebarActivePreset: z.enum(["all", "minimal", "developer", "admin"]).nullable().optional(), + comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(), + codexServiceTier: z + .object({ + enabled: z.boolean().optional(), + tier: z.enum(["default", "priority", "flex"]).optional(), + supportedModels: z.array(z.string().max(200)).max(200).optional(), + }) + .optional(), + // Claude Fast Mode: opt-in toggle that asks a paired CLIProxyAPI build + // (claude-fastmode-spoof) to rewrite SDK-shaped entrypoints so requests can + // reach Anthropic Fast Mode (speed:"fast"). Default off; only the listed + // Opus models are gated by the Anthropic binary KT() check. Schema is + // intentionally permissive on supportedModels so additional eligible model + // ids can be enabled without a schema bump. + claudeFastMode: z + .object({ + enabled: z.boolean().optional(), + supportedModels: z.array(z.string().max(200)).max(200).optional(), + }) + .optional(), + // Routing settings (#134) + fallbackStrategy: z.enum(ACCOUNT_FALLBACK_STRATEGY_VALUES).optional(), + wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(), + stickyRoundRobinLimit: z.number().int().min(0).max(1000).optional(), + requestRetry: z.number().int().min(0).max(10).optional(), + maxRetryIntervalSec: z.number().int().min(0).max(300).optional(), + maxBodySizeMb: z + .number() + .int() + .min(MIN_REQUEST_BODY_LIMIT_MB) + .max(MAX_REQUEST_BODY_LIMIT_MB) + .optional(), + // Auto intent classifier settings (multilingual routing) + intentDetectionEnabled: z.boolean().optional(), + intentSimpleMaxWords: z.number().int().min(1).max(500).optional(), + intentExtraCodeKeywords: z.array(z.string().max(100)).optional(), + intentExtraReasoningKeywords: z.array(z.string().max(100)).optional(), + intentExtraSimpleKeywords: z.array(z.string().max(100)).optional(), + // Protocol toggles (default: disabled) + mcpEnabled: z.boolean().optional(), + mcpTransport: z.enum(["stdio", "sse", "streamable-http"]).optional(), + a2aEnabled: z.boolean().optional(), + wsAuth: z.boolean().optional(), + // CLI Fingerprint compatibility (per-provider) + cliCompatProviders: z.array(z.string().max(100)).optional(), + // CC bridge transforms (issue #2260): config-driven pipeline that normalizes + // system blocks at the Claude Code bridge so any client (OpenCode, Cline, + // Cursor, Continue, raw API) ends up with classifier-correct structure. + ccBridgeTransforms: z + .object({ + enabled: z.boolean(), + pipeline: z + .array( + z.discriminatedUnion("kind", [ + z.object({ + kind: z.literal("drop_paragraph_if_contains"), + needles: z.array(z.string().max(500)).max(50), + caseSensitive: z.boolean().optional(), + }), + z.object({ + kind: z.literal("drop_paragraph_if_starts_with"), + prefixes: z.array(z.string().max(500)).max(50), + caseSensitive: z.boolean().optional(), + }), + z.object({ + kind: z.literal("replace_text"), + match: z.string().min(1).max(500), + replacement: z.string().max(500), + allOccurrences: z.boolean().optional(), + }), + z.object({ + kind: z.literal("replace_regex"), + pattern: z.string().min(1).max(500), + flags: z.string().max(10).optional(), + replacement: z.string().max(500), + }), + z.object({ + kind: z.literal("drop_block_if_contains"), + needles: z.array(z.string().max(500)).max(50), + }), + z.object({ + kind: z.literal("prepend_system_block"), + text: z.string().min(1).max(2000), + idempotencyKey: z.string().max(100).optional(), + }), + z.object({ + kind: z.literal("append_system_block"), + text: z.string().min(1).max(2000), + idempotencyKey: z.string().max(100).optional(), + }), + z.object({ + kind: z.literal("inject_billing_header"), + entrypoint: z.string().min(1).max(50), + versionFormat: z.enum(["ex-machina", "omniroute-daystamp"]), + cchAlgo: z.enum(["sha256-first-user", "xxhash64-body", "static-zero"]), + version: z.string().max(50).optional(), + }), + ]) ) - ) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ["localOnlyManageScopeBypassPrefixes", i], - message: `BYPASS_PREFIX_NOT_ALLOWED: ${prefix} is spawn-capable and cannot be bypassed`, - params: { code: "BYPASS_PREFIX_NOT_ALLOWED", prefix }, - }); - } - } - }); + .max(50), + }) + .optional(), + // System Transforms (issue #2260 v2): generic per-provider DSL covering + // native `claude`, `anthropic-compatible-cc-*` bridge, and any other + // provider key. Adds `obfuscate_words` op kind on top of the base set. + systemTransforms: z + .object({ + providers: z.record( + z.string().max(100), + z.object({ + enabled: z.boolean(), + pipeline: z + .array( + z.discriminatedUnion("kind", [ + z.object({ + kind: z.literal("drop_paragraph_if_contains"), + needles: z.array(z.string().max(500)).max(50), + caseSensitive: z.boolean().optional(), + }), + z.object({ + kind: z.literal("drop_paragraph_if_starts_with"), + prefixes: z.array(z.string().max(500)).max(50), + caseSensitive: z.boolean().optional(), + }), + z.object({ + kind: z.literal("replace_text"), + match: z.string().min(1).max(500), + replacement: z.string().max(500), + allOccurrences: z.boolean().optional(), + }), + z.object({ + kind: z.literal("replace_regex"), + pattern: z.string().min(1).max(500), + flags: z.string().max(10).optional(), + replacement: z.string().max(500), + }), + z.object({ + kind: z.literal("drop_block_if_contains"), + needles: z.array(z.string().max(500)).max(50), + }), + z.object({ + kind: z.literal("prepend_system_block"), + text: z.string().min(1).max(2000), + idempotencyKey: z.string().max(100).optional(), + }), + z.object({ + kind: z.literal("append_system_block"), + text: z.string().min(1).max(2000), + idempotencyKey: z.string().max(100).optional(), + }), + z.object({ + kind: z.literal("inject_billing_header"), + entrypoint: z.string().min(1).max(50), + versionFormat: z.enum(["ex-machina", "omniroute-daystamp"]), + cchAlgo: z.enum(["sha256-first-user", "xxhash64-body", "static-zero"]), + version: z.string().max(50).optional(), + }), + z.object({ + kind: z.literal("obfuscate_words"), + words: z.array(z.string().max(100)).max(200), + targets: z + .array(z.enum(["system", "messages", "tools"])) + .max(3) + .optional(), + }), + ]) + ) + .max(50), + }) + ), + }) + .optional(), + // Strip provider/model prefix at proxy layer (e.g. "openai/gpt-4" → "gpt-4") + stripModelPrefix: z.boolean().optional(), + // Cache control preservation mode + alwaysPreserveClientCache: z.enum(["auto", "always", "never"]).optional(), + antigravitySignatureCacheMode: z.enum(signatureCacheModeValues).optional(), + // Adaptive Volume Routing + adaptiveVolumeRouting: z.boolean().optional(), + // Usage token buffer — safety margin added to reported prompt/input token counts. + // Prevents CLI tools from overrunning context windows. Set to 0 to disable. + usageTokenBuffer: z.number().int().min(0).max(50000).optional(), + // Custom CLI agent definitions for ACP + customAgents: z + .array( + z.object({ + id: z.string().max(50), + name: z.string().max(100), + binary: z.string().max(200), + versionCommand: z.string().max(300), + providerAlias: z.string().max(50), + spawnArgs: z.array(z.string().max(200)), + protocol: z.enum(["stdio", "http"]), + }) + ) + .optional(), + // SkillsMP marketplace API key + skillsmpApiKey: z.string().max(200).optional(), + // Active skills provider (single source of truth for skills page) + skillsProvider: z.enum(["skillsmp", "skillssh"]).optional(), + // models.dev sync settings + modelsDevSyncEnabled: z.boolean().optional(), + modelsDevSyncInterval: z.number().int().min(3600000).max(604800000).optional(), + // Vision Bridge settings + visionBridgeEnabled: z.boolean().optional(), + visionBridgeModel: z.string().max(200).optional(), + visionBridgePrompt: z.string().max(5000).optional(), + visionBridgeTimeout: z.number().int().min(1000).max(300000).optional(), + visionBridgeMaxImages: z.number().int().min(1).max(20).optional(), + // Missing settings + lkgpEnabled: z.boolean().optional(), + backgroundDegradation: z.unknown().optional(), + bruteForceProtection: z.boolean().optional(), + // Auto-routing settings + autoRoutingEnabled: z.boolean().optional(), + autoRoutingDefaultVariant: z + .enum(["lkgp", "coding", "fast", "cheap", "offline", "smart"]) + .optional(), +}); export const databaseSettingsSchema = z.object( { diff --git a/src/types/settings.ts b/src/types/settings.ts index 3c70c4fcc0..69f6ea1d70 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -1,4 +1,9 @@ -import type { HideableSidebarItemId } from "@/shared/constants/sidebarVisibility"; +import type { + HideableSidebarItemId, + SidebarItemOrder, + SidebarPresetId, + SidebarSectionId, +} from "@/shared/constants/sidebarVisibility"; import type { ResilienceSettings } from "@/lib/resilience/settings"; import type { AccountFallbackStrategyValue, @@ -28,6 +33,9 @@ export interface Settings { showQuickStartOnHome?: boolean; showProviderTopologyOnHome?: boolean; hiddenSidebarItems?: HideableSidebarItemId[]; + sidebarSectionOrder?: SidebarSectionId[]; + sidebarItemOrder?: SidebarItemOrder; + sidebarActivePreset?: SidebarPresetId; resilienceSettings?: ResilienceSettings; // LOCAL_ONLY manage-scope bypass policy (DB-stored, hot-reloaded by // `applyRuntimeSettings` → `applyAuthzBypassSection`). The route guard diff --git a/tests/unit/sidebar-customization.test.ts b/tests/unit/sidebar-customization.test.ts new file mode 100644 index 0000000000..405265d71b --- /dev/null +++ b/tests/unit/sidebar-customization.test.ts @@ -0,0 +1,152 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const sidebarVisibility = await import("../../src/shared/constants/sidebarVisibility.ts"); + +const { + HIDEABLE_SIDEBAR_ITEM_IDS, + SIDEBAR_SECTIONS, + SIDEBAR_PRESETS, + applySectionOrder, + applyItemOrder, + normalizeHiddenSidebarItems, +} = sidebarVisibility; + +// ─── applySectionOrder ──────────────────────────────────────────────────────── + +test("applySectionOrder returns original order when order is empty", () => { + const sections = [...SIDEBAR_SECTIONS]; + const result = applySectionOrder(sections, []); + assert.deepEqual( + result.map((s) => s.id), + sections.map((s) => s.id) + ); +}); + +test("applySectionOrder reorders sections by provided list", () => { + const sections = [...SIDEBAR_SECTIONS].slice(0, 4); + const ids = sections.map((s) => s.id) as any[]; + const reversed = [...ids].reverse(); + const result = applySectionOrder(sections, reversed); + assert.deepEqual( + result.map((s) => s.id), + reversed + ); +}); + +test("applySectionOrder ignores unknown section IDs in order", () => { + const sections = [...SIDEBAR_SECTIONS].slice(0, 3); + const ids = sections.map((s) => s.id) as any[]; + const orderWithUnknown = ["totally-unknown-section" as any, ids[1], ids[0], ids[2]]; + const result = applySectionOrder(sections, orderWithUnknown); + // unknown ID is filtered; remaining IDs applied, then the rest appended + assert.equal(result[0].id, ids[1]); + assert.equal(result[1].id, ids[0]); + assert.equal(result[2].id, ids[2]); +}); + +test("applySectionOrder appends sections not in order list at end", () => { + const sections = [...SIDEBAR_SECTIONS].slice(0, 3); + const ids = sections.map((s) => s.id) as any[]; + // Only order the first two + const result = applySectionOrder(sections, [ids[2], ids[0]]); + assert.equal(result[0].id, ids[2]); + assert.equal(result[1].id, ids[0]); + assert.equal(result[2].id, ids[1]); // appended at end +}); + +// ─── applyItemOrder ─────────────────────────────────────────────────────────── + +test("applyItemOrder returns original children when order is empty", () => { + const section = SIDEBAR_SECTIONS.find((s) => s.id === "omni-proxy")!; + const children = [...section.children]; + const result = applyItemOrder(children, []); + assert.deepEqual(result.length, children.length); +}); + +test("applyItemOrder reorders items by provided list", () => { + const section = SIDEBAR_SECTIONS.find((s) => s.id === "help")!; + const children = [...section.children] as any[]; + const ids = children.map((c) => c.id); + const reversed = [...ids].reverse(); + const result = applyItemOrder(children, reversed) as any[]; + assert.deepEqual( + result.map((c) => c.id), + reversed + ); +}); + +test("applyItemOrder ignores unknown IDs in order list", () => { + const section = SIDEBAR_SECTIONS.find((s) => s.id === "help")!; + const children = [...section.children] as any[]; + const ids = children.map((c) => c.id); + const orderWithUnknown = ["ghost-item", ids[1], ids[0], ids[2]]; + const result = applyItemOrder(children, orderWithUnknown) as any[]; + assert.equal(result[0].id, ids[1]); + assert.equal(result[1].id, ids[0]); + assert.equal(result[2].id, ids[2]); +}); + +// ─── SIDEBAR_PRESETS ────────────────────────────────────────────────────────── + +test("SIDEBAR_PRESETS contains all four preset IDs", () => { + const ids = SIDEBAR_PRESETS.map((p) => p.id); + assert.ok(ids.includes("all"), "expected 'all' preset"); + assert.ok(ids.includes("minimal"), "expected 'minimal' preset"); + assert.ok(ids.includes("developer"), "expected 'developer' preset"); + assert.ok(ids.includes("admin"), "expected 'admin' preset"); +}); + +test("SIDEBAR_PRESETS all preset hiddenItems are valid HIDEABLE_SIDEBAR_ITEM_IDS", () => { + const validIds = new Set(HIDEABLE_SIDEBAR_ITEM_IDS); + for (const preset of SIDEBAR_PRESETS) { + for (const id of preset.hiddenItems) { + assert.ok(validIds.has(id as any), `Preset '${preset.id}' contains invalid item ID: '${id}'`); + } + } +}); + +test("SIDEBAR_PRESETS 'all' preset has no hidden items", () => { + const allPreset = SIDEBAR_PRESETS.find((p) => p.id === "all"); + assert.ok(allPreset, "expected 'all' preset to exist"); + assert.deepEqual(allPreset.hiddenItems, []); +}); + +test("SIDEBAR_PRESETS non-all presets have at least one hidden item", () => { + for (const preset of SIDEBAR_PRESETS.filter((p) => p.id !== "all")) { + assert.ok(preset.hiddenItems.length > 0, `Preset '${preset.id}' should hide at least one item`); + } +}); + +// ─── settings-sidebar ID ───────────────────────────────────────────────────── + +test("settings-sidebar is in HIDEABLE_SIDEBAR_ITEM_IDS", () => { + assert.ok( + HIDEABLE_SIDEBAR_ITEM_IDS.includes("settings-sidebar" as any), + "settings-sidebar should be hideable" + ); +}); + +test("settings-sidebar item is present in configuration section", () => { + const configSection = SIDEBAR_SECTIONS.find((s) => s.id === "configuration"); + assert.ok(configSection, "configuration section should exist"); + const items = configSection.children.flatMap((c) => + "type" in c && c.type === "group" ? c.items : [c as any] + ); + assert.ok( + items.some((item) => item.id === "settings-sidebar"), + "settings-sidebar should be in configuration section children" + ); +}); + +// ─── normalizeHiddenSidebarItems ───────────────────────────────────────────── + +test("normalizeHiddenSidebarItems accepts settings-sidebar", () => { + const result = normalizeHiddenSidebarItems(["settings-sidebar"]); + assert.deepEqual(result, ["settings-sidebar"]); +}); + +test("normalizeHiddenSidebarItems drops unknown IDs", () => { + const result = normalizeHiddenSidebarItems(["settings-sidebar", "ghost-id-xyz"]); + assert.deepEqual(result, ["settings-sidebar"]); +});