782 lines
42 KiB
JavaScript
782 lines
42 KiB
JavaScript
/* ═══════════════════════════════════════════════
|
||
Crank — i18n
|
||
Reads `crank_lang` from localStorage ('en'|'ru')
|
||
═══════════════════════════════════════════════ */
|
||
|
||
var TRANSLATIONS = {
|
||
en: {
|
||
// Nav
|
||
'nav.operations': 'Operations',
|
||
'nav.agents': 'Agents',
|
||
'nav.apikeys': 'API Keys',
|
||
'nav.logs': 'Logs',
|
||
'nav.usage': 'Usage',
|
||
'nav.settings': 'Settings',
|
||
'nav.logout': 'Log out',
|
||
'nav.workspaces': 'Your workspaces',
|
||
'nav.create_workspace': 'Create workspace',
|
||
'nav.notifications': 'Notifications',
|
||
|
||
// Operations page
|
||
'ops.title': 'Operations',
|
||
'ops.subtitle': 'Tool contracts that expose your upstream APIs as MCP tools',
|
||
'ops.new': 'New operation',
|
||
'ops.loading': 'Loading operations…',
|
||
'ops.empty.title': 'No operations found',
|
||
'ops.empty.sub': 'Try adjusting your search or filters.',
|
||
'ops.empty.initial.title': 'No operations yet',
|
||
'ops.empty.initial.sub': 'Create the first operation in this workspace to expose a live MCP tool.',
|
||
'ops.error.title': 'Backend connection failed',
|
||
'ops.delete.confirm': 'Delete this operation? This cannot be undone.',
|
||
'ops.delete.success.title': 'Operation deleted',
|
||
'ops.delete.success.message': '{name} was deleted.',
|
||
'ops.delete.error.title': 'Delete failed',
|
||
'ops.delete.error.message': 'Failed to delete operation',
|
||
'ops.action.edit': 'Edit operation',
|
||
'ops.action.delete':'Delete operation',
|
||
'ops.stats.synced': 'Catalog synced with backend',
|
||
'ops.stats.none': 'No operations yet',
|
||
'ops.stats.share': '{value}% of total',
|
||
'ops.stats.no_published': 'No published tools yet',
|
||
'ops.stats.requests_rollup': 'Summed from operation usage',
|
||
'ops.stats.no_traffic': 'No traffic recorded today',
|
||
'ops.stats.latency_weighted': "Weighted by today's calls",
|
||
'ops.stats.latency_waiting': 'Waiting for runtime samples',
|
||
'ops.results': 'Showing {shown} of {total}',
|
||
'ops.range': 'Showing {from}–{to} of {total} operations',
|
||
'ops.filter.protocol': 'Protocol: {value}',
|
||
'ops.filter.category': 'Category: {value}',
|
||
'ops.filter.agent': 'Agent: {value}',
|
||
'ops.no_active_agents': 'No active agents',
|
||
|
||
// Stats
|
||
'stats.total': 'Total operations',
|
||
'stats.active': 'Active',
|
||
'stats.requests': 'Requests today',
|
||
'stats.latency': 'Avg. latency',
|
||
|
||
// Tabs
|
||
'tab.all': 'All',
|
||
'tab.active': 'Active',
|
||
'tab.draft': 'Draft',
|
||
'tab.error': 'Error',
|
||
'tab.inactive': 'Inactive',
|
||
|
||
// Filters & sort
|
||
'filter.search': 'Search operations...',
|
||
'filter.protocol': 'Protocol',
|
||
'filter.status': 'Status',
|
||
'filter.category': 'Category',
|
||
'filter.agent': 'Agent',
|
||
'filter.showing': 'Showing',
|
||
'filter.of': 'of',
|
||
'filter.clear': 'Clear filter',
|
||
'filter.clear_all': 'Clear all',
|
||
'filter.remove': 'Remove filter',
|
||
'sort.created_desc':'Sort: Last created',
|
||
'sort.created_asc': 'Sort: Oldest first',
|
||
'sort.name_asc': 'Sort: Name A–Z',
|
||
'sort.name_desc': 'Sort: Name Z–A',
|
||
|
||
// Table headers
|
||
'th.name': 'NAME',
|
||
'th.protocol': 'PROTOCOL',
|
||
'th.status': 'STATUS',
|
||
'th.created': 'CREATED',
|
||
'th.url': 'TARGET URL',
|
||
|
||
// Pagination
|
||
'page.showing': 'Showing',
|
||
'page.of': 'of',
|
||
'page.ops': 'operations',
|
||
|
||
// API Keys page
|
||
'apikeys.title': 'API Keys',
|
||
'apikeys.subtitle': 'Keys authenticate external MCP clients and workspace automation against Crank.',
|
||
'apikeys.new': 'Create key',
|
||
'apikeys.th.name': 'NAME',
|
||
'apikeys.th.scope': 'SCOPE',
|
||
'apikeys.th.created':'CREATED',
|
||
'apikeys.th.last': 'LAST USED',
|
||
'apikeys.callout.title': 'Keys are only shown once.',
|
||
'apikeys.callout.body': 'Copy and store them securely immediately after creation — Crank stores only a hash. `Last used` updates after successful MCP authentication.',
|
||
'apikeys.active.title': 'Active keys',
|
||
'apikeys.active.subtitle': '{active} active · {revoked} revoked',
|
||
'apikeys.search': 'Filter keys…',
|
||
'apikeys.th.prefix': 'KEY PREFIX',
|
||
'apikeys.th.scopes': 'SCOPES',
|
||
'apikeys.th.status': 'STATUS',
|
||
'apikeys.scope_ref': 'Scope reference',
|
||
'apikeys.scope.read': 'Initialize MCP sessions, ping the server, and list tools for a workspace agent.',
|
||
'apikeys.scope.write': 'Execute `tools/call` requests against published agent toolsets.',
|
||
'apikeys.scope.deploy': 'Reserved for deploy-scoped automation. Today it also permits MCP read/write flows.',
|
||
'apikeys.modal.title': 'Create API key',
|
||
'apikeys.modal.name': 'Key name',
|
||
'apikeys.modal.name_hint': 'A descriptive label to identify the key. Only visible to admins.',
|
||
'apikeys.modal.name_placeholder': 'e.g. Production, CI pipeline',
|
||
'apikeys.modal.scopes': 'Scopes',
|
||
'apikeys.modal.reveal_title': 'Copy this key now.',
|
||
'apikeys.modal.reveal_body': "It won't be shown again.",
|
||
'apikeys.modal.copy': 'Copy to clipboard',
|
||
'apikeys.modal.cancel': 'Cancel',
|
||
'apikeys.modal.create': 'Create key',
|
||
'apikeys.modal.done': "Done — I've copied the key",
|
||
'apikeys.status.active': 'Active',
|
||
'apikeys.status.revoked': 'Revoked',
|
||
'apikeys.last_used.never': 'Never',
|
||
'apikeys.empty.none': 'No API keys yet',
|
||
'apikeys.empty.search': 'No keys match your search',
|
||
'apikeys.loading': 'Loading…',
|
||
'apikeys.error.api': 'Workspace or API is unavailable',
|
||
'apikeys.error.load': 'Failed to load API keys',
|
||
'apikeys.confirm.revoke': 'Revoke this key? All requests using it will fail immediately.',
|
||
'apikeys.confirm.delete': 'Delete this key? This cannot be undone.',
|
||
'apikeys.toast.revoke_title': 'Key revoked',
|
||
'apikeys.toast.revoke_message': '{name} was revoked.',
|
||
'apikeys.toast.revoke_error_title': 'Revoke failed',
|
||
'apikeys.toast.revoke_error_message': 'Failed to revoke key',
|
||
'apikeys.toast.delete_title': 'Key deleted',
|
||
'apikeys.toast.delete_message': '{name} was deleted.',
|
||
'apikeys.toast.delete_error_title': 'Delete failed',
|
||
'apikeys.toast.delete_error_message': 'Failed to delete key',
|
||
'apikeys.toast.scope_missing_title': 'Missing scope',
|
||
'apikeys.toast.scope_missing_message': 'Select at least one scope before creating a key.',
|
||
'apikeys.toast.create_title': 'API key created',
|
||
'apikeys.toast.create_message': 'Copy the key now. It will not be shown again.',
|
||
'apikeys.toast.create_error_title': 'Key creation failed',
|
||
'apikeys.toast.create_error_message': 'Failed to create key',
|
||
'apikeys.toast.copy_title': 'API key copied',
|
||
'apikeys.toast.copy_message': 'Store the raw key securely. It cannot be revealed again.',
|
||
'apikeys.toast.prefix_title': 'Key prefix copied',
|
||
'apikeys.action.copy_prefix': 'Copy key prefix',
|
||
'apikeys.action.revoke': 'Revoke key',
|
||
'apikeys.action.delete': 'Delete',
|
||
'apikeys.creating': 'Creating…',
|
||
|
||
// Logs page
|
||
'logs.title': 'Logs',
|
||
'logs.subtitle': 'Real-time invocation log for all operations in this workspace.',
|
||
'logs.search': 'Search logs...',
|
||
'logs.level.all': 'All',
|
||
'logs.level.info': 'Info',
|
||
'logs.level.warn': 'Warn',
|
||
'logs.level.error': 'Error',
|
||
'logs.level.debug': 'Debug',
|
||
'logs.live': 'Live',
|
||
'logs.paused': 'Paused',
|
||
'logs.refresh': 'Refresh',
|
||
'logs.range.30m': 'Last 30 min',
|
||
'logs.range.1h': 'Last 1 hour',
|
||
'logs.range.6h': 'Last 6 hours',
|
||
'logs.range.24h': 'Last 24 hours',
|
||
'logs.range.7d': 'Last 7 days',
|
||
'logs.search_messages': 'Search messages…',
|
||
'logs.detail.agent': 'Agent',
|
||
'logs.detail.request': 'Request preview',
|
||
'logs.detail.response': 'Response preview',
|
||
'logs.detail.meta': 'Metadata',
|
||
'logs.loading.title': 'Loading logs…',
|
||
'logs.loading.sub': 'Fetching invocation history for the current workspace.',
|
||
'logs.error.title': 'Unable to load logs',
|
||
'logs.error.api': 'Admin API is not available',
|
||
'logs.error.workspace': 'Workspace is not selected',
|
||
'logs.error.load': 'Failed to load logs',
|
||
'logs.empty.title': 'No log entries found',
|
||
'logs.empty.filtered': 'Try widening the period or clearing the current search and level filters.',
|
||
'logs.empty.initial': 'Run tests or published MCP tools to start collecting invocation history.',
|
||
'logs.live.on.title': 'Live mode enabled',
|
||
'logs.live.on.body': 'Polling logs every 4 seconds.',
|
||
'logs.live.off.title': 'Live mode paused',
|
||
'logs.live.off.body': 'Automatic polling is paused.',
|
||
'logs.refresh.title': 'Logs refreshed',
|
||
'logs.refresh.body': 'The latest invocation records were loaded for the current workspace.',
|
||
|
||
// Usage page
|
||
'usage.title': 'Usage',
|
||
'usage.subtitle': 'Invocation metrics for all operations in your workspace.',
|
||
'usage.export': 'Export CSV',
|
||
'usage.period.7d': 'Last 7 days',
|
||
'usage.period.30d': 'Last 30 days',
|
||
'usage.period.90d': 'Last 90 days',
|
||
'usage.period.this_month': 'This month',
|
||
'usage.period.label.7d': 'last 7 days',
|
||
'usage.period.label.30d': 'last 30 days',
|
||
'usage.period.label.90d': 'last 90 days',
|
||
'usage.period.label.this_month': 'this month',
|
||
'usage.stats.total': 'Total invocations',
|
||
'usage.stats.success': 'Success rate',
|
||
'usage.stats.p50': 'Median latency (p50)',
|
||
'usage.stats.p99': 'p99 latency',
|
||
'usage.stats.across': 'Across {period}',
|
||
'usage.stats.success_calls': '{count} successful calls',
|
||
'usage.stats.median': 'Median latency for selected period',
|
||
'usage.stats.error_calls': '{count} error calls',
|
||
'usage.chart.title': 'Invocations over time',
|
||
'usage.chart.success': 'Success',
|
||
'usage.chart.error': 'Error',
|
||
'usage.chart.empty.title': 'No usage data yet',
|
||
'usage.chart.empty.sub': 'Invocation metrics will appear here after tests or published tool calls in the selected period.',
|
||
'usage.table.title': 'By operation',
|
||
'usage.table.subtitle': 'Breakdown for {period}',
|
||
'usage.table.th.operation': 'Operation',
|
||
'usage.table.th.protocol': 'Protocol',
|
||
'usage.table.th.calls': 'Calls',
|
||
'usage.table.th.errors': 'Errors',
|
||
'usage.table.th.error_rate': 'Error rate',
|
||
'usage.table.th.latency': 'Latency (p50 / p95 / p99)',
|
||
'usage.table.th.share': 'Traffic share',
|
||
'usage.table.empty.title': 'No operation breakdown yet',
|
||
'usage.table.empty.sub': 'The selected period has no invocation samples to break down by operation.',
|
||
'usage.table.share': '{value}% of workspace traffic',
|
||
'usage.loading.title': 'Loading usage…',
|
||
'usage.loading.sub': 'Aggregating invocation metrics for the selected workspace.',
|
||
'usage.error.title': 'Unable to load usage',
|
||
'usage.error.api': 'Admin API is not available',
|
||
'usage.error.workspace': 'Workspace is not selected',
|
||
'usage.error.load': 'Failed to load usage',
|
||
'usage.export.empty.title': 'No usage data loaded',
|
||
'usage.export.empty.body': 'Load usage data before exporting the CSV snapshot.',
|
||
'usage.export.done.title': 'Usage exported',
|
||
'usage.export.done.body': 'The current usage snapshot was exported as CSV.',
|
||
'usage.chart.ok': '{count} ok',
|
||
'usage.chart.errors': '{count} errors',
|
||
'usage.chart.week': 'Wk {index}',
|
||
'usage.csv.header': 'Operation,Protocol,Calls,Errors,Error Rate (%),p50 (ms),p95 (ms),p99 (ms)',
|
||
|
||
// Settings sidebar
|
||
'settings.title': 'Settings',
|
||
'settings.nav.workspace': 'Workspace',
|
||
'settings.nav.profile': 'Profile',
|
||
'settings.nav.members': 'Members',
|
||
'settings.nav.security': 'Security',
|
||
'settings.nav.notif': 'Notifications',
|
||
'settings.nav.danger': 'Danger zone',
|
||
|
||
// Settings — workspace section
|
||
'settings.ws.title': 'Workspace settings',
|
||
'settings.ws.subtitle': 'General information about your workspace',
|
||
'settings.ws.name': 'Workspace name',
|
||
'settings.ws.display': 'Display name',
|
||
'settings.ws.desc': 'Description',
|
||
'settings.ws.tz': 'Timezone',
|
||
'settings.ws.protocol': 'Default protocol',
|
||
|
||
// Settings — language
|
||
'settings.lang.title': 'Language',
|
||
'settings.lang.subtitle': 'Interface display language',
|
||
'settings.lang.en': 'English',
|
||
'settings.lang.ru': 'Русский',
|
||
|
||
// Settings — profile section
|
||
'settings.profile.title': 'Profile',
|
||
'settings.profile.name': 'Full name',
|
||
'settings.profile.email': 'Email',
|
||
'settings.profile.role': 'Role',
|
||
|
||
// Common buttons
|
||
'btn.save': 'Save changes',
|
||
'btn.cancel': 'Cancel',
|
||
'btn.create': 'Create',
|
||
'btn.delete': 'Delete',
|
||
'btn.revoke': 'Revoke',
|
||
'btn.copy': 'Copy',
|
||
|
||
// Agents page
|
||
'agents.title': 'Agents',
|
||
'agents.subtitle': 'Named MCP endpoints that expose a curated subset of operations to an LLM',
|
||
'agents.new': 'New agent',
|
||
'agents.stats.total': 'Total agents',
|
||
'agents.stats.total_sub': 'across this workspace',
|
||
'agents.stats.published': 'Published',
|
||
'agents.stats.published_sub': '{value}% of total',
|
||
'agents.stats.tools': 'Operations exposed',
|
||
'agents.stats.tools_sub': 'across {count} agents',
|
||
'agents.stats.calls': 'Calls today',
|
||
'agents.stats.calls_sub': 'all agents combined',
|
||
'agents.search': 'Search agents...',
|
||
'agents.results': '{shown} of {total} agents',
|
||
'agents.callout': 'Each agent gets its own MCP endpoint. Connect your LLM client to a specific agent so it only sees the tools it needs — not all {count} operations in this workspace.',
|
||
'agents.loading': 'Loading agents…',
|
||
'agents.error.title': 'Failed to load agents',
|
||
'agents.error.api': 'Workspace or API is unavailable',
|
||
'agents.error.load': 'Failed to load agents',
|
||
'agents.empty.initial.title': 'No agents yet',
|
||
'agents.empty.initial.sub': 'Create your first agent to get a dedicated MCP endpoint with a curated set of tools.',
|
||
'agents.empty.search.title': 'No agents match "{query}"',
|
||
'agents.empty.search.sub': 'Try a different search term.',
|
||
'agents.card.tools': 'tools',
|
||
'agents.card.keys': 'API keys',
|
||
'agents.card.calls_today': 'calls today',
|
||
'agents.card.created': 'Created {date}',
|
||
'agents.card.copy_endpoint': 'Copy endpoint',
|
||
'agents.action.edit': 'Edit',
|
||
'agents.action.archive': 'Archive',
|
||
'agents.action.delete': 'Delete',
|
||
'agents.lifecycle.publish': 'Publish',
|
||
'agents.lifecycle.unpublish': 'Unpublish',
|
||
'agents.lifecycle.restore_draft': 'Restore draft',
|
||
'agents.lifecycle.published': 'Published',
|
||
'agents.lifecycle.archived': 'Archived',
|
||
'agents.lifecycle.draft': 'Draft',
|
||
'agents.drawer.new_title': 'New agent',
|
||
'agents.drawer.edit_title': 'Edit agent',
|
||
'agents.drawer.new_sub': 'Configure a named MCP endpoint for your LLM',
|
||
'agents.drawer.edit_sub': 'Update agent settings and tool selection',
|
||
'agents.drawer.identity': 'Identity',
|
||
'agents.drawer.display_name': 'Display name',
|
||
'agents.drawer.slug': 'Slug',
|
||
'agents.drawer.description': 'Description',
|
||
'agents.drawer.optional': '(optional)',
|
||
'agents.drawer.required': 'required',
|
||
'agents.drawer.status': 'Status',
|
||
'agents.drawer.endpoint': 'MCP endpoint',
|
||
'agents.drawer.placeholder.name': 'Customer Support',
|
||
'agents.drawer.placeholder.slug': 'customer-support',
|
||
'agents.drawer.placeholder.description': 'What does this agent do? What LLM or use-case is it for?',
|
||
'agents.drawer.operations': 'Operations',
|
||
'agents.drawer.selected': '{count} selected',
|
||
'agents.drawer.operations_sub': 'Select which operations this agent exposes. LLMs connecting to this agent will only see these tools.',
|
||
'agents.drawer.filter_ops': 'Filter operations…',
|
||
'agents.drawer.ops_no_match': 'No operations match "{query}"',
|
||
'agents.drawer.ops_selected': '{count} operations selected',
|
||
'agents.drawer.clear_all': 'Clear all',
|
||
'agents.drawer.recommendation': "You've selected {count} tools. LLMs perform best with <15 tools per agent. Consider splitting into separate agents by use-case.",
|
||
'agents.drawer.cancel': 'Cancel',
|
||
'agents.drawer.create': 'Create agent',
|
||
'agents.drawer.save': 'Save changes',
|
||
'agents.toast.saved_title_create': 'Agent created',
|
||
'agents.toast.saved_title_update': 'Agent updated',
|
||
'agents.toast.saved_message': '{name} was saved with {count} tool bindings.',
|
||
'agents.toast.save_error_title': 'Agent update failed',
|
||
'agents.toast.save_error_message': 'Failed to save agent',
|
||
'agents.toast.delete_confirm': 'Delete this agent? This cannot be undone.',
|
||
'agents.toast.delete_title': 'Agent deleted',
|
||
'agents.toast.delete_message': '{name} was deleted.',
|
||
'agents.toast.delete_error_title': 'Delete failed',
|
||
'agents.toast.delete_error_message': 'Failed to delete agent',
|
||
'agents.toast.lifecycle_title': 'Agent lifecycle updated',
|
||
'agents.toast.lifecycle_publish': '{name} was published.',
|
||
'agents.toast.lifecycle_unpublish': '{name} was returned to draft.',
|
||
'agents.toast.lifecycle_archive': '{name} was archived.',
|
||
'agents.toast.lifecycle_error_title': 'Lifecycle update failed',
|
||
'agents.toast.lifecycle_error_message': 'Failed to update agent lifecycle',
|
||
'agents.toast.endpoint_title': 'MCP endpoint copied',
|
||
|
||
// Login
|
||
'login.title': 'Sign in',
|
||
'login.subtitle': 'Welcome back to your workspace',
|
||
'login.email': 'Email',
|
||
'login.password': 'Password',
|
||
'login.submit': 'Sign in',
|
||
'login.sso': 'Continue with Google',
|
||
},
|
||
|
||
ru: {
|
||
// Nav
|
||
'nav.operations': 'Операции',
|
||
'nav.agents': 'Агенты',
|
||
'nav.apikeys': 'API Ключи',
|
||
'nav.logs': 'Логи',
|
||
'nav.usage': 'Использование',
|
||
'nav.settings': 'Настройки',
|
||
'nav.logout': 'Выйти',
|
||
'nav.workspaces': 'Ваши воркспейсы',
|
||
'nav.create_workspace': 'Создать воркспейс',
|
||
'nav.notifications': 'Уведомления',
|
||
|
||
// Operations page
|
||
'ops.title': 'Операции',
|
||
'ops.subtitle': 'Контракты инструментов, открывающие ваши API как MCP-инструменты',
|
||
'ops.new': 'Новая операция',
|
||
'ops.loading': 'Загрузка операций…',
|
||
'ops.empty.title': 'Операции не найдены',
|
||
'ops.empty.sub': 'Попробуйте изменить поисковый запрос или фильтры.',
|
||
'ops.empty.initial.title': 'Операций пока нет',
|
||
'ops.empty.initial.sub': 'Создайте первую операцию в этом воркспейсе, чтобы открыть живой MCP-инструмент.',
|
||
'ops.error.title': 'Не удалось подключиться к backend',
|
||
'ops.delete.confirm': 'Удалить эту операцию? Действие нельзя отменить.',
|
||
'ops.delete.success.title': 'Операция удалена',
|
||
'ops.delete.success.message': 'Операция {name} удалена.',
|
||
'ops.delete.error.title': 'Не удалось удалить',
|
||
'ops.delete.error.message': 'Не удалось удалить операцию',
|
||
'ops.action.edit': 'Редактировать операцию',
|
||
'ops.action.delete':'Удалить операцию',
|
||
'ops.stats.synced': 'Каталог синхронизирован с backend',
|
||
'ops.stats.none': 'Операций пока нет',
|
||
'ops.stats.share': '{value}% от общего числа',
|
||
'ops.stats.no_published': 'Опубликованных инструментов пока нет',
|
||
'ops.stats.requests_rollup': 'Сумма по статистике операций',
|
||
'ops.stats.no_traffic': 'Трафик сегодня не зафиксирован',
|
||
'ops.stats.latency_weighted': 'Взвешено по сегодняшним вызовам',
|
||
'ops.stats.latency_waiting': 'Ожидание runtime-метрик',
|
||
'ops.results': 'Показано {shown} из {total}',
|
||
'ops.range': 'Показано {from}–{to} из {total} операций',
|
||
'ops.filter.protocol': 'Протокол: {value}',
|
||
'ops.filter.category': 'Категория: {value}',
|
||
'ops.filter.agent': 'Агент: {value}',
|
||
'ops.no_active_agents': 'Нет активных агентов',
|
||
|
||
// Stats
|
||
'stats.total': 'Всего операций',
|
||
'stats.active': 'Активные',
|
||
'stats.requests': 'Запросов сегодня',
|
||
'stats.latency': 'Ср. задержка',
|
||
|
||
// Tabs
|
||
'tab.all': 'Все',
|
||
'tab.active': 'Активные',
|
||
'tab.draft': 'Черновик',
|
||
'tab.error': 'Ошибка',
|
||
'tab.inactive': 'Неактивные',
|
||
|
||
// Filters & sort
|
||
'filter.search': 'Поиск операций...',
|
||
'filter.protocol': 'Протокол',
|
||
'filter.status': 'Статус',
|
||
'filter.category': 'Категория',
|
||
'filter.agent': 'Агент',
|
||
'filter.showing': 'Показано',
|
||
'filter.of': 'из',
|
||
'filter.clear': 'Сбросить',
|
||
'filter.clear_all': 'Сбросить все',
|
||
'filter.remove': 'Убрать фильтр',
|
||
'sort.created_desc':'Сначала новые',
|
||
'sort.created_asc': 'Сначала старые',
|
||
'sort.name_asc': 'Название А–Я',
|
||
'sort.name_desc': 'Название Я–А',
|
||
|
||
// Table headers
|
||
'th.name': 'НАЗВАНИЕ',
|
||
'th.protocol': 'ПРОТОКОЛ',
|
||
'th.status': 'СТАТУС',
|
||
'th.created': 'СОЗДАН',
|
||
'th.url': 'ЦЕЛЕВОЙ URL',
|
||
|
||
// Pagination
|
||
'page.showing': 'Показано',
|
||
'page.of': 'из',
|
||
'page.ops': 'операций',
|
||
|
||
// API Keys page
|
||
'apikeys.title': 'API Ключи',
|
||
'apikeys.subtitle': 'Ключи аутентифицируют внешние MCP-клиенты и автоматизацию воркспейса в Crank.',
|
||
'apikeys.new': 'Создать ключ',
|
||
'apikeys.th.name': 'НАЗВАНИЕ',
|
||
'apikeys.th.scope': 'ОБЛАСТЬ',
|
||
'apikeys.th.created':'СОЗДАН',
|
||
'apikeys.th.last': 'ПОСЛЕДНЕЕ ИСПОЛЬЗОВАНИЕ',
|
||
'apikeys.callout.title': 'Ключ показывается только один раз.',
|
||
'apikeys.callout.body': 'Скопируйте и сохраните его сразу после создания — Crank хранит только хэш. `Последнее использование` обновляется после успешной MCP-аутентификации.',
|
||
'apikeys.active.title': 'Активные ключи',
|
||
'apikeys.active.subtitle': '{active} активных · {revoked} отозванных',
|
||
'apikeys.search': 'Фильтр ключей…',
|
||
'apikeys.th.prefix': 'ПРЕФИКС КЛЮЧА',
|
||
'apikeys.th.scopes': 'ОБЛАСТИ',
|
||
'apikeys.th.status': 'СТАТУС',
|
||
'apikeys.scope_ref': 'Справка по областям',
|
||
'apikeys.scope.read': 'Инициализация MCP-сессий, ping сервера и получение списка инструментов для агента воркспейса.',
|
||
'apikeys.scope.write': 'Выполнение `tools/call` для опубликованных наборов инструментов агента.',
|
||
'apikeys.scope.deploy': 'Зарезервировано для deploy-автоматизации. Сейчас также разрешает MCP read/write сценарии.',
|
||
'apikeys.modal.title': 'Создать API-ключ',
|
||
'apikeys.modal.name': 'Имя ключа',
|
||
'apikeys.modal.name_hint': 'Понятная метка для идентификации ключа. Видна только администраторам.',
|
||
'apikeys.modal.name_placeholder': 'например, Production, CI pipeline',
|
||
'apikeys.modal.scopes': 'Области',
|
||
'apikeys.modal.reveal_title': 'Скопируйте ключ сейчас.',
|
||
'apikeys.modal.reveal_body': 'Повторно он не будет показан.',
|
||
'apikeys.modal.copy': 'Скопировать',
|
||
'apikeys.modal.cancel': 'Отмена',
|
||
'apikeys.modal.create': 'Создать ключ',
|
||
'apikeys.modal.done': 'Готово — ключ скопирован',
|
||
'apikeys.status.active': 'Активен',
|
||
'apikeys.status.revoked': 'Отозван',
|
||
'apikeys.last_used.never': 'Никогда',
|
||
'apikeys.empty.none': 'API-ключей пока нет',
|
||
'apikeys.empty.search': 'Нет ключей по текущему поиску',
|
||
'apikeys.loading': 'Загрузка…',
|
||
'apikeys.error.api': 'Воркспейс или API недоступен',
|
||
'apikeys.error.load': 'Не удалось загрузить API-ключи',
|
||
'apikeys.confirm.revoke': 'Отозвать этот ключ? Все запросы с ним сразу начнут падать.',
|
||
'apikeys.confirm.delete': 'Удалить этот ключ? Действие нельзя отменить.',
|
||
'apikeys.toast.revoke_title': 'Ключ отозван',
|
||
'apikeys.toast.revoke_message': 'Ключ {name} отозван.',
|
||
'apikeys.toast.revoke_error_title': 'Не удалось отозвать',
|
||
'apikeys.toast.revoke_error_message': 'Не удалось отозвать ключ',
|
||
'apikeys.toast.delete_title': 'Ключ удален',
|
||
'apikeys.toast.delete_message': 'Ключ {name} удален.',
|
||
'apikeys.toast.delete_error_title': 'Не удалось удалить',
|
||
'apikeys.toast.delete_error_message': 'Не удалось удалить ключ',
|
||
'apikeys.toast.scope_missing_title': 'Не выбрана область',
|
||
'apikeys.toast.scope_missing_message': 'Выберите хотя бы одну область перед созданием ключа.',
|
||
'apikeys.toast.create_title': 'API-ключ создан',
|
||
'apikeys.toast.create_message': 'Скопируйте ключ сейчас. Повторно он не будет показан.',
|
||
'apikeys.toast.create_error_title': 'Не удалось создать ключ',
|
||
'apikeys.toast.create_error_message': 'Не удалось создать ключ',
|
||
'apikeys.toast.copy_title': 'API-ключ скопирован',
|
||
'apikeys.toast.copy_message': 'Сохраните исходный ключ в надежном месте. Повторно показать его нельзя.',
|
||
'apikeys.toast.prefix_title': 'Префикс ключа скопирован',
|
||
'apikeys.action.copy_prefix': 'Скопировать префикс ключа',
|
||
'apikeys.action.revoke': 'Отозвать ключ',
|
||
'apikeys.action.delete': 'Удалить',
|
||
'apikeys.creating': 'Создание…',
|
||
|
||
// Logs page
|
||
'logs.title': 'Логи',
|
||
'logs.subtitle': 'Живой журнал вызовов по всем операциям этого воркспейса.',
|
||
'logs.search': 'Поиск в логах...',
|
||
'logs.level.all': 'Все',
|
||
'logs.level.info': 'Info',
|
||
'logs.level.warn': 'Warn',
|
||
'logs.level.error': 'Error',
|
||
'logs.level.debug': 'Debug',
|
||
'logs.live': 'Live',
|
||
'logs.paused': 'Пауза',
|
||
'logs.refresh': 'Обновить',
|
||
'logs.range.30m': 'Последние 30 мин',
|
||
'logs.range.1h': 'Последний 1 час',
|
||
'logs.range.6h': 'Последние 6 часов',
|
||
'logs.range.24h': 'Последние 24 часа',
|
||
'logs.range.7d': 'Последние 7 дней',
|
||
'logs.search_messages': 'Поиск по сообщениям…',
|
||
'logs.detail.agent': 'Агент',
|
||
'logs.detail.request': 'Предпросмотр запроса',
|
||
'logs.detail.response': 'Предпросмотр ответа',
|
||
'logs.detail.meta': 'Метаданные',
|
||
'logs.loading.title': 'Загрузка логов…',
|
||
'logs.loading.sub': 'Получаем историю вызовов для текущего воркспейса.',
|
||
'logs.error.title': 'Не удалось загрузить логи',
|
||
'logs.error.api': 'Admin API недоступен',
|
||
'logs.error.workspace': 'Воркспейс не выбран',
|
||
'logs.error.load': 'Не удалось загрузить логи',
|
||
'logs.empty.title': 'Записей логов не найдено',
|
||
'logs.empty.filtered': 'Попробуйте расширить период или сбросить текущий поиск и фильтры уровня.',
|
||
'logs.empty.initial': 'Запустите тесты или опубликованные MCP-инструменты, чтобы начать собирать историю вызовов.',
|
||
'logs.live.on.title': 'Live режим включен',
|
||
'logs.live.on.body': 'Логи опрашиваются каждые 4 секунды.',
|
||
'logs.live.off.title': 'Live режим на паузе',
|
||
'logs.live.off.body': 'Автоматический опрос остановлен.',
|
||
'logs.refresh.title': 'Логи обновлены',
|
||
'logs.refresh.body': 'Получены последние записи вызовов для текущего воркспейса.',
|
||
|
||
// Usage page
|
||
'usage.title': 'Использование',
|
||
'usage.subtitle': 'Метрики вызовов по всем операциям этого воркспейса.',
|
||
'usage.export': 'Экспорт CSV',
|
||
'usage.period.7d': 'Последние 7 дней',
|
||
'usage.period.30d': 'Последние 30 дней',
|
||
'usage.period.90d': 'Последние 90 дней',
|
||
'usage.period.this_month': 'Этот месяц',
|
||
'usage.period.label.7d': 'последние 7 дней',
|
||
'usage.period.label.30d': 'последние 30 дней',
|
||
'usage.period.label.90d': 'последние 90 дней',
|
||
'usage.period.label.this_month': 'этот месяц',
|
||
'usage.stats.total': 'Всего вызовов',
|
||
'usage.stats.success': 'Доля успешных',
|
||
'usage.stats.p50': 'Медианная задержка (p50)',
|
||
'usage.stats.p99': 'Задержка p99',
|
||
'usage.stats.across': 'За период: {period}',
|
||
'usage.stats.success_calls': 'Успешных вызовов: {count}',
|
||
'usage.stats.median': 'Медианная задержка за выбранный период',
|
||
'usage.stats.error_calls': 'Ошибочных вызовов: {count}',
|
||
'usage.chart.title': 'Вызовы во времени',
|
||
'usage.chart.success': 'Успех',
|
||
'usage.chart.error': 'Ошибка',
|
||
'usage.chart.empty.title': 'Данных по использованию пока нет',
|
||
'usage.chart.empty.sub': 'Метрики появятся здесь после тестов или вызовов опубликованных инструментов за выбранный период.',
|
||
'usage.table.title': 'По операциям',
|
||
'usage.table.subtitle': 'Разбивка за период: {period}',
|
||
'usage.table.th.operation': 'Операция',
|
||
'usage.table.th.protocol': 'Протокол',
|
||
'usage.table.th.calls': 'Вызовы',
|
||
'usage.table.th.errors': 'Ошибки',
|
||
'usage.table.th.error_rate': 'Доля ошибок',
|
||
'usage.table.th.latency': 'Задержка (p50 / p95 / p99)',
|
||
'usage.table.th.share': 'Доля трафика',
|
||
'usage.table.empty.title': 'Разбивки по операциям пока нет',
|
||
'usage.table.empty.sub': 'За выбранный период нет вызовов, которые можно разложить по операциям.',
|
||
'usage.table.share': '{value}% трафика воркспейса',
|
||
'usage.loading.title': 'Загрузка использования…',
|
||
'usage.loading.sub': 'Собираем метрики вызовов для выбранного воркспейса.',
|
||
'usage.error.title': 'Не удалось загрузить использование',
|
||
'usage.error.api': 'Admin API недоступен',
|
||
'usage.error.workspace': 'Воркспейс не выбран',
|
||
'usage.error.load': 'Не удалось загрузить метрики использования',
|
||
'usage.export.empty.title': 'Данные по использованию не загружены',
|
||
'usage.export.empty.body': 'Сначала загрузите данные использования, а потом экспортируйте снимок CSV.',
|
||
'usage.export.done.title': 'Использование экспортировано',
|
||
'usage.export.done.body': 'Текущий снимок использования экспортирован в CSV.',
|
||
'usage.chart.ok': 'Успешных: {count}',
|
||
'usage.chart.errors': 'Ошибок: {count}',
|
||
'usage.chart.week': 'Нед. {index}',
|
||
'usage.csv.header': 'Операция,Протокол,Вызовы,Ошибки,Доля ошибок (%),p50 (мс),p95 (мс),p99 (мс)',
|
||
|
||
// Settings sidebar
|
||
'settings.title': 'Настройки',
|
||
'settings.nav.workspace': 'Воркспейс',
|
||
'settings.nav.profile': 'Профиль',
|
||
'settings.nav.members': 'Участники',
|
||
'settings.nav.security': 'Безопасность',
|
||
'settings.nav.notif': 'Уведомления',
|
||
'settings.nav.danger': 'Опасная зона',
|
||
|
||
// Settings — workspace section
|
||
'settings.ws.title': 'Настройки воркспейса',
|
||
'settings.ws.subtitle': 'Общая информация о вашем воркспейсе',
|
||
'settings.ws.name': 'Имя воркспейса',
|
||
'settings.ws.display': 'Отображаемое имя',
|
||
'settings.ws.desc': 'Описание',
|
||
'settings.ws.tz': 'Часовой пояс',
|
||
'settings.ws.protocol': 'Протокол по умолчанию',
|
||
|
||
// Settings — language
|
||
'settings.lang.title': 'Язык',
|
||
'settings.lang.subtitle': 'Язык отображения интерфейса',
|
||
'settings.lang.en': 'English',
|
||
'settings.lang.ru': 'Русский',
|
||
|
||
// Settings — profile section
|
||
'settings.profile.title': 'Профиль',
|
||
'settings.profile.name': 'Полное имя',
|
||
'settings.profile.email': 'Email',
|
||
'settings.profile.role': 'Роль',
|
||
|
||
// Common buttons
|
||
'btn.save': 'Сохранить',
|
||
'btn.cancel': 'Отмена',
|
||
'btn.create': 'Создать',
|
||
'btn.delete': 'Удалить',
|
||
'btn.revoke': 'Отозвать',
|
||
'btn.copy': 'Копировать',
|
||
|
||
// Agents page
|
||
'agents.title': 'Агенты',
|
||
'agents.subtitle': 'Именованные MCP endpoint-ы, открывающие LLM ограниченный набор операций',
|
||
'agents.new': 'Новый агент',
|
||
'agents.stats.total': 'Всего агентов',
|
||
'agents.stats.total_sub': 'в этом воркспейсе',
|
||
'agents.stats.published': 'Опубликованы',
|
||
'agents.stats.published_sub': '{value}% от общего числа',
|
||
'agents.stats.tools': 'Открытых операций',
|
||
'agents.stats.tools_sub': 'в {count} агентах',
|
||
'agents.stats.calls': 'Вызовов сегодня',
|
||
'agents.stats.calls_sub': 'по всем агентам',
|
||
'agents.search': 'Поиск агентов...',
|
||
'agents.results': '{shown} из {total} агентов',
|
||
'agents.callout': 'У каждого агента свой MCP endpoint. Подключайте LLM-клиент к конкретному агенту, чтобы он видел только нужные инструменты, а не все {count} операций этого воркспейса.',
|
||
'agents.loading': 'Загрузка агентов…',
|
||
'agents.error.title': 'Не удалось загрузить агентов',
|
||
'agents.error.api': 'Воркспейс или API недоступен',
|
||
'agents.error.load': 'Не удалось загрузить агентов',
|
||
'agents.empty.initial.title': 'Агентов пока нет',
|
||
'agents.empty.initial.sub': 'Создайте первого агента, чтобы получить отдельный MCP endpoint с нужным набором инструментов.',
|
||
'agents.empty.search.title': 'Нет агентов по запросу "{query}"',
|
||
'agents.empty.search.sub': 'Попробуйте другой поисковый запрос.',
|
||
'agents.card.tools': 'инструментов',
|
||
'agents.card.keys': 'API-ключей',
|
||
'agents.card.calls_today': 'вызовов сегодня',
|
||
'agents.card.created': 'Создан {date}',
|
||
'agents.card.copy_endpoint': 'Скопировать endpoint',
|
||
'agents.action.edit': 'Редактировать',
|
||
'agents.action.archive': 'Архивировать',
|
||
'agents.action.delete': 'Удалить',
|
||
'agents.lifecycle.publish': 'Опубликовать',
|
||
'agents.lifecycle.unpublish': 'Снять с публикации',
|
||
'agents.lifecycle.restore_draft': 'Вернуть в черновик',
|
||
'agents.lifecycle.published': 'Опубликован',
|
||
'agents.lifecycle.archived': 'Архивирован',
|
||
'agents.lifecycle.draft': 'Черновик',
|
||
'agents.drawer.new_title': 'Новый агент',
|
||
'agents.drawer.edit_title': 'Редактировать агента',
|
||
'agents.drawer.new_sub': 'Настройте именованный MCP endpoint для вашей LLM',
|
||
'agents.drawer.edit_sub': 'Обновите настройки агента и выбор инструментов',
|
||
'agents.drawer.identity': 'Идентичность',
|
||
'agents.drawer.display_name': 'Отображаемое имя',
|
||
'agents.drawer.slug': 'Slug',
|
||
'agents.drawer.description': 'Описание',
|
||
'agents.drawer.optional': '(необязательно)',
|
||
'agents.drawer.required': 'обязательно',
|
||
'agents.drawer.status': 'Статус',
|
||
'agents.drawer.endpoint': 'MCP endpoint',
|
||
'agents.drawer.placeholder.name': 'Customer Support',
|
||
'agents.drawer.placeholder.slug': 'customer-support',
|
||
'agents.drawer.placeholder.description': 'Что делает этот агент? Для какой LLM или сценария он нужен?',
|
||
'agents.drawer.operations': 'Операции',
|
||
'agents.drawer.selected': 'Выбрано: {count}',
|
||
'agents.drawer.operations_sub': 'Выберите операции, которые открывает этот агент. LLM, подключенная к агенту, увидит только эти инструменты.',
|
||
'agents.drawer.filter_ops': 'Фильтр операций…',
|
||
'agents.drawer.ops_no_match': 'Нет операций по запросу "{query}"',
|
||
'agents.drawer.ops_selected': 'Выбрано операций: {count}',
|
||
'agents.drawer.clear_all': 'Очистить все',
|
||
'agents.drawer.recommendation': 'Сейчас выбрано {count} инструментов. LLM лучше работает, когда у агента меньше 15 инструментов. Подумайте о разбиении по сценариям.',
|
||
'agents.drawer.cancel': 'Отмена',
|
||
'agents.drawer.create': 'Создать агента',
|
||
'agents.drawer.save': 'Сохранить изменения',
|
||
'agents.toast.saved_title_create': 'Агент создан',
|
||
'agents.toast.saved_title_update': 'Агент обновлен',
|
||
'agents.toast.saved_message': '{name} сохранен, привязано инструментов: {count}.',
|
||
'agents.toast.save_error_title': 'Не удалось обновить агента',
|
||
'agents.toast.save_error_message': 'Не удалось сохранить агента',
|
||
'agents.toast.delete_confirm': 'Удалить этого агента? Действие нельзя отменить.',
|
||
'agents.toast.delete_title': 'Агент удален',
|
||
'agents.toast.delete_message': 'Агент {name} удален.',
|
||
'agents.toast.delete_error_title': 'Не удалось удалить',
|
||
'agents.toast.delete_error_message': 'Не удалось удалить агента',
|
||
'agents.toast.lifecycle_title': 'Жизненный цикл агента обновлен',
|
||
'agents.toast.lifecycle_publish': '{name} опубликован.',
|
||
'agents.toast.lifecycle_unpublish': '{name} возвращен в черновик.',
|
||
'agents.toast.lifecycle_archive': '{name} архивирован.',
|
||
'agents.toast.lifecycle_error_title': 'Не удалось обновить жизненный цикл',
|
||
'agents.toast.lifecycle_error_message': 'Не удалось обновить состояние агента',
|
||
'agents.toast.endpoint_title': 'MCP endpoint скопирован',
|
||
|
||
// Login
|
||
'login.title': 'Войти',
|
||
'login.subtitle': 'Добро пожаловать обратно',
|
||
'login.email': 'Email',
|
||
'login.password': 'Пароль',
|
||
'login.submit': 'Войти',
|
||
'login.sso': 'Войти через Google',
|
||
}
|
||
};
|
||
|
||
function t(key) {
|
||
var lang = localStorage.getItem('crank_lang') || 'en';
|
||
var tr = TRANSLATIONS[lang] || TRANSLATIONS.en;
|
||
return tr[key] !== undefined ? tr[key] : (TRANSLATIONS.en[key] !== undefined ? TRANSLATIONS.en[key] : key);
|
||
}
|
||
|
||
function tf(key, vars) {
|
||
return Object.keys(vars || {}).reduce(function(result, name) {
|
||
return result.replaceAll('{' + name + '}', vars[name]);
|
||
}, t(key));
|
||
}
|
||
|
||
function applyLang() {
|
||
// text content
|
||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||
el.textContent = t(el.getAttribute('data-i18n'));
|
||
});
|
||
// placeholder
|
||
document.querySelectorAll('[data-i18n-ph]').forEach(function(el) {
|
||
el.placeholder = t(el.getAttribute('data-i18n-ph'));
|
||
});
|
||
// title
|
||
document.querySelectorAll('[data-i18n-title]').forEach(function(el) {
|
||
el.title = t(el.getAttribute('data-i18n-title'));
|
||
});
|
||
// highlight active language button in settings
|
||
var lang = localStorage.getItem('crank_lang') || 'en';
|
||
document.querySelectorAll('.lang-btn').forEach(function(btn) {
|
||
btn.classList.toggle('active', btn.dataset.lang === lang);
|
||
});
|
||
}
|
||
|
||
function setLang(lang) {
|
||
localStorage.setItem('crank_lang', lang);
|
||
applyLang();
|
||
// Notify Alpine components to re-render reactive getters
|
||
window.dispatchEvent(new CustomEvent('crank:langchange', { detail: { lang: lang } }));
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', applyLang);
|