/* ═══════════════════════════════════════════════ 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);