1279 lines
84 KiB
JavaScript
1279 lines
84 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',
|
||
'settings.page.title': 'Account settings',
|
||
'settings.page.subtitle': 'Manage the live profile, password and workspace settings available in this build. Planned capabilities stay visible, but they do not pretend to be wired yet.',
|
||
'settings.ws.slug_hint': 'Used in API endpoints and across the console. Only lowercase letters, numbers and hyphens.',
|
||
'settings.ws.description_optional': 'Description (optional)',
|
||
'settings.ws.description_placeholder': 'What does this workspace do?',
|
||
'settings.profile.avatar_hint': 'Your avatar is generated from your display name and email.',
|
||
'settings.profile.backend_hint': 'Your name and email are stored on the backend and used across the console and session identity.',
|
||
'settings.profile.first_name': 'First name',
|
||
'settings.profile.last_name': 'Last name',
|
||
'settings.profile.last_name_placeholder': 'Optional',
|
||
'settings.profile.email_label': 'Email address',
|
||
'settings.profile.email_hint': 'Changing email updates the login identity for the current account.',
|
||
'settings.profile.save': 'Save profile',
|
||
'settings.profile.saving': 'Saving…',
|
||
'settings.profile.saved': 'Saved ✓',
|
||
'settings.profile.saved_status': 'Profile saved.',
|
||
'settings.profile.save_error': 'Failed to save profile',
|
||
'settings.security.title': 'Security',
|
||
'settings.security.current_password': 'Current password',
|
||
'settings.security.new_password': 'New password',
|
||
'settings.security.new_password_placeholder': 'min. 12 characters',
|
||
'settings.security.confirm_password': 'Confirm new password',
|
||
'settings.security.change_password': 'Change password',
|
||
'settings.security.mismatch': 'New password and confirmation do not match.',
|
||
'settings.security.saved': 'Password updated.',
|
||
'settings.security.save_error': 'Failed to change password',
|
||
'settings.security.capabilities_title': 'Planned security capabilities',
|
||
'settings.security.capabilities_body': 'Two-factor authentication, passkeys and multi-session inventory are planned, but the current live flow is password-based login with one HttpOnly browser session.',
|
||
'settings.security.not_available': 'Not available in this build.',
|
||
'settings.session.title': 'Current session',
|
||
'settings.session.current': 'current',
|
||
'settings.session.loading': 'Loading current session details…',
|
||
'settings.session.unavailable': 'Session details are unavailable. Use Log out to revoke the current browser session.',
|
||
'settings.session.summary': '{email} · {role} in {workspace}. Use Log out to revoke the current browser session.',
|
||
'settings.notifications.title': 'Notification preferences',
|
||
'settings.notifications.not_ready_title': 'Notifications are not wired yet.',
|
||
'settings.notifications.not_ready_body': 'Alert routing and per-user preferences are planned, but this build does not persist or deliver notification settings.',
|
||
'settings.notifications.spike_title': 'Operation error spike',
|
||
'settings.notifications.spike_body': 'Alert when error rate exceeds a rolling threshold for a published tool.',
|
||
'settings.notifications.latency_title': 'Upstream latency degradation',
|
||
'settings.notifications.latency_body': 'Notify operators when p99 latency rises sharply above the baseline.',
|
||
'settings.notifications.digest_title': 'Quota and usage digests',
|
||
'settings.notifications.digest_body': 'Deliver periodic summaries for quota consumption, errors and workspace activity.',
|
||
'settings.workspace.saving': 'Saving…',
|
||
'settings.workspace.saved': 'Saved ✓',
|
||
'settings.workspace.save_error': 'Failed to save workspace settings',
|
||
'settings.workspace.save_error_title': 'Workspace save failed',
|
||
|
||
// Workspace setup
|
||
'workspace_setup.title': 'Workspace settings',
|
||
'workspace_setup.page_subtitle': 'Configure your workspace identity, default settings, and team members.',
|
||
'workspace_setup.back': 'Back',
|
||
'workspace_setup.identity.title': 'Workspace identity',
|
||
'workspace_setup.identity.avatar_hint': 'Avatar is derived from your workspace name.',
|
||
'workspace_setup.name': 'Workspace name',
|
||
'workspace_setup.slug': 'Slug',
|
||
'workspace_setup.required': 'required',
|
||
'workspace_setup.optional': '(optional)',
|
||
'workspace_setup.slug_hint': 'Only lowercase letters, numbers, and hyphens. Used in MCP endpoint URLs.',
|
||
'workspace_setup.description': 'Description',
|
||
'workspace_setup.description_placeholder': 'What does this workspace do?',
|
||
'workspace_setup.defaults.title': 'Default settings',
|
||
'workspace_setup.protocol': 'Default protocol',
|
||
'workspace_setup.protocol_hint': 'Pre-selected in the operation wizard.',
|
||
'workspace_setup.timezone': 'Timezone',
|
||
'workspace_setup.language': 'Interface language',
|
||
'workspace_setup.members.title': 'Team members',
|
||
'workspace_setup.members.invite': 'Invite member',
|
||
'workspace_setup.members.email': 'Email address',
|
||
'workspace_setup.members.role': 'Role',
|
||
'workspace_setup.members.send_invite': 'Send invite',
|
||
'workspace_setup.members.cancel': 'Cancel',
|
||
'workspace_setup.members.invite_sent': '✓ Invite sent.',
|
||
'workspace_setup.members.you': '(you)',
|
||
'workspace_setup.members.today': 'Today',
|
||
'workspace_setup.members.days_ago': '{count} days ago',
|
||
'workspace_setup.members.week_ago': '1 week ago',
|
||
'workspace_setup.members.never': 'Never',
|
||
'workspace_setup.members.pending': 'Pending invites',
|
||
'workspace_setup.members.invited_pending': 'Invited {date} · pending',
|
||
'workspace_setup.members.revoke': 'Revoke invite',
|
||
'workspace_setup.members.remove': 'Remove member',
|
||
'workspace_setup.roles.title': 'Roles & permissions',
|
||
'workspace_setup.roles.owner_body': 'Full workspace control, billing access, can transfer or delete the workspace.',
|
||
'workspace_setup.roles.admin_body': 'Manage members and roles, create and edit all operations and agents, manage API keys.',
|
||
'workspace_setup.roles.developer_body': 'Create and edit operations and agents. Cannot manage members or workspace settings.',
|
||
'workspace_setup.roles.viewer_body': 'Read-only access to operations, agents, and logs.',
|
||
'workspace_setup.invite.title': 'Invite team members',
|
||
'workspace_setup.invite.add_person': '+ Add person',
|
||
'workspace_setup.invite.later': 'You can invite more people later from Workspace settings.',
|
||
'workspace_setup.actions.create': 'Create workspace',
|
||
'workspace_setup.actions.save': 'Save changes',
|
||
'workspace_setup.create.title': 'Create a new workspace',
|
||
'workspace_setup.create.subtitle': "A workspace is a shared environment for your team's MCP operations and agents. Each workspace gets its own MCP endpoint namespace.",
|
||
'workspace_setup.create.footer': "You'll be the Owner of this workspace. You can invite additional members after creation.",
|
||
'workspace_setup.danger.title': 'Danger zone',
|
||
'workspace_setup.danger.export_title': 'Export all data',
|
||
'workspace_setup.danger.export_body': 'Download a JSON snapshot of workspace settings, memberships, invitations, operations, agents and platform API keys.',
|
||
'workspace_setup.danger.delete_title': 'Delete workspace',
|
||
'workspace_setup.danger.delete_body': 'Permanently deletes all operations, keys, logs and settings. This action cannot be undone. You will be logged out immediately.',
|
||
'workspace_setup.export': 'Export',
|
||
'workspace_setup.delete': 'Delete workspace',
|
||
'workspace_setup.role.owner': 'Owner',
|
||
'workspace_setup.role.admin': 'Admin',
|
||
'workspace_setup.role.operator': 'Developer',
|
||
'workspace_setup.role.viewer': 'Viewer',
|
||
'workspace_setup.member_count': '{count} members',
|
||
'workspace_setup.member_count_one': '1 member',
|
||
'workspace_setup.saving_create': 'Creating…',
|
||
'workspace_setup.saving_update': 'Saving…',
|
||
'workspace_setup.saved': 'Saved ✓',
|
||
'workspace_setup.save_error': 'Failed to save workspace',
|
||
'workspace_setup.save_error_title': 'Save failed',
|
||
'workspace_setup.invite_error': 'Failed to send invite',
|
||
'workspace_setup.invite_error_title': 'Invite failed',
|
||
'workspace_setup.role_error': 'Failed to update role',
|
||
'workspace_setup.role_error_title': 'Role update failed',
|
||
'workspace_setup.remove_confirm': 'Remove {name} from this workspace?',
|
||
'workspace_setup.remove_error': 'Failed to remove member',
|
||
'workspace_setup.remove_error_title': 'Member removal failed',
|
||
'workspace_setup.revoke_error': 'Failed to revoke invite',
|
||
'workspace_setup.revoke_error_title': 'Invite revoke failed',
|
||
'workspace_setup.export_error': 'Failed to export workspace',
|
||
'workspace_setup.export_error_title': 'Export failed',
|
||
'workspace_setup.delete_confirm': 'Delete this workspace? This cannot be undone.',
|
||
'workspace_setup.delete_error': 'Failed to delete workspace',
|
||
'workspace_setup.delete_error_title': 'Delete failed',
|
||
'workspace.switch_error': 'Failed to switch workspace',
|
||
'workspace.switch_error_title': 'Workspace switch failed',
|
||
'workspace_setup.custom_protocol.rest': 'REST',
|
||
'workspace_setup.custom_protocol.graphql': 'GraphQL',
|
||
'workspace_setup.custom_protocol.grpc': 'gRPC',
|
||
|
||
// Wizard
|
||
'wizard.back_catalog': 'Operations',
|
||
'wizard.progress.create': 'Create operation',
|
||
'wizard.progress.edit': 'Edit operation',
|
||
'wizard.exit': 'Exit wizard',
|
||
'wizard.step_of': 'Step <strong>{step}</strong> of {total}',
|
||
'wizard.status.completed': 'Completed',
|
||
'wizard.status.in_progress': 'In progress',
|
||
'wizard.status.not_started': 'Not started',
|
||
'wizard.help.title': 'Need help?',
|
||
'wizard.help.body': 'Unsure which protocol to choose? Read the',
|
||
'wizard.help.link': 'protocol comparison guide →',
|
||
'wizard.progress.section': 'Progress',
|
||
'wizard.step_name.protocol': 'Protocol',
|
||
'wizard.step_name.upstream': 'Upstream target',
|
||
'wizard.step_name.tool': 'Tool config',
|
||
'wizard.step_name.mapping': 'Mapping',
|
||
'wizard.button.back': 'Back',
|
||
'wizard.button.continue': 'Continue',
|
||
'wizard.button.save_draft': 'Save draft',
|
||
'wizard.button.create': 'Create operation',
|
||
'wizard.button.save_changes': 'Save changes',
|
||
'wizard.sidebar.brand.create': 'Create <span>operation</span>',
|
||
'wizard.sidebar.brand.edit': 'Edit <span>operation</span>',
|
||
'wizard.step1.title': 'Choose a protocol',
|
||
'wizard.step1.subtitle': 'Select the transport protocol your upstream service uses. This determines how Crank will communicate with your API and which configuration options are available in subsequent steps.',
|
||
'wizard.step1.tip_title': 'You can change this later',
|
||
'wizard.step1.tip_body': 'Switching protocol after configuring subsequent steps will clear schema and mapping data. Save your operation as a draft before experimenting. See protocol migration guide for details.',
|
||
'wizard.step2.title': 'Select upstream & endpoint',
|
||
'wizard.step2.subtitle': 'Choose an existing upstream or register a new one. Then define the specific endpoint path this operation will call.',
|
||
'wizard.step3.rest.label': 'Request config',
|
||
'wizard.step3.graphql.label': 'GQL operation',
|
||
'wizard.step3.grpc.label': 'RPC method',
|
||
'wizard.step3.rest.title': 'HTTP method & format',
|
||
'wizard.step3.rest.subtitle': 'Choose the HTTP verb this operation sends and configure content negotiation headers. Crank will serialize MCP tool arguments into the appropriate request body format.',
|
||
'wizard.step3.graphql.title': 'GraphQL operation',
|
||
'wizard.step3.graphql.subtitle': 'Define the GraphQL operation this tool will execute. All requests are sent as HTTP POST to the endpoint configured in the previous step. Variables are populated from MCP tool arguments at runtime.',
|
||
'wizard.step3.grpc.title': 'RPC method',
|
||
'wizard.step3.grpc.subtitle': 'Choose how to describe the gRPC service contract. Upload a .proto file, discover methods from a live server via reflection, or enter the route manually. Only unary RPCs are supported.',
|
||
'wizard.step4.title': 'Tool config',
|
||
'wizard.step4.subtitle': 'Name your tool, write the LLM-facing description, and define the input/output schemas. The description is the primary signal the model uses when deciding whether to call this tool.',
|
||
'wizard.step5.title': 'Mapping and execution',
|
||
'wizard.step5.subtitle': 'Define how MCP tool arguments map to upstream request fields, and how the upstream response maps back to MCP output. Then set execution parameters — auth is configured per-upstream in step 3.',
|
||
'wizard.live.failed_title': 'Live action failed',
|
||
'wizard.live.failed_body': 'The backend request failed. Review the draft and try again.',
|
||
'wizard.error.no_workspace': 'No workspace selected',
|
||
'wizard.error.tool_name': 'Tool name is required',
|
||
'wizard.error.parser_unavailable': 'YAML parser is not available',
|
||
'wizard.error.select_upstream': 'Select or register an upstream first',
|
||
'wizard.error.save': 'Failed to save operation',
|
||
'wizard.error.save_title': 'Save failed',
|
||
'wizard.save.created': 'Draft created',
|
||
'wizard.save.updated': 'Draft updated',
|
||
'wizard.save.body': 'The current draft is saved on the backend and ready for live actions in this wizard.',
|
||
'wizard.save.saved': 'Saved ✓',
|
||
'wizard.busy.input_sample': 'Saving input sample…',
|
||
'wizard.busy.output_sample': 'Saving output sample…',
|
||
'wizard.busy.generate': 'Generating draft…',
|
||
'wizard.busy.test': 'Running test…',
|
||
'wizard.busy.export_yaml': 'Exporting YAML…',
|
||
'wizard.busy.import_yaml': 'Importing YAML…',
|
||
'wizard.busy.publish': 'Publishing operation…',
|
||
'wizard.busy.descriptor': 'Uploading descriptor set…',
|
||
'wizard.sample.input_saved': 'Input sample saved',
|
||
'wizard.sample.input_saved_body': 'The input sample is now stored against the current draft version.',
|
||
'wizard.sample.output_saved': 'Output sample saved',
|
||
'wizard.sample.output_saved_body': 'The output sample is now stored against the current draft version.',
|
||
'wizard.sample.generated': 'Draft generated',
|
||
'wizard.sample.generated_body': 'Schemas and mappings were regenerated from the saved samples.',
|
||
'wizard.test.completed': 'Test run completed',
|
||
'wizard.test.failed': 'Test run returned errors',
|
||
'wizard.test.completed_body': 'The current draft executed successfully against the upstream.',
|
||
'wizard.test.failed_body': 'The draft executed, but the backend returned validation or runtime errors.',
|
||
'wizard.test.no_response': 'No test response yet',
|
||
'wizard.test.no_response_body': 'Run a test first, then copy the response preview into the output sample editor.',
|
||
'wizard.test.response_copied': 'Response copied',
|
||
'wizard.test.response_copied_body': 'The latest test response was copied into the output sample editor.',
|
||
'wizard.yaml.exported': 'YAML exported',
|
||
'wizard.yaml.exported_body': 'The current draft YAML was downloaded from the backend.',
|
||
'wizard.yaml.none': 'No YAML provided',
|
||
'wizard.yaml.none_body': 'Paste a YAML document or load it from a file before importing.',
|
||
'wizard.yaml.imported': 'YAML imported',
|
||
'wizard.yaml.imported_body': 'The imported operation draft was stored. The wizard will reopen it now.',
|
||
'wizard.yaml.loaded': 'YAML loaded',
|
||
'wizard.yaml.loaded_body': 'The YAML document was loaded into the import editor.',
|
||
'wizard.publish.done': 'Operation published',
|
||
'wizard.publish.done_body': 'Version {version} is now published and can be bound into agents.',
|
||
'wizard.descriptor.selected': 'Descriptor set selected',
|
||
'wizard.descriptor.selected_body': 'Upload the descriptor set to discover unary gRPC methods from the backend.',
|
||
'wizard.descriptor.no_file': 'No descriptor set selected',
|
||
'wizard.descriptor.no_file_body': 'Choose a descriptor-set file before uploading it, or save a draft that already has one attached.',
|
||
'wizard.descriptor.discovered': 'Descriptor services discovered',
|
||
'wizard.descriptor.discovered_body': 'The backend parsed the descriptor set. Pick a method to apply its route and schemas.',
|
||
'wizard.descriptor.applied': 'gRPC method applied',
|
||
'wizard.descriptor.applied_body': 'The route and schemas were filled from the uploaded descriptor set.',
|
||
'wizard.descriptor.uploaded': 'Descriptor set uploaded',
|
||
'wizard.grpc.discovery_title': 'gRPC discovery moved to live descriptor flow',
|
||
'wizard.grpc.discovery_body': 'Server reflection is not wired in this deployment. Save the draft, then use the descriptor-set tools in Step 5 to upload a real descriptor set and discover unary methods from the backend.',
|
||
'wizard.grpc.no_methods': 'No unary RPC methods found.',
|
||
'wizard.grpc.fields_unresolved': 'Fields not resolved.',
|
||
'wizard.grpc.fields_unresolved_body': 'Import or external types are not expanded client-side.',
|
||
'wizard.grpc.services_found': '{services} services · {methods} unary methods found',
|
||
'wizard.grpc.services_discovered': '{services} services · {methods} unary methods discovered',
|
||
'wizard.grpc.services_none': 'No unary methods discovered yet',
|
||
|
||
// 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',
|
||
'login.email_label': 'Email address',
|
||
'login.email_placeholder': 'you@acme.com',
|
||
'login.forgot': 'Forgot password?',
|
||
'login.divider': 'or continue with',
|
||
'login.sso_short': 'Google SSO',
|
||
'login.request_prefix': "Don't have an account?",
|
||
'login.request_link': 'Request access',
|
||
'login.error.required': 'Please enter your email and password.',
|
||
'login.error.invalid': 'Invalid email or password. Please try again.',
|
||
'login.error.generic': 'Unable to sign in right now. Please try again.',
|
||
},
|
||
|
||
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': 'Роль',
|
||
'settings.page.title': 'Настройки аккаунта',
|
||
'settings.page.subtitle': 'Управляйте живыми настройками профиля, пароля и воркспейса, доступными в этой сборке. Запланированные возможности остаются видимыми, но не притворяются уже подключенными.',
|
||
'settings.ws.slug_hint': 'Используется в API endpoint-ах и по всей консоли. Только строчные буквы, цифры и дефисы.',
|
||
'settings.ws.description_optional': 'Описание (необязательно)',
|
||
'settings.ws.description_placeholder': 'Чем занимается этот воркспейс?',
|
||
'settings.profile.avatar_hint': 'Аватар строится из отображаемого имени и email.',
|
||
'settings.profile.backend_hint': 'Имя и email хранятся в backend и используются по всей консоли и в сессионной идентичности.',
|
||
'settings.profile.first_name': 'Имя',
|
||
'settings.profile.last_name': 'Фамилия',
|
||
'settings.profile.last_name_placeholder': 'Необязательно',
|
||
'settings.profile.email_label': 'Email адрес',
|
||
'settings.profile.email_hint': 'Изменение email обновляет логин для текущего аккаунта.',
|
||
'settings.profile.save': 'Сохранить профиль',
|
||
'settings.profile.saving': 'Сохранение…',
|
||
'settings.profile.saved': 'Сохранено ✓',
|
||
'settings.profile.saved_status': 'Профиль сохранен.',
|
||
'settings.profile.save_error': 'Не удалось сохранить профиль',
|
||
'settings.security.title': 'Безопасность',
|
||
'settings.security.current_password': 'Текущий пароль',
|
||
'settings.security.new_password': 'Новый пароль',
|
||
'settings.security.new_password_placeholder': 'минимум 12 символов',
|
||
'settings.security.confirm_password': 'Подтвердите новый пароль',
|
||
'settings.security.change_password': 'Сменить пароль',
|
||
'settings.security.mismatch': 'Новый пароль и подтверждение не совпадают.',
|
||
'settings.security.saved': 'Пароль обновлен.',
|
||
'settings.security.save_error': 'Не удалось изменить пароль',
|
||
'settings.security.capabilities_title': 'Запланированные возможности безопасности',
|
||
'settings.security.capabilities_body': 'Двухфакторная аутентификация, passkeys и инвентарь нескольких сессий запланированы, но в текущем live-flow используется логин по паролю и одна HttpOnly browser session.',
|
||
'settings.security.not_available': 'Недоступно в этой сборке.',
|
||
'settings.session.title': 'Текущая сессия',
|
||
'settings.session.current': 'текущая',
|
||
'settings.session.loading': 'Загрузка деталей текущей сессии…',
|
||
'settings.session.unavailable': 'Детали сессии недоступны. Используйте «Выйти», чтобы отозвать текущую browser session.',
|
||
'settings.session.summary': '{email} · {role} в {workspace}. Используйте «Выйти», чтобы отозвать текущую browser session.',
|
||
'settings.notifications.title': 'Настройки уведомлений',
|
||
'settings.notifications.not_ready_title': 'Уведомления пока не подключены.',
|
||
'settings.notifications.not_ready_body': 'Маршрутизация алертов и настройки на пользователя запланированы, но эта сборка не сохраняет и не доставляет notification settings.',
|
||
'settings.notifications.spike_title': 'Всплеск ошибок операции',
|
||
'settings.notifications.spike_body': 'Алерт, когда доля ошибок превышает скользящий порог для опубликованного инструмента.',
|
||
'settings.notifications.latency_title': 'Деградация задержки upstream',
|
||
'settings.notifications.latency_body': 'Уведомлять операторов, когда p99 latency резко растет выше базовой линии.',
|
||
'settings.notifications.digest_title': 'Сводки по квотам и использованию',
|
||
'settings.notifications.digest_body': 'Доставлять периодические сводки по расходу квот, ошибкам и активности воркспейса.',
|
||
'settings.workspace.saving': 'Сохранение…',
|
||
'settings.workspace.saved': 'Сохранено ✓',
|
||
'settings.workspace.save_error': 'Не удалось сохранить настройки воркспейса',
|
||
'settings.workspace.save_error_title': 'Не удалось сохранить воркспейс',
|
||
|
||
// Workspace setup
|
||
'workspace_setup.title': 'Настройки воркспейса',
|
||
'workspace_setup.page_subtitle': 'Настройте идентичность воркспейса, значения по умолчанию и состав команды.',
|
||
'workspace_setup.back': 'Назад',
|
||
'workspace_setup.identity.title': 'Идентичность воркспейса',
|
||
'workspace_setup.identity.avatar_hint': 'Аватар формируется из имени воркспейса.',
|
||
'workspace_setup.name': 'Имя воркспейса',
|
||
'workspace_setup.slug': 'Slug',
|
||
'workspace_setup.required': 'обязательно',
|
||
'workspace_setup.optional': '(необязательно)',
|
||
'workspace_setup.slug_hint': 'Только строчные буквы, цифры и дефисы. Используется в URL MCP endpoint-ов.',
|
||
'workspace_setup.description': 'Описание',
|
||
'workspace_setup.description_placeholder': 'Чем занимается этот воркспейс?',
|
||
'workspace_setup.defaults.title': 'Настройки по умолчанию',
|
||
'workspace_setup.protocol': 'Протокол по умолчанию',
|
||
'workspace_setup.protocol_hint': 'Предварительно выбирается в мастере операций.',
|
||
'workspace_setup.timezone': 'Часовой пояс',
|
||
'workspace_setup.language': 'Язык интерфейса',
|
||
'workspace_setup.members.title': 'Участники команды',
|
||
'workspace_setup.members.invite': 'Пригласить участника',
|
||
'workspace_setup.members.email': 'Email адрес',
|
||
'workspace_setup.members.role': 'Роль',
|
||
'workspace_setup.members.send_invite': 'Отправить приглашение',
|
||
'workspace_setup.members.cancel': 'Отмена',
|
||
'workspace_setup.members.invite_sent': '✓ Приглашение отправлено.',
|
||
'workspace_setup.members.you': '(вы)',
|
||
'workspace_setup.members.today': 'Сегодня',
|
||
'workspace_setup.members.days_ago': '{count} дня назад',
|
||
'workspace_setup.members.week_ago': '1 неделю назад',
|
||
'workspace_setup.members.never': 'Никогда',
|
||
'workspace_setup.members.pending': 'Ожидающие приглашения',
|
||
'workspace_setup.members.invited_pending': 'Приглашен {date} · ожидает принятия',
|
||
'workspace_setup.members.revoke': 'Отозвать приглашение',
|
||
'workspace_setup.members.remove': 'Удалить участника',
|
||
'workspace_setup.roles.title': 'Роли и права',
|
||
'workspace_setup.roles.owner_body': 'Полный контроль над воркспейсом, доступ к биллингу, возможность передать или удалить воркспейс.',
|
||
'workspace_setup.roles.admin_body': 'Управление участниками и ролями, создание и редактирование всех операций и агентов, управление API-ключами.',
|
||
'workspace_setup.roles.developer_body': 'Создание и редактирование операций и агентов. Нельзя управлять участниками или настройками воркспейса.',
|
||
'workspace_setup.roles.viewer_body': 'Доступ только на чтение к операциям, агентам и логам.',
|
||
'workspace_setup.invite.title': 'Пригласить участников команды',
|
||
'workspace_setup.invite.add_person': '+ Добавить человека',
|
||
'workspace_setup.invite.later': 'Позже можно пригласить людей из настроек воркспейса.',
|
||
'workspace_setup.actions.create': 'Создать воркспейс',
|
||
'workspace_setup.actions.save': 'Сохранить изменения',
|
||
'workspace_setup.create.title': 'Создать новый воркспейс',
|
||
'workspace_setup.create.subtitle': 'Воркспейс — это общее окружение для MCP-операций и агентов вашей команды. У каждого воркспейса свой namespace MCP endpoint-ов.',
|
||
'workspace_setup.create.footer': 'Вы станете Owner этого воркспейса. После создания можно пригласить дополнительных участников.',
|
||
'workspace_setup.danger.title': 'Опасная зона',
|
||
'workspace_setup.danger.export_title': 'Экспортировать все данные',
|
||
'workspace_setup.danger.export_body': 'Скачать JSON-снимок настроек воркспейса, участников, приглашений, операций, агентов и platform API keys.',
|
||
'workspace_setup.danger.delete_title': 'Удалить воркспейс',
|
||
'workspace_setup.danger.delete_body': 'Безвозвратно удаляет все операции, ключи, логи и настройки. Действие нельзя отменить. Вы будете немедленно разлогинены.',
|
||
'workspace_setup.export': 'Экспорт',
|
||
'workspace_setup.delete': 'Удалить воркспейс',
|
||
'workspace_setup.role.owner': 'Owner',
|
||
'workspace_setup.role.admin': 'Admin',
|
||
'workspace_setup.role.operator': 'Developer',
|
||
'workspace_setup.role.viewer': 'Viewer',
|
||
'workspace_setup.member_count': '{count} участников',
|
||
'workspace_setup.member_count_one': '1 участник',
|
||
'workspace_setup.saving_create': 'Создание…',
|
||
'workspace_setup.saving_update': 'Сохранение…',
|
||
'workspace_setup.saved': 'Сохранено ✓',
|
||
'workspace_setup.save_error': 'Не удалось сохранить воркспейс',
|
||
'workspace_setup.save_error_title': 'Не удалось сохранить',
|
||
'workspace_setup.invite_error': 'Не удалось отправить приглашение',
|
||
'workspace_setup.invite_error_title': 'Не удалось пригласить',
|
||
'workspace_setup.role_error': 'Не удалось обновить роль',
|
||
'workspace_setup.role_error_title': 'Не удалось обновить роль',
|
||
'workspace_setup.remove_confirm': 'Удалить {name} из этого воркспейса?',
|
||
'workspace_setup.remove_error': 'Не удалось удалить участника',
|
||
'workspace_setup.remove_error_title': 'Не удалось удалить участника',
|
||
'workspace_setup.revoke_error': 'Не удалось отозвать приглашение',
|
||
'workspace_setup.revoke_error_title': 'Не удалось отозвать приглашение',
|
||
'workspace_setup.export_error': 'Не удалось экспортировать воркспейс',
|
||
'workspace_setup.export_error_title': 'Не удалось экспортировать',
|
||
'workspace_setup.delete_confirm': 'Удалить этот воркспейс? Действие нельзя отменить.',
|
||
'workspace_setup.delete_error': 'Не удалось удалить воркспейс',
|
||
'workspace_setup.delete_error_title': 'Не удалось удалить',
|
||
'workspace.switch_error': 'Не удалось переключить воркспейс',
|
||
'workspace.switch_error_title': 'Не удалось переключить воркспейс',
|
||
'workspace_setup.custom_protocol.rest': 'REST',
|
||
'workspace_setup.custom_protocol.graphql': 'GraphQL',
|
||
'workspace_setup.custom_protocol.grpc': 'gRPC',
|
||
|
||
// Wizard
|
||
'wizard.back_catalog': 'Операции',
|
||
'wizard.progress.create': 'Создать операцию',
|
||
'wizard.progress.edit': 'Редактировать операцию',
|
||
'wizard.exit': 'Выйти из мастера',
|
||
'wizard.step_of': 'Шаг <strong>{step}</strong> из {total}',
|
||
'wizard.status.completed': 'Завершено',
|
||
'wizard.status.in_progress': 'В процессе',
|
||
'wizard.status.not_started': 'Не начато',
|
||
'wizard.help.title': 'Нужна помощь?',
|
||
'wizard.help.body': 'Не уверены, какой протокол выбрать? Откройте',
|
||
'wizard.help.link': 'гайд по сравнению протоколов →',
|
||
'wizard.progress.section': 'Прогресс',
|
||
'wizard.step_name.protocol': 'Протокол',
|
||
'wizard.step_name.upstream': 'Upstream target',
|
||
'wizard.step_name.tool': 'Конфиг инструмента',
|
||
'wizard.step_name.mapping': 'Mapping',
|
||
'wizard.button.back': 'Назад',
|
||
'wizard.button.continue': 'Продолжить',
|
||
'wizard.button.save_draft': 'Сохранить черновик',
|
||
'wizard.button.create': 'Создать операцию',
|
||
'wizard.button.save_changes': 'Сохранить изменения',
|
||
'wizard.sidebar.brand.create': 'Создать <span>операцию</span>',
|
||
'wizard.sidebar.brand.edit': 'Редактировать <span>операцию</span>',
|
||
'wizard.step1.title': 'Выберите протокол',
|
||
'wizard.step1.subtitle': 'Выберите транспортный протокол upstream-сервиса. От этого зависит, как Crank будет общаться с вашим API и какие настройки будут доступны на следующих шагах.',
|
||
'wizard.step1.tip_title': 'Это можно изменить позже',
|
||
'wizard.step1.tip_body': 'Смена протокола после настройки следующих шагов очистит схемы и mapping. Сохраните операцию как черновик перед экспериментами. См. гайд по миграции протоколов.',
|
||
'wizard.step2.title': 'Выберите upstream и endpoint',
|
||
'wizard.step2.subtitle': 'Выберите существующий upstream или зарегистрируйте новый. Затем задайте конкретный путь endpoint-а для этой операции.',
|
||
'wizard.step3.rest.label': 'Конфиг запроса',
|
||
'wizard.step3.graphql.label': 'GQL операция',
|
||
'wizard.step3.grpc.label': 'RPC метод',
|
||
'wizard.step3.rest.title': 'HTTP метод и формат',
|
||
'wizard.step3.rest.subtitle': 'Выберите HTTP-метод, который отправляет эта операция, и настройте content negotiation headers. Crank сериализует аргументы MCP-инструмента в нужный формат request body.',
|
||
'wizard.step3.graphql.title': 'GraphQL операция',
|
||
'wizard.step3.graphql.subtitle': 'Определите GraphQL-операцию, которую будет выполнять этот инструмент. Все запросы отправляются как HTTP POST на endpoint, настроенный на предыдущем шаге. Variables заполняются из аргументов MCP-инструмента во время выполнения.',
|
||
'wizard.step3.grpc.title': 'RPC метод',
|
||
'wizard.step3.grpc.subtitle': 'Выберите, как описывать контракт gRPC-сервиса. Загрузите .proto файл, откройте методы через reflection или введите route вручную. Поддерживаются только unary RPC.',
|
||
'wizard.step4.title': 'Конфиг инструмента',
|
||
'wizard.step4.subtitle': 'Задайте имя инструмента, описание для LLM и определите входную/выходную схему. Описание — главный сигнал, по которому модель решает, вызывать ли этот инструмент.',
|
||
'wizard.step5.title': 'Mapping и исполнение',
|
||
'wizard.step5.subtitle': 'Определите, как аргументы MCP-инструмента мапятся в поля upstream-запроса и как ответ upstream-а мапится обратно в MCP output. Затем задайте execution-параметры — auth настраивается на уровне upstream на шаге 3.',
|
||
'wizard.live.failed_title': 'Не удалось выполнить live-действие',
|
||
'wizard.live.failed_body': 'Запрос к backend завершился ошибкой. Проверьте черновик и попробуйте еще раз.',
|
||
'wizard.error.no_workspace': 'Воркспейс не выбран',
|
||
'wizard.error.tool_name': 'Имя инструмента обязательно',
|
||
'wizard.error.parser_unavailable': 'YAML parser недоступен',
|
||
'wizard.error.select_upstream': 'Сначала выберите или зарегистрируйте upstream',
|
||
'wizard.error.save': 'Не удалось сохранить операцию',
|
||
'wizard.error.save_title': 'Не удалось сохранить',
|
||
'wizard.save.created': 'Черновик создан',
|
||
'wizard.save.updated': 'Черновик обновлен',
|
||
'wizard.save.body': 'Текущий черновик сохранен в backend и готов к live-действиям в этом мастере.',
|
||
'wizard.save.saved': 'Сохранено ✓',
|
||
'wizard.busy.input_sample': 'Сохранение входного sample…',
|
||
'wizard.busy.output_sample': 'Сохранение выходного sample…',
|
||
'wizard.busy.generate': 'Генерация черновика…',
|
||
'wizard.busy.test': 'Запуск теста…',
|
||
'wizard.busy.export_yaml': 'Экспорт YAML…',
|
||
'wizard.busy.import_yaml': 'Импорт YAML…',
|
||
'wizard.busy.publish': 'Публикация операции…',
|
||
'wizard.busy.descriptor': 'Загрузка descriptor set…',
|
||
'wizard.sample.input_saved': 'Входной sample сохранен',
|
||
'wizard.sample.input_saved_body': 'Входной sample теперь сохранен в текущей версии черновика.',
|
||
'wizard.sample.output_saved': 'Выходной sample сохранен',
|
||
'wizard.sample.output_saved_body': 'Выходной sample теперь сохранен в текущей версии черновика.',
|
||
'wizard.sample.generated': 'Черновик сгенерирован',
|
||
'wizard.sample.generated_body': 'Схемы и mapping были пересобраны из сохраненных sample-ов.',
|
||
'wizard.test.completed': 'Тестовый прогон завершен',
|
||
'wizard.test.failed': 'Тестовый прогон вернул ошибки',
|
||
'wizard.test.completed_body': 'Текущий черновик успешно выполнился против upstream.',
|
||
'wizard.test.failed_body': 'Черновик выполнился, но backend вернул ошибки валидации или runtime.',
|
||
'wizard.test.no_response': 'Тестового ответа пока нет',
|
||
'wizard.test.no_response_body': 'Сначала выполните тест, затем скопируйте response preview в editor output sample.',
|
||
'wizard.test.response_copied': 'Ответ скопирован',
|
||
'wizard.test.response_copied_body': 'Последний тестовый ответ скопирован в editor output sample.',
|
||
'wizard.yaml.exported': 'YAML экспортирован',
|
||
'wizard.yaml.exported_body': 'YAML текущего черновика скачан из backend.',
|
||
'wizard.yaml.none': 'YAML не предоставлен',
|
||
'wizard.yaml.none_body': 'Вставьте YAML-документ или загрузите файл перед импортом.',
|
||
'wizard.yaml.imported': 'YAML импортирован',
|
||
'wizard.yaml.imported_body': 'Импортированный черновик операции сохранен. Мастер сейчас откроет его заново.',
|
||
'wizard.yaml.loaded': 'YAML загружен',
|
||
'wizard.yaml.loaded_body': 'YAML-документ загружен в editor импорта.',
|
||
'wizard.publish.done': 'Операция опубликована',
|
||
'wizard.publish.done_body': 'Версия {version} опубликована и теперь может быть привязана к агентам.',
|
||
'wizard.descriptor.selected': 'Descriptor set выбран',
|
||
'wizard.descriptor.selected_body': 'Загрузите descriptor set, чтобы открыть unary gRPC methods из backend.',
|
||
'wizard.descriptor.no_file': 'Descriptor set не выбран',
|
||
'wizard.descriptor.no_file_body': 'Выберите файл descriptor set перед загрузкой или сохраните черновик, у которого он уже прикреплен.',
|
||
'wizard.descriptor.discovered': 'Сервисы descriptor-а открыты',
|
||
'wizard.descriptor.discovered_body': 'Backend разобрал descriptor set. Выберите метод, чтобы применить route и схемы.',
|
||
'wizard.descriptor.applied': 'gRPC метод применен',
|
||
'wizard.descriptor.applied_body': 'Route и схемы заполнены из загруженного descriptor set.',
|
||
'wizard.descriptor.uploaded': 'Descriptor set загружен',
|
||
'wizard.grpc.discovery_title': 'gRPC discovery переведен в live descriptor flow',
|
||
'wizard.grpc.discovery_body': 'Server reflection в этом развертывании не подключен. Сохраните черновик и используйте инструменты descriptor set на шаге 5, чтобы загрузить реальный descriptor set и открыть unary methods из backend.',
|
||
'wizard.grpc.no_methods': 'Unary RPC methods не найдены.',
|
||
'wizard.grpc.fields_unresolved': 'Поля не разрешены.',
|
||
'wizard.grpc.fields_unresolved_body': 'Import-ы или внешние типы не разворачиваются на стороне клиента.',
|
||
'wizard.grpc.services_found': 'Сервисов: {services} · unary методов: {methods}',
|
||
'wizard.grpc.services_discovered': 'Сервисов открыто: {services} · unary методов: {methods}',
|
||
'wizard.grpc.services_none': 'Unary methods пока не открыты',
|
||
|
||
// 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',
|
||
'login.email_label': 'Email адрес',
|
||
'login.email_placeholder': 'you@acme.com',
|
||
'login.forgot': 'Забыли пароль?',
|
||
'login.divider': 'или продолжить через',
|
||
'login.sso_short': 'Google SSO',
|
||
'login.request_prefix': 'Нет аккаунта?',
|
||
'login.request_link': 'Запросить доступ',
|
||
'login.error.required': 'Введите email и пароль.',
|
||
'login.error.invalid': 'Неверный email или пароль. Попробуйте еще раз.',
|
||
'login.error.generic': 'Сейчас не удается войти. Попробуйте еще раз.',
|
||
}
|
||
};
|
||
|
||
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'));
|
||
});
|
||
document.querySelectorAll('[data-i18n-html]').forEach(function(el) {
|
||
el.innerHTML = t(el.getAttribute('data-i18n-html'));
|
||
});
|
||
// 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);
|