From 355249a2a019692b8b1db0c1f34146699c20c868 Mon Sep 17 00:00:00 2001 From: qa1-abilities Date: Fri, 8 May 2026 15:43:37 +0800 Subject: [PATCH 1/3] feat(web): add Russian and Spanish translations --- web/src/i18n/es.ts | 429 +++++++++++++++++++++++++++++++++++++++++++++ web/src/i18n/ru.ts | 429 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 858 insertions(+) create mode 100644 web/src/i18n/es.ts create mode 100644 web/src/i18n/ru.ts diff --git a/web/src/i18n/es.ts b/web/src/i18n/es.ts new file mode 100644 index 000000000000..360a8e5c8c85 --- /dev/null +++ b/web/src/i18n/es.ts @@ -0,0 +1,429 @@ +import type { Translations } from "./types"; + +export const es: Translations = { + common: { + save: "Guardar", + saving: "Guardando...", + cancel: "Cancelar", + close: "Cerrar", + confirm: "Confirmar", + delete: "Eliminar", + refresh: "Actualizar", + retry: "Reintentar", + search: "Buscar...", + loading: "Cargando...", + create: "Crear", + creating: "Creando...", + set: "Establecer", + replace: "Reemplazar", + clear: "Limpiar", + live: "En vivo", + off: "Apagado", + enabled: "habilitado", + disabled: "deshabilitado", + active: "activo", + inactive: "inactivo", + unknown: "desconocido", + untitled: "Sin título", + none: "Ninguno", + form: "Formulario", + noResults: "Sin resultados", + of: "de", + page: "Página", + msgs: "mensajes", + tools: "herramientas", + match: "coincidencia", + other: "Otro", + configured: "configurado", + removed: "eliminado", + failedToToggle: "Error al alternar", + failedToRemove: "Error al eliminar", + failedToReveal: "Error al mostrar", + collapse: "Contraer", + expand: "Expandir", + general: "General", + messaging: "Mensajería", + pluginLoadFailed: + "No se pudo cargar el script del plugin. Comprueba la pestaña Network (dashboard-plugins/…) y la ruta del plugin en el servidor.", + pluginNotRegistered: + "El script del plugin no llamó a register(), o hubo un error. Abre la consola del navegador para más detalles.", + }, + + app: { + brand: "Hermes Agent", + brandShort: "HA", + closeNavigation: "Cerrar navegación", + closeModelTools: "Cerrar modelo y herramientas", + footer: { + org: "Nous Research", + }, + activeSessionsLabel: "Sesiones activas:", + gatewayStatusLabel: "Estado del gateway:", + gatewayStrip: { + failed: "Fallo al iniciar", + off: "Apagado", + running: "En ejecución", + starting: "Iniciando", + stopped: "Detenido", + }, + nav: { + analytics: "Analítica", + chat: "Chat", + config: "Configuración", + cron: "Cron", + documentation: "Documentación", + keys: "Claves", + logs: "Registros", + models: "Modelos", + profiles: "Perfiles", + plugins: "Plugins", + sessions: "Sesiones", + skills: "Habilidades", + }, + modelToolsSheetSubtitle: "y herramientas", + modelToolsSheetTitle: "Modelo", + navigation: "Navegación", + openDocumentation: "Abrir documentación en una pestaña nueva", + openNavigation: "Abrir navegación", + pluginNavSection: "Plugins", + sessionsActiveCount: "{count} activas", + statusOverview: "Resumen de estado", + system: "Sistema", + webUi: "Interfaz web", + }, + + status: { + actionFailed: "Acción fallida", + actionFinished: "Finalizado", + actions: "Acciones", + agent: "Agente", + activeSessions: "Sesiones activas", + connected: "Conectado", + connectedPlatforms: "Plataformas conectadas", + disconnected: "Desconectado", + error: "Error", + failed: "Fallo", + gateway: "Gateway", + gatewayFailedToStart: "El gateway no pudo iniciarse", + lastUpdate: "Última actualización", + noneRunning: "Ninguno", + notRunning: "No en ejecución", + pid: "PID", + platformDisconnected: "desconectado", + platformError: "error", + recentSessions: "Sesiones recientes", + restartGateway: "Reiniciar gateway", + restartingGateway: "Reiniciando gateway…", + running: "En ejecución", + runningRemote: "En ejecución (remoto)", + startFailed: "Fallo al iniciar", + starting: "Iniciando", + startedInBackground: "Iniciado en segundo plano — consulta los registros", + stopped: "Detenido", + updateHermes: "Actualizar Hermes", + updatingHermes: "Actualizando Hermes…", + waitingForOutput: "Esperando salida…", + }, + + sessions: { + title: "Sesiones", + searchPlaceholder: "Buscar contenido de mensajes...", + noSessions: "Aún no hay sesiones", + noMatch: "No hay sesiones que coincidan", + startConversation: "Inicia una conversación para verla aquí", + noMessages: "Sin mensajes", + untitledSession: "Sesión sin título", + deleteSession: "Eliminar sesión", + confirmDeleteTitle: "¿Eliminar sesión?", + confirmDeleteMessage: + "Esto eliminará permanentemente la conversación y todos sus mensajes. No se puede deshacer.", + sessionDeleted: "Sesión eliminada", + failedToDelete: "Error al eliminar la sesión", + resumeInChat: "Continuar en chat", + previousPage: "Página anterior", + nextPage: "Página siguiente", + roles: { + user: "Usuario", + assistant: "Asistente", + system: "Sistema", + tool: "Herramienta", + }, + }, + + analytics: { + period: "Período:", + totalTokens: "Tokens totales", + totalSessions: "Sesiones totales", + apiCalls: "Llamadas API", + dailyTokenUsage: "Uso diario de tokens", + dailyBreakdown: "Desglose diario", + perModelBreakdown: "Desglose por modelo", + topSkills: "Habilidades principales", + skill: "Habilidad", + loads: "Cargas", + edits: "Ediciones", + lastUsed: "Último uso", + input: "Entrada", + output: "Salida", + total: "Total", + noUsageData: "No hay datos de uso para este período", + startSession: "Inicia una sesión para ver analíticas aquí", + date: "Fecha", + model: "Modelo", + tokens: "Tokens", + perDayAvg: "media/día", + acrossModels: "en {count} modelos", + inOut: "{input} ent / {output} sal", + }, + + models: { + modelsUsed: "Modelos usados", + estimatedCost: "Coste estimado", + tokens: "tokens", + sessions: "sesiones", + avgPerSession: "media/sesión", + apiCalls: "llamadas API", + toolCalls: "llamadas a herramientas", + noModelsData: "No hay datos de uso de modelos", + startSession: "Inicia una sesión para ver datos de modelos aquí", + }, + + logs: { + title: "Registros", + autoRefresh: "Actualización automática", + file: "Archivo", + level: "Nivel", + component: "Componente", + lines: "Líneas", + noLogLines: "No se encontraron registros", + }, + + cron: { + confirmDeleteMessage: + "Se eliminará la tarea de la programación. No se puede deshacer.", + confirmDeleteTitle: "¿Eliminar tarea programada?", + newJob: "Nueva tarea", + nameOptional: "Nombre (opcional)", + namePlaceholder: "p. ej. Resumen diario", + prompt: "Indicación", + promptPlaceholder: "¿Qué debe hacer el agente en cada ejecución?", + schedule: "Programación (expresión cron)", + schedulePlaceholder: "0 9 * * *", + deliverTo: "Entregar a", + scheduledJobs: "Tareas programadas", + noJobs: "No hay tareas programadas. Crea una arriba.", + last: "Última", + next: "Siguiente", + pause: "Pausar", + resume: "Reanudar", + triggerNow: "Ejecutar ahora", + delivery: { + local: "Local", + telegram: "Telegram", + discord: "Discord", + slack: "Slack", + email: "Email", + }, + }, + + profiles: { + newProfile: "Nuevo perfil", + name: "Nombre", + namePlaceholder: "p. ej. coder, writer", + nameRequired: "El nombre es obligatorio", + nameRule: + "Solo letras minúsculas, dígitos, _ y -; debe empezar con letra o dígito; máx. 64 caracteres.", + invalidName: "Nombre de perfil no válido", + cloneFromDefault: "Clonar configuración del perfil por defecto", + allProfiles: "Perfiles", + noProfiles: "No se encontraron perfiles.", + defaultBadge: "predeterminado", + hasEnv: "env", + model: "Modelo", + skills: "Habilidades", + rename: "Renombrar", + editSoul: "Editar SOUL.md", + soulSection: "SOUL.md (personalidad / indicación del sistema)", + soulPlaceholder: "# Cómo debería comportarse este agente…", + saveSoul: "Guardar SOUL", + soulSaved: "SOUL.md guardado", + openInTerminal: "Copiar comando CLI", + commandCopied: "Copiado al portapapeles", + copyFailed: "No se pudo copiar", + confirmDeleteTitle: "¿Eliminar perfil?", + confirmDeleteMessage: + "Esto eliminará permanentemente el perfil '{name}' — configuración, claves, memoria, sesiones, habilidades, tareas cron. No se puede deshacer.", + created: "Creado", + deleted: "Eliminado", + renamed: "Renombrado", + }, + + pluginsPage: { + contextEngineLabel: "Motor de contexto", + dashboardSlots: "Espacios del panel", + disableRuntime: "Deshabilitar", + enableAfterInstall: "Habilitar tras instalar", + enableRuntime: "Habilitar", + forceReinstall: "Forzar reinstalación (eliminar carpeta existente primero)", + headline: + "Descubre, instala, habilita y actualiza plugins de Hermes (paridad con `hermes plugins`).", + identifierLabel: "URL de Git o owner/repo", + inactive: "inactivo", + installBtn: "Instalar desde Git", + installHeading: "Instalar desde GitHub / URL de Git", + installHint: "Usa la notación corta owner/repo o una URL https:// / git@ completa.", + memoryProviderLabel: "Proveedor de memoria", + missingEnvWarn: "Configura estas variables en «Claves» antes de ejecutar el plugin:", + noDashboardTab: "Sin pestaña de panel", + openTab: "Abrir", + orphanHeading: "Extensiones solo de panel (sin coincidencia de plugin.yaml del agente)", + pluginListHeading: "Plugins instalados", + providerDefaults: "incorporado / por defecto", + providersHeading: "Plugins de proveedores en ejecución", + providersHint: + "Escribe memory.provider (vacío = incorporado) y context.engine en config.yaml. Surte efecto en la siguiente sesión.", + refreshDashboard: "Reescanear extensiones del panel", + removeConfirm: "¿Eliminar este plugin de ~/.hermes/plugins/?", + removeHint: "Solo se pueden eliminar plugins instalados por el usuario en ~/.hermes/plugins.", + rescanHeading: "Registro de plugins SPA", + rescanHint: "Reescanea tras añadir archivos en disco para que la barra lateral cargue nuevos manifiestos.", + runtimeHeading: "Tiempo de ejecución del gateway (plugins YAML)", + saveProviders: "Guardar configuración de proveedores", + savedProviders: "Configuración de proveedores guardada.", + sourceBadge: "Fuente", + authRequired: "Autenticación requerida", + authRequiredHint: "Ejecuta este comando para autenticarte:", + updateGit: "git pull", + versionBadge: "Versión", + showInSidebar: "Mostrar en la barra lateral", + hideFromSidebar: "Ocultar de la barra lateral", + }, + + skills: { + title: "Habilidades", + searchPlaceholder: "Buscar habilidades y conjuntos de herramientas...", + enabledOf: "{enabled}/{total} habilitadas", + all: "Todas", + categories: "Categorías", + filters: "Filtros", + noSkills: "No se encontraron habilidades. Se cargan desde ~/.hermes/skills/", + noSkillsMatch: "Ninguna habilidad coincide con tu búsqueda.", + skillCount: "{count} habilidad{so}", + resultCount: "{count} resultado{so}", + noDescription: "Sin descripción disponible.", + toolsets: "Conjuntos de herramientas", + toolsetLabel: "Conjunto de herramientas {name}", + noToolsetsMatch: "Ningún conjunto coincide con la búsqueda.", + setupNeeded: "Configuración necesaria", + disabledForCli: "Deshabilitado para CLI", + more: "+{count} más", + }, + + config: { + configPath: "~/.hermes/config.yaml", + filters: "Filtros", + sections: "Secciones", + exportConfig: "Exportar configuración como JSON", + importConfig: "Importar configuración desde JSON", + resetDefaults: "Restablecer valores por defecto", + resetScopeTooltip: "Restablecer {scope} a valores por defecto", + confirmResetScope: "¿Restablecer todos los ajustes de {scope} a sus valores por defecto? Esto solo actualiza el formulario; los cambios no se escriben en config.yaml hasta que presiones Guardar.", + resetScopeToast: "{scope} restablecido a valores por defecto — revísalo y guarda", + rawYaml: "Configuración YAML en bruto", + searchResults: "Resultados de búsqueda", + fields: "campo{so}", + noFieldsMatch: 'Ningún campo coincide con "{query}"', + configSaved: "Configuración guardada", + yamlConfigSaved: "Configuración YAML guardada", + failedToSave: "Error al guardar", + failedToSaveYaml: "Error al guardar YAML", + failedToLoadRaw: "Error al cargar configuración en bruto", + configImported: "Configuración importada — revísala y guárdala", + invalidJson: "Archivo JSON no válido", + categories: { + general: "General", + agent: "Agente", + terminal: "Terminal", + display: "Pantalla", + delegation: "Delegación", + memory: "Memoria", + compression: "Compresión", + security: "Seguridad", + browser: "Navegador", + voice: "Voz", + tts: "Texto a voz", + stt: "Voz a texto", + logging: "Registro", + discord: "Discord", + auxiliary: "Auxiliar", + }, + }, + + env: { + changesNote: "Los cambios se guardan en disco inmediatamente. Las sesiones activas recogen las nuevas claves automáticamente.", + confirmClearMessage: + "El valor almacenado de esta variable se eliminará del archivo .env. No se puede deshacer desde la interfaz.", + confirmClearTitle: "¿Borrar esta clave?", + description: "Gestiona claves API y secretos almacenados en", + hideAdvanced: "Ocultar avanzado", + showAdvanced: "Mostrar avanzado", + llmProviders: "Proveedores LLM", + providersConfigured: "{configured} de {total} proveedores configurados", + getKey: "Obtener clave", + notConfigured: "{count} no configurados", + notSet: "No establecido", + keysCount: "{count} clave{so}", + enterValue: "Introduce valor...", + replaceCurrentValue: "Reemplazar valor actual ({preview})", + showValue: "Mostrar valor real", + hideValue: "Ocultar valor", + }, + + oauth: { + title: "Inicios de sesión de proveedores (OAuth)", + providerLogins: "Inicios de sesión de proveedores (OAuth)", + description: "{connected} de {total} proveedores OAuth conectados. Los flujos de inicio de sesión se ejecutan actualmente a través de CLI; haz clic en «Copiar comando» y pégalo en un terminal para configurarlos.", + connected: "Conectado", + expired: "Expirado", + notConnected: "No conectado. Ejecuta {command} en un terminal.", + runInTerminal: "en un terminal.", + noProviders: "No se detectaron proveedores con capacidad OAuth.", + login: "Iniciar sesión", + disconnect: "Desconectar", + managedExternally: "Gestionado externamente", + copied: "Copiado ✓", + cli: "CLI", + copyCliCommand: "Copiar comando CLI (para uso externo / alternativo)", + connect: "Conectar", + sessionExpires: "La sesión expira en {time}", + initiatingLogin: "Iniciando flujo de inicio de sesión…", + exchangingCode: "Intercambiando código por tokens…", + connectedClosing: "¡Conectado! Cerrando…", + loginFailed: "Error de inicio de sesión.", + sessionExpired: "La sesión ha expirado. Haz clic en Reintentar para iniciar un nuevo inicio de sesión.", + reOpenAuth: "Reabrir página de autorización", + reOpenVerification: "Reabrir página de verificación", + submitCode: "Enviar código", + pasteCode: "Pega el código de autorización (el sufijo #state es válido)", + waitingAuth: "Esperando a que autorices en el navegador…", + enterCodePrompt: "Se ha abierto una nueva pestaña. Introduce este código si se te solicita:", + pkceStep1: "Se abrió una nueva pestaña en claude.ai. Inicia sesión y haz clic en Autorizar.", + pkceStep2: "Copia el código de autorización que aparece tras autorizar.", + pkceStep3: "Pégalo abajo y envíalo.", + flowLabels: { + pkce: "Inicio de sesión en navegador (PKCE)", + device_code: "Código de dispositivo", + external: "CLI externa", + }, + expiresIn: "expira en {time}", + }, + + language: { + switchTo: "Cambiar idioma", + }, + + theme: { + title: "Tema", + switchTheme: "Cambiar tema", + }, +}; diff --git a/web/src/i18n/ru.ts b/web/src/i18n/ru.ts new file mode 100644 index 000000000000..ff74ac43446d --- /dev/null +++ b/web/src/i18n/ru.ts @@ -0,0 +1,429 @@ +import type { Translations } from "./types"; + +export const ru: Translations = { + common: { + save: "Сохранить", + saving: "Сохранение...", + cancel: "Отмена", + close: "Закрыть", + confirm: "Подтвердить", + delete: "Удалить", + refresh: "Обновить", + retry: "Повторить", + search: "Поиск...", + loading: "Загрузка...", + create: "Создать", + creating: "Создание...", + set: "Задать", + replace: "Заменить", + clear: "Очистить", + live: "В сети", + off: "Выкл", + enabled: "включено", + disabled: "отключено", + active: "активно", + inactive: "неактивно", + unknown: "неизвестно", + untitled: "Без названия", + none: "Нет", + form: "Форма", + noResults: "Нет результатов", + of: "из", + page: "Страница", + msgs: "сообщ.", + tools: "инстр.", + match: "совпадение", + other: "Другое", + configured: "настроено", + removed: "удалено", + failedToToggle: "Не удалось переключить", + failedToRemove: "Не удалось удалить", + failedToReveal: "Не удалось отобразить", + collapse: "Свернуть", + expand: "Развернуть", + general: "Общие", + messaging: "Мессенджеры", + pluginLoadFailed: + "Не удалось загрузить скрипт плагина. Проверьте вкладку Network (dashboard-plugins/…) и путь к плагину на сервере.", + pluginNotRegistered: + "Скрипт плагина не вызвал register(), или произошла ошибка. Откройте консоль браузера для подробностей.", + }, + + app: { + brand: "Hermes Agent", + brandShort: "HA", + closeNavigation: "Закрыть навигацию", + closeModelTools: "Закрыть модель и инструменты", + footer: { + org: "Nous Research", + }, + activeSessionsLabel: "Активные сессии:", + gatewayStatusLabel: "Статус шлюза:", + gatewayStrip: { + failed: "Ошибка запуска", + off: "Выкл", + running: "Работает", + starting: "Запускается", + stopped: "Остановлен", + }, + nav: { + analytics: "Аналитика", + chat: "Чат", + config: "Настройки", + cron: "Cron", + documentation: "Документация", + keys: "Ключи", + logs: "Логи", + models: "Модели", + profiles: "Профили", + plugins: "Плагины", + sessions: "Сессии", + skills: "Навыки", + }, + modelToolsSheetSubtitle: "и инструменты", + modelToolsSheetTitle: "Модель", + navigation: "Навигация", + openDocumentation: "Открыть документацию в новой вкладке", + openNavigation: "Открыть навигацию", + pluginNavSection: "Плагины", + sessionsActiveCount: "{count} активно", + statusOverview: "Обзор статуса", + system: "Система", + webUi: "Веб-интерфейс", + }, + + status: { + actionFailed: "Действие не выполнено", + actionFinished: "Готово", + actions: "Действия", + agent: "Агент", + activeSessions: "Активные сессии", + connected: "Подключено", + connectedPlatforms: "Подключённые платформы", + disconnected: "Отключено", + error: "Ошибка", + failed: "Ошибка", + gateway: "Шлюз", + gatewayFailedToStart: "Не удалось запустить шлюз", + lastUpdate: "Последнее обновление", + noneRunning: "Нет", + notRunning: "Не запущено", + pid: "PID", + platformDisconnected: "отключено", + platformError: "ошибка", + recentSessions: "Недавние сессии", + restartGateway: "Перезапустить шлюз", + restartingGateway: "Перезапуск шлюза…", + running: "Работает", + runningRemote: "Работает (удалённо)", + startFailed: "Ошибка запуска", + starting: "Запускается", + startedInBackground: "Запущено в фоне — проверьте логи", + stopped: "Остановлено", + updateHermes: "Обновить Hermes", + updatingHermes: "Обновление Hermes…", + waitingForOutput: "Ожидание вывода…", + }, + + sessions: { + title: "Сессии", + searchPlaceholder: "Поиск по содержимому сообщений...", + noSessions: "Пока нет сессий", + noMatch: "Нет сессий по вашему запросу", + startConversation: "Начните разговор, и он появится здесь", + noMessages: "Нет сообщений", + untitledSession: "Сессия без названия", + deleteSession: "Удалить сессию", + confirmDeleteTitle: "Удалить сессию?", + confirmDeleteMessage: + "Это навсегда удалит разговор и все его сообщения. Действие необратимо.", + sessionDeleted: "Сессия удалена", + failedToDelete: "Не удалось удалить сессию", + resumeInChat: "Продолжить в чате", + previousPage: "Предыдущая страница", + nextPage: "Следующая страница", + roles: { + user: "Пользователь", + assistant: "Ассистент", + system: "Система", + tool: "Инструмент", + }, + }, + + analytics: { + period: "Период:", + totalTokens: "Всего токенов", + totalSessions: "Всего сессий", + apiCalls: "Вызовы API", + dailyTokenUsage: "Токены в день", + dailyBreakdown: "По дням", + perModelBreakdown: "По моделям", + topSkills: "Топ навыков", + skill: "Навык", + loads: "Загрузок", + edits: "Изменений", + lastUsed: "Последнее использование", + input: "Вход", + output: "Выход", + total: "Всего", + noUsageData: "Нет данных за этот период", + startSession: "Начните сессию, чтобы увидеть аналитику", + date: "Дата", + model: "Модель", + tokens: "Токены", + perDayAvg: "ср./день", + acrossModels: "всего {count} моделей", + inOut: "{input} вх / {output} вых", + }, + + models: { + modelsUsed: "Использовано моделей", + estimatedCost: "Примерная стоимость", + tokens: "токенов", + sessions: "сессий", + avgPerSession: "ср./сессию", + apiCalls: "вызовов API", + toolCalls: "вызовов инструментов", + noModelsData: "Нет данных об использовании моделей", + startSession: "Начните сессию, чтобы увидеть данные по моделям", + }, + + logs: { + title: "Логи", + autoRefresh: "Автообновление", + file: "Файл", + level: "Уровень", + component: "Компонент", + lines: "Строки", + noLogLines: "Логи не найдены", + }, + + cron: { + confirmDeleteMessage: + "Задача будет удалена из расписания. Действие необратимо.", + confirmDeleteTitle: "Удалить задачу?", + newJob: "Новая задача", + nameOptional: "Название (необязательно)", + namePlaceholder: "Например: ежедневный отчёт", + prompt: "Промпт", + promptPlaceholder: "Что агент должен делать при каждом запуске?", + schedule: "Расписание (cron)", + schedulePlaceholder: "0 9 * * *", + deliverTo: "Куда доставлять", + scheduledJobs: "Запланированные задачи", + noJobs: "Нет задач. Создайте одну выше.", + last: "Последний запуск", + next: "Следующий запуск", + pause: "Пауза", + resume: "Продолжить", + triggerNow: "Запустить сейчас", + delivery: { + local: "Локально", + telegram: "Telegram", + discord: "Discord", + slack: "Slack", + email: "Email", + }, + }, + + profiles: { + newProfile: "Новый профиль", + name: "Название", + namePlaceholder: "Например: coder, writer", + nameRequired: "Название обязательно", + nameRule: + "Только строчные буквы, цифры, _ и -; начинаться с буквы или цифры; до 64 символов.", + invalidName: "Некорректное название профиля", + cloneFromDefault: "Клонировать из профиля по умолчанию", + allProfiles: "Профили", + noProfiles: "Профили не найдены.", + defaultBadge: "по умолчанию", + hasEnv: "env", + model: "Модель", + skills: "Навыки", + rename: "Переименовать", + editSoul: "Редактировать SOUL.md", + soulSection: "SOUL.md (личность / системный промпт)", + soulPlaceholder: "# Как должен вести себя этот агент…", + saveSoul: "Сохранить SOUL", + soulSaved: "SOUL.md сохранён", + openInTerminal: "Копировать CLI-команду", + commandCopied: "Скопировано в буфер обмена", + copyFailed: "Не удалось скопировать", + confirmDeleteTitle: "Удалить профиль?", + confirmDeleteMessage: + "Это навсегда удалит профиль '{name}' — конфиг, ключи, память, сессии, навыки, задачи cron. Действие необратимо.", + created: "Создан", + deleted: "Удалён", + renamed: "Переименован", + }, + + pluginsPage: { + contextEngineLabel: "Контекстный движок", + dashboardSlots: "Слоты панели", + disableRuntime: "Отключить", + enableAfterInstall: "Включить после установки", + enableRuntime: "Включить", + forceReinstall: "Переустановить принудительно (сначала удалить папку)", + headline: + "Находите, устанавливайте, включайте и обновляйте плагины Hermes (соответствует `hermes plugins`).", + identifierLabel: "Git URL или owner/repo", + inactive: "неактивно", + installBtn: "Установить из Git", + installHeading: "Установить из GitHub / Git URL", + installHint: "Используйте краткую запись owner/repo или полный https:// / git@ URL.", + memoryProviderLabel: "Провайдер памяти", + missingEnvWarn: "Задайте эти переменные в разделе «Ключи» перед запуском плагина:", + noDashboardTab: "Нет вкладки панели", + openTab: "Открыть", + orphanHeading: "Только расширения панели (нет соответствия plugin.yaml агента)", + pluginListHeading: "Установленные плагины", + providerDefaults: "встроенный / по умолчанию", + providersHeading: "Плагины провайдеров среды выполнения", + providersHint: + "Записывает memory.provider (пусто = встроенный) и context.engine в config.yaml. Вступит в силу при следующей сессии.", + refreshDashboard: "Пересканировать расширения панели", + removeConfirm: "Удалить этот плагин из ~/.hermes/plugins/?", + removeHint: "Удалять можно только плагины, установленные в ~/.hermes/plugins.", + rescanHeading: "Реестр SPA-плагинов", + rescanHint: "Пересканируйте после добавления файлов на диск, чтобы сайдбар подхватил новые манифесты.", + runtimeHeading: "Среда выполнения шлюза (YAML-плагины)", + saveProviders: "Сохранить настройки провайдеров", + savedProviders: "Настройки провайдеров сохранены.", + sourceBadge: "Источник", + authRequired: "Требуется авторизация", + authRequiredHint: "Выполните эту команду для авторизации:", + updateGit: "git pull", + versionBadge: "Версия", + showInSidebar: "Показать в сайдбаре", + hideFromSidebar: "Скрыть из сайдбара", + }, + + skills: { + title: "Навыки", + searchPlaceholder: "Поиск навыков и наборов инструментов...", + enabledOf: "{enabled}/{total} включено", + all: "Все", + categories: "Категории", + filters: "Фильтры", + noSkills: "Навыки не найдены. Навыки загружаются из ~/.hermes/skills/", + noSkillsMatch: "Нет навыков по вашему запросу.", + skillCount: "{count} навык{so}", + resultCount: "{count} результат{so}", + noDescription: "Описание отсутствует.", + toolsets: "Наборы инструментов", + toolsetLabel: "Набор инструментов {name}", + noToolsetsMatch: "Нет подходящих наборов инструментов.", + setupNeeded: "Требуется настройка", + disabledForCli: "Отключено для CLI", + more: "+{count} ещё", + }, + + config: { + configPath: "~/.hermes/config.yaml", + filters: "Фильтры", + sections: "Разделы", + exportConfig: "Экспортировать конфиг в JSON", + importConfig: "Импортировать конфиг из JSON", + resetDefaults: "Сбросить до умолчаний", + resetScopeTooltip: "Сбросить {scope} до умолчаний", + confirmResetScope: "Сбросить все настройки {scope} до значений по умолчанию? Это обновит только форму — изменения не запишутся в config.yaml, пока вы не нажмёте Сохранить.", + resetScopeToast: "{scope} сброшен до умолчаний — проверьте и сохраните", + rawYaml: "Конфигурация YAML", + searchResults: "Результаты поиска", + fields: "поле{so}", + noFieldsMatch: 'Нет полей, соответствующих "{query}"', + configSaved: "Конфигурация сохранена", + yamlConfigSaved: "YAML конфигурация сохранена", + failedToSave: "Не удалось сохранить", + failedToSaveYaml: "Не удалось сохранить YAML", + failedToLoadRaw: "Не удалось загрузить сырой конфиг", + configImported: "Конфиг импортирован — проверьте и сохраните", + invalidJson: "Некорректный JSON-файл", + categories: { + general: "Общие", + agent: "Агент", + terminal: "Терминал", + display: "Отображение", + delegation: "Делегирование", + memory: "Память", + compression: "Сжатие", + security: "Безопасность", + browser: "Браузер", + voice: "Голос", + tts: "Синтез речи", + stt: "Распознавание речи", + logging: "Логирование", + discord: "Discord", + auxiliary: "Вспомогательные", + }, + }, + + env: { + changesNote: "Изменения сохраняются на диск сразу. Активные сессии подхватят новые ключи автоматически.", + confirmClearMessage: + "Сохранённое значение этой переменной будет удалено из .env файла. Невозможно отменить через интерфейс.", + confirmClearTitle: "Очистить этот ключ?", + description: "Управление API-ключами и секретами, хранящимися в", + hideAdvanced: "Скрыть расширенные", + showAdvanced: "Показать расширенные", + llmProviders: "LLM-провайдеры", + providersConfigured: "Настроено {configured} из {total} провайдеров", + getKey: "Получить ключ", + notConfigured: "{count} не настроено", + notSet: "Не задано", + keysCount: "{count} ключ{so}", + enterValue: "Введите значение...", + replaceCurrentValue: "Заменить текущее значение ({preview})", + showValue: "Показать значение", + hideValue: "Скрыть значение", + }, + + oauth: { + title: "Авторизация OAuth", + providerLogins: "Авторизация OAuth", + description: "Подключено {connected} из {total} OAuth-провайдеров. Потоки авторизации сейчас запускаются через CLI; нажмите «Копировать команду» и вставьте в терминал.", + connected: "Подключено", + expired: "Истекло", + notConnected: "Не подключено. Выполните {command} в терминале.", + runInTerminal: "в терминале.", + noProviders: "Не обнаружено провайдеров с поддержкой OAuth.", + login: "Войти", + disconnect: "Отключить", + managedExternally: "Управляется внешне", + copied: "Скопировано ✓", + cli: "CLI", + copyCliCommand: "Копировать CLI-команду (для внешнего / резервного способа)", + connect: "Подключить", + sessionExpires: "Сессия истекает через {time}", + initiatingLogin: "Запуск потока авторизации…", + exchangingCode: "Обмен кода на токены…", + connectedClosing: "Подключено! Закрытие…", + loginFailed: "Ошибка входа.", + sessionExpired: "Сессия истекла. Нажмите «Повторить» для нового входа.", + reOpenAuth: "Повторно открыть страницу авторизации", + reOpenVerification: "Повторно открыть страницу проверки", + submitCode: "Отправить код", + pasteCode: "Вставьте код авторизации (суффикс #state допустим)", + waitingAuth: "Ожидание авторизации в браузере…", + enterCodePrompt: "Открыта новая вкладка. При необходимости введите этот код:", + pkceStep1: "Открыта новая вкладка claude.ai. Войдите и нажмите «Авторизовать».", + pkceStep2: "Скопируйте код авторизации, показанный после авторизации.", + pkceStep3: "Вставьте его ниже и отправьте.", + flowLabels: { + pkce: "Вход через браузер (PKCE)", + device_code: "Код устройства", + external: "Внешний CLI", + }, + expiresIn: "истекает через {time}", + }, + + language: { + switchTo: "Сменить язык", + }, + + theme: { + title: "Тема", + switchTheme: "Переключить тему", + }, +}; From f11c71944747885e6dca2a9a3a265b282a25d97b Mon Sep 17 00:00:00 2001 From: qa1-abilities Date: Fri, 8 May 2026 15:43:47 +0800 Subject: [PATCH 2/3] feat(web): add French, German and Korean translations --- web/src/i18n/de.ts | 429 +++++++++++++++++++++++++++++++++++++++++++++ web/src/i18n/fr.ts | 429 +++++++++++++++++++++++++++++++++++++++++++++ web/src/i18n/ko.ts | 429 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1287 insertions(+) create mode 100644 web/src/i18n/de.ts create mode 100644 web/src/i18n/fr.ts create mode 100644 web/src/i18n/ko.ts diff --git a/web/src/i18n/de.ts b/web/src/i18n/de.ts new file mode 100644 index 000000000000..93413606d48d --- /dev/null +++ b/web/src/i18n/de.ts @@ -0,0 +1,429 @@ +import type { Translations } from "./types"; + +export const de: Translations = { + common: { + save: "Speichern", + saving: "Speichern...", + cancel: "Abbrechen", + close: "Schließen", + confirm: "Bestätigen", + delete: "Löschen", + refresh: "Aktualisieren", + retry: "Wiederholen", + search: "Suchen...", + loading: "Laden...", + create: "Erstellen", + creating: "Erstellen...", + set: "Festlegen", + replace: "Ersetzen", + clear: "Leeren", + live: "Live", + off: "Aus", + enabled: "aktiviert", + disabled: "deaktiviert", + active: "aktiv", + inactive: "inaktiv", + unknown: "unbekannt", + untitled: "Ohne Titel", + none: "Keine", + form: "Formular", + noResults: "Keine Ergebnisse", + of: "von", + page: "Seite", + msgs: "Nachr.", + tools: "Werkz.", + match: "Treffer", + other: "Sonstige", + configured: "konfiguriert", + removed: "entfernt", + failedToToggle: "Umschalten fehlgeschlagen", + failedToRemove: "Entfernen fehlgeschlagen", + failedToReveal: "Anzeigen fehlgeschlagen", + collapse: "Zusammenklappen", + expand: "Erweitern", + general: "Allgemein", + messaging: "Nachrichten", + pluginLoadFailed: + "Das Skript des Plugins konnte nicht geladen werden. Prüfen Sie den Network-Tab (dashboard-plugins/…) und den Plugin-Pfad auf dem Server.", + pluginNotRegistered: + "Das Plugin-Skript hat register() nicht aufgerufen oder es ist ein Fehler aufgetreten. Öffnen Sie die Browser-Konsole für Details.", + }, + + app: { + brand: "Hermes Agent", + brandShort: "HA", + closeNavigation: "Navigation schließen", + closeModelTools: "Modell und Werkzeuge schließen", + footer: { + org: "Nous Research", + }, + activeSessionsLabel: "Aktive Sitzungen:", + gatewayStatusLabel: "Gateway-Status:", + gatewayStrip: { + failed: "Start fehlgeschlagen", + off: "Aus", + running: "Läuft", + starting: "Wird gestartet", + stopped: "Gestoppt", + }, + nav: { + analytics: "Analytik", + chat: "Chat", + config: "Konfiguration", + cron: "Cron", + documentation: "Dokumentation", + keys: "Schlüssel", + logs: "Protokolle", + models: "Modelle", + profiles: "Profile", + plugins: "Plugins", + sessions: "Sitzungen", + skills: "Fähigkeiten", + }, + modelToolsSheetSubtitle: "und Werkzeuge", + modelToolsSheetTitle: "Modell", + navigation: "Navigation", + openDocumentation: "Dokumentation in neuem Tab öffnen", + openNavigation: "Navigation öffnen", + pluginNavSection: "Plugins", + sessionsActiveCount: "{count} aktiv", + statusOverview: "Statusübersicht", + system: "System", + webUi: "Web-UI", + }, + + status: { + actionFailed: "Aktion fehlgeschlagen", + actionFinished: "Abgeschlossen", + actions: "Aktionen", + agent: "Agent", + activeSessions: "Aktive Sitzungen", + connected: "Verbunden", + connectedPlatforms: "Verbundene Plattformen", + disconnected: "Getrennt", + error: "Fehler", + failed: "Fehlgeschlagen", + gateway: "Gateway", + gatewayFailedToStart: "Gateway konnte nicht gestartet werden", + lastUpdate: "Letztes Update", + noneRunning: "Keine", + notRunning: "Nicht aktiv", + pid: "PID", + platformDisconnected: "getrennt", + platformError: "Fehler", + recentSessions: "Kürzliche Sitzungen", + restartGateway: "Gateway neu starten", + restartingGateway: "Gateway wird neu gestartet…", + running: "Läuft", + runningRemote: "Läuft (Remote)", + startFailed: "Start fehlgeschlagen", + starting: "Wird gestartet", + startedInBackground: "Im Hintergrund gestartet — prüfen Sie die Protokolle", + stopped: "Gestoppt", + updateHermes: "Hermes aktualisieren", + updatingHermes: "Hermes wird aktualisiert…", + waitingForOutput: "Warte auf Ausgabe…", + }, + + sessions: { + title: "Sitzungen", + searchPlaceholder: "Nachrichteninhalt durchsuchen...", + noSessions: "Noch keine Sitzungen", + noMatch: "Keine Sitzungen passen zu Ihrer Suche", + startConversation: "Starten Sie eine Unterhaltung, um sie hier zu sehen", + noMessages: "Keine Nachrichten", + untitledSession: "Sitzung ohne Titel", + deleteSession: "Sitzung löschen", + confirmDeleteTitle: "Sitzung löschen?", + confirmDeleteMessage: + "Dies löscht die Unterhaltung und alle Nachrichten dauerhaft. Dies kann nicht rückgängig gemacht werden.", + sessionDeleted: "Sitzung gelöscht", + failedToDelete: "Sitzung konnte nicht gelöscht werden", + resumeInChat: "Im Chat fortsetzen", + previousPage: "Vorherige Seite", + nextPage: "Nächste Seite", + roles: { + user: "Benutzer", + assistant: "Assistent", + system: "System", + tool: "Werkzeug", + }, + }, + + analytics: { + period: "Zeitraum:", + totalTokens: "Tokens insgesamt", + totalSessions: "Sitzungen insgesamt", + apiCalls: "API-Aufrufe", + dailyTokenUsage: "Tägliche Token-Nutzung", + dailyBreakdown: "Tägliche Aufschlüsselung", + perModelBreakdown: "Aufschlüsselung nach Modell", + topSkills: "Top-Fähigkeiten", + skill: "Fähigkeit", + loads: "Ladungen", + edits: "Bearbeitungen", + lastUsed: "Zuletzt verwendet", + input: "Eingabe", + output: "Ausgabe", + total: "Gesamt", + noUsageData: "Keine Nutzungsdaten für diesen Zeitraum", + startSession: "Starten Sie eine Sitzung, um hier Analysen zu sehen", + date: "Datum", + model: "Modell", + tokens: "Tokens", + perDayAvg: "ø/Tag", + acrossModels: "über {count} Modelle", + inOut: "{input} Ein / {output} Aus", + }, + + models: { + modelsUsed: "Verwendete Modelle", + estimatedCost: "Geschätzte Kosten", + tokens: "Tokens", + sessions: "Sitzungen", + avgPerSession: "ø/Sitzung", + apiCalls: "API-Aufrufe", + toolCalls: "Werkzeugaufrufe", + noModelsData: "Keine Modellnutzungsdaten", + startSession: "Starten Sie eine Sitzung, um hier Modelldaten zu sehen", + }, + + logs: { + title: "Protokolle", + autoRefresh: "Automatisch aktualisieren", + file: "Datei", + level: "Ebene", + component: "Komponente", + lines: "Zeilen", + noLogLines: "Keine Protokollzeilen gefunden", + }, + + cron: { + confirmDeleteMessage: + "Die Aufgabe wird aus dem Zeitplan entfernt. Dies kann nicht rückgängig gemacht werden.", + confirmDeleteTitle: "Geplante Aufgabe löschen?", + newJob: "Neue Aufgabe", + nameOptional: "Name (optional)", + namePlaceholder: "z. B. Tägliche Zusammenfassung", + prompt: "Eingabeaufforderung", + promptPlaceholder: "Was soll der Agent bei jedem Lauf tun?", + schedule: "Zeitplan (Cron-Ausdruck)", + schedulePlaceholder: "0 9 * * *", + deliverTo: "Zustellen an", + scheduledJobs: "Geplante Aufgaben", + noJobs: "Keine geplanten Aufgaben. Erstellen Sie eine oben.", + last: "Letzte", + next: "Nächste", + pause: "Pause", + resume: "Fortsetzen", + triggerNow: "Jetzt auslösen", + delivery: { + local: "Lokal", + telegram: "Telegram", + discord: "Discord", + slack: "Slack", + email: "E-Mail", + }, + }, + + profiles: { + newProfile: "Neues Profil", + name: "Name", + namePlaceholder: "z. B. coder, writer", + nameRequired: "Name ist erforderlich", + nameRule: + "Nur Kleinbuchstaben, Ziffern, _ und -; muss mit Buchstabe oder Ziffer beginnen; max. 64 Zeichen.", + invalidName: "Ungültiger Profilname", + cloneFromDefault: "Konfiguration aus Standardprofil klonen", + allProfiles: "Profile", + noProfiles: "Keine Profile gefunden.", + defaultBadge: "Standard", + hasEnv: "env", + model: "Modell", + skills: "Fähigkeiten", + rename: "Umbenennen", + editSoul: "SOUL.md bearbeiten", + soulSection: "SOUL.md (Persönlichkeit / System-Prompt)", + soulPlaceholder: "# Wie sich dieser Agent verhalten soll…", + saveSoul: "SOUL speichern", + soulSaved: "SOUL.md gespeichert", + openInTerminal: "CLI-Befehl kopieren", + commandCopied: "In die Zwischenablage kopiert", + copyFailed: "Kopieren fehlgeschlagen", + confirmDeleteTitle: "Profil löschen?", + confirmDeleteMessage: + "Dies löscht das Profil '{name}' dauerhaft — Konfiguration, Schlüssel, Speicher, Sitzungen, Fähigkeiten, Cron-Aufgaben. Kann nicht rückgängig gemacht werden.", + created: "Erstellt", + deleted: "Gelöscht", + renamed: "Umbenannt", + }, + + pluginsPage: { + contextEngineLabel: "Kontext-Engine", + dashboardSlots: "Dashboard-Slots", + disableRuntime: "Deaktivieren", + enableAfterInstall: "Nach Installation aktivieren", + enableRuntime: "Aktivieren", + forceReinstall: "Neuinstallation erzwingen (vorhandenen Ordner zuerst löschen)", + headline: + "Hermes-Plugins entdecken, installieren, aktivieren und aktualisieren (Parität mit `hermes plugins`).", + identifierLabel: "Git-URL oder owner/repo", + inactive: "inaktiv", + installBtn: "Aus Git installieren", + installHeading: "Von GitHub / Git-URL installieren", + installHint: "Verwenden Sie die Kurzschreibweise owner/repo oder eine vollständige https:// / git@ URL.", + memoryProviderLabel: "Speicher-Anbieter", + missingEnvWarn: "Setzen Sie diese Variablen in «Schlüssel» vor dem Ausführen des Plugins:", + noDashboardTab: "Kein Dashboard-Tab", + openTab: "Öffnen", + orphanHeading: "Nur Dashboard-Erweiterungen (keine Agent-plugin.yaml-Übereinstimmung)", + pluginListHeading: "Installierte Plugins", + providerDefaults: "integriert / Standard", + providersHeading: "Laufzeit-Anbieter-Plugins", + providersHint: + "Schreibt memory.provider (leer = integriert) und context.engine in config.yaml. Wirkt sich bei der nächsten Sitzung aus.", + refreshDashboard: "Dashboard-Erweiterungen erneut scannen", + removeConfirm: "Dieses Plugin aus ~/.hermes/plugins/ entfernen?", + removeHint: "Nur vom Benutzer installierte Plugins unter ~/.hermes/plugins können entfernt werden.", + rescanHeading: "SPA-Plugin-Registrierung", + rescanHint: "Erneut scannen, nachdem Dateien auf der Festplatte hinzugefügt wurden, damit die Seitenleiste neue Manifeste lädt.", + runtimeHeading: "Gateway-Laufzeit (YAML-Plugins)", + saveProviders: "Anbieter-Einstellungen speichern", + savedProviders: "Anbieter-Einstellungen gespeichert.", + sourceBadge: "Quelle", + authRequired: "Authentifizierung erforderlich", + authRequiredHint: "Führen Sie diesen Befehl aus, um sich zu authentifizieren:", + updateGit: "git pull", + versionBadge: "Version", + showInSidebar: "In Seitenleiste anzeigen", + hideFromSidebar: "Aus Seitenleiste ausblenden", + }, + + skills: { + title: "Fähigkeiten", + searchPlaceholder: "Fähigkeiten und Werkzeugsätze durchsuchen...", + enabledOf: "{enabled}/{total} aktiviert", + all: "Alle", + categories: "Kategorien", + filters: "Filter", + noSkills: "Keine Fähigkeiten gefunden. Fähigkeiten werden aus ~/.hermes/skills/ geladen", + noSkillsMatch: "Keine Fähigkeiten passen zu Ihrer Suche.", + skillCount: "{count} Fähigkeit{so}", + resultCount: "{count} Ergebnis{so}", + noDescription: "Keine Beschreibung verfügbar.", + toolsets: "Werkzeugsätze", + toolsetLabel: "{name} Werkzeugsatz", + noToolsetsMatch: "Kein Werkzeugsatz passt zur Suche.", + setupNeeded: "Einrichtung erforderlich", + disabledForCli: "Für CLI deaktiviert", + more: "+{count} weitere", + }, + + config: { + configPath: "~/.hermes/config.yaml", + filters: "Filter", + sections: "Abschnitte", + exportConfig: "Konfiguration als JSON exportieren", + importConfig: "Konfiguration aus JSON importieren", + resetDefaults: "Auf Standardwerte zurücksetzen", + resetScopeTooltip: "{scope} auf Standardwerte zurücksetzen", + confirmResetScope: "Alle {scope}-Einstellungen auf die Standardwerte zurücksetzen? Dies aktualisiert nur das Formular — Änderungen werden erst in config.yaml geschrieben, wenn Sie auf Speichern klicken.", + resetScopeToast: "{scope} auf Standardwerte zurückgesetzt — prüfen und speichern", + rawYaml: "Roh-YAML-Konfiguration", + searchResults: "Suchergebnisse", + fields: "Feld{so}", + noFieldsMatch: 'Keine Felder passen zu "{query}"', + configSaved: "Konfiguration gespeichert", + yamlConfigSaved: "YAML-Konfiguration gespeichert", + failedToSave: "Speichern fehlgeschlagen", + failedToSaveYaml: "YAML-Speichern fehlgeschlagen", + failedToLoadRaw: "Laden der Rohkonfiguration fehlgeschlagen", + configImported: "Konfiguration importiert — prüfen und speichern", + invalidJson: "Ungültige JSON-Datei", + categories: { + general: "Allgemein", + agent: "Agent", + terminal: "Terminal", + display: "Anzeige", + delegation: "Delegierung", + memory: "Speicher", + compression: "Komprimierung", + security: "Sicherheit", + browser: "Browser", + voice: "Stimme", + tts: "Text-zu-Sprache", + stt: "Sprache-zu-Text", + logging: "Protokollierung", + discord: "Discord", + auxiliary: "Hilfsmittel", + }, + }, + + env: { + changesNote: "Änderungen werden sofort auf die Festplatte gespeichert. Aktive Sitzungen übernehmen neue Schlüssel automatisch.", + confirmClearMessage: + "Der gespeicherte Wert dieser Variable wird aus der .env-Datei entfernt. Dies kann über die Benutzeroberfläche nicht rückgängig gemacht werden.", + confirmClearTitle: "Diesen Schlüssel löschen?", + description: "Verwalten Sie API-Schlüssel und Geheimnisse, die gespeichert sind in", + hideAdvanced: "Erweitert ausblenden", + showAdvanced: "Erweitert anzeigen", + llmProviders: "LLM-Anbieter", + providersConfigured: "{configured} von {total} Anbietern konfiguriert", + getKey: "Schlüssel abrufen", + notConfigured: "{count} nicht konfiguriert", + notSet: "Nicht festgelegt", + keysCount: "{count} Schlüssel{so}", + enterValue: "Wert eingeben...", + replaceCurrentValue: "Aktuellen Wert ersetzen ({preview})", + showValue: "Echten Wert anzeigen", + hideValue: "Wert ausblenden", + }, + + oauth: { + title: "Anbieter-Anmeldungen (OAuth)", + providerLogins: "Anbieter-Anmeldungen (OAuth)", + description: "{connected} von {total} OAuth-Anbietern verbunden. Anmeldeabläufe werden derzeit über die CLI ausgeführt; klicken Sie auf «Befehl kopieren» und fügen Sie ihn in ein Terminal ein, um einzurichten.", + connected: "Verbunden", + expired: "Abgelaufen", + notConnected: "Nicht verbunden. Führen Sie {command} in einem Terminal aus.", + runInTerminal: "in einem Terminal.", + noProviders: "Keine OAuth-fähigen Anbieter erkannt.", + login: "Anmelden", + disconnect: "Trennen", + managedExternally: "Extern verwaltet", + copied: "Kopiert ✓", + cli: "CLI", + copyCliCommand: "CLI-Befehl kopieren (für externen / alternativen Weg)", + connect: "Verbinden", + sessionExpires: "Sitzung läuft ab in {time}", + initiatingLogin: "Anmeldeablauf wird gestartet…", + exchangingCode: "Code wird gegen Tokens getauscht…", + connectedClosing: "Verbunden! Schließen…", + loginFailed: "Anmeldung fehlgeschlagen.", + sessionExpired: "Sitzung abgelaufen. Klicken Sie auf Wiederholen, um eine neue Anmeldung zu starten.", + reOpenAuth: "Autorisierungsseite erneut öffnen", + reOpenVerification: "Verifizierungsseite erneut öffnen", + submitCode: "Code senden", + pasteCode: "Autorisierungscode einfügen (Suffix #state ist in Ordnung)", + waitingAuth: "Warte auf Autorisierung im Browser…", + enterCodePrompt: "Ein neuer Tab wurde geöffnet. Geben Sie diesen Code ein, wenn Sie dazu aufgefordert werden:", + pkceStep1: "Ein neuer Tab zu claude.ai wurde geöffnet. Melden Sie sich an und klicken Sie auf Autorisieren.", + pkceStep2: "Kopieren Sie den Autorisierungscode, der nach der Autorisierung angezeigt wird.", + pkceStep3: "Fügen Sie ihn unten ein und senden Sie ihn ab.", + flowLabels: { + pkce: "Browser-Anmeldung (PKCE)", + device_code: "Gerätecode", + external: "Externe CLI", + }, + expiresIn: "läuft ab in {time}", + }, + + language: { + switchTo: "Sprache wechseln", + }, + + theme: { + title: "Design", + switchTheme: "Design wechseln", + }, +}; diff --git a/web/src/i18n/fr.ts b/web/src/i18n/fr.ts new file mode 100644 index 000000000000..245533d9dbda --- /dev/null +++ b/web/src/i18n/fr.ts @@ -0,0 +1,429 @@ +import type { Translations } from "./types"; + +export const fr: Translations = { + common: { + save: "Enregistrer", + saving: "Enregistrement...", + cancel: "Annuler", + close: "Fermer", + confirm: "Confirmer", + delete: "Supprimer", + refresh: "Actualiser", + retry: "Réessayer", + search: "Rechercher...", + loading: "Chargement...", + create: "Créer", + creating: "Création...", + set: "Définir", + replace: "Remplacer", + clear: "Effacer", + live: "En direct", + off: "Désactivé", + enabled: "activé", + disabled: "désactivé", + active: "actif", + inactive: "inactif", + unknown: "inconnu", + untitled: "Sans titre", + none: "Aucun", + form: "Formulaire", + noResults: "Aucun résultat", + of: "sur", + page: "Page", + msgs: "msgs", + tools: "outils", + match: "correspondance", + other: "Autre", + configured: "configuré", + removed: "supprimé", + failedToToggle: "Échec de la bascule", + failedToRemove: "Échec de la suppression", + failedToReveal: "Échec de l'affichage", + collapse: "Réduire", + expand: "Développer", + general: "Général", + messaging: "Messagerie", + pluginLoadFailed: + "Impossible de charger le script du plugin. Vérifiez l'onglet Network (dashboard-plugins/…) et le chemin du plugin sur le serveur.", + pluginNotRegistered: + "Le script du plugin n'a pas appelé register(), ou une erreur s'est produite. Ouvrez la console du navigateur pour plus de détails.", + }, + + app: { + brand: "Hermes Agent", + brandShort: "HA", + closeNavigation: "Fermer la navigation", + closeModelTools: "Fermer le modèle et les outils", + footer: { + org: "Nous Research", + }, + activeSessionsLabel: "Sessions actives :", + gatewayStatusLabel: "État de la passerelle :", + gatewayStrip: { + failed: "Échec du démarrage", + off: "Désactivé", + running: "En cours", + starting: "Démarrage en cours", + stopped: "Arrêté", + }, + nav: { + analytics: "Analytique", + chat: "Chat", + config: "Configuration", + cron: "Cron", + documentation: "Documentation", + keys: "Clés", + logs: "Journaux", + models: "Modèles", + profiles: "Profils", + plugins: "Plugins", + sessions: "Sessions", + skills: "Compétences", + }, + modelToolsSheetSubtitle: "et outils", + modelToolsSheetTitle: "Modèle", + navigation: "Navigation", + openDocumentation: "Ouvrir la documentation dans un nouvel onglet", + openNavigation: "Ouvrir la navigation", + pluginNavSection: "Plugins", + sessionsActiveCount: "{count} active(s)", + statusOverview: "Aperçu de l'état", + system: "Système", + webUi: "Interface Web", + }, + + status: { + actionFailed: "Action échouée", + actionFinished: "Terminé", + actions: "Actions", + agent: "Agent", + activeSessions: "Sessions actives", + connected: "Connecté", + connectedPlatforms: "Plateformes connectées", + disconnected: "Déconnecté", + error: "Erreur", + failed: "Échec", + gateway: "Passerelle", + gatewayFailedToStart: "La passerelle n'a pas pu démarrer", + lastUpdate: "Dernière mise à jour", + noneRunning: "Aucune", + notRunning: "Non en cours", + pid: "PID", + platformDisconnected: "déconnecté", + platformError: "erreur", + recentSessions: "Sessions récentes", + restartGateway: "Redémarrer la passerelle", + restartingGateway: "Redémarrage de la passerelle…", + running: "En cours", + runningRemote: "En cours (à distance)", + startFailed: "Échec du démarrage", + starting: "Démarrage en cours", + startedInBackground: "Démarré en arrière-plan — consultez les journaux", + stopped: "Arrêté", + updateHermes: "Mettre à jour Hermes", + updatingHermes: "Mise à jour de Hermes…", + waitingForOutput: "En attente de sortie…", + }, + + sessions: { + title: "Sessions", + searchPlaceholder: "Rechercher dans le contenu des messages...", + noSessions: "Aucune session pour l'instant", + noMatch: "Aucune session ne correspond à votre recherche", + startConversation: "Commencez une conversation pour la voir ici", + noMessages: "Aucun message", + untitledSession: "Session sans titre", + deleteSession: "Supprimer la session", + confirmDeleteTitle: "Supprimer la session ?", + confirmDeleteMessage: + "Cela supprimera définitivement la conversation et tous ses messages. Cette action est irréversible.", + sessionDeleted: "Session supprimée", + failedToDelete: "Échec de la suppression de la session", + resumeInChat: "Reprendre dans le chat", + previousPage: "Page précédente", + nextPage: "Page suivante", + roles: { + user: "Utilisateur", + assistant: "Assistant", + system: "Système", + tool: "Outil", + }, + }, + + analytics: { + period: "Période :", + totalTokens: "Tokens totaux", + totalSessions: "Sessions totales", + apiCalls: "Appels API", + dailyTokenUsage: "Utilisation quotidienne de tokens", + dailyBreakdown: "Détail quotidien", + perModelBreakdown: "Détail par modèle", + topSkills: "Compétences principales", + skill: "Compétence", + loads: "Chargements", + edits: "Modifications", + lastUsed: "Dernière utilisation", + input: "Entrée", + output: "Sortie", + total: "Total", + noUsageData: "Aucune donnée d'utilisation pour cette période", + startSession: "Démarrez une session pour voir les analyses ici", + date: "Date", + model: "Modèle", + tokens: "Tokens", + perDayAvg: "moy/jour", + acrossModels: "sur {count} modèles", + inOut: "{input} entrée / {output} sortie", + }, + + models: { + modelsUsed: "Modèles utilisés", + estimatedCost: "Coût estimé", + tokens: "tokens", + sessions: "sessions", + avgPerSession: "moy/session", + apiCalls: "appels API", + toolCalls: "appels d'outils", + noModelsData: "Aucune donnée d'utilisation des modèles", + startSession: "Démarrez une session pour voir les données des modèles ici", + }, + + logs: { + title: "Journaux", + autoRefresh: "Actualisation automatique", + file: "Fichier", + level: "Niveau", + component: "Composant", + lines: "Lignes", + noLogLines: "Aucune ligne de journal trouvée", + }, + + cron: { + confirmDeleteMessage: + "La tâche sera supprimée de la planification. Cette action est irréversible.", + confirmDeleteTitle: "Supprimer la tâche planifiée ?", + newJob: "Nouvelle tâche", + nameOptional: "Nom (facultatif)", + namePlaceholder: "p. ex. Résumé quotidien", + prompt: "Prompt", + promptPlaceholder: "Que doit faire l'agent à chaque exécution ?", + schedule: "Planification (expression cron)", + schedulePlaceholder: "0 9 * * *", + deliverTo: "Livrer à", + scheduledJobs: "Tâches planifiées", + noJobs: "Aucune tâche planifiée. Créez-en une ci-dessus.", + last: "Dernière", + next: "Prochaine", + pause: "Pause", + resume: "Reprendre", + triggerNow: "Déclencher maintenant", + delivery: { + local: "Local", + telegram: "Telegram", + discord: "Discord", + slack: "Slack", + email: "E-mail", + }, + }, + + profiles: { + newProfile: "Nouveau profil", + name: "Nom", + namePlaceholder: "p. ex. coder, writer", + nameRequired: "Le nom est obligatoire", + nameRule: + "Lettres minuscules, chiffres, _ et - uniquement ; doit commencer par une lettre ou un chiffre ; 64 caractères max.", + invalidName: "Nom de profil invalide", + cloneFromDefault: "Cloner la configuration du profil par défaut", + allProfiles: "Profils", + noProfiles: "Aucun profil trouvé.", + defaultBadge: "par défaut", + hasEnv: "env", + model: "Modèle", + skills: "Compétences", + rename: "Renommer", + editSoul: "Modifier SOUL.md", + soulSection: "SOUL.md (personnalité / prompt système)", + soulPlaceholder: "# Comment cet agent doit se comporter…", + saveSoul: "Enregistrer SOUL", + soulSaved: "SOUL.md enregistré", + openInTerminal: "Copier la commande CLI", + commandCopied: "Copié dans le presse-papiers", + copyFailed: "Impossible de copier", + confirmDeleteTitle: "Supprimer le profil ?", + confirmDeleteMessage: + "Cela supprimera définitivement le profil '{name}' — configuration, clés, mémoire, sessions, compétences, tâches cron. Cette action est irréversible.", + created: "Créé", + deleted: "Supprimé", + renamed: "Renommé", + }, + + pluginsPage: { + contextEngineLabel: "Moteur de contexte", + dashboardSlots: "Emplacements du tableau de bord", + disableRuntime: "Désactiver", + enableAfterInstall: "Activer après l'installation", + enableRuntime: "Activer", + forceReinstall: "Forcer la réinstallation (supprimer le dossier existant d'abord)", + headline: + "Découvrez, installez, activez et mettez à jour les plugins Hermes (parité avec `hermes plugins`).", + identifierLabel: "URL Git ou owner/repo", + inactive: "inactif", + installBtn: "Installer depuis Git", + installHeading: "Installer depuis GitHub / URL Git", + installHint: "Utilisez la notation courte owner/repo ou une URL https:// / git@ complète.", + memoryProviderLabel: "Fournisseur de mémoire", + missingEnvWarn: "Définissez ces variables dans « Clés » avant de pouvoir exécuter le plugin :", + noDashboardTab: "Aucun onglet de tableau de bord", + openTab: "Ouvrir", + orphanHeading: "Extensions de tableau de bord uniquement (aucune correspondance plugin.yaml de l'agent)", + pluginListHeading: "Plugins installés", + providerDefaults: "intégré / par défaut", + providersHeading: "Plugins fournisseurs d'exécution", + providersHint: + "Écrit memory.provider (vide = intégré) et context.engine dans config.yaml. Prend effet à la prochaine session.", + refreshDashboard: "Réanalyser les extensions du tableau de bord", + removeConfirm: "Supprimer ce plugin de ~/.hermes/plugins/ ?", + removeHint: "Seuls les plugins installés par l'utilisateur dans ~/.hermes/plugins peuvent être supprimés.", + rescanHeading: "Registre des plugins SPA", + rescanHint: "Réanalysez après avoir ajouté des fichiers sur le disque pour que la barre latérale récupère les nouveaux manifestes.", + runtimeHeading: "Environnement d'exécution de la passerelle (plugins YAML)", + saveProviders: "Enregistrer la configuration des fournisseurs", + savedProviders: "Configuration des fournisseurs enregistrée.", + sourceBadge: "Source", + authRequired: "Authentification requise", + authRequiredHint: "Exécutez cette commande pour vous authentifier :", + updateGit: "git pull", + versionBadge: "Version", + showInSidebar: "Afficher dans la barre latérale", + hideFromSidebar: "Masquer de la barre latérale", + }, + + skills: { + title: "Compétences", + searchPlaceholder: "Rechercher des compétences et des ensembles d'outils...", + enabledOf: "{enabled}/{total} activé(s)", + all: "Toutes", + categories: "Catégories", + filters: "Filtres", + noSkills: "Aucune compétence trouvée. Les compétences sont chargées depuis ~/.hermes/skills/", + noSkillsMatch: "Aucune compétence ne correspond à votre recherche.", + skillCount: "{count} compétence{so}", + resultCount: "{count} résultat{so}", + noDescription: "Aucune description disponible.", + toolsets: "Ensembles d'outils", + toolsetLabel: "Ensemble d'outils {name}", + noToolsetsMatch: "Aucun ensemble d'outils ne correspond à la recherche.", + setupNeeded: "Configuration nécessaire", + disabledForCli: "Désactivé pour CLI", + more: "+{count} de plus", + }, + + config: { + configPath: "~/.hermes/config.yaml", + filters: "Filtres", + sections: "Sections", + exportConfig: "Exporter la configuration en JSON", + importConfig: "Importer la configuration depuis JSON", + resetDefaults: "Réinitialiser aux valeurs par défaut", + resetScopeTooltip: "Réinitialiser {scope} aux valeurs par défaut", + confirmResetScope: "Réinitialiser tous les paramètres de {scope} à leurs valeurs par défaut ? Cela ne met à jour que le formulaire — les modifications ne sont pas écrites dans config.yaml tant que vous n'appuyez pas sur Enregistrer.", + resetScopeToast: "{scope} réinitialisé aux valeurs par défaut — vérifiez et enregistrez", + rawYaml: "Configuration YAML brute", + searchResults: "Résultats de recherche", + fields: "champ{so}", + noFieldsMatch: 'Aucun champ ne correspond à "{query}"', + configSaved: "Configuration enregistrée", + yamlConfigSaved: "Configuration YAML enregistrée", + failedToSave: "Échec de l'enregistrement", + failedToSaveYaml: "Échec de l'enregistrement YAML", + failedToLoadRaw: "Échec du chargement de la configuration brute", + configImported: "Configuration importée — vérifiez et enregistrez", + invalidJson: "Fichier JSON invalide", + categories: { + general: "Général", + agent: "Agent", + terminal: "Terminal", + display: "Affichage", + delegation: "Délégation", + memory: "Mémoire", + compression: "Compression", + security: "Sécurité", + browser: "Navigateur", + voice: "Voix", + tts: "Synthèse vocale", + stt: "Reconnaissance vocale", + logging: "Journalisation", + discord: "Discord", + auxiliary: "Auxiliaire", + }, + }, + + env: { + changesNote: "Les modifications sont enregistrées sur le disque immédiatement. Les sessions actives récupèrent les nouvelles clés automatiquement.", + confirmClearMessage: + "La valeur stockée de cette variable sera supprimée du fichier .env. Cette action ne peut pas être annulée depuis l'interface.", + confirmClearTitle: "Effacer cette clé ?", + description: "Gérez les clés API et les secrets stockés dans", + hideAdvanced: "Masquer l'avancé", + showAdvanced: "Afficher l'avancé", + llmProviders: "Fournisseurs LLM", + providersConfigured: "{configured} sur {total} fournisseurs configurés", + getKey: "Obtenir la clé", + notConfigured: "{count} non configuré(s)", + notSet: "Non défini", + keysCount: "{count} clé{so}", + enterValue: "Entrez une valeur...", + replaceCurrentValue: "Remplacer la valeur actuelle ({preview})", + showValue: "Afficher la valeur réelle", + hideValue: "Masquer la valeur", + }, + + oauth: { + title: "Connexions fournisseurs (OAuth)", + providerLogins: "Connexions fournisseurs (OAuth)", + description: "{connected} sur {total} fournisseurs OAuth connectés. Les flux de connexion s'exécutent actuellement via CLI ; cliquez sur « Copier la commande » et collez-la dans un terminal pour configurer.", + connected: "Connecté", + expired: "Expiré", + notConnected: "Non connecté. Exécutez {command} dans un terminal.", + runInTerminal: "dans un terminal.", + noProviders: "Aucun fournisseur compatible OAuth détecté.", + login: "Se connecter", + disconnect: "Déconnecter", + managedExternally: "Géré de manière externe", + copied: "Copié ✓", + cli: "CLI", + copyCliCommand: "Copier la commande CLI (pour méthode externe / de secours)", + connect: "Connecter", + sessionExpires: "La session expire dans {time}", + initiatingLogin: "Initiation du flux de connexion…", + exchangingCode: "Échange du code contre des tokens…", + connectedClosing: "Connecté ! Fermeture…", + loginFailed: "Échec de la connexion.", + sessionExpired: "La session a expiré. Cliquez sur Réessayer pour recommencer.", + reOpenAuth: "Rouvrir la page d'autorisation", + reOpenVerification: "Rouvrir la page de vérification", + submitCode: "Soumettre le code", + pasteCode: "Collez le code d'autorisation (le suffixe #state est accepté)", + waitingAuth: "En attendant que vous autorisiez dans le navigateur…", + enterCodePrompt: "Un nouvel onglet a été ouvert. Saisissez ce code si vous y êtes invité :", + pkceStep1: "Un nouvel onglet s'est ouvert sur claude.ai. Connectez-vous et cliquez sur Autoriser.", + pkceStep2: "Copiez le code d'autorisation affiché après l'autorisation.", + pkceStep3: "Collez-le ci-dessous et soumettez-le.", + flowLabels: { + pkce: "Connexion navigateur (PKCE)", + device_code: "Code d'appareil", + external: "CLI externe", + }, + expiresIn: "expire dans {time}", + }, + + language: { + switchTo: "Changer de langue", + }, + + theme: { + title: "Thème", + switchTheme: "Changer de thème", + }, +}; diff --git a/web/src/i18n/ko.ts b/web/src/i18n/ko.ts new file mode 100644 index 000000000000..d9d7a6e6dd31 --- /dev/null +++ b/web/src/i18n/ko.ts @@ -0,0 +1,429 @@ +import type { Translations } from "./types"; + +export const ko: Translations = { + common: { + save: "저장", + saving: "저장 중...", + cancel: "취소", + close: "닫기", + confirm: "확인", + delete: "삭제", + refresh: "새로고침", + retry: "다시 시도", + search: "검색...", + loading: "로딩 중...", + create: "생성", + creating: "생성 중...", + set: "설정", + replace: "교체", + clear: "지우기", + live: "실시간", + off: "끔", + enabled: "활성화됨", + disabled: "비활성화됨", + active: "활성", + inactive: "비활성", + unknown: "알 수 없음", + untitled: "제목 없음", + none: "없음", + form: "양식", + noResults: "결과 없음", + of: "의", + page: "페이지", + msgs: "메시지", + tools: "도구", + match: "일치", + other: "기타", + configured: "설정됨", + removed: "제거됨", + failedToToggle: "전환 실패", + failedToRemove: "제거 실패", + failedToReveal: "표시 실패", + collapse: "접기", + expand: "펼치기", + general: "일반", + messaging: "메시징", + pluginLoadFailed: + "이 플러그인의 스크립트를 로드할 수 없습니다. Network 탭(dashboard-plugins/…)과 서버의 플러그인 경로를 확인하세요.", + pluginNotRegistered: + "플러그인 스크립트가 register()를 호출하지 않았거나 오류가 발생했습니다. 자세한 내용은 브라우저 콘솔을 확인하세요.", + }, + + app: { + brand: "Hermes Agent", + brandShort: "HA", + closeNavigation: "탐색 닫기", + closeModelTools: "모델 및 도구 닫기", + footer: { + org: "Nous Research", + }, + activeSessionsLabel: "활성 세션:", + gatewayStatusLabel: "게이트웨이 상태:", + gatewayStrip: { + failed: "시작 실패", + off: "끔", + running: "실행 중", + starting: "시작 중", + stopped: "중지됨", + }, + nav: { + analytics: "분석", + chat: "채팅", + config: "설정", + cron: "Cron", + documentation: "문서", + keys: "키", + logs: "로그", + models: "모델", + profiles: "프로필", + plugins: "플러그인", + sessions: "세션", + skills: "스킬", + }, + modelToolsSheetSubtitle: "및 도구", + modelToolsSheetTitle: "모델", + navigation: "탐색", + openDocumentation: "새 탭에서 문서 열기", + openNavigation: "탐색 열기", + pluginNavSection: "플러그인", + sessionsActiveCount: "{count}개 활성", + statusOverview: "상태 개요", + system: "시스템", + webUi: "웹 UI", + }, + + status: { + actionFailed: "작업 실패", + actionFinished: "완료됨", + actions: "작업", + agent: "에이전트", + activeSessions: "활성 세션", + connected: "연결됨", + connectedPlatforms: "연결된 플랫폼", + disconnected: "연결 해제됨", + error: "오류", + failed: "실패", + gateway: "게이트웨이", + gatewayFailedToStart: "게이트웨이 시작 실패", + lastUpdate: "마지막 업데이트", + noneRunning: "없음", + notRunning: "실행 중 아님", + pid: "PID", + platformDisconnected: "연결 해제됨", + platformError: "오류", + recentSessions: "최근 세션", + restartGateway: "게이트웨이 재시작", + restartingGateway: "게이트웨이 재시작 중…", + running: "실행 중", + runningRemote: "실행 중 (원격)", + startFailed: "시작 실패", + starting: "시작 중", + startedInBackground: "백그라운드에서 시작됨 — 로그 확인", + stopped: "중지됨", + updateHermes: "Hermes 업데이트", + updatingHermes: "Hermes 업데이트 중…", + waitingForOutput: "출력 대기 중…", + }, + + sessions: { + title: "세션", + searchPlaceholder: "메시지 내용 검색...", + noSessions: "아직 세션 없음", + noMatch: "검색과 일치하는 세션이 없습니다", + startConversation: "대화를 시작하면 여기에 표시됩니다", + noMessages: "메시지 없음", + untitledSession: "제목 없는 세션", + deleteSession: "세션 삭제", + confirmDeleteTitle: "세션을 삭제하시겠습니까?", + confirmDeleteMessage: + "이 작업은 대화와 모든 메시지를 영구적으로 삭제합니다. 되돌릴 수 없습니다.", + sessionDeleted: "세션이 삭제되었습니다", + failedToDelete: "세션 삭제 실패", + resumeInChat: "채팅에서 계속하기", + previousPage: "이전 페이지", + nextPage: "다음 페이지", + roles: { + user: "사용자", + assistant: "어시스턴트", + system: "시스템", + tool: "도구", + }, + }, + + analytics: { + period: "기간:", + totalTokens: "총 토큰", + totalSessions: "총 세션", + apiCalls: "API 호출", + dailyTokenUsage: "일일 토큰 사용량", + dailyBreakdown: "일별 내역", + perModelBreakdown: "모델별 내역", + topSkills: "상위 스킬", + skill: "스킬", + loads: "로드", + edits: "편집", + lastUsed: "마지막 사용", + input: "입력", + output: "출력", + total: "합계", + noUsageData: "이 기간에 사용 데이터가 없습니다", + startSession: "세션을 시작하면 여기에 분석이 표시됩니다", + date: "날짜", + model: "모델", + tokens: "토큰", + perDayAvg: "일 평균", + acrossModels: "{count}개 모델", + inOut: "입력 {input} / 출력 {output}", + }, + + models: { + modelsUsed: "사용된 모델", + estimatedCost: "예상 비용", + tokens: "토큰", + sessions: "세션", + avgPerSession: "세션당 평균", + apiCalls: "API 호출", + toolCalls: "도구 호출", + noModelsData: "이 기간에 모델 사용 데이터가 없습니다", + startSession: "세션을 시작하면 여기에 모델 데이터가 표시됩니다", + }, + + logs: { + title: "로그", + autoRefresh: "자동 새로고침", + file: "파일", + level: "수준", + component: "구성 요소", + lines: "줄", + noLogLines: "로그 줄을 찾을 수 없습니다", + }, + + cron: { + confirmDeleteMessage: + "예약에서 작업이 제거됩니다. 이 작업은 되돌릴 수 없습니다.", + confirmDeleteTitle: "예약된 작업을 삭제하시겠습니까?", + newJob: "새 작업", + nameOptional: "이름 (선택 사항)", + namePlaceholder: "예: 일일 요약", + prompt: "프롬프트", + promptPlaceholder: "각 실행 시 에이전트가 수행할 작업은 무엇입니까?", + schedule: "일정 (cron 표현식)", + schedulePlaceholder: "0 9 * * *", + deliverTo: "전달 대상", + scheduledJobs: "예약된 작업", + noJobs: "예약된 작업이 없습니다. 위에서 생성하세요.", + last: "마지막", + next: "다음", + pause: "일시 중지", + resume: "계속", + triggerNow: "지금 실행", + delivery: { + local: "로컬", + telegram: "Telegram", + discord: "Discord", + slack: "Slack", + email: "이메일", + }, + }, + + profiles: { + newProfile: "새 프로필", + name: "이름", + namePlaceholder: "예: coder, writer", + nameRequired: "이름이 필요합니다", + nameRule: + "소문자, 숫자, _, -만 가능; 문자 또는 숫자로 시작; 최대 64자.", + invalidName: "잘못된 프로필 이름", + cloneFromDefault: "기본 프로필에서 설정 복제", + allProfiles: "프로필", + noProfiles: "프로필을 찾을 수 없습니다.", + defaultBadge: "기본", + hasEnv: "env", + model: "모델", + skills: "스킬", + rename: "이름 변경", + editSoul: "SOUL.md 편집", + soulSection: "SOUL.md (성격 / 시스템 프롬프트)", + soulPlaceholder: "# 이 에이전트가 어떻게 작동해야 하는지…", + saveSoul: "SOUL 저장", + soulSaved: "SOUL.md 저장됨", + openInTerminal: "CLI 명령 복사", + commandCopied: "클립보드에 복사됨", + copyFailed: "복사 실패", + confirmDeleteTitle: "프로필을 삭제하시겠습니까?", + confirmDeleteMessage: + "프로필 '{name}'이 영구적으로 삭제됩니다 — 설정, 키, 메모리, 세션, 스킬, cron 작업. 되돌릴 수 없습니다.", + created: "생성됨", + deleted: "삭제됨", + renamed: "이름 변경됨", + }, + + pluginsPage: { + contextEngineLabel: "컨텍스트 엔진", + dashboardSlots: "대시보드 슬롯", + disableRuntime: "비활성화", + enableAfterInstall: "설치 후 활성화", + enableRuntime: "활성화", + forceReinstall: "강제 재설치 (기존 폴더 먼저 삭제)", + headline: + "Hermes 플러그인을 검색, 설치, 활성화 및 업데이트합니다 (`hermes plugins`과 동일).", + identifierLabel: "Git URL 또는 owner/repo", + inactive: "비활성", + installBtn: "Git에서 설치", + installHeading: "GitHub / Git URL에서 설치", + installHint: "owner/repo 축약형 또는 전체 https:// / git@ URL을 사용하세요.", + memoryProviderLabel: "메모리 제공자", + missingEnvWarn: "플러그인을 실행하기 전에 «키»에서 다음 변수를 설정하세요:", + noDashboardTab: "대시보드 탭 없음", + openTab: "열기", + orphanHeading: "대시보드 전용 확장(에이전트 plugin.yaml 일치 없음)", + pluginListHeading: "설치된 플러그인", + providerDefaults: "기본 제공 / 기본값", + providersHeading: "런타임 제공자 플러그인", + providersHint: + "config.yaml에 memory.provider(비워두면 기본 제공) 및 context.engine을 기록합니다. 다음 세션에 적용됩니다.", + refreshDashboard: "대시보드 확장 다시 검색", + removeConfirm: "이 플러그인을 ~/.hermes/plugins/에서 제거하시겠습니까?", + removeHint: "~/.hermes/plugins에 사용자가 설치한 플러그인만 제거할 수 있습니다.", + rescanHeading: "SPA 플러그인 레지스트리", + rescanHint: "디스크에 파일을 추가한 후 다시 검색하면 사이드바에 새 매니페스트가 로드됩니다.", + runtimeHeading: "게이트웨이 런타임(YAML 플러그인)", + saveProviders: "제공자 설정 저장", + savedProviders: "제공자 설정이 저장되었습니다.", + sourceBadge: "소스", + authRequired: "인증 필요", + authRequiredHint: "인증을 위해 다음 명령을 실행하세요:", + updateGit: "git pull", + versionBadge: "버전", + showInSidebar: "사이드바에 표시", + hideFromSidebar: "사이드바에서 숨기기", + }, + + skills: { + title: "스킬", + searchPlaceholder: "스킬 및 도구 세트 검색...", + enabledOf: "{enabled}/{total} 활성화됨", + all: "모두", + categories: "카테고리", + filters: "필터", + noSkills: "스킬을 찾을 수 없습니다. ~/.hermes/skills/에서 로드됩니다", + noSkillsMatch: "검색 또는 필터와 일치하는 스킬이 없습니다.", + skillCount: "{count}개 스킬", + resultCount: "{count}개 결과", + noDescription: "설명이 없습니다.", + toolsets: "도구 세트", + toolsetLabel: "{name} 도구 세트", + noToolsetsMatch: "검색과 일치하는 도구 세트가 없습니다.", + setupNeeded: "설정 필요", + disabledForCli: "CLI용 비활성화", + more: "+{count} 더 보기", + }, + + config: { + configPath: "~/.hermes/config.yaml", + filters: "필터", + sections: "섹션", + exportConfig: "구성을 JSON으로보내기", + importConfig: "JSON에서 구성 가져오기", + resetDefaults: "기본값으로 재설정", + resetScopeTooltip: "{scope}을(를) 기본값으로 재설정", + confirmResetScope: "{scope}의 모든 설정을 기본값으로 재설정하시겠습니까? 이 작업은 양식만 업데이트하며, 저장 버튼을 누르기 전에는 config.yaml에 기록되지 않습니다.", + resetScopeToast: "{scope}이(가) 기본값으로 재설정됨 — 검토 후 저장하세요", + rawYaml: "원시 YAML 구성", + searchResults: "검색 결과", + fields: "필드", + noFieldsMatch: '"{query}"와 일치하는 필드가 없습니다', + configSaved: "구성이 저장되었습니다", + yamlConfigSaved: "YAML 구성이 저장되었습니다", + failedToSave: "저장 실패", + failedToSaveYaml: "YAML 저장 실패", + failedToLoadRaw: "원시 구성 로드 실패", + configImported: "구성이 가져와졌습니다 — 검토 후 저장하세요", + invalidJson: "잘못된 JSON 파일", + categories: { + general: "일반", + agent: "에이전트", + terminal: "터미널", + display: "표시", + delegation: "위임", + memory: "메모리", + compression: "압축", + security: "보안", + browser: "브라우저", + voice: "음성", + tts: "텍스트 음성 변환", + stt: "음성 텍스트 변환", + logging: "로깅", + discord: "Discord", + auxiliary: "보조", + }, + }, + + env: { + changesNote: "변경 사항은 즉시 디스크에 저장됩니다. 활성 세션은 새 키를 자동으로 가져옵니다.", + confirmClearMessage: + "이 변수의 저장된 값이 .env 파일에서 제거됩니다. UI에서 실행 취소할 수 없습니다.", + confirmClearTitle: "이 키를 지우시겠습니까?", + description: "다음 위치에 저장된 API 키 및 비밀 관리", + hideAdvanced: "고급 숨기기", + showAdvanced: "고급 표시", + llmProviders: "LLM 제공자", + providersConfigured: "{total}개 제공자 중 {configured}개 구성됨", + getKey: "키 가져오기", + notConfigured: "{count}개 미구성", + notSet: "설정되지 않음", + keysCount: "{count}개 키", + enterValue: "값 입력...", + replaceCurrentValue: "현재 값 교체 ({preview})", + showValue: "실제 값 표시", + hideValue: "값 숨기기", + }, + + oauth: { + title: "제공자 로그인 (OAuth)", + providerLogins: "제공자 로그인 (OAuth)", + description: "{total}개 OAuth 제공자 중 {connected}개 연결됨. 로그인 흐름은 현재 CLI를 통해 실행됩니다. «명령 복사»를 클릭하고 터미널에 붙여넣어 설정하세요.", + connected: "연결됨", + expired: "만료됨", + notConnected: "연결되지 않음. 터미널에서 {command}을(를) 실행하세요.", + runInTerminal: "터미널에서.", + noProviders: "OAuth 지원 제공자가 감지되지 않았습니다.", + login: "로그인", + disconnect: "연결 해제", + managedExternally: "외부에서 관리", + copied: "복사됨 ✓", + cli: "CLI", + copyCliCommand: "CLI 명령 복사 (외부 / 대체 방법용)", + connect: "연결", + sessionExpires: "세션이 {time} 후에 만료됩니다", + initiatingLogin: "로그인 흐름 시작 중…", + exchangingCode: "코드를 토큰으로 교환 중…", + connectedClosing: "연결됨! 닫는 중…", + loginFailed: "로그인 실패.", + sessionExpired: "세션이 만료되었습니다. 다시 시도를 클릭하여 새 로그인을 시작하세요.", + reOpenAuth: "인증 페이지 다시 열기", + reOpenVerification: "확인 페이지 다시 열기", + submitCode: "코드 제출", + pasteCode: "인증 코드 붙여넣기 (#state 접미사 포함 가능)", + waitingAuth: "브라우저에서 인증을 기다리는 중…", + enterCodePrompt: "새 탭이 열렸습니다. 필요한 경우 다음 코드를 입력하세요:", + pkceStep1: "claude.ai에 새 탭이 열렸습니다. 로그인하고 «인증»을 클릭하세요.", + pkceStep2: "인증 후 표시된 인증 코드를 복사하세요.", + pkceStep3: "아래에 붙여넣고 제출하세요.", + flowLabels: { + pkce: "브라우저 로그인 (PKCE)", + device_code: "장치 코드", + external: "외부 CLI", + }, + expiresIn: "{time} 후 만료", + }, + + language: { + switchTo: "언어 변경", + }, + + theme: { + title: "테마", + switchTheme: "테마 변경", + }, +}; From 8f2b954ecd38b6ba80547b3708cb0e0eb5743f5c Mon Sep 17 00:00:00 2001 From: qa1-abilities Date: Fri, 8 May 2026 15:47:15 +0800 Subject: [PATCH 3/3] feat(web): extend LanguageSwitcher and i18n context to support 7 locales --- web/src/components/LanguageSwitcher.tsx | 37 +++++++++++++++++++++---- web/src/i18n/context.tsx | 9 ++++-- web/src/i18n/en.ts | 2 +- web/src/i18n/index.ts | 7 +++++ web/src/i18n/types.ts | 2 +- web/src/i18n/zh.ts | 2 +- 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/web/src/components/LanguageSwitcher.tsx b/web/src/components/LanguageSwitcher.tsx index dc477021ee83..9f60c44e0937 100644 --- a/web/src/components/LanguageSwitcher.tsx +++ b/web/src/components/LanguageSwitcher.tsx @@ -1,15 +1,42 @@ import { Button } from "@nous-research/ui/ui/components/button"; import { Typography } from "@/components/NouiTypography"; import { useI18n } from "@/i18n/context"; +import type { Locale } from "@/i18n/types"; + +const CYCLE: Locale[] = ["en", "zh", "es", "ru", "fr", "de", "ko"]; + +const FLAGS: Record = { + en: "🇬🇧", + zh: "🇨🇳", + es: "🇪🇸", + ru: "🇷🇺", + fr: "🇫🇷", + de: "🇩🇪", + ko: "🇰🇷", +}; + +const LABELS: Record = { + en: "EN", + zh: "中文", + es: "ES", + ru: "RU", + fr: "FR", + de: "DE", + ko: "한국어", +}; /** - * Compact language toggle — shows a clickable flag that switches between - * English and Chinese. Persists choice to localStorage. + * Compact language toggle — cycles through English, Chinese, Spanish, Russian, + * French, German, and Korean. Persists choice to localStorage. */ export function LanguageSwitcher() { const { locale, setLocale, t } = useI18n(); - const toggle = () => setLocale(locale === "en" ? "zh" : "en"); + const toggle = () => { + const idx = CYCLE.indexOf(locale); + const next = CYCLE[(idx + 1) % CYCLE.length]; + setLocale(next); + }; return ( diff --git a/web/src/i18n/context.tsx b/web/src/i18n/context.tsx index 6fc6f6e56a05..2c11f2705ef9 100644 --- a/web/src/i18n/context.tsx +++ b/web/src/i18n/context.tsx @@ -2,14 +2,19 @@ import { createContext, useContext, useState, useCallback, type ReactNode } from import type { Locale, Translations } from "./types"; import { en } from "./en"; import { zh } from "./zh"; +import { ru } from "./ru"; +import { es } from "./es"; +import { fr } from "./fr"; +import { de } from "./de"; +import { ko } from "./ko"; -const TRANSLATIONS: Record = { en, zh }; +const TRANSLATIONS: Record = { en, zh, ru, es, fr, de, ko }; const STORAGE_KEY = "hermes-locale"; function getInitialLocale(): Locale { try { const stored = localStorage.getItem(STORAGE_KEY); - if (stored === "en" || stored === "zh") return stored; + if (stored === "en" || stored === "zh" || stored === "es" || stored === "ru" || stored === "fr" || stored === "de" || stored === "ko") return stored; } catch { // SSR or privacy mode } diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 55e3267b1ba2..c6decea3491e 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -419,7 +419,7 @@ export const en: Translations = { }, language: { - switchTo: "Switch to Chinese", + switchTo: "Switch language", }, theme: { diff --git a/web/src/i18n/index.ts b/web/src/i18n/index.ts index 7a9a9471ea99..fe30f08b9e7f 100644 --- a/web/src/i18n/index.ts +++ b/web/src/i18n/index.ts @@ -1,2 +1,9 @@ export { I18nProvider, useI18n } from "./context"; export type { Locale, Translations } from "./types"; +export { ru } from "./ru"; +export { es } from "./es"; +export { en } from "./en"; +export { zh } from "./zh"; +export { fr } from "./fr"; +export { de } from "./de"; +export { ko } from "./ko"; diff --git a/web/src/i18n/types.ts b/web/src/i18n/types.ts index d93260d26d70..b1e01e070031 100644 --- a/web/src/i18n/types.ts +++ b/web/src/i18n/types.ts @@ -1,4 +1,4 @@ -export type Locale = "en" | "zh"; +export type Locale = "en" | "zh" | "es" | "ru" | "fr" | "de" | "ko"; export interface Translations { // ── Common ── diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index b64de0661f33..1c5247dd1ee2 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -414,7 +414,7 @@ export const zh: Translations = { }, language: { - switchTo: "切换到英文", + switchTo: "切换语言", }, theme: {