From ad1387d07663627978717ef66bc7ba45c67030a4 Mon Sep 17 00:00:00 2001 From: Chebotov Nickolay Date: Fri, 6 Feb 2026 11:26:32 +0300 Subject: [PATCH 1/5] feat(i18n): add Russian locale and enable 'ru' language; translate core keys to Russian --- src/i18n/index.ts | 4 +- src/i18n/locales/ru.json | 1113 ++++++++++++++++++++++++++++++++++++++ src/types/common.ts | 2 +- src/utils/language.ts | 9 +- 4 files changed, 1123 insertions(+), 5 deletions(-) create mode 100644 src/i18n/locales/ru.json diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 600d753..930e323 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -6,12 +6,14 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import zhCN from './locales/zh-CN.json'; import en from './locales/en.json'; +import ru from './locales/ru.json'; import { getInitialLanguage } from '@/utils/language'; i18n.use(initReactI18next).init({ resources: { 'zh-CN': { translation: zhCN }, - en: { translation: en } + en: { translation: en }, + ru: { translation: ru } }, lng: getInitialLanguage(), fallbackLng: 'zh-CN', diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json new file mode 100644 index 0000000..e627273 --- /dev/null +++ b/src/i18n/locales/ru.json @@ -0,0 +1,1113 @@ +{ + "common": { + "login": "Войти", + "logout": "Выйти", + "back": "Назад", + "cancel": "Отмена", + "confirm": "Подтвердить", + "save": "Сохранить", + "delete": "Удалить", + "edit": "Редактировать", + "add": "Добавить", + "update": "Обновить", + "refresh": "Обновить", + "close": "Закрыть", + "success": "Успешно", + "error": "Ошибка", + "info": "Информация", + "warning": "Внимание", + "loading": "Загрузка...", + "connecting": "Подключение...", + "connected": "Подключено", + "disconnected": "Отключено", + "connecting_status": "Подключение", + "connected_status": "Подключено", + "disconnected_status": "Отключено", + "yes": "Да", + "no": "Нет", + "not_set": "Не задано", + "optional": "Необязательно", + "required": "Обязательно", + "api_key": "Ключ", + "base_url": "Адрес", + "prefix": "Префикс", + "proxy_url": "Прокси", + "alias": "Псевдоним", + "failure": "Сбой", + "unknown_error": "Неизвестная ошибка", + "quota_update_required": "Пожалуйста, обновите CPA или проверьте наличие обновлений", + "quota_check_credential": "Пожалуйста, проверьте статус учётных данных", + "copy": "Копировать", + "custom_headers_label": "Пользовательские заголовки", + "custom_headers_hint": "Необязательно — HTTP-заголовки для отправки с запросом. Оставьте пустым для удаления.", + "custom_headers_add": "Добавить заголовок", + "custom_headers_key_placeholder": "Имя заголовка, например X-Custom-Header", + "custom_headers_value_placeholder": "Значение заголовка", + "model_name_placeholder": "Имя модели, напр. claude-3-5-sonnet-20241022", + "model_alias_placeholder": "Псевдоним модели (необязательно)" + }, + "title": { + "main": "CLI Proxy API Management Center", + "login": "CLI Proxy API Management Center", + "abbr": "CPAMC" + }, + "auto_login": { + "title": "Автовход...", + "message": "Пытаемся подключиться к серверу, используя сохранённые данные" + }, + "login": { + "subtitle": "Введите данные подключения, чтобы получить доступ к панели управления", + "connection_title": "Адрес подключения", + "connection_current": "Текущий URL", + "connection_auto_hint": "Система автоматически использует текущий URL для подключения", + "custom_connection_label": "Пользовательский URL подключения:", + "custom_connection_placeholder": "Напр.: https://example.com:8317", + "custom_connection_hint": "По умолчанию используется текущий URL. При необходимости замените его.", + "use_current_address": "Использовать текущий URL", + "remember_password_label": "Запомнить пароль", + "management_key_label": "Ключ управления:", + "management_key_placeholder": "Введите ключ управления", + "connect_button": "Подключиться", + "submit_button": "Войти", + "submitting": "Подключение...", + "error_title": "Ошибка входа", + "error_required": "Пожалуйста, заполните все данные подключения", + "error_invalid": "Подключение не удалось, проверьте адрес и ключ", + "error_network": "Сетевая ошибка, проверьте подключение или адрес сервера", + "error_timeout": "Время ожидания истекло, сервер не отвечает", + "error_unauthorized": "Аутентификация не удалась, неверный ключ управления", + "error_forbidden": "Доступ запрещён, недостаточно прав", + "error_not_found": "Неверный адрес сервера или интерфейс управления не включён", + "error_server": "Внутренняя ошибка сервера, попробуйте позже", + "error_cors": "Блокировка CORS, проверьте конфигурацию сервера", + "error_ssl": "Ошибка проверки SSL/TLS сертификата" + }, + "header": { + "check_connection": "Проверить подключение", + "refresh_all": "Обновить всё", + "logout": "Выйти" + }, + "connection": { + "title": "Информация о подключении", + "server_address": "Адрес сервера:", + "management_key": "Ключ управления:", + "status": "Статус подключения:" + }, + "nav": { + "dashboard": "Панель", + "basic_settings": "Основные настройки", + "api_keys": "API ключи", + "ai_providers": "Поставщики AI", + "auth_files": "Файлы аутентификации", + "oauth": "OAuth вход", + "quota_management": "Управление квотами", + "usage_stats": "Статистика использования", + "config_management": "Панель конфигурации", + "logs": "Просмотр логов", + "system_info": "Информация системы" + }, + "dashboard": { + "title": "Dashboard", + "subtitle": "Welcome to CLI Proxy API Management Center", + "openai_providers": "OpenAI Providers", + "quick_actions": "Quick Actions", + "current_config": "Current Configuration", + "management_keys": "Management Keys", + "provider_keys_detail": "G:{{gemini}} C:{{codex}} Cl:{{claude}} O:{{openai}}", + "oauth_credentials": "OAuth Credentials", + "usage_overview": "Usage Overview", + "total_requests": "Total Requests", + "total_tokens": "Total Tokens", + "rpm_30min": "RPM (30min)", + "tpm_30min": "TPM (30min)", + "models_used": "Models Used", + "no_usage_data": "No usage data available", + "view_detailed_usage": "View Detailed Stats", + "edit_settings": "Edit Settings", + "available_models": "Available Models", + "available_models_desc": "Total models from all providers" + }, + "basic_settings": { + "title": "Basic Settings", + "debug_title": "Debug Mode", + "debug_enable": "Enable Debug Mode", + "proxy_title": "Proxy Settings", + "proxy_url_label": "Proxy URL:", + "proxy_url_placeholder": "e.g.: socks5://user:pass@127.0.0.1:1080/", + "proxy_update": "Update", + "proxy_clear": "Clear", + "retry_title": "Request Retry", + "retry_count_label": "Retry Count:", + "retry_update": "Update", + "quota_title": "Quota Exceeded Behavior", + "quota_switch_project": "Auto Switch Project", + "quota_switch_preview": "Switch to Preview Model", + "usage_statistics_title": "Usage Statistics", + "usage_statistics_enable": "Enable usage statistics", + "logging_title": "Logging", + "logging_to_file_enable": "Enable logging to file", + "logs_max_total_size_title": "Log Size Limit", + "logs_max_total_size_label": "Total log size cap (MB):", + "logs_max_total_size_hint": "Set to 0 to disable the limit.", + "logs_max_total_size_update": "Update", + "request_log_title": "Request Logging", + "request_log_enable": "Enable request logging", + "request_log_warning": "Keep this off unless you need detailed troubleshooting.", + "force_model_prefix_enable": "Force model prefix", + "ws_auth_title": "WebSocket Authentication", + "ws_auth_enable": "Require auth for /ws/*", + "routing_title": "Routing Strategy", + "routing_strategy_label": "Routing strategy:", + "routing_strategy_hint": "round-robin cycles through keys; fill-first prioritizes the first available key.", + "routing_strategy_update": "Update", + "routing_strategy_round_robin": "round-robin (cycle)", + "routing_strategy_fill_first": "fill-first (prioritize)" + }, + "api_keys": { + "title": "API Keys Management", + "proxy_auth_title": "Proxy Service Authentication Keys", + "add_button": "Add Key", + "empty_title": "No API Keys", + "empty_desc": "Click the button above to add the first key", + "item_title": "API Key", + "add_modal_title": "Add API Key", + "add_modal_key_label": "API Key:", + "add_modal_key_placeholder": "Please enter API key", + "edit_modal_title": "Edit API Key", + "edit_modal_key_label": "API Key:", + "delete_confirm": "Are you sure you want to delete this API key?" + }, + "ai_providers": { + "title": "AI Providers Configuration", + "gemini_title": "Gemini API Keys", + "gemini_add_button": "Add Key", + "gemini_empty_title": "No Gemini Keys", + "gemini_empty_desc": "Click the button above to add the first key", + "gemini_item_title": "Gemini Key", + "gemini_add_modal_title": "Add Gemini API Key", + "gemini_add_modal_key_label": "API Keys:", + "gemini_add_modal_key_placeholder": "Enter Gemini API key", + "gemini_add_modal_key_hint": "Add keys one by one and optionally specify a Base URL.", + "gemini_keys_add_btn": "Add Key", + "gemini_base_url_label": "Base URL (Optional):", + "gemini_base_url_placeholder": "e.g.: https://generativelanguage.googleapis.com", + "gemini_edit_modal_title": "Edit Gemini API Key", + "gemini_edit_modal_key_label": "API Key:", + "gemini_delete_confirm": "Are you sure you want to delete this Gemini key?", + "excluded_models_label": "Excluded models (optional):", + "excluded_models_placeholder": "Comma or newline separated, e.g. gemini-1.5-pro, gemini-1.5-flash", + "excluded_models_hint": "Leave empty to allow all models; values are trimmed and deduplicated automatically.", + "excluded_models_count": "Excluding {{count}} models", + "prefix_label": "Prefix (Optional):", + "prefix_placeholder": "e.g.: team-a", + "prefix_hint": "When set, call models as prefix/ to target this entry.", + "config_toggle_label": "Enabled", + "config_disabled_badge": "Disabled", + "codex_title": "Codex API Configuration", + "codex_add_button": "Add Configuration", + "codex_empty_title": "No Codex Configuration", + "codex_empty_desc": "Click the button above to add the first configuration", + "codex_item_title": "Codex Configuration", + "codex_add_modal_title": "Add Codex API Configuration", + "codex_add_modal_key_label": "API Key:", + "codex_add_modal_key_placeholder": "Please enter Codex API key", + "codex_add_modal_url_label": "Base URL (Required):", + "codex_add_modal_url_placeholder": "e.g.: https://api.example.com", + "codex_add_modal_proxy_label": "Proxy URL (Optional):", + "codex_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080", + "codex_edit_modal_title": "Edit Codex API Configuration", + "codex_edit_modal_key_label": "API Key:", + "codex_edit_modal_url_label": "Base URL (Required):", + "codex_edit_modal_proxy_label": "Proxy URL (Optional):", + "codex_delete_confirm": "Are you sure you want to delete this Codex configuration?", + "claude_title": "Claude API Configuration", + "claude_add_button": "Add Configuration", + "claude_empty_title": "No Claude Configuration", + "claude_empty_desc": "Click the button above to add the first configuration", + "claude_item_title": "Claude Configuration", + "claude_add_modal_title": "Add Claude API Configuration", + "claude_add_modal_key_label": "API Key:", + "claude_add_modal_key_placeholder": "Please enter Claude API key", + "claude_add_modal_url_label": "Base URL (Optional):", + "claude_add_modal_url_placeholder": "e.g.: https://api.anthropic.com", + "claude_add_modal_proxy_label": "Proxy URL (Optional):", + "claude_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080", + "claude_edit_modal_title": "Edit Claude API Configuration", + "claude_edit_modal_key_label": "API Key:", + "claude_edit_modal_url_label": "Base URL (Optional):", + "claude_edit_modal_proxy_label": "Proxy URL (Optional):", + "claude_delete_confirm": "Are you sure you want to delete this Claude configuration?", + "claude_models_label": "Custom Models (Optional):", + "claude_models_hint": "Leave empty to allow all models, or add name[, alias] entries to limit/alias them.", + "claude_models_add_btn": "Add Model", + "claude_models_count": "Models Count", + "vertex_title": "Vertex API Configuration", + "vertex_add_button": "Add Configuration", + "vertex_empty_title": "No Vertex Configuration", + "vertex_empty_desc": "Click the button above to add the first configuration", + "vertex_item_title": "Vertex Configuration", + "vertex_add_modal_title": "Add Vertex API Configuration", + "vertex_add_modal_key_label": "API Key:", + "vertex_add_modal_key_placeholder": "Please enter Vertex API key", + "vertex_add_modal_url_label": "Base URL (Required):", + "vertex_add_modal_url_placeholder": "e.g.: https://example.com/api", + "vertex_add_modal_proxy_label": "Proxy URL (Optional):", + "vertex_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080", + "vertex_edit_modal_title": "Edit Vertex API Configuration", + "vertex_edit_modal_key_label": "API Key:", + "vertex_edit_modal_url_label": "Base URL (Required):", + "vertex_edit_modal_proxy_label": "Proxy URL (Optional):", + "vertex_delete_confirm": "Are you sure you want to delete this Vertex configuration?", + "vertex_models_label": "Model aliases (alias required):", + "vertex_models_add_btn": "Add Mapping", + "vertex_models_hint": "Each alias needs both the original model and the alias.", + "vertex_models_count": "Alias count", + "ampcode_title": "Amp CLI Integration (ampcode)", + "ampcode_modal_title": "Configure Ampcode", + "ampcode_upstream_url_label": "Upstream URL", + "ampcode_upstream_url_placeholder": "e.g. https://ampcode.com", + "ampcode_upstream_url_hint": "Optional. Leave empty to use the default/auto-discovered control plane URL.", + "ampcode_upstream_api_key_label": "Upstream API Key (Amp Official)", + "ampcode_upstream_api_key_placeholder": "Enter sk-amp... (leave empty to keep current)", + "ampcode_upstream_api_key_hint": "Optional. Leaving it empty will not change the current Amp official key. Use the button below to clear it.", + "ampcode_upstream_api_key_current": "Current Amp official key: {{key}}", + "ampcode_clear_upstream_api_key": "Clear official key", + "ampcode_clear_upstream_api_key_confirm": "Are you sure you want to clear the Ampcode upstream API key (Amp official)?", + "ampcode_force_model_mappings_label": "Force model mappings", + "ampcode_force_model_mappings_hint": "When enabled, mappings override local API-key availability checks.", + "ampcode_model_mappings_label": "Model mappings (from → to)", + "ampcode_model_mappings_hint": "Rewrites model names in Amp requests. Leave empty to disable mappings.", + "ampcode_model_mappings_add_btn": "Add mapping", + "ampcode_model_mappings_from_placeholder": "from model (source)", + "ampcode_model_mappings_to_placeholder": "to model (target)", + "ampcode_model_mappings_count": "Mappings Count", + "ampcode_mappings_overwrite_confirm": "Existing mappings could not be loaded. Continuing may overwrite or clear them. Continue?", + "openai_title": "OpenAI Compatible Providers", + "openai_add_button": "Add Provider", + "openai_empty_title": "No OpenAI Compatible Providers", + "openai_empty_desc": "Click the button above to add the first provider", + "openai_add_modal_title": "Add OpenAI Compatible Provider", + "openai_add_modal_name_label": "Provider Name:", + "openai_add_modal_name_placeholder": "e.g.: openrouter", + "openai_add_modal_url_label": "Base URL:", + "openai_add_modal_url_placeholder": "e.g.: https://openrouter.ai/api/v1", + "openai_add_modal_keys_label": "API Keys", + "openai_edit_modal_keys_label": "API Keys", + "openai_keys_hint": "Add each key separately with an optional proxy URL to keep things organized.", + "openai_keys_add_btn": "Add Key", + "openai_key_placeholder": "sk-... key", + "openai_proxy_placeholder": "Optional proxy URL (e.g. socks5://...)", + "openai_add_modal_models_label": "Model List (name[, alias] one per line):", + "openai_models_hint": "Example: gpt-4o-mini or moonshotai/kimi-k2:free, kimi-k2", + "openai_model_name_placeholder": "Model name, e.g. moonshotai/kimi-k2:free", + "openai_model_alias_placeholder": "Model alias (optional)", + "openai_models_add_btn": "Add Model", + "openai_models_fetch_button": "Fetch via /models", + "openai_models_fetch_title": "Pick Models from /models", + "openai_models_fetch_hint": "Call the /models endpoint using the Base URL above, sending the first API key as Bearer plus custom headers.", + "openai_models_fetch_url_label": "Request URL", + "openai_models_fetch_refresh": "Refresh", + "openai_models_fetch_loading": "Fetching models from /models...", + "openai_models_fetch_empty": "No models returned. Please check the endpoint or auth.", + "openai_models_fetch_error": "Failed to fetch models", + "openai_models_fetch_back": "Back to edit", + "openai_models_fetch_apply": "Add selected models", + "openai_models_search_label": "Search models", + "openai_models_search_placeholder": "Filter by name, alias, or description", + "openai_models_search_empty": "No models match your search. Try a different keyword.", + "openai_models_fetch_invalid_url": "Please enter a valid Base URL first", + "openai_models_fetch_added": "{{count}} new models added", + "openai_edit_modal_title": "Edit OpenAI Compatible Provider", + "openai_edit_modal_name_label": "Provider Name:", + "openai_edit_modal_url_label": "Base URL:", + "openai_edit_modal_models_label": "Model List (name[, alias] one per line):", + "openai_delete_confirm": "Are you sure you want to delete this OpenAI provider?", + "openai_keys_count": "Keys Count", + "openai_models_count": "Models Count", + "openai_test_title": "Connection Test", + "openai_test_hint": "Send a /chat/completions request with the current settings to verify availability.", + "openai_test_model_placeholder": "Model to test", + "openai_test_action": "Run Test", + "openai_test_running": "Sending test request...", + "openai_test_timeout": "Test request timed out after {{seconds}} seconds.", + "openai_test_success": "Test succeeded. The model responded.", + "openai_test_failed": "Test failed", + "openai_test_select_placeholder": "Choose from current models", + "openai_test_select_empty": "No models configured. Add models first" + }, + "auth_files": { + "title": "Auth Files Management", + "title_section": "Auth Files", + "description": "Manage all CLI Proxy JSON auth files here (e.g. Qwen, Gemini, Vertex). Uploading a credential immediately enables the corresponding AI integration.", + "upload_button": "Upload File", + "delete_all_button": "Delete All", + "empty_title": "No Auth Files", + "empty_desc": "Click the button above to upload the first file", + "search_empty_title": "No matching files", + "search_empty_desc": "Try changing the filters or clearing the search box.", + "file_size": "Size", + "file_modified": "Modified", + "download_button": "Download", + "delete_button": "Delete", + "delete_confirm": "Are you sure you want to delete file", + "delete_all_confirm": "Are you sure you want to delete all auth files? This operation cannot be undone!", + "delete_filtered_confirm": "Are you sure you want to delete all {{type}} auth files? This operation cannot be undone!", + "upload_error_json": "Only JSON files are allowed", + "upload_error_size": "File size cannot exceed {{maxSize}}", + "upload_success": "File uploaded successfully", + "download_success": "File downloaded successfully", + "delete_success": "File deleted successfully", + "delete_all_success": "Successfully deleted", + "delete_filtered_success": "Deleted {{count}} {{type}} auth files successfully", + "delete_filtered_partial": "{{type}} auth files deletion finished: {{success}} succeeded, {{failed}} failed", + "delete_filtered_none": "No deletable auth files under the current filter ({{type}})", + "files_count": "files", + "pagination_prev": "Previous", + "pagination_next": "Next", + "pagination_info": "Page {{current}} / {{total}} · {{count}} files", + "search_label": "Search configs", + "search_placeholder": "Filter by name, type, or provider", + "page_size_label": "Per page", + "page_size_unit": "items", + "view_mode_paged": "Paged", + "view_mode_all": "Show all", + "too_many_files_warning": "Too many credentials. Showing all may cause performance issues, please use paged view.", + "filter_all": "All", + "filter_qwen": "Qwen", + "filter_gemini": "Gemini", + "filter_gemini-cli": "GeminiCLI", + "filter_aistudio": "AIStudio", + "filter_claude": "Claude", + "filter_codex": "Codex", + "filter_antigravity": "Antigravity", + "filter_iflow": "iFlow", + "filter_vertex": "Vertex", + "filter_empty": "Empty", + "filter_unknown": "Other", + "type_qwen": "Qwen", + "type_gemini": "Gemini", + "type_gemini-cli": "GeminiCLI", + "type_aistudio": "AIStudio", + "type_claude": "Claude", + "type_codex": "Codex", + "type_antigravity": "Antigravity", + "type_iflow": "iFlow", + "type_vertex": "Vertex", + "type_empty": "Empty", + "type_unknown": "Other", + "type_virtual": "Virtual auth file", + "models_button": "Models", + "models_title": "Supported models", + "models_loading": "Loading model list...", + "models_empty": "No available models for this credential", + "models_empty_desc": "This credential may not be loaded by the server yet, or no models are bound to it.", + "models_unsupported": "This feature is not supported in the current version", + "models_unsupported_desc": "Please update CLI Proxy API to the latest version and try again", + "models_excluded_badge": "Excluded", + "models_excluded_hint": "This model is excluded by OAuth", + "status_toggle_label": "Enabled", + "status_enabled_success": "\"{{name}}\" enabled", + "status_disabled_success": "\"{{name}}\" disabled", + "prefix_proxy_button": "Edit prefix/proxy_url", + "prefix_proxy_loading": "Loading credential...", + "prefix_proxy_source_label": "Credential JSON", + "prefix_label": "prefix", + "proxy_url_label": "proxy_url", + "prefix_placeholder": "", + "proxy_url_placeholder": "socks5://username:password@proxy_ip:port/", + "prefix_proxy_invalid_json": "This credential is not a JSON object and cannot be edited.", + "prefix_proxy_saved_success": "Updated \"{{name}}\" successfully", + "card_tools_title": "Tools", + "quota_refresh_single": "Refresh quota", + "quota_refresh_hint": "Refresh quota for this credential only", + "quota_refresh_success": "Quota refreshed for \"{{name}}\"", + "quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}" + }, + "antigravity_quota": { + "title": "Antigravity Quota", + "empty_title": "No Antigravity Auth Files", + "empty_desc": "Upload an Antigravity credential to view remaining quota.", + "idle": "Not loaded. Click Refresh Button.", + "loading": "Loading quota...", + "load_failed": "Failed to load quota: {{message}}", + "missing_auth_index": "Auth file missing auth_index", + "empty_models": "No quota data available", + "refresh_button": "Refresh Quota", + "fetch_all": "Fetch All" + }, + "codex_quota": { + "title": "Codex Quota", + "empty_title": "No Codex Auth Files", + "empty_desc": "Upload a Codex credential to view quota.", + "idle": "Not loaded. Click Refresh Button.", + "loading": "Loading quota...", + "load_failed": "Failed to load quota: {{message}}", + "missing_auth_index": "Auth file missing auth_index", + "missing_account_id": "Codex credential missing ChatGPT account ID", + "empty_windows": "No quota data available", + "no_access": "This credential has no Codex access (plan: free).", + "refresh_button": "Refresh Quota", + "fetch_all": "Fetch All", + "primary_window": "5-hour limit", + "secondary_window": "Weekly limit", + "code_review_primary_window": "Code review 5-hour limit", + "code_review_secondary_window": "Code review weekly limit", + "plan_label": "Plan", + "plan_plus": "Plus", + "plan_team": "Team", + "plan_free": "Free" + }, + "gemini_cli_quota": { + "title": "Gemini CLI Quota", + "empty_title": "No Gemini CLI Auth Files", + "empty_desc": "Upload a Gemini CLI credential to view remaining quota.", + "idle": "Not loaded. Click Refresh Button.", + "loading": "Loading quota...", + "load_failed": "Failed to load quota: {{message}}", + "missing_auth_index": "Auth file missing auth_index", + "missing_project_id": "Gemini CLI credential missing project ID", + "empty_buckets": "No quota data available", + "refresh_button": "Refresh Quota", + "fetch_all": "Fetch All", + "remaining_amount": "Remaining {{count}}" + }, + "vertex_import": { + "title": "Vertex JSON Login", + "description": "Upload a Google service account JSON to store it as auth-dir/vertex-.json using the same rules as the CLI vertex-import helper.", + "location_label": "Region (optional)", + "location_placeholder": "us-central1", + "location_hint": "Leave empty to use the default region us-central1.", + "file_label": "Service account key JSON", + "file_hint": "Only Google Cloud service account key JSON files are accepted.", + "file_placeholder": "No file selected", + "choose_file": "Choose File", + "import_button": "Import Vertex Credential", + "file_required": "Select a .json credential file first", + "success": "Vertex credential imported successfully", + "result_title": "Credential saved", + "result_project": "Project ID", + "result_email": "Service account", + "result_location": "Region", + "result_file": "Persisted file" + }, + "oauth_excluded": { + "title": "OAuth Excluded Models", + "description": "Per-provider exclusions are shown as cards; click edit to adjust. Wildcards * are supported and the scope follows the auth file filter.", + "add": "Add Exclusion", + "add_title": "Add provider exclusion", + "edit_title": "Edit exclusions for {{provider}}", + "refresh": "Refresh", + "refreshing": "Refreshing...", + "provider_label": "Provider", + "provider_auto": "Follow current filter", + "provider_placeholder": "e.g. gemini-cli", + "provider_hint": "Defaults to the current filter; pick an existing provider or type a new name.", + "models_label": "Models to exclude", + "models_loading": "Loading models...", + "models_unsupported": "Current CPA version does not support fetching model lists.", + "models_loaded": "{{count}} models loaded. Check the models to exclude.", + "no_models_available": "No models available for this provider.", + "save": "Save/Update", + "saving": "Saving...", + "save_success": "Excluded models updated", + "save_failed": "Failed to update excluded models", + "delete": "Delete Provider", + "delete_confirm": "Delete the exclusion list for {{provider}}?", + "delete_success": "Exclusion list removed", + "delete_failed": "Failed to delete exclusion list", + "deleting": "Deleting...", + "no_models": "No excluded models", + "model_count": "{{count}} models excluded", + "list_empty_all": "No exclusions yet—use “Add Exclusion” to create one.", + "list_empty_filtered": "No exclusions in this scope; click “Add Exclusion” to add.", + "disconnected": "Connect to the server to view exclusions", + "load_failed": "Failed to load exclusion list", + "provider_required": "Please enter a provider first", + "scope_all": "Scope: All providers", + "scope_provider": "Scope: {{provider}}", + "upgrade_required": "This feature requires a newer CLI Proxy API (CPA) version. Please upgrade.", + "upgrade_required_title": "Please upgrade CLI Proxy API", + "upgrade_required_desc": "The current server does not support the OAuth excluded models API. Please upgrade to the latest CLI Proxy API (CPA) version." + }, + "oauth_model_alias": { + "title": "OAuth Model Aliases", + "add": "Add Alias", + "add_title": "Add provider model aliases", + "provider_label": "Provider", + "provider_placeholder": "e.g. gemini-cli / vertex", + "provider_hint": "Defaults to the current filter; pick an existing provider or type a new name.", + "model_source_loading": "Loading models...", + "model_source_unsupported": "The current CPA version does not support fetching model lists (manual input still works).", + "model_source_loaded": "{{count}} models loaded. Use the dropdown in 'Source model name', or type custom values. Saving an empty list removes that provider. Enable 'Keep original' to keep the original name while adding the alias.", + "alias_label": "Model aliases", + "alias_name_placeholder": "Source model name", + "alias_placeholder": "Alias (required)", + "alias_fork_label": "Keep original", + "add_alias": "Add alias", + "save": "Save/Update", + "save_success": "Model aliases updated", + "save_failed": "Failed to update model aliases", + "delete": "Delete Provider", + "delete_confirm": "Delete model aliases for {{provider}}?", + "delete_link_title": "Unlink mapping", + "delete_link_confirm": "Unlink mapping from {{sourceModel}} ({{provider}}) to alias {{alias}}?", + "delete_alias_title": "Delete Alias", + "delete_alias_confirm": "Delete alias {{alias}} and unmap all associated models?", + "delete_success": "Model aliases removed", + "delete_failed": "Failed to delete model aliases", + "no_models": "No model aliases", + "model_count": "{{count}} aliases", + "list_empty_all": "No model aliases yet—use “Add Alias” to create one.", + "chart_title": "All mappings overview", + "diagram_providers": "Providers", + "diagram_source_models": "Source Models", + "diagram_aliases": "Aliases", + "diagram_expand": "Expand", + "diagram_collapse": "Collapse", + "diagram_add_alias": "Add Alias", + "diagram_rename": "Rename", + "diagram_rename_alias_title": "Rename alias", + "diagram_rename_alias_label": "New alias name", + "diagram_rename_placeholder": "Enter alias name...", + "diagram_delete_link": "Unlink from {{provider}} / {{name}}", + "diagram_delete_alias": "Delete alias", + "diagram_please_enter_alias": "Please enter an alias name.", + "diagram_alias_exists": "This alias already exists.", + "diagram_add_alias_title": "Add alias", + "diagram_add_alias_label": "Alias name", + "diagram_add_placeholder": "Enter new alias name...", + "diagram_rename_btn": "Rename", + "diagram_add_btn": "Add", + "diagram_settings": "Settings", + "diagram_settings_title": "Alias settings — {{alias}}", + "diagram_settings_source_title": "Source model settings", + "diagram_settings_empty": "No mappings for this alias yet.", + "diagram_tap_hint": "On touch devices: tap a source model, then tap an alias to link.", + "view_mode": "View mode", + "view_mode_diagram": "Diagram", + "view_mode_list": "List", + "provider_required": "Please enter a provider first", + "upgrade_required": "This feature requires a newer CLI Proxy API (CPA) version. Please upgrade.", + "upgrade_required_title": "Please upgrade CLI Proxy API", + "upgrade_required_desc": "The current server does not support the OAuth model aliases API. Please upgrade to the latest CLI Proxy API (CPA) version." + }, + "auth_login": { + "codex_oauth_title": "Codex OAuth", + "codex_oauth_button": "Start Codex Login", + "codex_oauth_hint": "Login to Codex service through OAuth flow, automatically obtain and save authentication files.", + "codex_oauth_url_label": "Authorization URL:", + "codex_open_link": "Open Link", + "codex_copy_link": "Copy Link", + "codex_oauth_status_waiting": "Waiting for authentication...", + "codex_oauth_status_success": "Authentication successful!", + "codex_oauth_status_error": "Authentication failed:", + "codex_oauth_start_error": "Failed to start Codex OAuth:", + "codex_oauth_polling_error": "Failed to check authentication status:", + "anthropic_oauth_title": "Anthropic OAuth", + "anthropic_oauth_button": "Start Anthropic Login", + "anthropic_oauth_hint": "Login to Anthropic (Claude) service through OAuth flow, automatically obtain and save authentication files.", + "anthropic_oauth_url_label": "Authorization URL:", + "anthropic_open_link": "Open Link", + "anthropic_copy_link": "Copy Link", + "anthropic_oauth_status_waiting": "Waiting for authentication...", + "anthropic_oauth_status_success": "Authentication successful!", + "anthropic_oauth_status_error": "Authentication failed:", + "anthropic_oauth_start_error": "Failed to start Anthropogenic OAuth:", + "anthropic_oauth_polling_error": "Failed to check authentication status:", + "antigravity_oauth_title": "Antigravity OAuth", + "antigravity_oauth_button": "Start Antigravity Login", + "antigravity_oauth_hint": "Login to Antigravity service (Google account) through OAuth flow, automatically obtain and save authentication files.", + "antigravity_oauth_url_label": "Authorization URL:", + "antigravity_open_link": "Open Link", + "antigravity_copy_link": "Copy Link", + "antigravity_oauth_status_waiting": "Waiting for authentication...", + "antigravity_oauth_status_success": "Authentication successful!", + "antigravity_oauth_status_error": "Authentication failed:", + "antigravity_oauth_start_error": "Failed to start Antigravity OAuth:", + "antigravity_oauth_polling_error": "Failed to check authentication status:", + "gemini_cli_oauth_title": "Gemini CLI OAuth", + "gemini_cli_oauth_button": "Start Gemini CLI Login", + "gemini_cli_oauth_hint": "Login to Google Gemini CLI service through OAuth flow, automatically obtain and save authentication files.", + "gemini_cli_project_id_label": "Google Cloud Project ID (Optional):", + "gemini_cli_project_id_placeholder": "Leave blank to auto-select first available project", + "gemini_cli_project_id_hint": "Optional. If not provided, the system will automatically select the first available project from your account.", + "gemini_cli_project_id_required": "Please enter a Google Cloud project ID.", + "gemini_cli_oauth_url_label": "Authorization URL:", + "gemini_cli_open_link": "Open Link", + "gemini_cli_copy_link": "Copy Link", + "gemini_cli_oauth_status_waiting": "Waiting for authentication...", + "gemini_cli_oauth_status_success": "Authentication successful!", + "gemini_cli_oauth_status_error": "Authentication failed:", + "gemini_cli_oauth_start_error": "Failed to start Gemini CLI OAuth:", + "gemini_cli_oauth_polling_error": "Failed to check authentication status:", + "qwen_oauth_title": "Qwen OAuth", + "qwen_oauth_button": "Start Qwen Login", + "qwen_oauth_hint": "Login to Qwen service through device authorization flow, automatically obtain and save authentication files.", + "qwen_oauth_url_label": "Authorization URL:", + "qwen_open_link": "Open Link", + "qwen_copy_link": "Copy Link", + "qwen_oauth_status_waiting": "Waiting for authentication...", + "qwen_oauth_status_success": "Authentication successful!", + "qwen_oauth_status_error": "Authentication failed:", + "qwen_oauth_start_error": "Failed to start Qwen OAuth:", + "qwen_oauth_polling_error": "Failed to check authentication status:", + "oauth_callback_label": "Callback URL", + "oauth_callback_placeholder": "http://localhost:1455/auth/callback?code=...&state=...", + "oauth_callback_hint": "Remote browser mode: after the provider redirects to http://localhost:..., copy the full URL and submit it here.", + "oauth_callback_button": "Submit Callback URL", + "oauth_callback_required": "Please paste the full redirect URL first.", + "oauth_callback_success": "Callback URL submitted. Continue waiting for authentication.", + "oauth_callback_error": "Failed to submit callback URL:", + "oauth_callback_upgrade_hint": "Please update CLI Proxy API or check the connection.", + "oauth_callback_status_success": "Callback URL submitted, waiting for authentication...", + "oauth_callback_status_error": "Callback URL submission failed:", + "missing_state": "Unable to retrieve authentication state parameter", + "iflow_oauth_title": "iFlow OAuth", + "iflow_oauth_button": "Start iFlow Login", + "iflow_oauth_hint": "Login to iFlow service through OAuth flow, automatically obtain and save authentication files.", + "iflow_oauth_url_label": "Authorization URL:", + "iflow_open_link": "Open Link", + "iflow_copy_link": "Copy Link", + "iflow_oauth_status_waiting": "Waiting for authentication...", + "iflow_oauth_status_success": "Authentication successful!", + "iflow_oauth_status_error": "Authentication failed:", + "iflow_oauth_start_error": "Failed to start iFlow OAuth:", + "iflow_oauth_polling_error": "Failed to check authentication status:", + "iflow_cookie_title": "iFlow Cookie Login", + "iflow_cookie_label": "Cookie Value:", + "iflow_cookie_placeholder": "Enter the BXAuth value, starting with BXAuth=", + "iflow_cookie_hint": "Submit an existing cookie to finish login without opening the authorization link; the credential file will be saved automatically.", + "iflow_cookie_key_hint": "Note: Create a key on the platform first.", + "iflow_cookie_button": "Submit Cookie Login", + "iflow_cookie_status_success": "Cookie login succeeded and credentials are saved.", + "iflow_cookie_status_error": "Cookie login failed:", + "iflow_cookie_status_duplicate": "Duplicate config:", + "iflow_cookie_start_error": "Failed to submit cookie login:", + "iflow_cookie_config_duplicate": "A config file already exists (duplicate). Remove the existing file and try again if you want to re-save it.", + "iflow_cookie_required": "Please provide the Cookie value first.", + "iflow_cookie_result_title": "Cookie Login Result", + "iflow_cookie_result_email": "Account", + "iflow_cookie_result_expired": "Expires At", + "iflow_cookie_result_path": "Saved Path", + "iflow_cookie_result_type": "Type", + "remote_access_disabled": "This login method is not available for remote access. Please access from localhost." + }, + "usage_stats": { + "title": "Usage Statistics", + "total_requests": "Total Requests", + "success_requests": "Success Requests", + "failed_requests": "Failed Requests", + "total_tokens": "Total Tokens", + "cached_tokens": "Cached Tokens", + "reasoning_tokens": "Reasoning Tokens", + "rpm_30m": "RPM", + "tpm_30m": "TPM", + "rate_30m": "Rate (last 30 min)", + "model_name": "Model Name", + "model_price_settings": "Model Pricing Settings", + "saved_prices": "Saved Prices", + "requests_trend": "Request Trends", + "tokens_trend": "Token Usage Trends", + "api_details": "API Details", + "by_hour": "By Hour", + "by_day": "By Day", + "refresh": "Refresh", + "export": "Export", + "import": "Import", + "export_success": "Usage export downloaded", + "import_success": "Import complete: added {{added}}, skipped {{skipped}}, total {{total}}, failed {{failed}}", + "import_invalid": "Invalid usage export file", + "chart_line_label_1": "Line 1", + "chart_line_label_2": "Line 2", + "chart_line_label_3": "Line 3", + "chart_line_label_4": "Line 4", + "chart_line_label_5": "Line 5", + "chart_line_label_6": "Line 6", + "chart_line_label_7": "Line 7", + "chart_line_label_8": "Line 8", + "chart_line_label_9": "Line 9", + "chart_line_hidden": "Hide", + "chart_line_actions_label": "Lines to display", + "chart_line_add": "Add line", + "chart_line_all": "All", + "chart_line_delete": "Delete line", + "chart_line_hint": "Show up to 9 model lines at once", + "no_data": "No Data Available", + "loading_error": "Loading Failed", + "api_endpoint": "API Endpoint", + "requests_count": "Request Count", + "tokens_count": "Token Count", + "models": "Model Statistics", + "success_rate": "Success Rate", + "total_cost": "Total Cost", + "total_cost_hint": "Based on configured model pricing", + "model_price_title": "Model Pricing", + "model_price_reset": "Clear Prices", + "model_price_model_label": "Model", + "model_price_select_placeholder": "Choose a model", + "model_price_select_hint": "Models come from usage details", + "model_price_prompt": "Prompt price", + "model_price_completion": "Completion price", + "model_price_cache": "Cache price", + "model_price_save": "Save Price", + "model_price_empty": "No model prices set", + "model_price_model": "Model", + "model_price_saved": "Model price saved", + "model_price_model_required": "Please choose a model to set pricing", + "cost_trend": "Cost Overview", + "cost_axis_label": "Cost ($)", + "cost_need_price": "Set a model price to view cost stats", + "cost_need_usage": "No usage data available to calculate cost", + "cost_no_data": "No cost data yet" + }, + "stats": { + "success": "Success", + "failure": "Failure" + }, + "logs": { + "title": "Logs Viewer", + "refresh_button": "Refresh Logs", + "clear_button": "Clear Logs", + "download_button": "Download Logs", + "error_log_button": "Select Error Log", + "error_logs_modal_title": "Error Request Logs", + "error_logs_description": "Pick an error request log file to download (only generated when request logging is off).", + "error_logs_request_log_enabled": "Request logging is enabled, so this list will always be empty. Disable request logging and refresh to view error logs.", + "error_logs_empty": "No error request log files found", + "error_logs_load_error": "Failed to load error log list", + "error_logs_size": "Size", + "error_logs_modified": "Last modified", + "error_logs_download": "Download", + "error_log_download_success": "Error log downloaded successfully", + "request_log_download_title": "Download Request Log", + "request_log_download_confirm": "Download request log for ID {{id}}?", + "request_log_download_success": "Request log downloaded successfully", + "empty_title": "No Logs Available", + "empty_desc": "When \"Enable logging to file\" is enabled, logs will be displayed here", + "log_content": "Log Content", + "loading": "Loading logs...", + "load_error": "Failed to load logs", + "clear_confirm": "Are you sure you want to clear all logs? This action cannot be undone!", + "clear_success": "Logs cleared successfully", + "download_success": "Logs downloaded successfully", + "auto_refresh": "Auto Refresh", + "auto_refresh_enabled": "Auto refresh enabled", + "auto_refresh_disabled": "Auto refresh disabled", + "load_more_hint": "Scroll up to load more", + "hidden_lines": "Hidden: {{count}} lines", + "loaded_lines": "Loaded: {{count}} lines", + "filtered_lines": "Filtered: {{count}} lines", + "hide_management_logs": "Hide {{prefix}} logs", + "show_raw_logs": "Show Raw Logs", + "show_raw_logs_hint": "Show original log text for easier multi-line copy", + "search_placeholder": "Search logs by content or keyword", + "search_empty_title": "No matching logs found", + "search_empty_desc": "Try a different keyword or clear the filters.", + "double_click_copy_hint": "Double-click to copy raw log line", + "copy_success": "Log copied to clipboard", + "copy_failed": "Copy failed", + "lines": "lines", + "removed": "Filtered", + "upgrade_required_title": "Please Upgrade CLI Proxy API", + "upgrade_required_desc": "The current server version does not support the logs viewing feature. Please upgrade to the latest version of CLI Proxy API to use this feature." + }, + "config_management": { + "title": "Config Panel", + "editor_title": "Configuration File", + "reload": "Reload", + "save": "Save", + "description": "Edit config.yaml via visual editor or source file", + "status_idle": "Waiting for action", + "status_loading": "Loading configuration...", + "status_loaded": "Configuration loaded", + "status_dirty": "Unsaved changes", + "status_disconnected": "Connect to the server to load the configuration", + "status_load_failed": "Load failed", + "status_saving": "Saving configuration...", + "status_saved": "Configuration saved", + "status_save_failed": "Save failed", + "save_success": "Configuration saved successfully", + "error_yaml_not_supported": "Server did not return YAML. Verify the /config.yaml endpoint is available.", + "editor_placeholder": "key: value", + "search_placeholder": "Search config...", + "search_button": "Search", + "search_no_results": "No results", + "search_prev": "Previous", + "search_next": "Next", + "tabs": { + "visual": "Visual Editor", + "source": "Source File Editor" + }, + "visual": { + "sections": { + "server": { + "title": "Server Configuration", + "description": "Basic server settings", + "host": "Host Address", + "port": "Port" + }, + "tls": { + "title": "TLS/SSL Configuration", + "description": "HTTPS secure connection settings", + "enable": "Enable TLS", + "enable_desc": "Enable HTTPS secure connection", + "cert": "Certificate File Path", + "key": "Private Key File Path" + }, + "remote": { + "title": "Remote Management", + "description": "Remote access and control panel settings", + "allow_remote": "Allow Remote Access", + "allow_remote_desc": "Allow management access from other hosts", + "disable_panel": "Disable Control Panel", + "disable_panel_desc": "Disable the built-in web control panel", + "secret_key": "Management Key", + "secret_key_placeholder": "Set management key", + "panel_repo": "Panel Repository" + }, + "auth": { + "title": "Authentication Configuration", + "description": "API keys and authentication directory settings", + "auth_dir": "Auth Directory (auth-dir)", + "auth_dir_hint": "Directory path for authentication files (supports ~)" + }, + "system": { + "title": "System Configuration", + "description": "Debug, logging, statistics, and performance settings", + "debug": "Debug Mode", + "debug_desc": "Enable verbose debug logging", + "commercial_mode": "Commercial Mode", + "commercial_mode_desc": "Disable high-overhead middleware to reduce memory under high concurrency", + "logging_to_file": "Log to File", + "logging_to_file_desc": "Save logs to rotating files", + "usage_statistics": "Usage Statistics", + "usage_statistics_desc": "Collect usage statistics", + "logs_max_size": "Log File Size Limit (MB)", + "usage_retention_days": "Usage Records Retention Days", + "usage_retention_hint": "0 means no limit (no cleanup)" + }, + "network": { + "title": "Network Configuration", + "description": "Proxy, retry, and routing settings", + "proxy_url": "Proxy URL", + "request_retry": "Request Retry Count", + "max_retry_interval": "Max Retry Interval (seconds)", + "routing_strategy": "Routing Strategy", + "routing_strategy_hint": "Select credential selection strategy", + "strategy_round_robin": "Round Robin", + "strategy_fill_first": "Fill First", + "force_model_prefix": "Force Model Prefix", + "force_model_prefix_desc": "Unprefixed model requests only use credentials without prefix", + "ws_auth": "WebSocket Authentication", + "ws_auth_desc": "Enable WebSocket authentication (/v1/ws)" + }, + "quota": { + "title": "Quota Fallback", + "description": "Fallback strategy when quota is exceeded", + "switch_project": "Switch Project", + "switch_project_desc": "Automatically switch to another project when quota is exceeded", + "switch_preview_model": "Switch to Preview Model", + "switch_preview_model_desc": "Switch to preview model version when quota is exceeded" + }, + "streaming": { + "title": "Streaming Configuration", + "description": "Keepalive and bootstrap retry settings", + "keepalive_seconds": "Keepalive Seconds", + "keepalive_hint": "Set to 0 or leave empty to disable keepalive", + "bootstrap_retries": "Bootstrap Retries", + "bootstrap_hint": "Number of retries during stream startup (before first byte)", + "nonstream_keepalive": "Non-stream Keepalive Interval (seconds)", + "nonstream_keepalive_hint": "Send blank lines every N seconds for non-streaming responses to prevent idle timeout, set to 0 or leave empty to disable", + "disabled": "Disabled" + }, + "payload": { + "title": "Payload Configuration", + "description": "Default values, override rules, and filter rules", + "default_rules": "Default Rules", + "default_rules_desc": "Use these default values when parameters are not specified in the request", + "override_rules": "Override Rules", + "override_rules_desc": "Force override parameter values in the request", + "filter_rules": "Filter Rules", + "filter_rules_desc": "Pre-filter upstream request body via JSON Path, automatically remove non-compliant/redundant parameters (Request Sanitization)" + } + }, + "api_keys": { + "label": "API Keys List (api-keys)", + "add": "Add API Key", + "empty": "No API keys", + "hint": "Each entry represents an API key (consistent with 'API Key Management' page style)", + "edit_title": "Edit API Key", + "add_title": "Add API Key", + "input_label": "API Key", + "input_placeholder": "Paste your API key", + "input_hint": "This only modifies the local config file content, it will not sync to the API Key Management interface", + "error_empty": "Please enter an API key", + "error_invalid": "API key contains invalid characters" + }, + "payload_rules": { + "rule": "Rule", + "models": "Applicable Models", + "model_name": "Model Name", + "provider_type": "Provider Type", + "add_model": "Add Model", + "params": "Parameter Settings", + "remove_params": "Remove Parameters", + "json_path": "JSON Path (e.g., temperature)", + "json_path_filter": "JSON Path (gjson/sjson), e.g., generationConfig.thinkingConfig.thinkingBudget", + "param_type": "Parameter Type", + "add_param": "Add Parameter", + "no_rules": "No rules", + "add_rule": "Add Rule", + "value_string": "String value", + "value_number": "Number value (e.g., 0.7)", + "value_boolean": "true or false", + "value_json": "JSON value", + "value_default": "Value" + }, + "common": { + "edit": "Edit", + "delete": "Delete", + "cancel": "Cancel", + "update": "Update", + "add": "Add" + } + } + }, + "quota_management": { + "title": "Quota Management", + "description": "Monitor OAuth quota status for Antigravity, Codex, and Gemini CLI credentials.", + "refresh_files": "Refresh auth files", + "refresh_files_and_quota": "Refresh files & quota" + }, + "system_info": { + "title": "Management Center Info", + "connection_status_title": "Connection Status", + "api_status_label": "API Status:", + "config_status_label": "Config Status:", + "last_update_label": "Last Update:", + "cache_data": "Cache Data", + "real_time_data": "Real-time Data", + "not_loaded": "Not Loaded", + "seconds_ago": "seconds ago", + "models_title": "Available Models", + "models_desc": "Shows the /models response and uses saved API keys for auth automatically.", + "models_loading": "Loading available models...", + "models_empty": "No models returned by /models", + "models_error": "Failed to load model list", + "models_count": "{{count}} available models", + "version_check_title": "Update Check", + "version_check_desc": "Call the /latest-version endpoint to compare with the server version and see if an update is available.", + "version_current_label": "Current version", + "version_latest_label": "Latest version", + "version_check_button": "Check for updates", + "version_check_idle": "Click to check for updates", + "version_checking": "Checking for the latest version...", + "version_update_available": "An update is available: {{version}}", + "version_is_latest": "You are on the latest version", + "version_check_error": "Update check failed", + "version_current_missing": "Server version is unavailable; cannot compare", + "version_unknown": "Unknown", + "quick_links_title": "Quick Links", + "quick_links_desc": "Access project repositories and documentation for help and updates.", + "link_main_repo": "Main Repository", + "link_main_repo_desc": "CLI Proxy API core program source code", + "link_webui_repo": "WebUI Repository", + "link_webui_repo_desc": "Management Center frontend source code", + "link_docs": "Documentation", + "link_docs_desc": "Usage tutorials and configuration guides", + "clear_login_title": "Local Login Data", + "clear_login_desc": "Clear locally saved login data and sign out. Usage stats pricing settings will remain untouched.", + "clear_login_button": "Clear login data", + "clear_login_confirm": "Clear local login data and sign out now?" + }, + "notification": { + "debug_updated": "Debug settings updated", + "proxy_updated": "Proxy settings updated", + "proxy_cleared": "Proxy settings cleared", + "retry_updated": "Retry settings updated", + "quota_switch_project_updated": "Project switch settings updated", + "quota_switch_preview_updated": "Preview model switch settings updated", + "usage_statistics_updated": "Usage statistics settings updated", + "logging_to_file_updated": "Logging settings updated", + "logs_max_total_size_updated": "Log size limit updated", + "request_log_updated": "Request logging setting updated", + "force_model_prefix_updated": "Model prefix setting updated", + "ws_auth_updated": "WebSocket authentication setting updated", + "routing_strategy_updated": "Routing strategy updated", + "login_storage_cleared": "Local login data cleared", + "api_key_added": "API key added successfully", + "api_key_updated": "API key updated successfully", + "api_key_deleted": "API key deleted successfully", + "api_key_invalid_chars": "API key can only contain letters, numbers, and symbols", + "gemini_key_added": "Gemini key added successfully", + "gemini_key_updated": "Gemini key updated successfully", + "gemini_key_deleted": "Gemini key deleted successfully", + "gemini_multi_input_required": "Please enter at least one Gemini key", + "gemini_multi_failed": "Gemini bulk add failed", + "gemini_multi_summary": "Gemini bulk add finished: {{success}} added, {{skipped}} skipped, {{failed}} failed", + "codex_config_added": "Codex configuration added successfully", + "codex_config_updated": "Codex configuration updated successfully", + "codex_config_deleted": "Codex configuration deleted successfully", + "codex_base_url_required": "Please enter the Codex Base URL", + "claude_config_added": "Claude configuration added successfully", + "claude_config_updated": "Claude configuration updated successfully", + "claude_config_deleted": "Claude configuration deleted successfully", + "vertex_config_added": "Vertex configuration added successfully", + "vertex_config_updated": "Vertex configuration updated successfully", + "vertex_config_deleted": "Vertex configuration deleted successfully", + "vertex_base_url_required": "Please enter the Vertex Base URL", + "config_enabled": "Configuration enabled", + "config_disabled": "Configuration disabled", + "field_required": "Required fields cannot be empty", + "openai_provider_required": "Please fill in provider name and Base URL", + "openai_provider_added": "OpenAI provider added successfully", + "openai_provider_updated": "OpenAI provider updated successfully", + "openai_provider_deleted": "OpenAI provider deleted successfully", + "ampcode_updated": "Ampcode configuration updated", + "ampcode_upstream_api_key_cleared": "Ampcode upstream API key override cleared", + "openai_model_name_required": "Model name is required", + "openai_test_url_required": "Please provide a valid Base URL before testing", + "openai_test_key_required": "Please add at least one API key before testing", + "openai_test_model_required": "Please select a model to test", + "data_refreshed": "Data refreshed successfully", + "connection_required": "Please establish connection first", + "refresh_failed": "Refresh failed", + "update_failed": "Update failed", + "add_failed": "Add failed", + "delete_failed": "Delete failed", + "upload_failed": "Upload failed", + "download_failed": "Download failed", + "login_failed": "Login failed", + "please_enter": "Please enter", + "please_fill": "Please fill", + "provider_name_url": "provider name and Base URL", + "api_key": "API key", + "gemini_api_key": "Gemini API key", + "codex_api_key": "Codex API key", + "claude_api_key": "Claude API key", + "link_copied": "Link copied to clipboard" + }, + "language": { + "switch": "Language", + "chinese": "中文", + "english": "English" + }, + "theme": { + "switch": "Theme", + "light": "Light", + "dark": "Dark", + "switch_to_light": "Switch to light mode", + "switch_to_dark": "Switch to dark mode", + "auto": "Follow system" + }, + "sidebar": { + "toggle_expand": "Expand sidebar", + "toggle_collapse": "Collapse sidebar" + }, + "footer": { + "api_version": "CLI Proxy API Version", + "build_date": "Build Time", + "version": "Management UI Version", + "author": "Author" + } +} diff --git a/src/types/common.ts b/src/types/common.ts index c41d23b..f78aa62 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -4,7 +4,7 @@ export type Theme = 'light' | 'dark' | 'auto'; -export type Language = 'zh-CN' | 'en'; +export type Language = 'zh-CN' | 'en' | 'ru'; export type NotificationType = 'info' | 'success' | 'warning' | 'error'; diff --git a/src/utils/language.ts b/src/utils/language.ts index e4775cc..1975384 100644 --- a/src/utils/language.ts +++ b/src/utils/language.ts @@ -5,11 +5,11 @@ const parseStoredLanguage = (value: string): Language | null => { try { const parsed = JSON.parse(value); const candidate = parsed?.state?.language ?? parsed?.language ?? parsed; - if (candidate === 'zh-CN' || candidate === 'en') { + if (candidate === 'zh-CN' || candidate === 'en' || candidate === 'ru') { return candidate; } } catch { - if (value === 'zh-CN' || value === 'en') { + if (value === 'zh-CN' || value === 'en' || value === 'ru') { return value; } } @@ -36,7 +36,10 @@ const getBrowserLanguage = (): Language => { return 'zh-CN'; } const raw = navigator.languages?.[0] || navigator.language || 'zh-CN'; - return raw.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en'; + const lower = raw.toLowerCase(); + if (lower.startsWith('zh')) return 'zh-CN'; + if (lower.startsWith('ru')) return 'ru'; + return 'en'; }; export const getInitialLanguage = (): Language => getStoredLanguage() ?? getBrowserLanguage(); From ad6a3bd7327538a0bc8a0e357418a905f537e820 Mon Sep 17 00:00:00 2001 From: Chebotov Nickolay Date: Fri, 6 Feb 2026 12:26:46 +0300 Subject: [PATCH 2/5] feat: expand Russian localization --- package-lock.json | 16 - src/i18n/locales/en.json | 3 +- src/i18n/locales/ru.json | 1777 ++++++++++++++++---------------- src/i18n/locales/zh-CN.json | 3 +- src/pages/LoginPage.tsx | 11 +- src/stores/useLanguageStore.ts | 6 +- 6 files changed, 906 insertions(+), 910 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ce56de..b8645c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -467,7 +466,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1933,7 +1931,6 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2021,7 +2018,6 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -2339,7 +2335,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2551,7 +2546,6 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -2816,7 +2810,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3293,7 +3286,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -3623,7 +3615,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3730,7 +3721,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3748,7 +3738,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3857,7 +3846,6 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -4040,7 +4028,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4117,7 +4104,6 @@ "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -4247,7 +4233,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -4275,7 +4260,6 @@ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 6b90898..d5bbd13 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1090,7 +1090,8 @@ "language": { "switch": "Language", "chinese": "中文", - "english": "English" + "english": "English", + "russian": "Русский" }, "theme": { "switch": "Theme", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index e627273..54954df 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -107,272 +107,272 @@ "system_info": "Информация системы" }, "dashboard": { - "title": "Dashboard", - "subtitle": "Welcome to CLI Proxy API Management Center", - "openai_providers": "OpenAI Providers", - "quick_actions": "Quick Actions", - "current_config": "Current Configuration", - "management_keys": "Management Keys", + "title": "Панель управления", + "subtitle": "Добро пожаловать в CLI Proxy API Management Center", + "openai_providers": "Поставщики OpenAI", + "quick_actions": "Быстрые действия", + "current_config": "Текущая конфигурация", + "management_keys": "Ключи управления", "provider_keys_detail": "G:{{gemini}} C:{{codex}} Cl:{{claude}} O:{{openai}}", - "oauth_credentials": "OAuth Credentials", - "usage_overview": "Usage Overview", - "total_requests": "Total Requests", - "total_tokens": "Total Tokens", - "rpm_30min": "RPM (30min)", - "tpm_30min": "TPM (30min)", - "models_used": "Models Used", - "no_usage_data": "No usage data available", - "view_detailed_usage": "View Detailed Stats", - "edit_settings": "Edit Settings", - "available_models": "Available Models", - "available_models_desc": "Total models from all providers" + "oauth_credentials": "Учётные данные OAuth", + "usage_overview": "Обзор использования", + "total_requests": "Всего запросов", + "total_tokens": "Всего токенов", + "rpm_30min": "RPM (30 мин)", + "tpm_30min": "TPM (30 мин)", + "models_used": "Используемые модели", + "no_usage_data": "Данные об использовании отсутствуют", + "view_detailed_usage": "Просмотреть детальную статистику", + "edit_settings": "Изменить настройки", + "available_models": "Доступные модели", + "available_models_desc": "Всего моделей от всех провайдеров" }, "basic_settings": { - "title": "Basic Settings", - "debug_title": "Debug Mode", - "debug_enable": "Enable Debug Mode", - "proxy_title": "Proxy Settings", - "proxy_url_label": "Proxy URL:", - "proxy_url_placeholder": "e.g.: socks5://user:pass@127.0.0.1:1080/", - "proxy_update": "Update", - "proxy_clear": "Clear", - "retry_title": "Request Retry", - "retry_count_label": "Retry Count:", - "retry_update": "Update", - "quota_title": "Quota Exceeded Behavior", - "quota_switch_project": "Auto Switch Project", - "quota_switch_preview": "Switch to Preview Model", - "usage_statistics_title": "Usage Statistics", - "usage_statistics_enable": "Enable usage statistics", - "logging_title": "Logging", - "logging_to_file_enable": "Enable logging to file", - "logs_max_total_size_title": "Log Size Limit", - "logs_max_total_size_label": "Total log size cap (MB):", - "logs_max_total_size_hint": "Set to 0 to disable the limit.", - "logs_max_total_size_update": "Update", - "request_log_title": "Request Logging", - "request_log_enable": "Enable request logging", - "request_log_warning": "Keep this off unless you need detailed troubleshooting.", - "force_model_prefix_enable": "Force model prefix", - "ws_auth_title": "WebSocket Authentication", - "ws_auth_enable": "Require auth for /ws/*", - "routing_title": "Routing Strategy", - "routing_strategy_label": "Routing strategy:", - "routing_strategy_hint": "round-robin cycles through keys; fill-first prioritizes the first available key.", - "routing_strategy_update": "Update", - "routing_strategy_round_robin": "round-robin (cycle)", - "routing_strategy_fill_first": "fill-first (prioritize)" + "title": "Основные настройки", + "debug_title": "Режим отладки", + "debug_enable": "Включить режим отладки", + "proxy_title": "Настройки прокси", + "proxy_url_label": "URL прокси:", + "proxy_url_placeholder": "например: socks5://user:pass@127.0.0.1:1080/", + "proxy_update": "Обновить", + "proxy_clear": "Очистить", + "retry_title": "Повтор запросов", + "retry_count_label": "Количество повторов:", + "retry_update": "Обновить", + "quota_title": "Поведение при превышении квоты", + "quota_switch_project": "Автоматически переключать проект", + "quota_switch_preview": "Переключаться на preview-модель", + "usage_statistics_title": "Статистика использования", + "usage_statistics_enable": "Включить статистику использования", + "logging_title": "Журналирование", + "logging_to_file_enable": "Включить журналирование в файл", + "logs_max_total_size_title": "Лимит размера журналов", + "logs_max_total_size_label": "Максимальный общий размер журналов (МБ):", + "logs_max_total_size_hint": "Установите 0, чтобы отключить лимит.", + "logs_max_total_size_update": "Обновить", + "request_log_title": "Журналирование запросов", + "request_log_enable": "Включить журналирование запросов", + "request_log_warning": "Оставьте выключенным, если подробная диагностика не нужна.", + "force_model_prefix_enable": "Включить принудительный префикс модели", + "ws_auth_title": "Аутентификация WebSocket", + "ws_auth_enable": "Требовать аутентификацию для /ws/*", + "routing_title": "Стратегия маршрутизации", + "routing_strategy_label": "Стратегия маршрутизации:", + "routing_strategy_hint": "round-robin циклически перебирает ключи; fill-first отдаёт приоритет первому доступному ключу.", + "routing_strategy_update": "Обновить", + "routing_strategy_round_robin": "round-robin (цикл)", + "routing_strategy_fill_first": "fill-first (приоритет)" }, "api_keys": { - "title": "API Keys Management", - "proxy_auth_title": "Proxy Service Authentication Keys", - "add_button": "Add Key", - "empty_title": "No API Keys", - "empty_desc": "Click the button above to add the first key", - "item_title": "API Key", - "add_modal_title": "Add API Key", - "add_modal_key_label": "API Key:", - "add_modal_key_placeholder": "Please enter API key", - "edit_modal_title": "Edit API Key", - "edit_modal_key_label": "API Key:", - "delete_confirm": "Are you sure you want to delete this API key?" + "title": "Управление API-ключами", + "proxy_auth_title": "Ключи аутентификации прокси-сервиса", + "add_button": "Добавить ключ", + "empty_title": "API-ключи отсутствуют", + "empty_desc": "Нажмите кнопку выше, чтобы добавить первый ключ", + "item_title": "API-ключ", + "add_modal_title": "Добавление API-ключа", + "add_modal_key_label": "API-ключ:", + "add_modal_key_placeholder": "Введите API-ключ", + "edit_modal_title": "Редактирование API-ключа", + "edit_modal_key_label": "API-ключ:", + "delete_confirm": "Удалить этот API-ключ?" }, "ai_providers": { - "title": "AI Providers Configuration", - "gemini_title": "Gemini API Keys", - "gemini_add_button": "Add Key", - "gemini_empty_title": "No Gemini Keys", - "gemini_empty_desc": "Click the button above to add the first key", - "gemini_item_title": "Gemini Key", - "gemini_add_modal_title": "Add Gemini API Key", - "gemini_add_modal_key_label": "API Keys:", - "gemini_add_modal_key_placeholder": "Enter Gemini API key", - "gemini_add_modal_key_hint": "Add keys one by one and optionally specify a Base URL.", - "gemini_keys_add_btn": "Add Key", - "gemini_base_url_label": "Base URL (Optional):", - "gemini_base_url_placeholder": "e.g.: https://generativelanguage.googleapis.com", - "gemini_edit_modal_title": "Edit Gemini API Key", - "gemini_edit_modal_key_label": "API Key:", - "gemini_delete_confirm": "Are you sure you want to delete this Gemini key?", - "excluded_models_label": "Excluded models (optional):", - "excluded_models_placeholder": "Comma or newline separated, e.g. gemini-1.5-pro, gemini-1.5-flash", - "excluded_models_hint": "Leave empty to allow all models; values are trimmed and deduplicated automatically.", - "excluded_models_count": "Excluding {{count}} models", - "prefix_label": "Prefix (Optional):", - "prefix_placeholder": "e.g.: team-a", - "prefix_hint": "When set, call models as prefix/ to target this entry.", - "config_toggle_label": "Enabled", - "config_disabled_badge": "Disabled", - "codex_title": "Codex API Configuration", - "codex_add_button": "Add Configuration", - "codex_empty_title": "No Codex Configuration", - "codex_empty_desc": "Click the button above to add the first configuration", - "codex_item_title": "Codex Configuration", - "codex_add_modal_title": "Add Codex API Configuration", - "codex_add_modal_key_label": "API Key:", - "codex_add_modal_key_placeholder": "Please enter Codex API key", - "codex_add_modal_url_label": "Base URL (Required):", - "codex_add_modal_url_placeholder": "e.g.: https://api.example.com", - "codex_add_modal_proxy_label": "Proxy URL (Optional):", - "codex_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080", - "codex_edit_modal_title": "Edit Codex API Configuration", - "codex_edit_modal_key_label": "API Key:", - "codex_edit_modal_url_label": "Base URL (Required):", - "codex_edit_modal_proxy_label": "Proxy URL (Optional):", - "codex_delete_confirm": "Are you sure you want to delete this Codex configuration?", - "claude_title": "Claude API Configuration", - "claude_add_button": "Add Configuration", - "claude_empty_title": "No Claude Configuration", - "claude_empty_desc": "Click the button above to add the first configuration", - "claude_item_title": "Claude Configuration", - "claude_add_modal_title": "Add Claude API Configuration", - "claude_add_modal_key_label": "API Key:", - "claude_add_modal_key_placeholder": "Please enter Claude API key", - "claude_add_modal_url_label": "Base URL (Optional):", - "claude_add_modal_url_placeholder": "e.g.: https://api.anthropic.com", - "claude_add_modal_proxy_label": "Proxy URL (Optional):", - "claude_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080", - "claude_edit_modal_title": "Edit Claude API Configuration", - "claude_edit_modal_key_label": "API Key:", - "claude_edit_modal_url_label": "Base URL (Optional):", - "claude_edit_modal_proxy_label": "Proxy URL (Optional):", - "claude_delete_confirm": "Are you sure you want to delete this Claude configuration?", - "claude_models_label": "Custom Models (Optional):", - "claude_models_hint": "Leave empty to allow all models, or add name[, alias] entries to limit/alias them.", - "claude_models_add_btn": "Add Model", - "claude_models_count": "Models Count", - "vertex_title": "Vertex API Configuration", - "vertex_add_button": "Add Configuration", - "vertex_empty_title": "No Vertex Configuration", - "vertex_empty_desc": "Click the button above to add the first configuration", - "vertex_item_title": "Vertex Configuration", - "vertex_add_modal_title": "Add Vertex API Configuration", - "vertex_add_modal_key_label": "API Key:", - "vertex_add_modal_key_placeholder": "Please enter Vertex API key", - "vertex_add_modal_url_label": "Base URL (Required):", - "vertex_add_modal_url_placeholder": "e.g.: https://example.com/api", - "vertex_add_modal_proxy_label": "Proxy URL (Optional):", - "vertex_add_modal_proxy_placeholder": "e.g.: socks5://proxy.example.com:1080", - "vertex_edit_modal_title": "Edit Vertex API Configuration", - "vertex_edit_modal_key_label": "API Key:", - "vertex_edit_modal_url_label": "Base URL (Required):", - "vertex_edit_modal_proxy_label": "Proxy URL (Optional):", - "vertex_delete_confirm": "Are you sure you want to delete this Vertex configuration?", - "vertex_models_label": "Model aliases (alias required):", - "vertex_models_add_btn": "Add Mapping", - "vertex_models_hint": "Each alias needs both the original model and the alias.", - "vertex_models_count": "Alias count", - "ampcode_title": "Amp CLI Integration (ampcode)", - "ampcode_modal_title": "Configure Ampcode", + "title": "Конфигурация AI-провайдеров", + "gemini_title": "API-ключи Gemini", + "gemini_add_button": "Добавить ключ", + "gemini_empty_title": "Ключи Gemini отсутствуют", + "gemini_empty_desc": "Нажмите кнопку выше, чтобы добавить первый ключ", + "gemini_item_title": "Ключ Gemini", + "gemini_add_modal_title": "Добавление API-ключа Gemini", + "gemini_add_modal_key_label": "API-ключи:", + "gemini_add_modal_key_placeholder": "Введите API-ключ Gemini", + "gemini_add_modal_key_hint": "Добавляйте ключи по одному и при необходимости указывайте базовый URL.", + "gemini_keys_add_btn": "Добавить ключ", + "gemini_base_url_label": "Базовый URL (необязательно):", + "gemini_base_url_placeholder": "например: https://generativelanguage.googleapis.com", + "gemini_edit_modal_title": "Редактирование API-ключа Gemini", + "gemini_edit_modal_key_label": "API-ключ:", + "gemini_delete_confirm": "Удалить этот ключ Gemini?", + "excluded_models_label": "Исключённые модели (необязательно):", + "excluded_models_placeholder": "Через запятую или с новой строки, например gemini-1.5-pro, gemini-1.5-flash", + "excluded_models_hint": "Оставьте пустым, чтобы разрешить все модели; значения автоматически обрезаются и дедуплицируются.", + "excluded_models_count": "Исключено моделей: {{count}}", + "prefix_label": "Префикс (необязательно):", + "prefix_placeholder": "например: team-a", + "prefix_hint": "Если задано, обращайтесь к моделям как prefix/, чтобы выбрать эту запись.", + "config_toggle_label": "Включено", + "config_disabled_badge": "Отключено", + "codex_title": "Конфигурация Codex API", + "codex_add_button": "Добавить конфигурацию", + "codex_empty_title": "Конфигурации Codex отсутствуют", + "codex_empty_desc": "Нажмите кнопку выше, чтобы добавить первую конфигурацию", + "codex_item_title": "Конфигурация Codex", + "codex_add_modal_title": "Добавление конфигурации Codex API", + "codex_add_modal_key_label": "API-ключ:", + "codex_add_modal_key_placeholder": "Введите API-ключ Codex", + "codex_add_modal_url_label": "Базовый URL (обязательно):", + "codex_add_modal_url_placeholder": "например: https://api.example.com", + "codex_add_modal_proxy_label": "URL прокси (необязательно):", + "codex_add_modal_proxy_placeholder": "например: socks5://proxy.example.com:1080", + "codex_edit_modal_title": "Редактирование конфигурации Codex API", + "codex_edit_modal_key_label": "API-ключ:", + "codex_edit_modal_url_label": "Базовый URL (обязательно):", + "codex_edit_modal_proxy_label": "URL прокси (необязательно):", + "codex_delete_confirm": "Удалить эту конфигурацию Codex?", + "claude_title": "Конфигурация Claude API", + "claude_add_button": "Добавить конфигурацию", + "claude_empty_title": "Конфигурации Claude отсутствуют", + "claude_empty_desc": "Нажмите кнопку выше, чтобы добавить первую конфигурацию", + "claude_item_title": "Конфигурация Claude", + "claude_add_modal_title": "Добавление конфигурации Claude API", + "claude_add_modal_key_label": "API-ключ:", + "claude_add_modal_key_placeholder": "Введите API-ключ Claude", + "claude_add_modal_url_label": "Базовый URL (необязательно):", + "claude_add_modal_url_placeholder": "например: https://api.anthropic.com", + "claude_add_modal_proxy_label": "URL прокси (необязательно):", + "claude_add_modal_proxy_placeholder": "например: socks5://proxy.example.com:1080", + "claude_edit_modal_title": "Редактирование конфигурации Claude API", + "claude_edit_modal_key_label": "API-ключ:", + "claude_edit_modal_url_label": "Базовый URL (необязательно):", + "claude_edit_modal_proxy_label": "URL прокси (необязательно):", + "claude_delete_confirm": "Удалить эту конфигурацию Claude?", + "claude_models_label": "Пользовательские модели (необязательно):", + "claude_models_hint": "Оставьте пустым, чтобы разрешить все модели, или добавьте записи name[, alias], чтобы ограничить/переименовать их.", + "claude_models_add_btn": "Добавить модель", + "claude_models_count": "Количество моделей", + "vertex_title": "Конфигурация Vertex API", + "vertex_add_button": "Добавить конфигурацию", + "vertex_empty_title": "Конфигурации Vertex отсутствуют", + "vertex_empty_desc": "Нажмите кнопку выше, чтобы добавить первую конфигурацию", + "vertex_item_title": "Конфигурация Vertex", + "vertex_add_modal_title": "Добавление конфигурации Vertex API", + "vertex_add_modal_key_label": "API-ключ:", + "vertex_add_modal_key_placeholder": "Введите API-ключ Vertex", + "vertex_add_modal_url_label": "Базовый URL (обязательно):", + "vertex_add_modal_url_placeholder": "например: https://example.com/api", + "vertex_add_modal_proxy_label": "URL прокси (необязательно):", + "vertex_add_modal_proxy_placeholder": "например: socks5://proxy.example.com:1080", + "vertex_edit_modal_title": "Редактирование конфигурации Vertex API", + "vertex_edit_modal_key_label": "API-ключ:", + "vertex_edit_modal_url_label": "Базовый URL (обязательно):", + "vertex_edit_modal_proxy_label": "URL прокси (необязательно):", + "vertex_delete_confirm": "Удалить эту конфигурацию Vertex?", + "vertex_models_label": "Псевдонимы моделей (требуется псевдоним):", + "vertex_models_add_btn": "Добавить сопоставление", + "vertex_models_hint": "Каждому псевдониму требуются исходная модель и псевдоним.", + "vertex_models_count": "Количество псевдонимов", + "ampcode_title": "Интеграция Amp CLI (ampcode)", + "ampcode_modal_title": "Настройка Ampcode", "ampcode_upstream_url_label": "Upstream URL", - "ampcode_upstream_url_placeholder": "e.g. https://ampcode.com", - "ampcode_upstream_url_hint": "Optional. Leave empty to use the default/auto-discovered control plane URL.", - "ampcode_upstream_api_key_label": "Upstream API Key (Amp Official)", - "ampcode_upstream_api_key_placeholder": "Enter sk-amp... (leave empty to keep current)", - "ampcode_upstream_api_key_hint": "Optional. Leaving it empty will not change the current Amp official key. Use the button below to clear it.", - "ampcode_upstream_api_key_current": "Current Amp official key: {{key}}", - "ampcode_clear_upstream_api_key": "Clear official key", - "ampcode_clear_upstream_api_key_confirm": "Are you sure you want to clear the Ampcode upstream API key (Amp official)?", - "ampcode_force_model_mappings_label": "Force model mappings", - "ampcode_force_model_mappings_hint": "When enabled, mappings override local API-key availability checks.", - "ampcode_model_mappings_label": "Model mappings (from → to)", - "ampcode_model_mappings_hint": "Rewrites model names in Amp requests. Leave empty to disable mappings.", - "ampcode_model_mappings_add_btn": "Add mapping", - "ampcode_model_mappings_from_placeholder": "from model (source)", - "ampcode_model_mappings_to_placeholder": "to model (target)", - "ampcode_model_mappings_count": "Mappings Count", - "ampcode_mappings_overwrite_confirm": "Existing mappings could not be loaded. Continuing may overwrite or clear them. Continue?", - "openai_title": "OpenAI Compatible Providers", - "openai_add_button": "Add Provider", - "openai_empty_title": "No OpenAI Compatible Providers", - "openai_empty_desc": "Click the button above to add the first provider", - "openai_add_modal_title": "Add OpenAI Compatible Provider", - "openai_add_modal_name_label": "Provider Name:", - "openai_add_modal_name_placeholder": "e.g.: openrouter", - "openai_add_modal_url_label": "Base URL:", - "openai_add_modal_url_placeholder": "e.g.: https://openrouter.ai/api/v1", - "openai_add_modal_keys_label": "API Keys", - "openai_edit_modal_keys_label": "API Keys", - "openai_keys_hint": "Add each key separately with an optional proxy URL to keep things organized.", - "openai_keys_add_btn": "Add Key", - "openai_key_placeholder": "sk-... key", - "openai_proxy_placeholder": "Optional proxy URL (e.g. socks5://...)", - "openai_add_modal_models_label": "Model List (name[, alias] one per line):", - "openai_models_hint": "Example: gpt-4o-mini or moonshotai/kimi-k2:free, kimi-k2", - "openai_model_name_placeholder": "Model name, e.g. moonshotai/kimi-k2:free", - "openai_model_alias_placeholder": "Model alias (optional)", - "openai_models_add_btn": "Add Model", - "openai_models_fetch_button": "Fetch via /models", - "openai_models_fetch_title": "Pick Models from /models", - "openai_models_fetch_hint": "Call the /models endpoint using the Base URL above, sending the first API key as Bearer plus custom headers.", - "openai_models_fetch_url_label": "Request URL", - "openai_models_fetch_refresh": "Refresh", - "openai_models_fetch_loading": "Fetching models from /models...", - "openai_models_fetch_empty": "No models returned. Please check the endpoint or auth.", - "openai_models_fetch_error": "Failed to fetch models", - "openai_models_fetch_back": "Back to edit", - "openai_models_fetch_apply": "Add selected models", - "openai_models_search_label": "Search models", - "openai_models_search_placeholder": "Filter by name, alias, or description", - "openai_models_search_empty": "No models match your search. Try a different keyword.", - "openai_models_fetch_invalid_url": "Please enter a valid Base URL first", - "openai_models_fetch_added": "{{count}} new models added", - "openai_edit_modal_title": "Edit OpenAI Compatible Provider", - "openai_edit_modal_name_label": "Provider Name:", - "openai_edit_modal_url_label": "Base URL:", - "openai_edit_modal_models_label": "Model List (name[, alias] one per line):", - "openai_delete_confirm": "Are you sure you want to delete this OpenAI provider?", - "openai_keys_count": "Keys Count", - "openai_models_count": "Models Count", - "openai_test_title": "Connection Test", - "openai_test_hint": "Send a /chat/completions request with the current settings to verify availability.", - "openai_test_model_placeholder": "Model to test", - "openai_test_action": "Run Test", - "openai_test_running": "Sending test request...", - "openai_test_timeout": "Test request timed out after {{seconds}} seconds.", - "openai_test_success": "Test succeeded. The model responded.", - "openai_test_failed": "Test failed", - "openai_test_select_placeholder": "Choose from current models", - "openai_test_select_empty": "No models configured. Add models first" + "ampcode_upstream_url_placeholder": "например: https://ampcode.com", + "ampcode_upstream_url_hint": "Необязательно. Оставьте пустым, чтобы использовать URL плоскости управления по умолчанию/обнаруженный автоматически.", + "ampcode_upstream_api_key_label": "Upstream API-ключ (официальный Amp)", + "ampcode_upstream_api_key_placeholder": "Введите sk-amp... (оставьте пустым, чтобы сохранить текущий)", + "ampcode_upstream_api_key_hint": "Необязательно. Пустое значение не изменит текущий официальный ключ Amp. Используйте кнопку ниже, чтобы очистить его.", + "ampcode_upstream_api_key_current": "Текущий официальный ключ Amp: {{key}}", + "ampcode_clear_upstream_api_key": "Очистить официальный ключ", + "ampcode_clear_upstream_api_key_confirm": "Очистить upstream API-ключ Ampcode (официальный Amp)?", + "ampcode_force_model_mappings_label": "Принудительно применять сопоставления моделей", + "ampcode_force_model_mappings_hint": "При включении сопоставления переопределяют локальные проверки доступности API-ключей.", + "ampcode_model_mappings_label": "Сопоставления моделей (из → в)", + "ampcode_model_mappings_hint": "Переименовывает модели в запросах Amp. Оставьте пустым, чтобы отключить сопоставления.", + "ampcode_model_mappings_add_btn": "Добавить сопоставление", + "ampcode_model_mappings_from_placeholder": "исходная модель", + "ampcode_model_mappings_to_placeholder": "целевая модель", + "ampcode_model_mappings_count": "Количество сопоставлений", + "ampcode_mappings_overwrite_confirm": "Не удалось загрузить существующие сопоставления. Продолжение может перезаписать или очистить их. Продолжить?", + "openai_title": "Совместимые с OpenAI провайдеры", + "openai_add_button": "Добавить провайдера", + "openai_empty_title": "Провайдеры OpenAI отсутствуют", + "openai_empty_desc": "Нажмите кнопку выше, чтобы добавить первого провайдера", + "openai_add_modal_title": "Добавление совместимого с OpenAI провайдера", + "openai_add_modal_name_label": "Имя провайдера:", + "openai_add_modal_name_placeholder": "например: openrouter", + "openai_add_modal_url_label": "Базовый URL:", + "openai_add_modal_url_placeholder": "например: https://openrouter.ai/api/v1", + "openai_add_modal_keys_label": "API-ключи", + "openai_edit_modal_keys_label": "API-ключи", + "openai_keys_hint": "Добавляйте каждый ключ отдельно с необязательным URL прокси для удобства.", + "openai_keys_add_btn": "Добавить ключ", + "openai_key_placeholder": "ключ вида sk-...", + "openai_proxy_placeholder": "Необязательный URL прокси (например, socks5://...)", + "openai_add_modal_models_label": "Список моделей (name[, alias] по строкам):", + "openai_models_hint": "Пример: gpt-4o-mini или moonshotai/kimi-k2:free, kimi-k2", + "openai_model_name_placeholder": "Имя модели, например moonshotai/kimi-k2:free", + "openai_model_alias_placeholder": "Псевдоним модели (необязательно)", + "openai_models_add_btn": "Добавить модель", + "openai_models_fetch_button": "Получить через /models", + "openai_models_fetch_title": "Выбор моделей из /models", + "openai_models_fetch_hint": "Вызовите эндпоинт /models, используя указанный выше базовый URL, отправив первый API-ключ как Bearer с дополнительными заголовками.", + "openai_models_fetch_url_label": "URL запроса", + "openai_models_fetch_refresh": "Обновить", + "openai_models_fetch_loading": "Получение моделей из /models...", + "openai_models_fetch_empty": "Модели не вернулись. Проверьте эндпоинт или авторизацию.", + "openai_models_fetch_error": "Не удалось получить модели", + "openai_models_fetch_back": "Вернуться к редактированию", + "openai_models_fetch_apply": "Добавить выбранные модели", + "openai_models_search_label": "Поиск моделей", + "openai_models_search_placeholder": "Фильтр по имени, псевдониму или описанию", + "openai_models_search_empty": "Модели по запросу не найдены. Попробуйте другой ключ.", + "openai_models_fetch_invalid_url": "Сначала введите корректный базовый URL", + "openai_models_fetch_added": "Добавлено новых моделей: {{count}}", + "openai_edit_modal_title": "Редактирование совместимого с OpenAI провайдера", + "openai_edit_modal_name_label": "Имя провайдера:", + "openai_edit_modal_url_label": "Базовый URL:", + "openai_edit_modal_models_label": "Список моделей (name[, alias] по строкам):", + "openai_delete_confirm": "Удалить этого провайдера OpenAI?", + "openai_keys_count": "Количество ключей", + "openai_models_count": "Количество моделей", + "openai_test_title": "Тест подключения", + "openai_test_hint": "Отправьте запрос /chat/completions с текущими настройками, чтобы проверить доступность.", + "openai_test_model_placeholder": "Модель для теста", + "openai_test_action": "Запустить тест", + "openai_test_running": "Отправка тестового запроса...", + "openai_test_timeout": "Тестовый запрос превысил тайм-аут {{seconds}} с", + "openai_test_success": "Тест выполнен успешно. Модель ответила.", + "openai_test_failed": "Тест не выполнен", + "openai_test_select_placeholder": "Выберите из текущих моделей", + "openai_test_select_empty": "Модели не настроены. Сначала добавьте модели" }, "auth_files": { - "title": "Auth Files Management", - "title_section": "Auth Files", - "description": "Manage all CLI Proxy JSON auth files here (e.g. Qwen, Gemini, Vertex). Uploading a credential immediately enables the corresponding AI integration.", - "upload_button": "Upload File", - "delete_all_button": "Delete All", - "empty_title": "No Auth Files", - "empty_desc": "Click the button above to upload the first file", - "search_empty_title": "No matching files", - "search_empty_desc": "Try changing the filters or clearing the search box.", - "file_size": "Size", - "file_modified": "Modified", - "download_button": "Download", - "delete_button": "Delete", - "delete_confirm": "Are you sure you want to delete file", - "delete_all_confirm": "Are you sure you want to delete all auth files? This operation cannot be undone!", - "delete_filtered_confirm": "Are you sure you want to delete all {{type}} auth files? This operation cannot be undone!", - "upload_error_json": "Only JSON files are allowed", - "upload_error_size": "File size cannot exceed {{maxSize}}", - "upload_success": "File uploaded successfully", - "download_success": "File downloaded successfully", - "delete_success": "File deleted successfully", - "delete_all_success": "Successfully deleted", - "delete_filtered_success": "Deleted {{count}} {{type}} auth files successfully", - "delete_filtered_partial": "{{type}} auth files deletion finished: {{success}} succeeded, {{failed}} failed", - "delete_filtered_none": "No deletable auth files under the current filter ({{type}})", - "files_count": "files", - "pagination_prev": "Previous", - "pagination_next": "Next", - "pagination_info": "Page {{current}} / {{total}} · {{count}} files", - "search_label": "Search configs", - "search_placeholder": "Filter by name, type, or provider", - "page_size_label": "Per page", - "page_size_unit": "items", - "view_mode_paged": "Paged", - "view_mode_all": "Show all", - "too_many_files_warning": "Too many credentials. Showing all may cause performance issues, please use paged view.", - "filter_all": "All", + "title": "Управление файлами авторизации", + "title_section": "Файлы авторизации", + "description": "Управляйте всеми JSON-файлами авторизации CLI Proxy (например, Qwen, Gemini, Vertex). Загрузка учётных данных сразу включает соответствующую интеграцию AI.", + "upload_button": "Загрузить файл", + "delete_all_button": "Удалить всё", + "empty_title": "Файлы авторизации отсутствуют", + "empty_desc": "Нажмите кнопку выше, чтобы загрузить первый файл", + "search_empty_title": "Файлы не найдены", + "search_empty_desc": "Попробуйте изменить фильтры или очистить строку поиска.", + "file_size": "Размер", + "file_modified": "Изменён", + "download_button": "Скачать", + "delete_button": "Удалить", + "delete_confirm": "Удалить файл", + "delete_all_confirm": "Удалить все файлы авторизации? Это действие нельзя отменить!", + "delete_filtered_confirm": "Удалить все файлы авторизации {{type}}? Это действие нельзя отменить!", + "upload_error_json": "Допустимы только файлы JSON", + "upload_error_size": "Размер файла не может превышать {{maxSize}}", + "upload_success": "Файл успешно загружен", + "download_success": "Файл успешно скачан", + "delete_success": "Файл успешно удалён", + "delete_all_success": "Удаление завершено", + "delete_filtered_success": "Удалено файлов {{type}}: {{count}}", + "delete_filtered_partial": "Удаление файлов {{type}} завершено: успешных {{success}}, ошибок {{failed}}", + "delete_filtered_none": "Нет файлов {{type}} для удаления при текущем фильтре", + "files_count": "файлов", + "pagination_prev": "Предыдущая", + "pagination_next": "Следующая", + "pagination_info": "Страница {{current}} / {{total}} · {{count}} файлов", + "search_label": "Поиск конфигов", + "search_placeholder": "Фильтр по имени, типу или провайдеру", + "page_size_label": "На странице", + "page_size_unit": "элементов", + "view_mode_paged": "Постранично", + "view_mode_all": "Показать все", + "too_many_files_warning": "Слишком много учётных данных. Полный список может повлиять на производительность, используйте постраничный режим.", + "filter_all": "Все", "filter_qwen": "Qwen", "filter_gemini": "Gemini", "filter_gemini-cli": "GeminiCLI", @@ -382,8 +382,8 @@ "filter_antigravity": "Antigravity", "filter_iflow": "iFlow", "filter_vertex": "Vertex", - "filter_empty": "Empty", - "filter_unknown": "Other", + "filter_empty": "Пусто", + "filter_unknown": "Другое", "type_qwen": "Qwen", "type_gemini": "Gemini", "type_gemini-cli": "GeminiCLI", @@ -393,721 +393,722 @@ "type_antigravity": "Antigravity", "type_iflow": "iFlow", "type_vertex": "Vertex", - "type_empty": "Empty", - "type_unknown": "Other", - "type_virtual": "Virtual auth file", - "models_button": "Models", - "models_title": "Supported models", - "models_loading": "Loading model list...", - "models_empty": "No available models for this credential", - "models_empty_desc": "This credential may not be loaded by the server yet, or no models are bound to it.", - "models_unsupported": "This feature is not supported in the current version", - "models_unsupported_desc": "Please update CLI Proxy API to the latest version and try again", - "models_excluded_badge": "Excluded", - "models_excluded_hint": "This model is excluded by OAuth", - "status_toggle_label": "Enabled", - "status_enabled_success": "\"{{name}}\" enabled", - "status_disabled_success": "\"{{name}}\" disabled", - "prefix_proxy_button": "Edit prefix/proxy_url", - "prefix_proxy_loading": "Loading credential...", - "prefix_proxy_source_label": "Credential JSON", + "type_empty": "Пусто", + "type_unknown": "Другое", + "type_virtual": "Виртуальный файл авторизации", + "models_button": "Модели", + "models_title": "Поддерживаемые модели", + "models_loading": "Загрузка списка моделей...", + "models_empty": "Для этих учётных данных нет доступных моделей", + "models_empty_desc": "Возможно, учётные данные ещё не загружены сервером или к ним не привязаны модели.", + "models_unsupported": "Функция не поддерживается в текущей версии", + "models_unsupported_desc": "Обновите CLI Proxy API до последней версии и повторите попытку", + "models_excluded_badge": "Исключена", + "models_excluded_hint": "Эта модель исключена OAuth", + "status_toggle_label": "Включено", + "status_enabled_success": "\"{{name}}\" включён", + "status_disabled_success": "\"{{name}}\" отключён", + "prefix_proxy_button": "Изменить prefix/proxy_url", + "prefix_proxy_loading": "Загрузка учётных данных...", + "prefix_proxy_source_label": "JSON учётных данных", "prefix_label": "prefix", "proxy_url_label": "proxy_url", "prefix_placeholder": "", "proxy_url_placeholder": "socks5://username:password@proxy_ip:port/", - "prefix_proxy_invalid_json": "This credential is not a JSON object and cannot be edited.", - "prefix_proxy_saved_success": "Updated \"{{name}}\" successfully", - "card_tools_title": "Tools", - "quota_refresh_single": "Refresh quota", - "quota_refresh_hint": "Refresh quota for this credential only", - "quota_refresh_success": "Quota refreshed for \"{{name}}\"", - "quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}" + "prefix_proxy_invalid_json": "Эти учётные данные не являются JSON-объектом и не могут быть изменены.", + "prefix_proxy_saved_success": "\"{{name}}\" успешно обновлён", + "card_tools_title": "Инструменты", + "quota_refresh_single": "Обновить квоту", + "quota_refresh_hint": "Обновить квоту только для этих учётных данных", + "quota_refresh_success": "Квота для \"{{name}}\" обновлена", + "quota_refresh_failed": "Не удалось обновить квоту для \"{{name}}\": {{message}}" }, "antigravity_quota": { - "title": "Antigravity Quota", - "empty_title": "No Antigravity Auth Files", - "empty_desc": "Upload an Antigravity credential to view remaining quota.", - "idle": "Not loaded. Click Refresh Button.", - "loading": "Loading quota...", - "load_failed": "Failed to load quota: {{message}}", - "missing_auth_index": "Auth file missing auth_index", - "empty_models": "No quota data available", - "refresh_button": "Refresh Quota", - "fetch_all": "Fetch All" + "title": "Квота Antigravity", + "empty_title": "Файлы авторизации Antigravity отсутствуют", + "empty_desc": "Загрузите учётные данные Antigravity, чтобы увидеть оставшуюся квоту.", + "idle": "Не загружено. Нажмите \"Обновить квоту\".", + "loading": "Загрузка квоты...", + "load_failed": "Не удалось загрузить квоту: {{message}}", + "missing_auth_index": "В файле авторизации отсутствует auth_index", + "empty_models": "Данные по квоте отсутствуют", + "refresh_button": "Обновить квоту", + "fetch_all": "Получить все" }, "codex_quota": { - "title": "Codex Quota", - "empty_title": "No Codex Auth Files", - "empty_desc": "Upload a Codex credential to view quota.", - "idle": "Not loaded. Click Refresh Button.", - "loading": "Loading quota...", - "load_failed": "Failed to load quota: {{message}}", - "missing_auth_index": "Auth file missing auth_index", - "missing_account_id": "Codex credential missing ChatGPT account ID", - "empty_windows": "No quota data available", - "no_access": "This credential has no Codex access (plan: free).", - "refresh_button": "Refresh Quota", - "fetch_all": "Fetch All", - "primary_window": "5-hour limit", - "secondary_window": "Weekly limit", - "code_review_primary_window": "Code review 5-hour limit", - "code_review_secondary_window": "Code review weekly limit", - "plan_label": "Plan", + "title": "Квота Codex", + "empty_title": "Файлы авторизации Codex отсутствуют", + "empty_desc": "Загрузите учётные данные Codex, чтобы увидеть квоту.", + "idle": "Не загружено. Нажмите \"Обновить квоту\".", + "loading": "Загрузка квоты...", + "load_failed": "Не удалось загрузить квоту: {{message}}", + "missing_auth_index": "В файле авторизации отсутствует auth_index", + "missing_account_id": "В учётных данных Codex отсутствует идентификатор аккаунта ChatGPT", + "empty_windows": "Данные по квоте отсутствуют", + "no_access": "У этих учётных данных нет доступа Codex (план: free).", + "refresh_button": "Обновить квоту", + "fetch_all": "Получить все", + "primary_window": "Лимит на 5 часов", + "secondary_window": "Недельный лимит", + "code_review_primary_window": "Лимит code review на 5 часов", + "code_review_secondary_window": "Недельный лимит code review", + "plan_label": "Тариф", "plan_plus": "Plus", "plan_team": "Team", "plan_free": "Free" }, "gemini_cli_quota": { - "title": "Gemini CLI Quota", - "empty_title": "No Gemini CLI Auth Files", - "empty_desc": "Upload a Gemini CLI credential to view remaining quota.", - "idle": "Not loaded. Click Refresh Button.", - "loading": "Loading quota...", - "load_failed": "Failed to load quota: {{message}}", - "missing_auth_index": "Auth file missing auth_index", - "missing_project_id": "Gemini CLI credential missing project ID", - "empty_buckets": "No quota data available", - "refresh_button": "Refresh Quota", - "fetch_all": "Fetch All", - "remaining_amount": "Remaining {{count}}" + "title": "Квота Gemini CLI", + "empty_title": "Файлы авторизации Gemini CLI отсутствуют", + "empty_desc": "Загрузите учётные данные Gemini CLI, чтобы увидеть оставшуюся квоту.", + "idle": "Не загружено. Нажмите \"Обновить квоту\".", + "loading": "Загрузка квоты...", + "load_failed": "Не удалось загрузить квоту: {{message}}", + "missing_auth_index": "В файле авторизации отсутствует auth_index", + "missing_project_id": "В учётных данных Gemini CLI отсутствует идентификатор проекта", + "empty_buckets": "Данные по квоте отсутствуют", + "refresh_button": "Обновить квоту", + "fetch_all": "Получить все", + "remaining_amount": "Осталось {{count}}" }, "vertex_import": { - "title": "Vertex JSON Login", - "description": "Upload a Google service account JSON to store it as auth-dir/vertex-.json using the same rules as the CLI vertex-import helper.", - "location_label": "Region (optional)", + "title": "Вход с Vertex JSON", + "description": "Загрузите JSON ключа сервисного аккаунта Google, чтобы сохранить его как auth-dir/vertex-.json по тем же правилам, что и помощник CLI vertex-import.", + "location_label": "Регион (необязательно)", "location_placeholder": "us-central1", - "location_hint": "Leave empty to use the default region us-central1.", - "file_label": "Service account key JSON", - "file_hint": "Only Google Cloud service account key JSON files are accepted.", - "file_placeholder": "No file selected", - "choose_file": "Choose File", - "import_button": "Import Vertex Credential", - "file_required": "Select a .json credential file first", - "success": "Vertex credential imported successfully", - "result_title": "Credential saved", - "result_project": "Project ID", - "result_email": "Service account", - "result_location": "Region", - "result_file": "Persisted file" + "location_hint": "Оставьте пустым, чтобы использовать регион us-central1 по умолчанию.", + "file_label": "JSON ключ сервисного аккаунта", + "file_hint": "Принимаются только JSON-файлы ключей сервисных аккаунтов Google Cloud.", + "file_placeholder": "Файл не выбран", + "choose_file": "Выбрать файл", + "import_button": "Импортировать учётные данные Vertex", + "file_required": "Сначала выберите файл учётных данных .json", + "success": "Учётные данные Vertex успешно импортированы", + "result_title": "Учётные данные сохранены", + "result_project": "ID проекта", + "result_email": "Сервисный аккаунт", + "result_location": "Регион", + "result_file": "Сохранённый файл" }, "oauth_excluded": { - "title": "OAuth Excluded Models", - "description": "Per-provider exclusions are shown as cards; click edit to adjust. Wildcards * are supported and the scope follows the auth file filter.", - "add": "Add Exclusion", - "add_title": "Add provider exclusion", - "edit_title": "Edit exclusions for {{provider}}", - "refresh": "Refresh", - "refreshing": "Refreshing...", - "provider_label": "Provider", - "provider_auto": "Follow current filter", - "provider_placeholder": "e.g. gemini-cli", - "provider_hint": "Defaults to the current filter; pick an existing provider or type a new name.", - "models_label": "Models to exclude", - "models_loading": "Loading models...", - "models_unsupported": "Current CPA version does not support fetching model lists.", - "models_loaded": "{{count}} models loaded. Check the models to exclude.", - "no_models_available": "No models available for this provider.", - "save": "Save/Update", - "saving": "Saving...", - "save_success": "Excluded models updated", - "save_failed": "Failed to update excluded models", - "delete": "Delete Provider", - "delete_confirm": "Delete the exclusion list for {{provider}}?", - "delete_success": "Exclusion list removed", - "delete_failed": "Failed to delete exclusion list", - "deleting": "Deleting...", - "no_models": "No excluded models", - "model_count": "{{count}} models excluded", - "list_empty_all": "No exclusions yet—use “Add Exclusion” to create one.", - "list_empty_filtered": "No exclusions in this scope; click “Add Exclusion” to add.", - "disconnected": "Connect to the server to view exclusions", - "load_failed": "Failed to load exclusion list", - "provider_required": "Please enter a provider first", - "scope_all": "Scope: All providers", - "scope_provider": "Scope: {{provider}}", - "upgrade_required": "This feature requires a newer CLI Proxy API (CPA) version. Please upgrade.", - "upgrade_required_title": "Please upgrade CLI Proxy API", - "upgrade_required_desc": "The current server does not support the OAuth excluded models API. Please upgrade to the latest CLI Proxy API (CPA) version." + "title": "Исключённые модели OAuth", + "description": "Исключения по провайдерам отображаются карточками; нажмите \"Изменить\", чтобы настроить. Поддерживаются шаблоны *. Объём зависит от фильтра файлов авторизации.", + "add": "Добавить исключение", + "add_title": "Добавление исключения для провайдера", + "edit_title": "Редактирование исключений для {{provider}}", + "refresh": "Обновить", + "refreshing": "Обновляется...", + "provider_label": "Провайдер", + "provider_auto": "Следовать текущему фильтру", + "provider_placeholder": "например: gemini-cli", + "provider_hint": "По умолчанию используется текущий фильтр; выберите существующего провайдера или введите новое имя.", + "models_label": "Модели для исключения", + "models_loading": "Загрузка моделей...", + "models_unsupported": "Текущая версия CPA не поддерживает загрузку списка моделей.", + "models_loaded": "Загружено моделей: {{count}}. Отметьте те, которые нужно исключить.", + "no_models_available": "Для этого провайдера нет доступных моделей.", + "save": "Сохранить/обновить", + "saving": "Сохранение...", + "save_success": "Исключения обновлены", + "save_failed": "Не удалось обновить исключения", + "delete": "Удалить провайдера", + "delete_confirm": "Удалить список исключений для {{provider}}?", + "delete_success": "Список исключений удалён", + "delete_failed": "Не удалось удалить список исключений", + "deleting": "Удаление...", + "no_models": "Исключённых моделей нет", + "model_count": "Исключено моделей: {{count}}", + "list_empty_all": "Исключений ещё нет — используйте \"Добавить исключение\".", + "list_empty_filtered": "В этом диапазоне исключений нет; нажмите \"Добавить исключение\".", + "disconnected": "Подключитесь к серверу, чтобы просматривать исключения", + "load_failed": "Не удалось загрузить список исключений", + "provider_required": "Сначала укажите провайдера", + "scope_all": "Область: все провайдеры", + "scope_provider": "Область: {{provider}}", + "upgrade_required": "Эта функция требует более новой версии CLI Proxy API (CPA). Обновите систему.", + "upgrade_required_title": "Пожалуйста, обновите CLI Proxy API", + "upgrade_required_desc": "Текущая версия сервера не поддерживает API исключённых моделей OAuth. Обновите CLI Proxy API (CPA) до последней версии." }, "oauth_model_alias": { - "title": "OAuth Model Aliases", - "add": "Add Alias", - "add_title": "Add provider model aliases", - "provider_label": "Provider", - "provider_placeholder": "e.g. gemini-cli / vertex", - "provider_hint": "Defaults to the current filter; pick an existing provider or type a new name.", - "model_source_loading": "Loading models...", - "model_source_unsupported": "The current CPA version does not support fetching model lists (manual input still works).", - "model_source_loaded": "{{count}} models loaded. Use the dropdown in 'Source model name', or type custom values. Saving an empty list removes that provider. Enable 'Keep original' to keep the original name while adding the alias.", - "alias_label": "Model aliases", - "alias_name_placeholder": "Source model name", - "alias_placeholder": "Alias (required)", - "alias_fork_label": "Keep original", - "add_alias": "Add alias", - "save": "Save/Update", - "save_success": "Model aliases updated", - "save_failed": "Failed to update model aliases", - "delete": "Delete Provider", - "delete_confirm": "Delete model aliases for {{provider}}?", - "delete_link_title": "Unlink mapping", - "delete_link_confirm": "Unlink mapping from {{sourceModel}} ({{provider}}) to alias {{alias}}?", - "delete_alias_title": "Delete Alias", - "delete_alias_confirm": "Delete alias {{alias}} and unmap all associated models?", - "delete_success": "Model aliases removed", - "delete_failed": "Failed to delete model aliases", - "no_models": "No model aliases", - "model_count": "{{count}} aliases", - "list_empty_all": "No model aliases yet—use “Add Alias” to create one.", - "chart_title": "All mappings overview", - "diagram_providers": "Providers", - "diagram_source_models": "Source Models", - "diagram_aliases": "Aliases", - "diagram_expand": "Expand", - "diagram_collapse": "Collapse", - "diagram_add_alias": "Add Alias", - "diagram_rename": "Rename", - "diagram_rename_alias_title": "Rename alias", - "diagram_rename_alias_label": "New alias name", - "diagram_rename_placeholder": "Enter alias name...", - "diagram_delete_link": "Unlink from {{provider}} / {{name}}", - "diagram_delete_alias": "Delete alias", - "diagram_please_enter_alias": "Please enter an alias name.", - "diagram_alias_exists": "This alias already exists.", - "diagram_add_alias_title": "Add alias", - "diagram_add_alias_label": "Alias name", - "diagram_add_placeholder": "Enter new alias name...", - "diagram_rename_btn": "Rename", - "diagram_add_btn": "Add", - "diagram_settings": "Settings", - "diagram_settings_title": "Alias settings — {{alias}}", - "diagram_settings_source_title": "Source model settings", - "diagram_settings_empty": "No mappings for this alias yet.", - "diagram_tap_hint": "On touch devices: tap a source model, then tap an alias to link.", - "view_mode": "View mode", - "view_mode_diagram": "Diagram", - "view_mode_list": "List", - "provider_required": "Please enter a provider first", - "upgrade_required": "This feature requires a newer CLI Proxy API (CPA) version. Please upgrade.", - "upgrade_required_title": "Please upgrade CLI Proxy API", - "upgrade_required_desc": "The current server does not support the OAuth model aliases API. Please upgrade to the latest CLI Proxy API (CPA) version." + "title": "Псевдонимы моделей OAuth", + "add": "Добавить псевдоним", + "add_title": "Добавление псевдонимов моделей провайдера", + "provider_label": "Провайдер", + "provider_placeholder": "например: gemini-cli / vertex", + "provider_hint": "По умолчанию используется текущий фильтр; выберите существующего провайдера или введите новое имя.", + "model_source_loading": "Загрузка моделей...", + "model_source_unsupported": "Текущая версия CPA не поддерживает загрузку списка моделей (ручной ввод остаётся доступным).", + "model_source_loaded": "Загружено моделей: {{count}}. Используйте выпадающий список \"Исходная модель\" или введите своё значение. Сохранение пустого списка удаляет провайдера. Включите \"Сохранить оригинал\", чтобы оставить исходное имя вместе с псевдонимом.", + "alias_label": "Псевдонимы моделей", + "alias_name_placeholder": "Исходное имя модели", + "alias_placeholder": "Псевдоним (обязательно)", + "alias_fork_label": "Сохранить оригинал", + "add_alias": "Добавить псевдоним", + "save": "Сохранить/обновить", + "save_success": "Псевдонимы моделей обновлены", + "save_failed": "Не удалось обновить псевдонимы моделей", + "delete": "Удалить провайдера", + "delete_confirm": "Удалить псевдонимы моделей для {{provider}}?", + "delete_link_title": "Убрать сопоставление", + "delete_link_confirm": "Удалить сопоставление из {{sourceModel}} ({{provider}}) к псевдониму {{alias}}?", + "delete_alias_title": "Удалить псевдоним", + "delete_alias_confirm": "Удалить псевдоним {{alias}} и все связанные сопоставления?", + "delete_success": "Псевдонимы моделей удалены", + "delete_failed": "Не удалось удалить псевдонимы моделей", + "no_models": "Псевдонимов нет", + "model_count": "Количество псевдонимов: {{count}}", + "list_empty_all": "Псевдонимы ещё не созданы — используйте \"Добавить псевдоним\".", + "chart_title": "Обзор всех сопоставлений", + "diagram_providers": "Провайдеры", + "diagram_source_models": "Исходные модели", + "diagram_aliases": "Псевдонимы", + "diagram_expand": "Развернуть", + "diagram_collapse": "Свернуть", + "diagram_add_alias": "Добавить псевдоним", + "diagram_rename": "Переименовать", + "diagram_rename_alias_title": "Переименование псевдонима", + "diagram_rename_alias_label": "Новое имя псевдонима", + "diagram_rename_placeholder": "Введите имя псевдонима...", + "diagram_delete_link": "Убрать связь {{provider}} / {{name}}", + "diagram_delete_alias": "Удалить псевдоним", + "diagram_please_enter_alias": "Введите имя псевдонима.", + "diagram_alias_exists": "Этот псевдоним уже существует.", + "diagram_add_alias_title": "Добавление псевдонима", + "diagram_add_alias_label": "Имя псевдонима", + "diagram_add_placeholder": "Введите новое имя псевдонима...", + "diagram_rename_btn": "Переименовать", + "diagram_add_btn": "Добавить", + "diagram_settings": "Настройки", + "diagram_settings_title": "Настройки псевдонима — {{alias}}", + "diagram_settings_source_title": "Настройки исходной модели", + "diagram_settings_empty": "Для этого псевдонима ещё нет сопоставлений.", + "diagram_tap_hint": "На сенсорных устройствах: коснитесь исходной модели, затем псевдонима для связывания.", + "view_mode": "Режим просмотра", + "view_mode_diagram": "Диаграмма", + "view_mode_list": "Список", + "provider_required": "Сначала укажите провайдера", + "upgrade_required": "Эта функция требует более новой версии CLI Proxy API (CPA). Обновите систему.", + "upgrade_required_title": "Пожалуйста, обновите CLI Proxy API", + "upgrade_required_desc": "Текущая версия сервера не поддерживает API псевдонимов моделей OAuth. Обновите CLI Proxy API (CPA) до последней версии." }, "auth_login": { "codex_oauth_title": "Codex OAuth", - "codex_oauth_button": "Start Codex Login", - "codex_oauth_hint": "Login to Codex service through OAuth flow, automatically obtain and save authentication files.", - "codex_oauth_url_label": "Authorization URL:", - "codex_open_link": "Open Link", - "codex_copy_link": "Copy Link", - "codex_oauth_status_waiting": "Waiting for authentication...", - "codex_oauth_status_success": "Authentication successful!", - "codex_oauth_status_error": "Authentication failed:", - "codex_oauth_start_error": "Failed to start Codex OAuth:", - "codex_oauth_polling_error": "Failed to check authentication status:", + "codex_oauth_button": "Начать вход Codex", + "codex_oauth_hint": "Выполните вход в сервис Codex через OAuth и автоматически получите/сохраните файлы авторизации.", + "codex_oauth_url_label": "URL авторизации:", + "codex_open_link": "Открыть ссылку", + "codex_copy_link": "Скопировать ссылку", + "codex_oauth_status_waiting": "Ожидание аутентификации...", + "codex_oauth_status_success": "Аутентификация успешна!", + "codex_oauth_status_error": "Ошибка аутентификации:", + "codex_oauth_start_error": "Не удалось запустить Codex OAuth:", + "codex_oauth_polling_error": "Не удалось проверить статус аутентификации:", "anthropic_oauth_title": "Anthropic OAuth", - "anthropic_oauth_button": "Start Anthropic Login", - "anthropic_oauth_hint": "Login to Anthropic (Claude) service through OAuth flow, automatically obtain and save authentication files.", - "anthropic_oauth_url_label": "Authorization URL:", - "anthropic_open_link": "Open Link", - "anthropic_copy_link": "Copy Link", - "anthropic_oauth_status_waiting": "Waiting for authentication...", - "anthropic_oauth_status_success": "Authentication successful!", - "anthropic_oauth_status_error": "Authentication failed:", - "anthropic_oauth_start_error": "Failed to start Anthropogenic OAuth:", - "anthropic_oauth_polling_error": "Failed to check authentication status:", + "anthropic_oauth_button": "Начать вход Anthropic", + "anthropic_oauth_hint": "Выполните вход в сервис Anthropic (Claude) через OAuth и автоматически получите/сохраните файлы авторизации.", + "anthropic_oauth_url_label": "URL авторизации:", + "anthropic_open_link": "Открыть ссылку", + "anthropic_copy_link": "Скопировать ссылку", + "anthropic_oauth_status_waiting": "Ожидание аутентификации...", + "anthropic_oauth_status_success": "Аутентификация успешна!", + "anthropic_oauth_status_error": "Ошибка аутентификации:", + "anthropic_oauth_start_error": "Не удалось запустить Anthropic OAuth:", + "anthropic_oauth_polling_error": "Не удалось проверить статус аутентификации:", "antigravity_oauth_title": "Antigravity OAuth", - "antigravity_oauth_button": "Start Antigravity Login", - "antigravity_oauth_hint": "Login to Antigravity service (Google account) through OAuth flow, automatically obtain and save authentication files.", - "antigravity_oauth_url_label": "Authorization URL:", - "antigravity_open_link": "Open Link", - "antigravity_copy_link": "Copy Link", - "antigravity_oauth_status_waiting": "Waiting for authentication...", - "antigravity_oauth_status_success": "Authentication successful!", - "antigravity_oauth_status_error": "Authentication failed:", - "antigravity_oauth_start_error": "Failed to start Antigravity OAuth:", - "antigravity_oauth_polling_error": "Failed to check authentication status:", + "antigravity_oauth_button": "Начать вход Antigravity", + "antigravity_oauth_hint": "Выполните вход в сервис Antigravity (Google) через OAuth и автоматически получите/сохраните файлы авторизации.", + "antigravity_oauth_url_label": "URL авторизации:", + "antigravity_open_link": "Открыть ссылку", + "antigravity_copy_link": "Скопировать ссылку", + "antigravity_oauth_status_waiting": "Ожидание аутентификации...", + "antigravity_oauth_status_success": "Аутентификация успешна!", + "antigravity_oauth_status_error": "Ошибка аутентификации:", + "antigravity_oauth_start_error": "Не удалось запустить Antigravity OAuth:", + "antigravity_oauth_polling_error": "Не удалось проверить статус аутентификации:", "gemini_cli_oauth_title": "Gemini CLI OAuth", - "gemini_cli_oauth_button": "Start Gemini CLI Login", - "gemini_cli_oauth_hint": "Login to Google Gemini CLI service through OAuth flow, automatically obtain and save authentication files.", - "gemini_cli_project_id_label": "Google Cloud Project ID (Optional):", - "gemini_cli_project_id_placeholder": "Leave blank to auto-select first available project", - "gemini_cli_project_id_hint": "Optional. If not provided, the system will automatically select the first available project from your account.", - "gemini_cli_project_id_required": "Please enter a Google Cloud project ID.", - "gemini_cli_oauth_url_label": "Authorization URL:", - "gemini_cli_open_link": "Open Link", - "gemini_cli_copy_link": "Copy Link", - "gemini_cli_oauth_status_waiting": "Waiting for authentication...", - "gemini_cli_oauth_status_success": "Authentication successful!", - "gemini_cli_oauth_status_error": "Authentication failed:", - "gemini_cli_oauth_start_error": "Failed to start Gemini CLI OAuth:", - "gemini_cli_oauth_polling_error": "Failed to check authentication status:", + "gemini_cli_oauth_button": "Начать вход Gemini CLI", + "gemini_cli_oauth_hint": "Выполните вход в сервис Google Gemini CLI через OAuth и автоматически получите/сохраните файлы авторизации.", + "gemini_cli_project_id_label": "Google Cloud Project ID (необязательно):", + "gemini_cli_project_id_placeholder": "Оставьте пустым, чтобы выбрать первый доступный проект автоматически", + "gemini_cli_project_id_hint": "Необязательно. Если не указано, система автоматически выберет первый доступный проект вашей учётной записи.", + "gemini_cli_project_id_required": "Укажите идентификатор проекта Google Cloud.", + "gemini_cli_oauth_url_label": "URL авторизации:", + "gemini_cli_open_link": "Открыть ссылку", + "gemini_cli_copy_link": "Скопировать ссылку", + "gemini_cli_oauth_status_waiting": "Ожидание аутентификации...", + "gemini_cli_oauth_status_success": "Аутентификация успешна!", + "gemini_cli_oauth_status_error": "Ошибка аутентификации:", + "gemini_cli_oauth_start_error": "Не удалось запустить Gemini CLI OAuth:", + "gemini_cli_oauth_polling_error": "Не удалось проверить статус аутентификации:", "qwen_oauth_title": "Qwen OAuth", - "qwen_oauth_button": "Start Qwen Login", - "qwen_oauth_hint": "Login to Qwen service through device authorization flow, automatically obtain and save authentication files.", - "qwen_oauth_url_label": "Authorization URL:", - "qwen_open_link": "Open Link", - "qwen_copy_link": "Copy Link", - "qwen_oauth_status_waiting": "Waiting for authentication...", - "qwen_oauth_status_success": "Authentication successful!", - "qwen_oauth_status_error": "Authentication failed:", - "qwen_oauth_start_error": "Failed to start Qwen OAuth:", - "qwen_oauth_polling_error": "Failed to check authentication status:", + "qwen_oauth_button": "Начать вход Qwen", + "qwen_oauth_hint": "Выполните вход в сервис Qwen через поток авторизации устройства и автоматически получите/сохраните файлы авторизации.", + "qwen_oauth_url_label": "URL авторизации:", + "qwen_open_link": "Открыть ссылку", + "qwen_copy_link": "Скопировать ссылку", + "qwen_oauth_status_waiting": "Ожидание аутентификации...", + "qwen_oauth_status_success": "Аутентификация успешна!", + "qwen_oauth_status_error": "Ошибка аутентификации:", + "qwen_oauth_start_error": "Не удалось запустить Qwen OAuth:", + "qwen_oauth_polling_error": "Не удалось проверить статус аутентификации:", "oauth_callback_label": "Callback URL", "oauth_callback_placeholder": "http://localhost:1455/auth/callback?code=...&state=...", - "oauth_callback_hint": "Remote browser mode: after the provider redirects to http://localhost:..., copy the full URL and submit it here.", - "oauth_callback_button": "Submit Callback URL", - "oauth_callback_required": "Please paste the full redirect URL first.", - "oauth_callback_success": "Callback URL submitted. Continue waiting for authentication.", - "oauth_callback_error": "Failed to submit callback URL:", - "oauth_callback_upgrade_hint": "Please update CLI Proxy API or check the connection.", - "oauth_callback_status_success": "Callback URL submitted, waiting for authentication...", - "oauth_callback_status_error": "Callback URL submission failed:", - "missing_state": "Unable to retrieve authentication state parameter", + "oauth_callback_hint": "Режим удалённого браузера: после перенаправления провайдера на http://localhost:... скопируйте полный URL и отправьте его здесь.", + "oauth_callback_button": "Отправить Callback URL", + "oauth_callback_required": "Сначала вставьте полный URL перенаправления.", + "oauth_callback_success": "Callback URL отправлен. Продолжайте ожидать аутентификацию.", + "oauth_callback_error": "Не удалось отправить Callback URL:", + "oauth_callback_upgrade_hint": "Обновите CLI Proxy API или проверьте подключение.", + "oauth_callback_status_success": "Callback URL отправлен, ожидаем аутентификацию...", + "oauth_callback_status_error": "Не удалось отправить Callback URL:", + "missing_state": "Не удалось получить параметр состояния аутентификации", "iflow_oauth_title": "iFlow OAuth", - "iflow_oauth_button": "Start iFlow Login", - "iflow_oauth_hint": "Login to iFlow service through OAuth flow, automatically obtain and save authentication files.", - "iflow_oauth_url_label": "Authorization URL:", - "iflow_open_link": "Open Link", - "iflow_copy_link": "Copy Link", - "iflow_oauth_status_waiting": "Waiting for authentication...", - "iflow_oauth_status_success": "Authentication successful!", - "iflow_oauth_status_error": "Authentication failed:", - "iflow_oauth_start_error": "Failed to start iFlow OAuth:", - "iflow_oauth_polling_error": "Failed to check authentication status:", - "iflow_cookie_title": "iFlow Cookie Login", - "iflow_cookie_label": "Cookie Value:", - "iflow_cookie_placeholder": "Enter the BXAuth value, starting with BXAuth=", - "iflow_cookie_hint": "Submit an existing cookie to finish login without opening the authorization link; the credential file will be saved automatically.", - "iflow_cookie_key_hint": "Note: Create a key on the platform first.", - "iflow_cookie_button": "Submit Cookie Login", - "iflow_cookie_status_success": "Cookie login succeeded and credentials are saved.", - "iflow_cookie_status_error": "Cookie login failed:", - "iflow_cookie_status_duplicate": "Duplicate config:", - "iflow_cookie_start_error": "Failed to submit cookie login:", - "iflow_cookie_config_duplicate": "A config file already exists (duplicate). Remove the existing file and try again if you want to re-save it.", - "iflow_cookie_required": "Please provide the Cookie value first.", - "iflow_cookie_result_title": "Cookie Login Result", - "iflow_cookie_result_email": "Account", - "iflow_cookie_result_expired": "Expires At", - "iflow_cookie_result_path": "Saved Path", - "iflow_cookie_result_type": "Type", - "remote_access_disabled": "This login method is not available for remote access. Please access from localhost." + "iflow_oauth_button": "Начать вход iFlow", + "iflow_oauth_hint": "Выполните вход в сервис iFlow через OAuth и автоматически получите/сохраните файлы авторизации.", + "iflow_oauth_url_label": "URL авторизации:", + "iflow_open_link": "Открыть ссылку", + "iflow_copy_link": "Скопировать ссылку", + "iflow_oauth_status_waiting": "Ожидание аутентификации...", + "iflow_oauth_status_success": "Аутентификация успешна!", + "iflow_oauth_status_error": "Ошибка аутентификации:", + "iflow_oauth_start_error": "Не удалось запустить iFlow OAuth:", + "iflow_oauth_polling_error": "Не удалось проверить статус аутентификации:", + "iflow_cookie_title": "Вход iFlow по cookie", + "iflow_cookie_label": "Значение cookie:", + "iflow_cookie_placeholder": "Введите значение BXAuth, начиная с BXAuth=", + "iflow_cookie_hint": "Отправьте существующий cookie, чтобы завершить вход без открытия ссылки авторизации; файл учётных данных будет сохранён автоматически.", + "iflow_cookie_key_hint": "Примечание: сначала создайте ключ на платформе.", + "iflow_cookie_button": "Отправить вход по cookie", + "iflow_cookie_status_success": "Вход по cookie выполнен, учётные данные сохранены.", + "iflow_cookie_status_error": "Ошибка входа по cookie:", + "iflow_cookie_status_duplicate": "Дублирующая конфигурация:", + "iflow_cookie_start_error": "Не удалось отправить вход по cookie:", + "iflow_cookie_config_duplicate": "Такая конфигурация уже существует. Удалите файл и повторите, если хотите перезаписать.", + "iflow_cookie_required": "Сначала укажите значение cookie.", + "iflow_cookie_result_title": "Результат входа по cookie", + "iflow_cookie_result_email": "Аккаунт", + "iflow_cookie_result_expired": "Истекает", + "iflow_cookie_result_path": "Путь сохранения", + "iflow_cookie_result_type": "Тип", + "remote_access_disabled": "Этот способ входа недоступен при удалённом доступе. Подключитесь с localhost." }, "usage_stats": { - "title": "Usage Statistics", - "total_requests": "Total Requests", - "success_requests": "Success Requests", - "failed_requests": "Failed Requests", - "total_tokens": "Total Tokens", - "cached_tokens": "Cached Tokens", - "reasoning_tokens": "Reasoning Tokens", + "title": "Статистика использования", + "total_requests": "Всего запросов", + "success_requests": "Успешные запросы", + "failed_requests": "Неуспешные запросы", + "total_tokens": "Всего токенов", + "cached_tokens": "Кэшированные токены", + "reasoning_tokens": "Токены рассуждений", "rpm_30m": "RPM", "tpm_30m": "TPM", - "rate_30m": "Rate (last 30 min)", - "model_name": "Model Name", - "model_price_settings": "Model Pricing Settings", - "saved_prices": "Saved Prices", - "requests_trend": "Request Trends", - "tokens_trend": "Token Usage Trends", - "api_details": "API Details", - "by_hour": "By Hour", - "by_day": "By Day", - "refresh": "Refresh", - "export": "Export", - "import": "Import", - "export_success": "Usage export downloaded", - "import_success": "Import complete: added {{added}}, skipped {{skipped}}, total {{total}}, failed {{failed}}", - "import_invalid": "Invalid usage export file", - "chart_line_label_1": "Line 1", - "chart_line_label_2": "Line 2", - "chart_line_label_3": "Line 3", - "chart_line_label_4": "Line 4", - "chart_line_label_5": "Line 5", - "chart_line_label_6": "Line 6", - "chart_line_label_7": "Line 7", - "chart_line_label_8": "Line 8", - "chart_line_label_9": "Line 9", - "chart_line_hidden": "Hide", - "chart_line_actions_label": "Lines to display", - "chart_line_add": "Add line", - "chart_line_all": "All", - "chart_line_delete": "Delete line", - "chart_line_hint": "Show up to 9 model lines at once", - "no_data": "No Data Available", - "loading_error": "Loading Failed", - "api_endpoint": "API Endpoint", - "requests_count": "Request Count", - "tokens_count": "Token Count", - "models": "Model Statistics", - "success_rate": "Success Rate", - "total_cost": "Total Cost", - "total_cost_hint": "Based on configured model pricing", - "model_price_title": "Model Pricing", - "model_price_reset": "Clear Prices", - "model_price_model_label": "Model", - "model_price_select_placeholder": "Choose a model", - "model_price_select_hint": "Models come from usage details", - "model_price_prompt": "Prompt price", - "model_price_completion": "Completion price", - "model_price_cache": "Cache price", - "model_price_save": "Save Price", - "model_price_empty": "No model prices set", - "model_price_model": "Model", - "model_price_saved": "Model price saved", - "model_price_model_required": "Please choose a model to set pricing", - "cost_trend": "Cost Overview", - "cost_axis_label": "Cost ($)", - "cost_need_price": "Set a model price to view cost stats", - "cost_need_usage": "No usage data available to calculate cost", - "cost_no_data": "No cost data yet" + "rate_30m": "Скорость (последние 30 мин)", + "model_name": "Название модели", + "model_price_settings": "Настройки стоимости моделей", + "saved_prices": "Сохранённые цены", + "requests_trend": "Динамика запросов", + "tokens_trend": "Динамика токенов", + "api_details": "Детали API", + "by_hour": "По часам", + "by_day": "По дням", + "refresh": "Обновить", + "export": "Экспорт", + "import": "Импорт", + "export_success": "Экспорт использования скачан", + "import_success": "Импорт завершён: добавлено {{added}}, пропущено {{skipped}}, всего {{total}}, ошибок {{failed}}", + "import_invalid": "Недопустимый файл экспорта использования", + "chart_line_label_1": "Линия 1", + "chart_line_label_2": "Линия 2", + "chart_line_label_3": "Линия 3", + "chart_line_label_4": "Линия 4", + "chart_line_label_5": "Линия 5", + "chart_line_label_6": "Линия 6", + "chart_line_label_7": "Линия 7", + "chart_line_label_8": "Линия 8", + "chart_line_label_9": "Линия 9", + "chart_line_hidden": "Скрыть", + "chart_line_actions_label": "Линии для отображения", + "chart_line_add": "Добавить линию", + "chart_line_all": "Все", + "chart_line_delete": "Удалить линию", + "chart_line_hint": "Отображайте до 9 линий моделей одновременно", + "no_data": "Данные отсутствуют", + "loading_error": "Не удалось загрузить", + "api_endpoint": "API-эндпоинт", + "requests_count": "Количество запросов", + "tokens_count": "Количество токенов", + "models": "Статистика моделей", + "success_rate": "Успешность", + "total_cost": "Общая стоимость", + "total_cost_hint": "Основано на настроенной стоимости моделей", + "model_price_title": "Стоимость моделей", + "model_price_reset": "Сбросить цены", + "model_price_model_label": "Модель", + "model_price_select_placeholder": "Выберите модель", + "model_price_select_hint": "Модели берутся из детальной статистики использования", + "model_price_prompt": "Цена prompt", + "model_price_completion": "Цена completion", + "model_price_cache": "Цена кэша", + "model_price_save": "Сохранить цену", + "model_price_empty": "Цена для моделей не задана", + "model_price_model": "Модель", + "model_price_saved": "Цена модели сохранена", + "model_price_model_required": "Выберите модель для задания цены", + "cost_trend": "Обзор стоимости", + "cost_axis_label": "Стоимость ($)", + "cost_need_price": "Задайте стоимость модели, чтобы увидеть статистику затрат", + "cost_need_usage": "Нет данных использования для расчёта стоимости", + "cost_no_data": "Данных о стоимости ещё нет" }, "stats": { - "success": "Success", - "failure": "Failure" + "success": "Успех", + "failure": "Сбой" }, "logs": { - "title": "Logs Viewer", - "refresh_button": "Refresh Logs", - "clear_button": "Clear Logs", - "download_button": "Download Logs", - "error_log_button": "Select Error Log", - "error_logs_modal_title": "Error Request Logs", - "error_logs_description": "Pick an error request log file to download (only generated when request logging is off).", - "error_logs_request_log_enabled": "Request logging is enabled, so this list will always be empty. Disable request logging and refresh to view error logs.", - "error_logs_empty": "No error request log files found", - "error_logs_load_error": "Failed to load error log list", - "error_logs_size": "Size", - "error_logs_modified": "Last modified", - "error_logs_download": "Download", - "error_log_download_success": "Error log downloaded successfully", - "request_log_download_title": "Download Request Log", - "request_log_download_confirm": "Download request log for ID {{id}}?", - "request_log_download_success": "Request log downloaded successfully", - "empty_title": "No Logs Available", - "empty_desc": "When \"Enable logging to file\" is enabled, logs will be displayed here", - "log_content": "Log Content", - "loading": "Loading logs...", - "load_error": "Failed to load logs", - "clear_confirm": "Are you sure you want to clear all logs? This action cannot be undone!", - "clear_success": "Logs cleared successfully", - "download_success": "Logs downloaded successfully", - "auto_refresh": "Auto Refresh", - "auto_refresh_enabled": "Auto refresh enabled", - "auto_refresh_disabled": "Auto refresh disabled", - "load_more_hint": "Scroll up to load more", - "hidden_lines": "Hidden: {{count}} lines", - "loaded_lines": "Loaded: {{count}} lines", - "filtered_lines": "Filtered: {{count}} lines", - "hide_management_logs": "Hide {{prefix}} logs", - "show_raw_logs": "Show Raw Logs", - "show_raw_logs_hint": "Show original log text for easier multi-line copy", - "search_placeholder": "Search logs by content or keyword", - "search_empty_title": "No matching logs found", - "search_empty_desc": "Try a different keyword or clear the filters.", - "double_click_copy_hint": "Double-click to copy raw log line", - "copy_success": "Log copied to clipboard", - "copy_failed": "Copy failed", - "lines": "lines", - "removed": "Filtered", - "upgrade_required_title": "Please Upgrade CLI Proxy API", - "upgrade_required_desc": "The current server version does not support the logs viewing feature. Please upgrade to the latest version of CLI Proxy API to use this feature." + "title": "Просмотр журналов", + "refresh_button": "Обновить журналы", + "clear_button": "Очистить журналы", + "download_button": "Скачать журналы", + "error_log_button": "Выбрать журнал ошибок", + "error_logs_modal_title": "Журналы ошибок запросов", + "error_logs_description": "Выберите файл журнала ошибок запроса для скачивания (создаётся только при отключённом журналировании запросов).", + "error_logs_request_log_enabled": "Журналирование запросов включено, поэтому этот список всегда будет пустым. Отключите журналирование запросов и обновите список, чтобы просмотреть журналы ошибок.", + "error_logs_empty": "Файлы журнала ошибок запросов не найдены", + "error_logs_load_error": "Не удалось загрузить список журналов ошибок", + "error_logs_size": "Размер", + "error_logs_modified": "Изменён", + "error_logs_download": "Скачать", + "error_log_download_success": "Журнал ошибок успешно скачан", + "request_log_download_title": "Скачать журнал запросов", + "request_log_download_confirm": "Скачать журнал запросов с идентификатором {{id}}?", + "request_log_download_success": "Журнал запросов успешно скачан", + "empty_title": "Журналы недоступны", + "empty_desc": "Когда включена опция \"Включить журналирование в файл\", журналы появятся здесь", + "log_content": "Содержимое журнала", + "loading": "Загрузка журналов...", + "load_error": "Не удалось загрузить журналы", + "clear_confirm": "Очистить все журналы? Действие нельзя отменить!", + "clear_success": "Журналы успешно очищены", + "download_success": "Журналы успешно скачаны", + "auto_refresh": "Автообновление", + "auto_refresh_enabled": "Автообновление включено", + "auto_refresh_disabled": "Автообновление выключено", + "load_more_hint": "Прокрутите вверх, чтобы загрузить ещё", + "hidden_lines": "Скрыто: {{count}} строк", + "loaded_lines": "Загружено: {{count}} строк", + "filtered_lines": "Отфильтровано: {{count}} строк", + "hide_management_logs": "Скрыть журналы {{prefix}}", + "show_raw_logs": "Показать исходные журналы", + "show_raw_logs_hint": "Показать текст журнала без обработки для удобного копирования в несколько строк", + "search_placeholder": "Искать по содержимому или ключевым словам", + "search_empty_title": "Подходящих журналов не найдено", + "search_empty_desc": "Попробуйте другой запрос или сбросьте фильтры.", + "double_click_copy_hint": "Дважды нажмите, чтобы скопировать строку журнала", + "copy_success": "Строка журнала скопирована", + "copy_failed": "Не удалось скопировать", + "lines": "строк", + "removed": "Отфильтровано", + "upgrade_required_title": "Обновите CLI Proxy API", + "upgrade_required_desc": "Текущая версия сервера не поддерживает просмотр журналов. Обновите CLI Proxy API до последней версии, чтобы использовать эту функцию." }, "config_management": { - "title": "Config Panel", - "editor_title": "Configuration File", - "reload": "Reload", - "save": "Save", - "description": "Edit config.yaml via visual editor or source file", - "status_idle": "Waiting for action", - "status_loading": "Loading configuration...", - "status_loaded": "Configuration loaded", - "status_dirty": "Unsaved changes", - "status_disconnected": "Connect to the server to load the configuration", - "status_load_failed": "Load failed", - "status_saving": "Saving configuration...", - "status_saved": "Configuration saved", - "status_save_failed": "Save failed", - "save_success": "Configuration saved successfully", - "error_yaml_not_supported": "Server did not return YAML. Verify the /config.yaml endpoint is available.", + "title": "Панель конфигурации", + "editor_title": "Файл конфигурации", + "reload": "Перезагрузить", + "save": "Сохранить", + "description": "Редактируйте config.yaml через визуальный редактор или исходный файл", + "status_idle": "Ожидание действия", + "status_loading": "Загрузка конфигурации...", + "status_loaded": "Конфигурация загружена", + "status_dirty": "Есть несохранённые изменения", + "status_disconnected": "Подключитесь к серверу, чтобы загрузить конфигурацию", + "status_load_failed": "Не удалось загрузить", + "status_saving": "Сохранение конфигурации...", + "status_saved": "Конфигурация сохранена", + "status_save_failed": "Не удалось сохранить", + "save_success": "Конфигурация успешно сохранена", + "error_yaml_not_supported": "Сервер не вернул YAML. Убедитесь, что доступна конечная точка /config.yaml.", "editor_placeholder": "key: value", - "search_placeholder": "Search config...", - "search_button": "Search", - "search_no_results": "No results", - "search_prev": "Previous", - "search_next": "Next", + "search_placeholder": "Поиск по конфигурации...", + "search_button": "Поиск", + "search_no_results": "Нет результатов", + "search_prev": "Назад", + "search_next": "Вперёд", "tabs": { - "visual": "Visual Editor", - "source": "Source File Editor" + "visual": "Визуальный редактор", + "source": "Редактор файла" }, "visual": { "sections": { "server": { - "title": "Server Configuration", - "description": "Basic server settings", - "host": "Host Address", - "port": "Port" + "title": "Настройки сервера", + "description": "Базовые параметры сервера", + "host": "Адрес хоста", + "port": "Порт" }, "tls": { - "title": "TLS/SSL Configuration", - "description": "HTTPS secure connection settings", - "enable": "Enable TLS", - "enable_desc": "Enable HTTPS secure connection", - "cert": "Certificate File Path", - "key": "Private Key File Path" + "title": "Настройка TLS/SSL", + "description": "Параметры безопасного HTTPS-соединения", + "enable": "Включить TLS", + "enable_desc": "Включить безопасное HTTPS-соединение", + "cert": "Путь к сертификату", + "key": "Путь к закрытому ключу" }, "remote": { - "title": "Remote Management", - "description": "Remote access and control panel settings", - "allow_remote": "Allow Remote Access", - "allow_remote_desc": "Allow management access from other hosts", - "disable_panel": "Disable Control Panel", - "disable_panel_desc": "Disable the built-in web control panel", - "secret_key": "Management Key", - "secret_key_placeholder": "Set management key", - "panel_repo": "Panel Repository" + "title": "Удалённое управление", + "description": "Настройки удалённого доступа и панели управления", + "allow_remote": "Разрешить удалённый доступ", + "allow_remote_desc": "Разрешить управление с других хостов", + "disable_panel": "Отключить панель", + "disable_panel_desc": "Отключить встроенную веб-панель управления", + "secret_key": "Ключ управления", + "secret_key_placeholder": "Задайте ключ управления", + "panel_repo": "Репозиторий панели" }, "auth": { - "title": "Authentication Configuration", - "description": "API keys and authentication directory settings", - "auth_dir": "Auth Directory (auth-dir)", - "auth_dir_hint": "Directory path for authentication files (supports ~)" + "title": "Настройки аутентификации", + "description": "API-ключи и каталог аутентификации", + "auth_dir": "Каталог auth-dir", + "auth_dir_hint": "Путь к каталогу с файлами аутентификации (поддерживает ~)" }, "system": { - "title": "System Configuration", - "description": "Debug, logging, statistics, and performance settings", - "debug": "Debug Mode", - "debug_desc": "Enable verbose debug logging", - "commercial_mode": "Commercial Mode", - "commercial_mode_desc": "Disable high-overhead middleware to reduce memory under high concurrency", - "logging_to_file": "Log to File", - "logging_to_file_desc": "Save logs to rotating files", - "usage_statistics": "Usage Statistics", - "usage_statistics_desc": "Collect usage statistics", - "logs_max_size": "Log File Size Limit (MB)", - "usage_retention_days": "Usage Records Retention Days", - "usage_retention_hint": "0 means no limit (no cleanup)" + "title": "Системные настройки", + "description": "Отладка, журналирование, статистика и производительность", + "debug": "Режим отладки", + "debug_desc": "Включить подробные отладочные журналы", + "commercial_mode": "Коммерческий режим", + "commercial_mode_desc": "Отключить тяжёлое промежуточное ПО, чтобы снизить расход памяти при высокой нагрузке", + "logging_to_file": "Журналировать в файл", + "logging_to_file_desc": "Сохранять журналы во вращающиеся файлы", + "usage_statistics": "Статистика использования", + "usage_statistics_desc": "Собирать статистику использования", + "logs_max_size": "Максимальный размер файла журнала (МБ)", + "usage_retention_days": "Хранить статистику (дней)", + "usage_retention_hint": "0 означает без ограничений (без очистки)" }, "network": { - "title": "Network Configuration", - "description": "Proxy, retry, and routing settings", - "proxy_url": "Proxy URL", - "request_retry": "Request Retry Count", - "max_retry_interval": "Max Retry Interval (seconds)", - "routing_strategy": "Routing Strategy", - "routing_strategy_hint": "Select credential selection strategy", - "strategy_round_robin": "Round Robin", - "strategy_fill_first": "Fill First", - "force_model_prefix": "Force Model Prefix", - "force_model_prefix_desc": "Unprefixed model requests only use credentials without prefix", - "ws_auth": "WebSocket Authentication", - "ws_auth_desc": "Enable WebSocket authentication (/v1/ws)" + "title": "Сетевые настройки", + "description": "Параметры прокси, повторов и маршрутизации", + "proxy_url": "URL прокси", + "request_retry": "Количество повторов запросов", + "max_retry_interval": "Максимальный интервал повтора (сек)", + "routing_strategy": "Стратегия маршрутизации", + "routing_strategy_hint": "Выберите стратегию подбора учётных данных", + "strategy_round_robin": "По кругу", + "strategy_fill_first": "Сначала заполнить", + "force_model_prefix": "Принудительный префикс модели", + "force_model_prefix_desc": "Запросы к моделям без префикса используют только учётные данные без префикса", + "ws_auth": "Аутентификация WebSocket", + "ws_auth_desc": "Включить аутентификацию WebSocket (/v1/ws)" }, "quota": { - "title": "Quota Fallback", - "description": "Fallback strategy when quota is exceeded", - "switch_project": "Switch Project", - "switch_project_desc": "Automatically switch to another project when quota is exceeded", - "switch_preview_model": "Switch to Preview Model", - "switch_preview_model_desc": "Switch to preview model version when quota is exceeded" + "title": "Резерв по квоте", + "description": "Стратегия при превышении квоты", + "switch_project": "Переключить проект", + "switch_project_desc": "Автоматически переходить на другой проект при превышении квоты", + "switch_preview_model": "Переключить на preview-модель", + "switch_preview_model_desc": "Переключаться на preview-версию модели при превышении квоты" }, "streaming": { - "title": "Streaming Configuration", - "description": "Keepalive and bootstrap retry settings", - "keepalive_seconds": "Keepalive Seconds", - "keepalive_hint": "Set to 0 or leave empty to disable keepalive", - "bootstrap_retries": "Bootstrap Retries", - "bootstrap_hint": "Number of retries during stream startup (before first byte)", - "nonstream_keepalive": "Non-stream Keepalive Interval (seconds)", - "nonstream_keepalive_hint": "Send blank lines every N seconds for non-streaming responses to prevent idle timeout, set to 0 or leave empty to disable", - "disabled": "Disabled" + "title": "Настройки стриминга", + "description": "Параметры keepalive и повторов запуска", + "keepalive_seconds": "Период keepalive (сек)", + "keepalive_hint": "Установите 0 или оставьте поле пустым, чтобы отключить keepalive", + "bootstrap_retries": "Повторы запуска", + "bootstrap_hint": "Количество попыток при запуске стрима (до первого байта)", + "nonstream_keepalive": "Интервал keepalive для нестиминговых ответов (сек)", + "nonstream_keepalive_hint": "Отправлять пустые строки каждые N секунд для нестиминговых ответов, чтобы избежать простоя; установите 0 или оставьте пустым для отключения", + "disabled": "Отключено" }, "payload": { - "title": "Payload Configuration", - "description": "Default values, override rules, and filter rules", - "default_rules": "Default Rules", - "default_rules_desc": "Use these default values when parameters are not specified in the request", - "override_rules": "Override Rules", - "override_rules_desc": "Force override parameter values in the request", - "filter_rules": "Filter Rules", - "filter_rules_desc": "Pre-filter upstream request body via JSON Path, automatically remove non-compliant/redundant parameters (Request Sanitization)" + "title": "Настройки полезной нагрузки", + "description": "Значения по умолчанию, правила переопределения и фильтрации", + "default_rules": "Правила по умолчанию", + "default_rules_desc": "Использовать эти значения, если параметр не указан в запросе", + "override_rules": "Правила переопределения", + "override_rules_desc": "Принудительно задавать значения параметров в запросе", + "filter_rules": "Правила фильтрации", + "filter_rules_desc": "Предварительно фильтровать тело исходящего запроса через JSON Path, автоматически удалять несоответствующие или лишние параметры (санитизация запроса)" } }, "api_keys": { - "label": "API Keys List (api-keys)", - "add": "Add API Key", - "empty": "No API keys", - "hint": "Each entry represents an API key (consistent with 'API Key Management' page style)", - "edit_title": "Edit API Key", - "add_title": "Add API Key", - "input_label": "API Key", - "input_placeholder": "Paste your API key", - "input_hint": "This only modifies the local config file content, it will not sync to the API Key Management interface", - "error_empty": "Please enter an API key", - "error_invalid": "API key contains invalid characters" + "label": "Список API-ключей (api-keys)", + "add": "Добавить API-ключ", + "empty": "API-ключи отсутствуют", + "hint": "Каждая запись — это API-ключ (в том же стиле, что и на странице управления API-ключами)", + "edit_title": "Редактирование API-ключа", + "add_title": "Добавление API-ключа", + "input_label": "API-ключ", + "input_placeholder": "Вставьте API-ключ", + "input_hint": "Меняет только содержимое локального файла конфигурации, не синхронизируется с интерфейсом управления API-ключами", + "error_empty": "Введите API-ключ", + "error_invalid": "API-ключ содержит недопустимые символы" }, "payload_rules": { - "rule": "Rule", - "models": "Applicable Models", - "model_name": "Model Name", - "provider_type": "Provider Type", - "add_model": "Add Model", - "params": "Parameter Settings", - "remove_params": "Remove Parameters", - "json_path": "JSON Path (e.g., temperature)", - "json_path_filter": "JSON Path (gjson/sjson), e.g., generationConfig.thinkingConfig.thinkingBudget", - "param_type": "Parameter Type", - "add_param": "Add Parameter", - "no_rules": "No rules", - "add_rule": "Add Rule", - "value_string": "String value", - "value_number": "Number value (e.g., 0.7)", - "value_boolean": "true or false", - "value_json": "JSON value", - "value_default": "Value" + "rule": "Правило", + "models": "Применимые модели", + "model_name": "Название модели", + "provider_type": "Тип провайдера", + "add_model": "Добавить модель", + "params": "Настройки параметров", + "remove_params": "Удалить параметры", + "json_path": "JSON Path (например, temperature)", + "json_path_filter": "JSON Path (gjson/sjson), например generationConfig.thinkingConfig.thinkingBudget", + "param_type": "Тип параметра", + "add_param": "Добавить параметр", + "no_rules": "Правил нет", + "add_rule": "Добавить правило", + "value_string": "Строковое значение", + "value_number": "Числовое значение (например, 0.7)", + "value_boolean": "true или false", + "value_json": "Значение JSON", + "value_default": "Значение" }, "common": { - "edit": "Edit", - "delete": "Delete", - "cancel": "Cancel", - "update": "Update", - "add": "Add" + "edit": "Изменить", + "delete": "Удалить", + "cancel": "Отменить", + "update": "Обновить", + "add": "Добавить" } } }, "quota_management": { - "title": "Quota Management", - "description": "Monitor OAuth quota status for Antigravity, Codex, and Gemini CLI credentials.", - "refresh_files": "Refresh auth files", - "refresh_files_and_quota": "Refresh files & quota" + "title": "Управление квотами", + "description": "Следите за статусом квот OAuth для учётных данных Antigravity, Codex и Gemini CLI.", + "refresh_files": "Обновить файлы авторизации", + "refresh_files_and_quota": "Обновить файлы и квоты" }, "system_info": { - "title": "Management Center Info", - "connection_status_title": "Connection Status", - "api_status_label": "API Status:", - "config_status_label": "Config Status:", - "last_update_label": "Last Update:", - "cache_data": "Cache Data", - "real_time_data": "Real-time Data", - "not_loaded": "Not Loaded", - "seconds_ago": "seconds ago", - "models_title": "Available Models", - "models_desc": "Shows the /models response and uses saved API keys for auth automatically.", - "models_loading": "Loading available models...", - "models_empty": "No models returned by /models", - "models_error": "Failed to load model list", - "models_count": "{{count}} available models", - "version_check_title": "Update Check", - "version_check_desc": "Call the /latest-version endpoint to compare with the server version and see if an update is available.", - "version_current_label": "Current version", - "version_latest_label": "Latest version", - "version_check_button": "Check for updates", - "version_check_idle": "Click to check for updates", - "version_checking": "Checking for the latest version...", - "version_update_available": "An update is available: {{version}}", - "version_is_latest": "You are on the latest version", - "version_check_error": "Update check failed", - "version_current_missing": "Server version is unavailable; cannot compare", - "version_unknown": "Unknown", - "quick_links_title": "Quick Links", - "quick_links_desc": "Access project repositories and documentation for help and updates.", - "link_main_repo": "Main Repository", - "link_main_repo_desc": "CLI Proxy API core program source code", - "link_webui_repo": "WebUI Repository", - "link_webui_repo_desc": "Management Center frontend source code", - "link_docs": "Documentation", - "link_docs_desc": "Usage tutorials and configuration guides", - "clear_login_title": "Local Login Data", - "clear_login_desc": "Clear locally saved login data and sign out. Usage stats pricing settings will remain untouched.", - "clear_login_button": "Clear login data", - "clear_login_confirm": "Clear local login data and sign out now?" + "title": "Информация о центре управления", + "connection_status_title": "Статус подключения", + "api_status_label": "Статус API:", + "config_status_label": "Статус конфигурации:", + "last_update_label": "Последнее обновление:", + "cache_data": "Данные из кэша", + "real_time_data": "Данные в реальном времени", + "not_loaded": "Не загружено", + "seconds_ago": "секунд назад", + "models_title": "Доступные модели", + "models_desc": "Показывает ответ /models и автоматически использует сохранённые API-ключи для авторизации.", + "models_loading": "Загрузка доступных моделей...", + "models_empty": "Сервис /models не вернул модели", + "models_error": "Не удалось загрузить список моделей", + "models_count": "Доступно моделей: {{count}}", + "version_check_title": "Проверка обновлений", + "version_check_desc": "Вызовите эндпоинт /latest-version, чтобы сравнить с версией сервера и узнать о доступных обновлениях.", + "version_current_label": "Текущая версия", + "version_latest_label": "Последняя версия", + "version_check_button": "Проверить обновления", + "version_check_idle": "Нажмите, чтобы проверить обновления", + "version_checking": "Поиск последней версии...", + "version_update_available": "Доступно обновление: {{version}}", + "version_is_latest": "Установлена последняя версия", + "version_check_error": "Не удалось проверить обновление", + "version_current_missing": "Версия сервера недоступна; сравнение невозможно", + "version_unknown": "Неизвестно", + "quick_links_title": "Быстрые ссылки", + "quick_links_desc": "Доступ к репозиториям проекта и документации для помощи и обновлений.", + "link_main_repo": "Основной репозиторий", + "link_main_repo_desc": "Исходный код основной программы CLI Proxy API", + "link_webui_repo": "Репозиторий WebUI", + "link_webui_repo_desc": "Исходный код фронтенда центра управления", + "link_docs": "Документация", + "link_docs_desc": "Учебные пособия и руководства по настройке", + "clear_login_title": "Локальные данные входа", + "clear_login_desc": "Очистите локально сохранённые данные входа и выполните выход. Настройки стоимости статистики использования сохранятся.", + "clear_login_button": "Очистить данные входа", + "clear_login_confirm": "Очистить локальные данные входа и выйти?" }, "notification": { - "debug_updated": "Debug settings updated", - "proxy_updated": "Proxy settings updated", - "proxy_cleared": "Proxy settings cleared", - "retry_updated": "Retry settings updated", - "quota_switch_project_updated": "Project switch settings updated", - "quota_switch_preview_updated": "Preview model switch settings updated", - "usage_statistics_updated": "Usage statistics settings updated", - "logging_to_file_updated": "Logging settings updated", - "logs_max_total_size_updated": "Log size limit updated", - "request_log_updated": "Request logging setting updated", - "force_model_prefix_updated": "Model prefix setting updated", - "ws_auth_updated": "WebSocket authentication setting updated", - "routing_strategy_updated": "Routing strategy updated", - "login_storage_cleared": "Local login data cleared", - "api_key_added": "API key added successfully", - "api_key_updated": "API key updated successfully", - "api_key_deleted": "API key deleted successfully", - "api_key_invalid_chars": "API key can only contain letters, numbers, and symbols", - "gemini_key_added": "Gemini key added successfully", - "gemini_key_updated": "Gemini key updated successfully", - "gemini_key_deleted": "Gemini key deleted successfully", - "gemini_multi_input_required": "Please enter at least one Gemini key", - "gemini_multi_failed": "Gemini bulk add failed", - "gemini_multi_summary": "Gemini bulk add finished: {{success}} added, {{skipped}} skipped, {{failed}} failed", - "codex_config_added": "Codex configuration added successfully", - "codex_config_updated": "Codex configuration updated successfully", - "codex_config_deleted": "Codex configuration deleted successfully", - "codex_base_url_required": "Please enter the Codex Base URL", - "claude_config_added": "Claude configuration added successfully", - "claude_config_updated": "Claude configuration updated successfully", - "claude_config_deleted": "Claude configuration deleted successfully", - "vertex_config_added": "Vertex configuration added successfully", - "vertex_config_updated": "Vertex configuration updated successfully", - "vertex_config_deleted": "Vertex configuration deleted successfully", - "vertex_base_url_required": "Please enter the Vertex Base URL", - "config_enabled": "Configuration enabled", - "config_disabled": "Configuration disabled", - "field_required": "Required fields cannot be empty", - "openai_provider_required": "Please fill in provider name and Base URL", - "openai_provider_added": "OpenAI provider added successfully", - "openai_provider_updated": "OpenAI provider updated successfully", - "openai_provider_deleted": "OpenAI provider deleted successfully", - "ampcode_updated": "Ampcode configuration updated", - "ampcode_upstream_api_key_cleared": "Ampcode upstream API key override cleared", - "openai_model_name_required": "Model name is required", - "openai_test_url_required": "Please provide a valid Base URL before testing", - "openai_test_key_required": "Please add at least one API key before testing", - "openai_test_model_required": "Please select a model to test", - "data_refreshed": "Data refreshed successfully", - "connection_required": "Please establish connection first", - "refresh_failed": "Refresh failed", - "update_failed": "Update failed", - "add_failed": "Add failed", - "delete_failed": "Delete failed", - "upload_failed": "Upload failed", - "download_failed": "Download failed", - "login_failed": "Login failed", - "please_enter": "Please enter", - "please_fill": "Please fill", - "provider_name_url": "provider name and Base URL", - "api_key": "API key", - "gemini_api_key": "Gemini API key", - "codex_api_key": "Codex API key", - "claude_api_key": "Claude API key", - "link_copied": "Link copied to clipboard" + "debug_updated": "Настройки отладки обновлены", + "proxy_updated": "Настройки прокси обновлены", + "proxy_cleared": "Настройки прокси очищены", + "retry_updated": "Настройки повторов обновлены", + "quota_switch_project_updated": "Настройки переключения проектов обновлены", + "quota_switch_preview_updated": "Настройки переключения на preview-модель обновлены", + "usage_statistics_updated": "Настройки статистики использования обновлены", + "logging_to_file_updated": "Настройки журналирования обновлены", + "logs_max_total_size_updated": "Лимит размера журналов обновлён", + "request_log_updated": "Настройка журналирования запросов обновлена", + "force_model_prefix_updated": "Настройка префикса модели обновлена", + "ws_auth_updated": "Настройка аутентификации WebSocket обновлена", + "routing_strategy_updated": "Стратегия маршрутизации обновлена", + "login_storage_cleared": "Локальные данные входа очищены", + "api_key_added": "API-ключ успешно добавлен", + "api_key_updated": "API-ключ успешно обновлён", + "api_key_deleted": "API-ключ успешно удалён", + "api_key_invalid_chars": "API-ключ может содержать только буквы, цифры и символы", + "gemini_key_added": "Ключ Gemini успешно добавлен", + "gemini_key_updated": "Ключ Gemini успешно обновлён", + "gemini_key_deleted": "Ключ Gemini успешно удалён", + "gemini_multi_input_required": "Введите хотя бы один ключ Gemini", + "gemini_multi_failed": "Пакетное добавление Gemini не удалось", + "gemini_multi_summary": "Пакетное добавление Gemini завершено: добавлено {{success}}, пропущено {{skipped}}, ошибок {{failed}}", + "codex_config_added": "Конфигурация Codex успешно добавлена", + "codex_config_updated": "Конфигурация Codex успешно обновлена", + "codex_config_deleted": "Конфигурация Codex успешно удалена", + "codex_base_url_required": "Введите базовый URL Codex", + "claude_config_added": "Конфигурация Claude успешно добавлена", + "claude_config_updated": "Конфигурация Claude успешно обновлена", + "claude_config_deleted": "Конфигурация Claude успешно удалена", + "vertex_config_added": "Конфигурация Vertex успешно добавлена", + "vertex_config_updated": "Конфигурация Vertex успешно обновлена", + "vertex_config_deleted": "Конфигурация Vertex успешно удалена", + "vertex_base_url_required": "Введите базовый URL Vertex", + "config_enabled": "Конфигурация включена", + "config_disabled": "Конфигурация выключена", + "field_required": "Обязательные поля не могут быть пустыми", + "openai_provider_required": "Заполните имя провайдера и базовый URL", + "openai_provider_added": "Провайдер OpenAI успешно добавлен", + "openai_provider_updated": "Провайдер OpenAI успешно обновлён", + "openai_provider_deleted": "Провайдер OpenAI успешно удалён", + "ampcode_updated": "Настройки Ampcode обновлены", + "ampcode_upstream_api_key_cleared": "Переопределение upstream-ключа Ampcode очищено", + "openai_model_name_required": "Введите имя модели", + "openai_test_url_required": "Укажите корректный базовый URL перед тестированием", + "openai_test_key_required": "Добавьте хотя бы один API-ключ перед тестированием", + "openai_test_model_required": "Выберите модель для теста", + "data_refreshed": "Данные успешно обновлены", + "connection_required": "Сначала установите подключение", + "refresh_failed": "Не удалось обновить", + "update_failed": "Не удалось обновить", + "add_failed": "Не удалось добавить", + "delete_failed": "Не удалось удалить", + "upload_failed": "Не удалось загрузить", + "download_failed": "Не удалось скачать", + "login_failed": "Вход не выполнен", + "please_enter": "Пожалуйста, введите", + "please_fill": "Пожалуйста, заполните", + "provider_name_url": "имя провайдера и базовый URL", + "api_key": "API-ключ", + "gemini_api_key": "API-ключ Gemini", + "codex_api_key": "API-ключ Codex", + "claude_api_key": "API-ключ Claude", + "link_copied": "Ссылка скопирована в буфер обмена" }, "language": { - "switch": "Language", + "switch": "Язык", "chinese": "中文", - "english": "English" + "english": "English", + "russian": "Русский" }, "theme": { - "switch": "Theme", - "light": "Light", - "dark": "Dark", - "switch_to_light": "Switch to light mode", - "switch_to_dark": "Switch to dark mode", - "auto": "Follow system" + "switch": "Тема", + "light": "Светлая", + "dark": "Тёмная", + "switch_to_light": "Переключиться на светлую тему", + "switch_to_dark": "Переключиться на тёмную тему", + "auto": "Следовать системе" }, "sidebar": { - "toggle_expand": "Expand sidebar", - "toggle_collapse": "Collapse sidebar" + "toggle_expand": "Развернуть боковую панель", + "toggle_collapse": "Свернуть боковую панель" }, "footer": { - "api_version": "CLI Proxy API Version", - "build_date": "Build Time", - "version": "Management UI Version", - "author": "Author" + "api_version": "Версия CLI Proxy API", + "build_date": "Время сборки", + "version": "Версия интерфейса управления", + "author": "Автор" } } diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 20f8a7f..42d1a7a 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -1090,7 +1090,8 @@ "language": { "switch": "语言", "chinese": "中文", - "english": "English" + "english": "English", + "russian": "Русский" }, "theme": { "switch": "主题", diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 255af84..39f4cc4 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -7,7 +7,7 @@ import { IconEye, IconEyeOff } from '@/components/ui/icons'; import { useAuthStore, useLanguageStore, useNotificationStore } from '@/stores'; import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection'; import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; -import type { ApiError } from '@/types'; +import type { ApiError, Language } from '@/types'; import styles from './LoginPage.module.scss'; /** @@ -78,7 +78,14 @@ export function LoginPage() { const [error, setError] = useState(''); const detectedBase = useMemo(() => detectApiBaseFromLocation(), []); - const nextLanguageLabel = language === 'zh-CN' ? t('language.english') : t('language.chinese'); + const nextLanguage: Language = language === 'zh-CN' ? 'en' : language === 'en' ? 'ru' : 'zh-CN'; + const nextLanguageLabel = t( + nextLanguage === 'zh-CN' + ? 'language.chinese' + : nextLanguage === 'en' + ? 'language.english' + : 'language.russian' + ); useEffect(() => { const init = async () => { diff --git a/src/stores/useLanguageStore.ts b/src/stores/useLanguageStore.ts index 9de3bed..e04a516 100644 --- a/src/stores/useLanguageStore.ts +++ b/src/stores/useLanguageStore.ts @@ -29,8 +29,10 @@ export const useLanguageStore = create()( toggleLanguage: () => { const { language, setLanguage } = get(); - const newLanguage: Language = language === 'zh-CN' ? 'en' : 'zh-CN'; - setLanguage(newLanguage); + const order: Language[] = ['zh-CN', 'en', 'ru']; + const currentIndex = order.indexOf(language); + const nextLanguage = order[(currentIndex + 1) % order.length]; + setLanguage(nextLanguage); } }), { From d5ccef8b24b1a7e4c6e080149f670e72b82779f1 Mon Sep 17 00:00:00 2001 From: Chebotov Nickolay Date: Fri, 6 Feb 2026 12:29:23 +0300 Subject: [PATCH 3/5] chore: restore package lock --- package-lock.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/package-lock.json b/package-lock.json index b8645c8..2ce56de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -466,6 +467,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1931,6 +1933,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2018,6 +2021,7 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -2335,6 +2339,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2546,6 +2551,7 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -2810,6 +2816,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3286,6 +3293,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -3615,6 +3623,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3721,6 +3730,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3738,6 +3748,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3846,6 +3857,7 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -4028,6 +4040,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4104,6 +4117,7 @@ "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -4233,6 +4247,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -4260,6 +4275,7 @@ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 0bb8090686cb256ca8e9c9762f8d7fc96bb43ed9 Mon Sep 17 00:00:00 2001 From: Chebotov Nickolay Date: Fri, 6 Feb 2026 15:08:53 +0300 Subject: [PATCH 4/5] fix: address language review feedback --- src/i18n/locales/ru.json | 6 +++--- src/pages/LoginPage.tsx | 12 ++++-------- src/stores/useLanguageStore.ts | 7 +++---- src/utils/constants.ts | 11 +++++++++++ src/utils/language.ts | 10 +++++----- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 54954df..0945efc 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -47,8 +47,8 @@ "model_alias_placeholder": "Псевдоним модели (необязательно)" }, "title": { - "main": "CLI Proxy API Management Center", - "login": "CLI Proxy API Management Center", + "main": "Центр управления CLI Proxy API", + "login": "Центр управления CLI Proxy API", "abbr": "CPAMC" }, "auto_login": { @@ -108,7 +108,7 @@ }, "dashboard": { "title": "Панель управления", - "subtitle": "Добро пожаловать в CLI Proxy API Management Center", + "subtitle": "Добро пожаловать в Центр управления CLI Proxy API", "openai_providers": "Поставщики OpenAI", "quick_actions": "Быстрые действия", "current_config": "Текущая конфигурация", diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 39f4cc4..7290683 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -6,6 +6,7 @@ import { Input } from '@/components/ui/Input'; import { IconEye, IconEyeOff } from '@/components/ui/icons'; import { useAuthStore, useLanguageStore, useNotificationStore } from '@/stores'; import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection'; +import { LANGUAGE_LABEL_KEYS, LANGUAGE_ORDER } from '@/utils/constants'; import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; import type { ApiError, Language } from '@/types'; import styles from './LoginPage.module.scss'; @@ -78,14 +79,9 @@ export function LoginPage() { const [error, setError] = useState(''); const detectedBase = useMemo(() => detectApiBaseFromLocation(), []); - const nextLanguage: Language = language === 'zh-CN' ? 'en' : language === 'en' ? 'ru' : 'zh-CN'; - const nextLanguageLabel = t( - nextLanguage === 'zh-CN' - ? 'language.chinese' - : nextLanguage === 'en' - ? 'language.english' - : 'language.russian' - ); + const nextLanguageIndex = LANGUAGE_ORDER.indexOf(language); + const nextLanguage: Language = LANGUAGE_ORDER[(nextLanguageIndex + 1) % LANGUAGE_ORDER.length]; + const nextLanguageLabel = t(LANGUAGE_LABEL_KEYS[nextLanguage]); useEffect(() => { const init = async () => { diff --git a/src/stores/useLanguageStore.ts b/src/stores/useLanguageStore.ts index e04a516..e80183e 100644 --- a/src/stores/useLanguageStore.ts +++ b/src/stores/useLanguageStore.ts @@ -6,7 +6,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { Language } from '@/types'; -import { STORAGE_KEY_LANGUAGE } from '@/utils/constants'; +import { LANGUAGE_ORDER, STORAGE_KEY_LANGUAGE } from '@/utils/constants'; import i18n from '@/i18n'; import { getInitialLanguage } from '@/utils/language'; @@ -29,9 +29,8 @@ export const useLanguageStore = create()( toggleLanguage: () => { const { language, setLanguage } = get(); - const order: Language[] = ['zh-CN', 'en', 'ru']; - const currentIndex = order.indexOf(language); - const nextLanguage = order[(currentIndex + 1) % order.length]; + const currentIndex = LANGUAGE_ORDER.indexOf(language); + const nextLanguage = LANGUAGE_ORDER[(currentIndex + 1) % LANGUAGE_ORDER.length]; setLanguage(nextLanguage); } }), diff --git a/src/utils/constants.ts b/src/utils/constants.ts index eb1b650..1b9ac60 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -3,6 +3,8 @@ * 从原项目 src/utils/constants.js 迁移 */ +import type { Language } from '@/types'; + // 缓存过期时间(毫秒) export const CACHE_EXPIRY_MS = 30 * 1000; // 与基线保持一致,减少管理端压力 @@ -33,6 +35,15 @@ export const STORAGE_KEY_LANGUAGE = 'cli-proxy-language'; export const STORAGE_KEY_SIDEBAR = 'cli-proxy-sidebar-collapsed'; export const STORAGE_KEY_AUTH_FILES_PAGE_SIZE = 'cli-proxy-auth-files-page-size'; +// 语言配置 +export const LANGUAGE_ORDER: Language[] = ['zh-CN', 'en', 'ru']; +export const LANGUAGE_LABEL_KEYS: Record = { + 'zh-CN': 'language.chinese', + en: 'language.english', + ru: 'language.russian' +}; +export const SUPPORTED_LANGUAGES: Language[] = LANGUAGE_ORDER; + // 通知持续时间 export const NOTIFICATION_DURATION_MS = 3000; diff --git a/src/utils/language.ts b/src/utils/language.ts index 1975384..8aa1767 100644 --- a/src/utils/language.ts +++ b/src/utils/language.ts @@ -1,16 +1,16 @@ import type { Language } from '@/types'; -import { STORAGE_KEY_LANGUAGE } from '@/utils/constants'; +import { STORAGE_KEY_LANGUAGE, SUPPORTED_LANGUAGES } from '@/utils/constants'; const parseStoredLanguage = (value: string): Language | null => { try { const parsed = JSON.parse(value); const candidate = parsed?.state?.language ?? parsed?.language ?? parsed; - if (candidate === 'zh-CN' || candidate === 'en' || candidate === 'ru') { - return candidate; + if (SUPPORTED_LANGUAGES.includes(candidate as Language)) { + return candidate as Language; } } catch { - if (value === 'zh-CN' || value === 'en' || value === 'ru') { - return value; + if (SUPPORTED_LANGUAGES.includes(value as Language)) { + return value as Language; } } return null; From 50ab96c3ed203b8eae8fe99b89367c7c30a55f92 Mon Sep 17 00:00:00 2001 From: Chebotov Nickolay Date: Fri, 6 Feb 2026 15:20:25 +0300 Subject: [PATCH 5/5] feat: add language dropdown --- src/components/layout/MainLayout.tsx | 32 +++++++++++++++++++++---- src/pages/LoginPage.module.scss | 19 +++++++++++++-- src/pages/LoginPage.tsx | 29 +++++++++++++---------- src/styles/layout.scss | 35 ++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index dc263dd..d4802bb 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -35,6 +35,8 @@ import { } from '@/stores'; import { configApi, versionApi } from '@/services/api'; import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh'; +import { LANGUAGE_LABEL_KEYS, LANGUAGE_ORDER } from '@/utils/constants'; +import type { Language } from '@/types'; const sidebarIcons: Record = { dashboard: , @@ -189,7 +191,15 @@ export function MainLayout() { const theme = useThemeStore((state) => state.theme); const cycleTheme = useThemeStore((state) => state.cycleTheme); - const toggleLanguage = useLanguageStore((state) => state.toggleLanguage); + const language = useLanguageStore((state) => state.language); + const setLanguage = useLanguageStore((state) => state.setLanguage); + + const handleLanguageChange = useCallback( + (event: React.ChangeEvent) => { + setLanguage(event.target.value as Language); + }, + [setLanguage] + ); const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); @@ -566,9 +576,23 @@ export function MainLayout() { > {headerIcons.update} - +
+ + +
+ {LANGUAGE_ORDER.map((lang) => ( + + ))} +
{t('login.subtitle')}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss index 0ba8d9d..43feaf5 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss @@ -190,6 +190,41 @@ gap: $spacing-xs; flex-shrink: 0; + .language-select-wrapper { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--text-secondary); + + &:hover { + color: var(--text-primary); + } + } + + .language-select-icon { + display: inline-flex; + align-items: center; + justify-content: center; + } + + .language-select { + border: 1px solid var(--border-color); + border-radius: $radius-md; + padding: 10px 12px; + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); + cursor: pointer; + height: 40px; + box-sizing: border-box; + + &:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); + } + } + svg { display: block; }