feat(ui): add i18n support with Simplified Chinese (zh-CN)
- Add lightweight i18n module with auto-detection - Add English (en) and Simplified Chinese (zh-CN) locales - Update navigation.ts to use i18n for tab titles and descriptions - Support browser language detection and localStorage persistence Closes #3902
This commit is contained in:
parent
5f4715acfc
commit
e4523991e3
149
ui/src/i18n/index.ts
Normal file
149
ui/src/i18n/index.ts
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Lightweight i18n module for Control UI
|
||||
*
|
||||
* Usage:
|
||||
* import { t, setLocale, getLocale } from './i18n';
|
||||
*
|
||||
* // Get translated string
|
||||
* t('nav.chat') // => "Chat" or "聊天"
|
||||
*
|
||||
* // Change locale
|
||||
* setLocale('zh-CN');
|
||||
*
|
||||
* // Get current locale
|
||||
* getLocale() // => 'zh-CN'
|
||||
*/
|
||||
|
||||
import type { Locale, TranslationKey, TranslationKeys } from './types';
|
||||
import { en } from './locales/en';
|
||||
import { zhCN } from './locales/zh-CN';
|
||||
|
||||
const STORAGE_KEY = 'moltbot-locale';
|
||||
|
||||
const locales: Record<Locale, TranslationKeys> = {
|
||||
'en': en,
|
||||
'zh-CN': zhCN,
|
||||
};
|
||||
|
||||
let currentLocale: Locale = 'en';
|
||||
|
||||
/**
|
||||
* Detect the best locale based on browser settings
|
||||
*/
|
||||
function detectLocale(): Locale {
|
||||
// Check localStorage first
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored && isValidLocale(stored)) {
|
||||
return stored as Locale;
|
||||
}
|
||||
} catch {
|
||||
// localStorage not available
|
||||
}
|
||||
|
||||
// Check browser language
|
||||
const browserLang = navigator.language || (navigator as any).userLanguage || 'en';
|
||||
|
||||
// Check for exact match
|
||||
if (isValidLocale(browserLang)) {
|
||||
return browserLang as Locale;
|
||||
}
|
||||
|
||||
// Check for language code match (e.g., 'zh' matches 'zh-CN')
|
||||
const langCode = browserLang.split('-')[0].toLowerCase();
|
||||
if (langCode === 'zh') {
|
||||
return 'zh-CN';
|
||||
}
|
||||
|
||||
return 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a locale is valid
|
||||
*/
|
||||
function isValidLocale(locale: string): locale is Locale {
|
||||
return locale in locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current locale
|
||||
*/
|
||||
export function getLocale(): Locale {
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available locales
|
||||
*/
|
||||
export function getAvailableLocales(): Locale[] {
|
||||
return Object.keys(locales) as Locale[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale display name
|
||||
*/
|
||||
export function getLocaleDisplayName(locale: Locale): string {
|
||||
switch (locale) {
|
||||
case 'en':
|
||||
return 'English';
|
||||
case 'zh-CN':
|
||||
return '简体中文';
|
||||
default:
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current locale
|
||||
*/
|
||||
export function setLocale(locale: Locale): void {
|
||||
if (!isValidLocale(locale)) {
|
||||
console.warn(`Invalid locale: ${locale}, falling back to 'en'`);
|
||||
locale = 'en';
|
||||
}
|
||||
|
||||
currentLocale = locale;
|
||||
|
||||
// Persist to localStorage
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, locale);
|
||||
} catch {
|
||||
// localStorage not available
|
||||
}
|
||||
|
||||
// Dispatch event for components to react
|
||||
window.dispatchEvent(new CustomEvent('locale-changed', { detail: { locale } }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a translated string
|
||||
*/
|
||||
export function t(key: TranslationKey): string {
|
||||
const translations = locales[currentLocale] ?? locales['en'];
|
||||
return translations[key] ?? key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a translated string with interpolation
|
||||
*
|
||||
* Usage:
|
||||
* tf('greeting', { name: 'World' }) // "Hello, {name}!" => "Hello, World!"
|
||||
*/
|
||||
export function tf(key: TranslationKey, params: Record<string, string | number>): string {
|
||||
let result = t(key);
|
||||
for (const [param, value] of Object.entries(params)) {
|
||||
result = result.replace(new RegExp(`\\{${param}\\}`, 'g'), String(value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize i18n with auto-detection
|
||||
*/
|
||||
export function initI18n(): Locale {
|
||||
currentLocale = detectLocale();
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
// Auto-initialize on module load
|
||||
initI18n();
|
||||
151
ui/src/i18n/locales/en.ts
Normal file
151
ui/src/i18n/locales/en.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import type { TranslationKeys } from '../types';
|
||||
|
||||
export const en: TranslationKeys = {
|
||||
// Navigation groups
|
||||
'nav.chat': 'Chat',
|
||||
'nav.control': 'Control',
|
||||
'nav.agent': 'Agent',
|
||||
'nav.settings': 'Settings',
|
||||
|
||||
// Tab titles
|
||||
'tab.overview': 'Overview',
|
||||
'tab.channels': 'Channels',
|
||||
'tab.instances': 'Instances',
|
||||
'tab.sessions': 'Sessions',
|
||||
'tab.cron': 'Cron Jobs',
|
||||
'tab.skills': 'Skills',
|
||||
'tab.nodes': 'Nodes',
|
||||
'tab.chat': 'Chat',
|
||||
'tab.config': 'Config',
|
||||
'tab.debug': 'Debug',
|
||||
'tab.logs': 'Logs',
|
||||
|
||||
// Tab subtitles/descriptions
|
||||
'tab.overview.desc': 'Gateway status, entry points, and a fast health read.',
|
||||
'tab.channels.desc': 'Manage channels and settings.',
|
||||
'tab.instances.desc': 'Presence beacons from connected clients and nodes.',
|
||||
'tab.sessions.desc': 'Inspect active sessions and adjust per-session defaults.',
|
||||
'tab.cron.desc': 'Schedule wakeups and recurring agent runs.',
|
||||
'tab.skills.desc': 'Manage skill availability and API key injection.',
|
||||
'tab.nodes.desc': 'Paired devices, capabilities, and command exposure.',
|
||||
'tab.chat.desc': 'Direct gateway chat session for quick interventions.',
|
||||
'tab.config.desc': 'Edit ~/.clawdbot/clawdbot.json safely.',
|
||||
'tab.debug.desc': 'Gateway snapshots, events, and manual RPC calls.',
|
||||
'tab.logs.desc': 'Live tail of the gateway file logs.',
|
||||
|
||||
// Common actions
|
||||
'action.save': 'Save',
|
||||
'action.cancel': 'Cancel',
|
||||
'action.apply': 'Apply',
|
||||
'action.reset': 'Reset',
|
||||
'action.delete': 'Delete',
|
||||
'action.edit': 'Edit',
|
||||
'action.add': 'Add',
|
||||
'action.remove': 'Remove',
|
||||
'action.refresh': 'Refresh',
|
||||
'action.copy': 'Copy',
|
||||
'action.close': 'Close',
|
||||
'action.confirm': 'Confirm',
|
||||
'action.send': 'Send',
|
||||
'action.stop': 'Stop',
|
||||
'action.retry': 'Retry',
|
||||
|
||||
// Status
|
||||
'status.online': 'Online',
|
||||
'status.offline': 'Offline',
|
||||
'status.connected': 'Connected',
|
||||
'status.disconnected': 'Disconnected',
|
||||
'status.loading': 'Loading...',
|
||||
'status.error': 'Error',
|
||||
'status.success': 'Success',
|
||||
'status.pending': 'Pending',
|
||||
'status.idle': 'Idle',
|
||||
'status.running': 'Running',
|
||||
'status.ok': 'OK',
|
||||
|
||||
// Header
|
||||
'header.health': 'Health',
|
||||
'header.brand.title': 'MOLTBOT',
|
||||
'header.brand.sub': 'Gateway Dashboard',
|
||||
'header.expandSidebar': 'Expand sidebar',
|
||||
'header.collapseSidebar': 'Collapse sidebar',
|
||||
|
||||
// Theme
|
||||
'theme.light': 'Light',
|
||||
'theme.dark': 'Dark',
|
||||
'theme.system': 'System',
|
||||
|
||||
// Chat
|
||||
'chat.placeholder': 'Type a message...',
|
||||
'chat.send': 'Send',
|
||||
'chat.thinking': 'Thinking...',
|
||||
'chat.attachFile': 'Attach file',
|
||||
'chat.clearHistory': 'Clear history',
|
||||
|
||||
// Channels
|
||||
'channels.whatsapp': 'WhatsApp',
|
||||
'channels.telegram': 'Telegram',
|
||||
'channels.discord': 'Discord',
|
||||
'channels.slack': 'Slack',
|
||||
'channels.signal': 'Signal',
|
||||
'channels.imessage': 'iMessage',
|
||||
'channels.nostr': 'Nostr',
|
||||
'channels.googlechat': 'Google Chat',
|
||||
|
||||
// Sessions
|
||||
'sessions.title': 'Sessions',
|
||||
'sessions.active': 'Active',
|
||||
'sessions.tokens': 'Tokens',
|
||||
'sessions.model': 'Model',
|
||||
'sessions.lastActivity': 'Last Activity',
|
||||
|
||||
// Cron
|
||||
'cron.title': 'Cron Jobs',
|
||||
'cron.schedule': 'Schedule',
|
||||
'cron.nextRun': 'Next Run',
|
||||
'cron.lastRun': 'Last Run',
|
||||
'cron.enabled': 'Enabled',
|
||||
'cron.disabled': 'Disabled',
|
||||
'cron.addJob': 'Add Job',
|
||||
|
||||
// Config
|
||||
'config.title': 'Configuration',
|
||||
'config.saved': 'Saved',
|
||||
'config.unsaved': 'Unsaved changes',
|
||||
'config.saveChanges': 'Save Changes',
|
||||
'config.discardChanges': 'Discard Changes',
|
||||
|
||||
// Logs
|
||||
'logs.title': 'Logs',
|
||||
'logs.level': 'Level',
|
||||
'logs.filter': 'Filter',
|
||||
'logs.export': 'Export',
|
||||
'logs.clear': 'Clear',
|
||||
|
||||
// Skills
|
||||
'skills.title': 'Skills',
|
||||
'skills.installed': 'Installed',
|
||||
'skills.available': 'Available',
|
||||
'skills.install': 'Install',
|
||||
'skills.uninstall': 'Uninstall',
|
||||
|
||||
// Nodes
|
||||
'nodes.title': 'Nodes',
|
||||
'nodes.paired': 'Paired',
|
||||
'nodes.pending': 'Pending',
|
||||
'nodes.approve': 'Approve',
|
||||
'nodes.reject': 'Reject',
|
||||
|
||||
// Errors
|
||||
'error.connection': 'Connection error',
|
||||
'error.timeout': 'Request timed out',
|
||||
'error.unknown': 'An unknown error occurred',
|
||||
'error.invalidInput': 'Invalid input',
|
||||
|
||||
// Misc
|
||||
'misc.noData': 'No data',
|
||||
'misc.loading': 'Loading...',
|
||||
'misc.never': 'Never',
|
||||
'misc.justNow': 'Just now',
|
||||
'misc.ago': 'ago',
|
||||
};
|
||||
151
ui/src/i18n/locales/zh-CN.ts
Normal file
151
ui/src/i18n/locales/zh-CN.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import type { TranslationKeys } from '../types';
|
||||
|
||||
export const zhCN: TranslationKeys = {
|
||||
// Navigation groups
|
||||
'nav.chat': '聊天',
|
||||
'nav.control': '控制',
|
||||
'nav.agent': '代理',
|
||||
'nav.settings': '设置',
|
||||
|
||||
// Tab titles
|
||||
'tab.overview': '概览',
|
||||
'tab.channels': '通道',
|
||||
'tab.instances': '实例',
|
||||
'tab.sessions': '会话',
|
||||
'tab.cron': '定时任务',
|
||||
'tab.skills': '技能',
|
||||
'tab.nodes': '节点',
|
||||
'tab.chat': '聊天',
|
||||
'tab.config': '配置',
|
||||
'tab.debug': '调试',
|
||||
'tab.logs': '日志',
|
||||
|
||||
// Tab subtitles/descriptions
|
||||
'tab.overview.desc': '网关状态、入口点和快速健康检查。',
|
||||
'tab.channels.desc': '管理通道和设置。',
|
||||
'tab.instances.desc': '来自已连接客户端和节点的状态信标。',
|
||||
'tab.sessions.desc': '检查活跃会话并调整每会话默认设置。',
|
||||
'tab.cron.desc': '安排唤醒和定期代理运行。',
|
||||
'tab.skills.desc': '管理技能可用性和 API 密钥注入。',
|
||||
'tab.nodes.desc': '已配对设备、功能和命令暴露。',
|
||||
'tab.chat.desc': '直接网关聊天会话,用于快速干预。',
|
||||
'tab.config.desc': '安全编辑 ~/.clawdbot/clawdbot.json。',
|
||||
'tab.debug.desc': '网关快照、事件和手动 RPC 调用。',
|
||||
'tab.logs.desc': '实时查看网关文件日志。',
|
||||
|
||||
// Common actions
|
||||
'action.save': '保存',
|
||||
'action.cancel': '取消',
|
||||
'action.apply': '应用',
|
||||
'action.reset': '重置',
|
||||
'action.delete': '删除',
|
||||
'action.edit': '编辑',
|
||||
'action.add': '添加',
|
||||
'action.remove': '移除',
|
||||
'action.refresh': '刷新',
|
||||
'action.copy': '复制',
|
||||
'action.close': '关闭',
|
||||
'action.confirm': '确认',
|
||||
'action.send': '发送',
|
||||
'action.stop': '停止',
|
||||
'action.retry': '重试',
|
||||
|
||||
// Status
|
||||
'status.online': '在线',
|
||||
'status.offline': '离线',
|
||||
'status.connected': '已连接',
|
||||
'status.disconnected': '已断开',
|
||||
'status.loading': '加载中...',
|
||||
'status.error': '错误',
|
||||
'status.success': '成功',
|
||||
'status.pending': '等待中',
|
||||
'status.idle': '空闲',
|
||||
'status.running': '运行中',
|
||||
'status.ok': '正常',
|
||||
|
||||
// Header
|
||||
'header.health': '健康状态',
|
||||
'header.brand.title': 'MOLTBOT',
|
||||
'header.brand.sub': '网关控制台',
|
||||
'header.expandSidebar': '展开侧边栏',
|
||||
'header.collapseSidebar': '收起侧边栏',
|
||||
|
||||
// Theme
|
||||
'theme.light': '浅色',
|
||||
'theme.dark': '深色',
|
||||
'theme.system': '跟随系统',
|
||||
|
||||
// Chat
|
||||
'chat.placeholder': '输入消息...',
|
||||
'chat.send': '发送',
|
||||
'chat.thinking': '思考中...',
|
||||
'chat.attachFile': '添加附件',
|
||||
'chat.clearHistory': '清除历史',
|
||||
|
||||
// Channels
|
||||
'channels.whatsapp': 'WhatsApp',
|
||||
'channels.telegram': 'Telegram',
|
||||
'channels.discord': 'Discord',
|
||||
'channels.slack': 'Slack',
|
||||
'channels.signal': 'Signal',
|
||||
'channels.imessage': 'iMessage',
|
||||
'channels.nostr': 'Nostr',
|
||||
'channels.googlechat': 'Google Chat',
|
||||
|
||||
// Sessions
|
||||
'sessions.title': '会话',
|
||||
'sessions.active': '活跃',
|
||||
'sessions.tokens': 'Token 数',
|
||||
'sessions.model': '模型',
|
||||
'sessions.lastActivity': '最后活动',
|
||||
|
||||
// Cron
|
||||
'cron.title': '定时任务',
|
||||
'cron.schedule': '调度',
|
||||
'cron.nextRun': '下次运行',
|
||||
'cron.lastRun': '上次运行',
|
||||
'cron.enabled': '已启用',
|
||||
'cron.disabled': '已禁用',
|
||||
'cron.addJob': '添加任务',
|
||||
|
||||
// Config
|
||||
'config.title': '配置',
|
||||
'config.saved': '已保存',
|
||||
'config.unsaved': '有未保存的更改',
|
||||
'config.saveChanges': '保存更改',
|
||||
'config.discardChanges': '放弃更改',
|
||||
|
||||
// Logs
|
||||
'logs.title': '日志',
|
||||
'logs.level': '级别',
|
||||
'logs.filter': '筛选',
|
||||
'logs.export': '导出',
|
||||
'logs.clear': '清空',
|
||||
|
||||
// Skills
|
||||
'skills.title': '技能',
|
||||
'skills.installed': '已安装',
|
||||
'skills.available': '可用',
|
||||
'skills.install': '安装',
|
||||
'skills.uninstall': '卸载',
|
||||
|
||||
// Nodes
|
||||
'nodes.title': '节点',
|
||||
'nodes.paired': '已配对',
|
||||
'nodes.pending': '等待中',
|
||||
'nodes.approve': '批准',
|
||||
'nodes.reject': '拒绝',
|
||||
|
||||
// Errors
|
||||
'error.connection': '连接错误',
|
||||
'error.timeout': '请求超时',
|
||||
'error.unknown': '发生未知错误',
|
||||
'error.invalidInput': '输入无效',
|
||||
|
||||
// Misc
|
||||
'misc.noData': '暂无数据',
|
||||
'misc.loading': '加载中...',
|
||||
'misc.never': '从未',
|
||||
'misc.justNow': '刚刚',
|
||||
'misc.ago': '前',
|
||||
};
|
||||
157
ui/src/i18n/types.ts
Normal file
157
ui/src/i18n/types.ts
Normal file
@ -0,0 +1,157 @@
|
||||
/**
|
||||
* i18n type definitions for Control UI
|
||||
*/
|
||||
|
||||
export type Locale = 'en' | 'zh-CN';
|
||||
|
||||
export interface TranslationKeys {
|
||||
// Navigation groups
|
||||
'nav.chat': string;
|
||||
'nav.control': string;
|
||||
'nav.agent': string;
|
||||
'nav.settings': string;
|
||||
|
||||
// Tab titles
|
||||
'tab.overview': string;
|
||||
'tab.channels': string;
|
||||
'tab.instances': string;
|
||||
'tab.sessions': string;
|
||||
'tab.cron': string;
|
||||
'tab.skills': string;
|
||||
'tab.nodes': string;
|
||||
'tab.chat': string;
|
||||
'tab.config': string;
|
||||
'tab.debug': string;
|
||||
'tab.logs': string;
|
||||
|
||||
// Tab subtitles/descriptions
|
||||
'tab.overview.desc': string;
|
||||
'tab.channels.desc': string;
|
||||
'tab.instances.desc': string;
|
||||
'tab.sessions.desc': string;
|
||||
'tab.cron.desc': string;
|
||||
'tab.skills.desc': string;
|
||||
'tab.nodes.desc': string;
|
||||
'tab.chat.desc': string;
|
||||
'tab.config.desc': string;
|
||||
'tab.debug.desc': string;
|
||||
'tab.logs.desc': string;
|
||||
|
||||
// Common actions
|
||||
'action.save': string;
|
||||
'action.cancel': string;
|
||||
'action.apply': string;
|
||||
'action.reset': string;
|
||||
'action.delete': string;
|
||||
'action.edit': string;
|
||||
'action.add': string;
|
||||
'action.remove': string;
|
||||
'action.refresh': string;
|
||||
'action.copy': string;
|
||||
'action.close': string;
|
||||
'action.confirm': string;
|
||||
'action.send': string;
|
||||
'action.stop': string;
|
||||
'action.retry': string;
|
||||
|
||||
// Status
|
||||
'status.online': string;
|
||||
'status.offline': string;
|
||||
'status.connected': string;
|
||||
'status.disconnected': string;
|
||||
'status.loading': string;
|
||||
'status.error': string;
|
||||
'status.success': string;
|
||||
'status.pending': string;
|
||||
'status.idle': string;
|
||||
'status.running': string;
|
||||
'status.ok': string;
|
||||
|
||||
// Header
|
||||
'header.health': string;
|
||||
'header.brand.title': string;
|
||||
'header.brand.sub': string;
|
||||
'header.expandSidebar': string;
|
||||
'header.collapseSidebar': string;
|
||||
|
||||
// Theme
|
||||
'theme.light': string;
|
||||
'theme.dark': string;
|
||||
'theme.system': string;
|
||||
|
||||
// Chat
|
||||
'chat.placeholder': string;
|
||||
'chat.send': string;
|
||||
'chat.thinking': string;
|
||||
'chat.attachFile': string;
|
||||
'chat.clearHistory': string;
|
||||
|
||||
// Channels
|
||||
'channels.whatsapp': string;
|
||||
'channels.telegram': string;
|
||||
'channels.discord': string;
|
||||
'channels.slack': string;
|
||||
'channels.signal': string;
|
||||
'channels.imessage': string;
|
||||
'channels.nostr': string;
|
||||
'channels.googlechat': string;
|
||||
|
||||
// Sessions
|
||||
'sessions.title': string;
|
||||
'sessions.active': string;
|
||||
'sessions.tokens': string;
|
||||
'sessions.model': string;
|
||||
'sessions.lastActivity': string;
|
||||
|
||||
// Cron
|
||||
'cron.title': string;
|
||||
'cron.schedule': string;
|
||||
'cron.nextRun': string;
|
||||
'cron.lastRun': string;
|
||||
'cron.enabled': string;
|
||||
'cron.disabled': string;
|
||||
'cron.addJob': string;
|
||||
|
||||
// Config
|
||||
'config.title': string;
|
||||
'config.saved': string;
|
||||
'config.unsaved': string;
|
||||
'config.saveChanges': string;
|
||||
'config.discardChanges': string;
|
||||
|
||||
// Logs
|
||||
'logs.title': string;
|
||||
'logs.level': string;
|
||||
'logs.filter': string;
|
||||
'logs.export': string;
|
||||
'logs.clear': string;
|
||||
|
||||
// Skills
|
||||
'skills.title': string;
|
||||
'skills.installed': string;
|
||||
'skills.available': string;
|
||||
'skills.install': string;
|
||||
'skills.uninstall': string;
|
||||
|
||||
// Nodes
|
||||
'nodes.title': string;
|
||||
'nodes.paired': string;
|
||||
'nodes.pending': string;
|
||||
'nodes.approve': string;
|
||||
'nodes.reject': string;
|
||||
|
||||
// Errors
|
||||
'error.connection': string;
|
||||
'error.timeout': string;
|
||||
'error.unknown': string;
|
||||
'error.invalidInput': string;
|
||||
|
||||
// Misc
|
||||
'misc.noData': string;
|
||||
'misc.loading': string;
|
||||
'misc.never': string;
|
||||
'misc.justNow': string;
|
||||
'misc.ago': string;
|
||||
}
|
||||
|
||||
export type TranslationKey = keyof TranslationKeys;
|
||||
@ -1,15 +1,20 @@
|
||||
import type { IconName } from "./icons.js";
|
||||
import { t } from "../i18n";
|
||||
|
||||
export const TAB_GROUPS = [
|
||||
{ label: "Chat", tabs: ["chat"] },
|
||||
{ labelKey: "nav.chat" as const, tabs: ["chat"] },
|
||||
{
|
||||
label: "Control",
|
||||
labelKey: "nav.control" as const,
|
||||
tabs: ["overview", "channels", "instances", "sessions", "cron"],
|
||||
},
|
||||
{ label: "Agent", tabs: ["skills", "nodes"] },
|
||||
{ label: "Settings", tabs: ["config", "debug", "logs"] },
|
||||
{ labelKey: "nav.agent" as const, tabs: ["skills", "nodes"] },
|
||||
{ labelKey: "nav.settings" as const, tabs: ["config", "debug", "logs"] },
|
||||
] as const;
|
||||
|
||||
export function getTabGroupLabel(labelKey: string): string {
|
||||
return t(labelKey as any);
|
||||
}
|
||||
|
||||
export type Tab =
|
||||
| "overview"
|
||||
| "channels"
|
||||
@ -129,60 +134,10 @@ export function iconForTab(tab: Tab): IconName {
|
||||
}
|
||||
}
|
||||
|
||||
export function titleForTab(tab: Tab) {
|
||||
switch (tab) {
|
||||
case "overview":
|
||||
return "Overview";
|
||||
case "channels":
|
||||
return "Channels";
|
||||
case "instances":
|
||||
return "Instances";
|
||||
case "sessions":
|
||||
return "Sessions";
|
||||
case "cron":
|
||||
return "Cron Jobs";
|
||||
case "skills":
|
||||
return "Skills";
|
||||
case "nodes":
|
||||
return "Nodes";
|
||||
case "chat":
|
||||
return "Chat";
|
||||
case "config":
|
||||
return "Config";
|
||||
case "debug":
|
||||
return "Debug";
|
||||
case "logs":
|
||||
return "Logs";
|
||||
default:
|
||||
return "Control";
|
||||
}
|
||||
export function titleForTab(tab: Tab): string {
|
||||
return t(`tab.${tab}` as any);
|
||||
}
|
||||
|
||||
export function subtitleForTab(tab: Tab) {
|
||||
switch (tab) {
|
||||
case "overview":
|
||||
return "Gateway status, entry points, and a fast health read.";
|
||||
case "channels":
|
||||
return "Manage channels and settings.";
|
||||
case "instances":
|
||||
return "Presence beacons from connected clients and nodes.";
|
||||
case "sessions":
|
||||
return "Inspect active sessions and adjust per-session defaults.";
|
||||
case "cron":
|
||||
return "Schedule wakeups and recurring agent runs.";
|
||||
case "skills":
|
||||
return "Manage skill availability and API key injection.";
|
||||
case "nodes":
|
||||
return "Paired devices, capabilities, and command exposure.";
|
||||
case "chat":
|
||||
return "Direct gateway chat session for quick interventions.";
|
||||
case "config":
|
||||
return "Edit ~/.clawdbot/moltbot.json safely.";
|
||||
case "debug":
|
||||
return "Gateway snapshots, events, and manual RPC calls.";
|
||||
case "logs":
|
||||
return "Live tail of the gateway file logs.";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
export function subtitleForTab(tab: Tab): string {
|
||||
return t(`tab.${tab}.desc` as any);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user