feat: localize agents page

This commit is contained in:
a.tolmachev
2026-04-02 23:00:12 +03:00
parent a491c246e0
commit 12b43542a9
3 changed files with 274 additions and 80 deletions
+50 -25
View File
@@ -35,6 +35,10 @@ function mapOperation(operation) {
};
}
function currentLocale() {
return localStorage.getItem('crank_lang') === 'ru' ? 'ru-RU' : 'en-US';
}
document.addEventListener('alpine:init', function() {
Alpine.data('agents', function() {
return {
@@ -94,7 +98,7 @@ document.addEventListener('alpine:init', function() {
this.agents = [];
this.operations = [];
this.loading = false;
this.loadError = 'Workspace or API is unavailable';
this.loadError = this.tKey('agents.error.api');
return;
}
@@ -108,7 +112,7 @@ document.addEventListener('alpine:init', function() {
} catch (error) {
this.agents = [];
this.operations = [];
this.loadError = error.message || 'Failed to load agents';
this.loadError = error.message || this.tKey('agents.error.load');
}
this.loading = false;
@@ -290,21 +294,29 @@ document.addEventListener('alpine:init', function() {
await this.reload();
if (window.CrankUi) {
window.CrankUi.success(
this.form.display_name + ' was saved with ' + this.form.selectedOps.length + ' tool bindings.',
this.drawerMode === 'create' ? 'Agent created' : 'Agent updated'
this.tfKey('agents.toast.saved_message', {
name: this.form.display_name,
count: this.form.selectedOps.length
}),
this.drawerMode === 'create'
? this.tKey('agents.toast.saved_title_create')
: this.tKey('agents.toast.saved_title_update')
);
}
this.closeDrawer();
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to save agent', 'Agent update failed');
window.CrankUi.error(
error.message || this.tKey('agents.toast.save_error_message'),
this.tKey('agents.toast.save_error_title')
);
}
this.saving = false;
}
},
async deleteAgent(id) {
if (!confirm('Delete this agent? This cannot be undone.')) return;
if (!confirm(this.tKey('agents.toast.delete_confirm'))) return;
try {
var agent = this.agents.find(function(item) { return item.id === id; });
@@ -312,13 +324,16 @@ document.addEventListener('alpine:init', function() {
await this.reload();
if (window.CrankUi) {
window.CrankUi.success(
(agent ? agent.display_name : 'Agent') + ' was deleted.',
'Agent deleted'
this.tfKey('agents.toast.delete_message', { name: agent ? agent.display_name : '' }),
this.tKey('agents.toast.delete_title')
);
}
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to delete agent', 'Delete failed');
window.CrankUi.error(
error.message || this.tKey('agents.toast.delete_error_message'),
this.tKey('agents.toast.delete_error_title')
);
}
}
},
@@ -340,37 +355,39 @@ document.addEventListener('alpine:init', function() {
}
await this.reload();
if (window.CrankUi) {
var actionLabel = action === 'publish'
? 'published'
: action === 'unpublish'
? 'returned to draft'
: 'archived';
window.CrankUi.success(
agent.display_name + ' was ' + actionLabel + '.',
'Agent lifecycle updated'
action === 'publish'
? this.tfKey('agents.toast.lifecycle_publish', { name: agent.display_name })
: action === 'unpublish'
? this.tfKey('agents.toast.lifecycle_unpublish', { name: agent.display_name })
: this.tfKey('agents.toast.lifecycle_archive', { name: agent.display_name }),
this.tKey('agents.toast.lifecycle_title')
);
}
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to update agent lifecycle', 'Lifecycle update failed');
window.CrankUi.error(
error.message || this.tKey('agents.toast.lifecycle_error_message'),
this.tKey('agents.toast.lifecycle_error_title')
);
}
}
},
lifecycleAction(agent) {
if (agent.raw_status === 'published') {
return { key: 'unpublish', label: 'Unpublish' };
return { key: 'unpublish', label: this.tKey('agents.lifecycle.unpublish') };
}
if (agent.raw_status === 'archived') {
return { key: 'unpublish', label: 'Restore draft' };
return { key: 'unpublish', label: this.tKey('agents.lifecycle.restore_draft') };
}
return { key: 'publish', label: 'Publish' };
return { key: 'publish', label: this.tKey('agents.lifecycle.publish') };
},
lifecycleLabel(agent) {
if (agent.raw_status === 'published') return 'Published';
if (agent.raw_status === 'archived') return 'Archived';
return 'Draft';
if (agent.raw_status === 'published') return this.tKey('agents.lifecycle.published');
if (agent.raw_status === 'archived') return this.tKey('agents.lifecycle.archived');
return this.tKey('agents.lifecycle.draft');
},
mcpEndpoint(agent) {
@@ -384,7 +401,7 @@ document.addEventListener('alpine:init', function() {
var endpoint = this.mcpEndpoint(agent);
navigator.clipboard.writeText(endpoint).catch(function() {});
if (window.CrankUi) {
window.CrankUi.info(endpoint, 'MCP endpoint copied');
window.CrankUi.info(endpoint, this.tKey('agents.toast.endpoint_title'));
}
},
@@ -406,7 +423,7 @@ document.addEventListener('alpine:init', function() {
formatDate(dateStr) {
if (!dateStr) return '—';
return new Date(dateStr).toLocaleDateString('en-US', {
return new Date(dateStr).toLocaleDateString(currentLocale(), {
month: 'short',
day: 'numeric',
year: 'numeric',
@@ -417,6 +434,14 @@ document.addEventListener('alpine:init', function() {
if (!value) return '0';
return value >= 1000 ? (value / 1000).toFixed(1) + 'k' : String(value);
},
tKey(key) {
return window.t ? t(key) : key;
},
tfKey(key, vars) {
return window.tf ? tf(key, vars) : this.tKey(key);
},
};
});
});
+168
View File
@@ -13,6 +13,9 @@ var TRANSLATIONS = {
'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',
@@ -148,6 +151,87 @@ var TRANSLATIONS = {
'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',
@@ -166,6 +250,9 @@ var TRANSLATIONS = {
'nav.usage': 'Использование',
'nav.settings': 'Настройки',
'nav.logout': 'Выйти',
'nav.workspaces': 'Ваши воркспейсы',
'nav.create_workspace': 'Создать воркспейс',
'nav.notifications': 'Уведомления',
// Operations page
'ops.title': 'Операции',
@@ -301,6 +388,87 @@ var TRANSLATIONS = {
'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': 'Добро пожаловать обратно',