mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-20 05:01:30 +00:00
* feat(i18n): localize /model command output
Reported by @tianma8888: when Chinese users run /model, the labels
("Provider:", "Context:", "_session only_", etc.) are still English.
This routes the static prose through the existing i18n catalog so it
follows display.language / HERMES_LANGUAGE.
Changes:
- locales/{en,zh,ja,de,es,fr,tr,uk}.yaml: add 17 keys under
gateway.model.* covering switched/provider/context/max_output/cost/
capabilities/prompt_caching/warning/saved_global/session_only_hint/
current_label/current_tag/more_models_suffix/usage_*.
- gateway/run.py _handle_model_command: replace hardcoded f-strings in
the picker callback, the text-list fallback, and the direct-switch
confirmation block with t("gateway.model.<key>", ...).
What stays English:
- model IDs, provider slugs, capability strings, cost figures, and the
"[Note: model was just switched...]" prepended to the model's next
prompt (LLM-facing, not user-facing).
- The two slightly-different session-only hints unify on a single key
with the em-dash phrasing.
Validation: tests/agent/test_i18n.py 27/27 passing (parity contract
holds), tests/gateway/ -k 'model or i18n' 74/74 passing.
* feat(i18n): localize all gateway slash command outputs
Expands the i18n catalog from 7 strings to 234 keys across 35 gateway
slash command handlers, so non-English users see localized output for
\`/profile\`, \`/status\`, \`/help\`, \`/personality\`, \`/voice\`, \`/reset\`,
\`/agents\`, \`/restart\`, \`/commands\`, \`/goal\`, \`/retry\`, \`/undo\`,
\`/sethome\`, \`/title\`, \`/yolo\`, \`/background\`, \`/approve\`, \`/deny\`,
\`/insights\`, \`/debug\`, \`/rollback\`, \`/reasoning\`, \`/fast\`,
\`/verbose\`, \`/footer\`, \`/compress\`, \`/topic\`, \`/kanban\`,
\`/resume\`, \`/branch\`, \`/usage\`, \`/reload-mcp\`, \`/reload-skills\`,
\`/update\`, \`/stop\` (plus the \`/model\` block already added in the
previous commit).
Reported by @tianma8888 — Chinese users want command output prose in
their language, not just the labels we already had.
Translations are hand-written for all 8 supported locales (en, zh, ja,
de, es, fr, tr, uk), matching each catalog's existing style: full-width
punctuation in zh, em-dashes in zh/ja/uk, French spaced colons,
German noun capitalization, etc.
What stays English (unchanged):
- Identifiers/values: model IDs, file paths, profile names, session IDs,
command flag names like --global, URLs, config keys.
- Backtick code spans: \`/foo\`, \`config.yaml\`.
- Log messages (logger.info/warning/error).
- LLM-facing system notes prepended to next prompt (e.g. [Note: model
was just switched...]).
- Strings produced by external modules (gateway_help_lines,
format_gateway, manual_compression_feedback) — those have their
own surfaces.
New shared keys for cross-handler boilerplate:
- gateway.shared.session_db_unavailable (5 call sites: branch, title,
resume, topic, _disable_telegram_topic_mode_for_chat)
- gateway.shared.session_not_found (1 site)
- gateway.shared.warn_passthrough (2 sites in /title's f"⚠️ {e}" pattern)
YAML gotcha fixed: \`yolo.on\` and \`yolo.off\` were originally written
unquoted, which YAML 1.1 parses as boolean True/False keys. Renamed to
\`yolo.enabled\` / \`yolo.disabled\` for both safety and clarity.
Test fix: tests/agent/test_i18n.py::test_t_missing_key_in_non_english_falls_back_to_english
now resets the catalog cache on teardown, so the fake "foo: English Foo"
locale doesn't poison the module-level cache for subsequent tests in
the same xdist worker. (Without this, every gateway slash command test
that shares a worker with the i18n suite would see the fake catalog.)
Validation:
- tests/agent/test_i18n.py: 27/27 (parity contract — every key in every
locale, matching placeholder tokens).
- tests/gateway/: 5077 passed, 0 failed (full gateway suite).
- 180 t() call sites added across 35 handlers; 1872 catalog entries
total (234 keys × 8 locales).
* feat(i18n): add 8 new locales — af, ko, it, ga, zh-hant, pt, ru, hu
Expands the static-message catalog from 8 → 16 languages, each with full
270-key parity against the English source-of-truth. Every locale now
covers the same surface PR #22914 added: approval prompts plus all 35
gateway slash command outputs.
New locales:
- af Afrikaans (community ask in #21961 by @GodsBoy; PRs #21962, #21970)
- ko Korean (PRs #20297 by @tmdgusya, #22285 by @project820)
- it Italian (PR #20371 by @leprincep35700)
- ga Irish/Gaeilge (PR #20962 by @ryanmcc09-dot)
- zh-hant Traditional Chinese (PRs #20523 by @jackey8616, #13140 by @anomixer)
- pt Portuguese (PRs #20443 by @pedroborges, #15737 by @carloshenriquecarniatto, #22063 by @Magaav)
- ru Russian (PR #22770 by @DrMaks22)
- hu Hungarian (PR #22336 by @lunasec007)
Each locale uses native-quality translations matching the existing tone
and conventions of the older 8 locales:
- zh-hant uses 繁體 characters with TW/HK technical vocabulary (軟體
not 软件, 連線 not 连接, 設定 not 设置, 訊息 not 消息, 工作階段 not 会话, 程式
not 程序, 預設 not 默认, 伺服器 not 服务器), full-width punctuation 「:()」.
- ko uses formal 합니다체 (습니다/합니다) register throughout.
- pt uses European Portuguese as baseline with neutral PT/BR vocabulary
where possible.
- ga uses standard An Caighdeán Oifigiúil; English loanwords retained
for tech terms without good Irish equivalents (gateway, API, JSON).
- All preserve {placeholder} tokens, backtick code spans, slash commands,
brand names (Hermes, MCP, TTS, YOLO, OpenAI, Telegram, etc.), and emoji.
Aliases added in agent/i18n.py:
- af-za, Afrikaans → af
- ko-kr, Korean, 한국어 → ko
- it-it, italiano → it
- ga-ie, Irish, Gaeilge → ga
- zh-tw, zh-hk, zh-mo, traditional-chinese → zh-hant (note: zh-tw used to
alias to zh; now aliases to its own zh-hant catalog)
- zh-cn, zh-hans, zh-sg → zh (unchanged from before)
- pt-pt, pt-br, brazilian, portuguese → pt
- ru-ru, Russian, русский → ru
- hu-hu, Magyar → hu
The zh-tw alias re-routing is intentional: previously typing 'zh-TW' got
the Simplified Chinese catalog (wrong vocabulary for Taiwan/HK users).
Now those users get the proper Traditional Chinese catalog.
Validation:
- tests/agent/test_i18n.py: 43/43 (parity contract holds for all 16
languages × 270 keys = 4320 catalog entries, with matching placeholder
tokens).
- E2E alias resolution verified for all 19 alias inputs (Afrikaans, ko-KR,
한국어, italiano, Gaeilge, zh-TW, zh-HK, traditional-chinese, pt-BR,
brazilian, Magyar, etc.).
- tests/gateway/: 5198 passed (3 pre-existing TTS routing failures
unrelated to i18n).
Credit to all contributors whose PRs surfaced these language requests.
Their original PRs may now be closed as superseded with credit.
* feat(dashboard-i18n): add 14 web dashboard locales matching the static catalog
Brings the React dashboard (web/src/) up to the same 16-language
coverage the static catalog already has after the previous commits in
this PR. The Translations interface is TypeScript-typed, so every new
locale must provide every key — tsc -b is the parity guard.
Languages added (each is a complete 429-line locale file):
- af Afrikaans
- ja Japanese (PR #22513 by @snuffxxx surfaced this)
- de German (PR #21749 by @mag1art)
- es Spanish (PR #21749)
- fr French (PRs #21749, #10310 by @foXaCe)
- tr Turkish
- uk Ukrainian
- ko Korean (PRs #21749, #18894 by @ovstng, #22285 by @project820)
- it Italian
- ga Irish (Gaeilge)
- zh-hant Traditional Chinese (PR #13140 by @anomixer)
- pt Portuguese (PRs #22063 by @Magaav, #22182 by @wesleysimplicio, #15737 by @carloshenriquecarniatto)
- ru Russian (PRs #21749, #22770 by @DrMaks22)
- hu Hungarian (PR #22336 by @lunasec007)
Each translation covers all 15 namespaces with full key parity vs en.ts,
preserves every {placeholder} token verbatim, keeps identifiers
untranslated (brand names, file paths, cron expressions, code spans),
translates the language.switchTo tooltip into the target language, and
matches existing tone conventions (zh-hant uses TW/HK vocab; ja uses
formal desu/masu; ko uses formal seumnida register; ga uses An
Caighdean Oifigiuil with English loanwords for tech vocab without good
Irish equivalents).
Plumbing:
- web/src/i18n/types.ts: Locale union expanded to all 16 codes.
- web/src/i18n/context.tsx: imports all 16 catalogs; exports
LOCALE_META (endonym + flag per locale); isLocale() type guard.
- web/src/i18n/index.ts: re-export LOCALE_META.
- web/src/components/LanguageSwitcher.tsx: replaced two-state EN-ZH
toggle with a click-to-open dropdown listing all 16 languages.
Note: zh-hant.ts exports zhHant (camelCase) since hyphen is invalid in
a JS identifier; the canonical 'zh-hant' string keys it in TRANSLATIONS.
Validation:
- npx tsc -b: 0 errors. Every locale satisfies Translations.
- npm run build (tsc + vite production): green, 2062 modules.
- Each locale file is exactly 429 lines.
Out of scope: plugin dashboards (kanban/achievements ship as prebuilt
bundles with no source in repo); Docusaurus docs (separate surface);
TUI (no i18n yet).
* feat(plugin-i18n): localize achievements + kanban plugin dashboards across all 16 locales
Brings the two shipped plugin dashboards (hermes-achievements, kanban)
under the same i18n umbrella as the core dashboard PR #22914 just
established. Both bundles now read user-facing strings from the host's
i18n catalog via SDK.useI18n() instead of hardcoded English.
## Approach
Plugin dashboards ship as prebuilt IIFE bundles in
plugins/<name>/dashboard/dist/index.js — no build step, no source in
repo (upstream-authored, vendored as compiled JS). Earlier contributor
PRs (#22594, #22595, #18747) tried direct edits but didn't actually
wire the bundles to read translations.
This change does the wiring properly:
1. Each bundle gets a useI18n shim at IIFE scope:
const useI18n = SDK.useI18n
|| function () { return { t: { kanban: null }, locale: "en" }; };
Older host SDKs without useI18n still load the bundle and render
English fallbacks.
2. A small tx(t, path, fallback, vars) helper resolves dotted keys
under the plugin's namespace (t.kanban.* or t.achievements.*) and
interpolates {placeholder} tokens.
3. Every React component starts with const { t } = useI18n() and
each user-visible string is wrapped in tx(t, "key", "English fallback").
Helpers called outside React components (window.prompt callers,
constants used during init) take t as a parameter.
4. Top-level constants that were English dictionaries (COLUMN_LABEL,
COLUMN_HELP, DESTRUCTIVE_TRANSITIONS, DIAGNOSTIC_EVENT_LABELS in
kanban) become getColumnLabel(t, status)-style functions backed by
FALLBACK_* dictionaries.
## Translations added
Two new top-level namespaces added to the dashboard's TypeScript-typed
Translations interface:
- achievements: ~70 keys covering the hero, scan banner, achievement
card, share dialog, stats, filters, and empty states.
- kanban: ~145 keys covering the board, columns (with nested
columnLabels and columnHelp sub-dicts), card detail panel,
bulk-actions toolbar, dependency editor, board switcher, and
diagnostic callouts.
Each key is provided across all 16 supported locales:
en, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, pt, ru, hu.
Total new translation entries: ~3,440 (215 keys × 16 locales).
## What stays English (deliberate)
- API paths, CSS class names, data-* attributes, JSON keys, regex
strings, URLs, file paths (~/.hermes/kanban.db, boards/_archived/).
- State identifier strings used as lookup keys (triage / todo / ready /
running / blocked / done / archived) — labels translate, key strings
don't.
- The PNG share-card text rendered to canvas in the achievements
ShareDialog (HERMES AGENT watermark, UNLOCKED stamp, tier names) —
these become part of a globally-shared image and stay English.
- localStorage keys (hermes.kanban.selectedBoard).
- Brand names (Kanban, Hermes, WebSocket, Nous Research).
## Contributor credit
PR #22594 by @02356abc and PR #22595 by @02356abc supplied the
en + zh kanban namespace skeleton (145 keys); used as the en source-
of-truth in this commit and translated to the other 14 locales.
PR #18747 by @laolaoshiren first surfaced the achievements
localization request.
## Validation
- npx tsc -b: 0 errors. All 16 locale .ts files satisfy the
Translations type with full key parity.
- npm run build (tsc + vite production build): green, 2062 modules,
1.56MB JS / 95KB CSS, ~2.5s build.
- node --check on both plugin bundles: parse cleanly.
- 126 tx() call sites in kanban, 46 in achievements.
## Out of scope
- TUI (ui-tui/) has no i18n infrastructure yet.
- Docusaurus docs (website/i18n/) — already had zh-Hans; expanding
is a separate translation workstream (Thai / Korean / Hindi PRs).
696 lines
28 KiB
TypeScript
696 lines
28 KiB
TypeScript
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: "Web 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: "새 Cron 작업",
|
||
nameOptional: "이름 (선택 사항)",
|
||
namePlaceholder: "예: 일일 요약",
|
||
prompt: "프롬프트",
|
||
promptPlaceholder: "에이전트가 매 실행 시 무엇을 해야 합니까?",
|
||
schedule: "스케줄 (cron 표현식)",
|
||
schedulePlaceholder: "0 9 * * *",
|
||
deliverTo: "전달 대상",
|
||
scheduledJobs: "예약된 작업",
|
||
noJobs: "구성된 cron 작업이 없습니다. 위에서 하나 만드세요.",
|
||
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: "플러그인을 실행하기 전에 Keys에서 다음 항목을 설정하세요:",
|
||
noDashboardTab: "대시보드 탭 없음",
|
||
openTab: "열기",
|
||
orphanHeading: "대시보드 전용 확장 (일치하는 agent 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}개 스킬",
|
||
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: "{configured}/{total} 제공자가 구성됨",
|
||
getKey: "키 받기",
|
||
notConfigured: "{count}개 구성되지 않음",
|
||
notSet: "설정되지 않음",
|
||
keysCount: "{count}개 키",
|
||
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로 새 탭이 열렸습니다. 로그인하고 Authorize를 클릭하세요.",
|
||
pkceStep2: "인증 후 표시된 인증 코드를 복사하세요.",
|
||
pkceStep3: "아래에 붙여넣고 제출하세요.",
|
||
flowLabels: {
|
||
pkce: "브라우저 로그인 (PKCE)",
|
||
device_code: "디바이스 코드",
|
||
external: "외부 CLI",
|
||
},
|
||
expiresIn: "{time} 후 만료",
|
||
},
|
||
|
||
language: {
|
||
switchTo: "영어로 전환",
|
||
},
|
||
|
||
theme: {
|
||
title: "테마",
|
||
switchTheme: "테마 전환",
|
||
},
|
||
|
||
achievements: {
|
||
hero: {
|
||
kicker: "Agentic Gamerscore",
|
||
title: "Hermes Achievements",
|
||
subtitle:
|
||
"실제 세션 기록에서 획득하는 Hermes 컬렉터블 배지입니다. 알려져 있지만 아직 달성되지 않은 업적은 Discovered로 표시되며, Secret 업적은 일치하는 동작이 처음 나타날 때까지 숨겨집니다.",
|
||
scan_subtitle:
|
||
"Hermes 세션 기록을 스캔하고 있습니다. 기록이 많으면 첫 스캔에 5~10초가 걸릴 수 있습니다.",
|
||
},
|
||
actions: {
|
||
rescan: "다시 스캔",
|
||
},
|
||
stats: {
|
||
unlocked: "해제됨",
|
||
unlocked_hint: "획득한 배지",
|
||
discovered: "발견됨",
|
||
discovered_hint: "알려져 있으나 아직 획득하지 못함",
|
||
secrets: "시크릿",
|
||
secrets_hint: "첫 신호가 있을 때까지 숨겨짐",
|
||
highest_tier: "최고 등급",
|
||
highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
|
||
latest: "최근",
|
||
latest_hint_empty: "Hermes를 더 사용해 보세요",
|
||
none_yet: "아직 없음",
|
||
},
|
||
state: {
|
||
unlocked: "해제됨",
|
||
discovered: "발견됨",
|
||
secret: "시크릿",
|
||
},
|
||
tier: {
|
||
target: "목표 {tier}",
|
||
hidden: "숨김",
|
||
complete: "완료",
|
||
objective: "목표",
|
||
},
|
||
progress: {
|
||
hidden: "숨김",
|
||
},
|
||
scan: {
|
||
building_headline: "업적 프로필을 구성하고 있습니다…",
|
||
building_detail:
|
||
"세션, 도구 호출, 모델 메타데이터, 해제 상태를 읽고 있습니다.",
|
||
starting_headline: "업적 스캔을 시작합니다…",
|
||
progress_detail:
|
||
"{total}개 중 {scanned}개의 세션을 스캔했습니다 · {pct}%. 더 많은 기록이 들어오면 배지가 해제됩니다.",
|
||
idle_detail:
|
||
"세션, 도구 호출, 모델 메타데이터, 해제 상태를 읽고 있습니다. 배지가 해제되면 여기에 표시됩니다.",
|
||
},
|
||
guide: {
|
||
tiers_header: "등급",
|
||
secret_header: "시크릿 업적",
|
||
secret_body:
|
||
"시크릿은 정확한 트리거 조건을 숨깁니다. Hermes가 관련 신호를 감지하면 카드가 Discovered로 바뀌고 요건이 표시됩니다.",
|
||
scan_status_header: "스캔 상태",
|
||
scan_status_body:
|
||
"Hermes는 로컬 기록을 한 번 스캔한 뒤 카드를 자동으로 표시합니다. 몇 초 걸리더라도 멈춘 것이 아닙니다.",
|
||
what_scanned_header: "스캔 대상",
|
||
what_scanned_body:
|
||
"세션, 도구 호출, 모델 메타데이터, 오류, 업적 및 로컬 해제 상태입니다.",
|
||
},
|
||
card: {
|
||
share_title: "이 업적 공유",
|
||
share_label: "{name} 공유",
|
||
share_text: "공유",
|
||
how_to_reveal: "공개하는 방법",
|
||
what_counts: "인정되는 조건",
|
||
evidence_label: "근거",
|
||
evidence_session_fallback: "세션",
|
||
no_evidence: "아직 근거가 없습니다",
|
||
},
|
||
latest: {
|
||
header: "최근 해제",
|
||
},
|
||
empty: {
|
||
no_secrets_header: "이번 스캔에 남은 숨겨진 시크릿이 없습니다.",
|
||
no_secrets_body:
|
||
"힌트: 시크릿은 보통 비정상적인 실패나 파워 유저 패턴에서 시작됩니다 — 포트 충돌, 권한 차단, 누락된 환경 변수, YAML 실수, Docker 충돌, 롤백/체크포인트 사용, 캐시 적중, 또는 많은 오류 메시지 뒤의 작은 수정 등입니다.",
|
||
},
|
||
filters: {
|
||
all_categories: "전체",
|
||
visibility_all: "전체",
|
||
visibility_unlocked: "해제됨",
|
||
visibility_discovered: "발견됨",
|
||
visibility_secret: "시크릿",
|
||
},
|
||
share: {
|
||
dialog_label: "업적 공유",
|
||
header: "공유: {name}",
|
||
close: "닫기",
|
||
rendering: "렌더링 중…",
|
||
card_alt: "{name} 공유 카드",
|
||
error_generic: "문제가 발생했습니다.",
|
||
x_title: "미리 작성된 게시물로 X를 엽니다",
|
||
x_button: "X에 공유",
|
||
copy_title: "게시물에 붙여넣을 수 있도록 이미지를 복사합니다",
|
||
copy_button: "이미지 복사",
|
||
copied: "복사됨 ✓",
|
||
download_button: "PNG 다운로드",
|
||
hint:
|
||
"X에 공유를 누르면 새 탭에서 미리 작성된 게시물이 열립니다. 1200×630 배지를 첨부하려면 먼저 이미지 복사를 누르세요 — X 작성기에서 바로 붙여넣을 수 있습니다. PNG 다운로드는 파일을 저장하여 어디서나 사용할 수 있게 합니다.",
|
||
clipboard_unsupported:
|
||
"이 브라우저에서는 클립보드 이미지 복사를 지원하지 않습니다 — 대신 다운로드를 이용하세요.",
|
||
tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
|
||
},
|
||
},
|
||
kanban: {
|
||
loading: "Kanban 보드를 불러오는 중입니다…",
|
||
loadFailed: "Kanban 보드를 불러오지 못했습니다: ",
|
||
loadFailedHint:
|
||
"백엔드는 처음 읽을 때 kanban.db를 자동으로 생성합니다. 문제가 계속되면 대시보드 로그를 확인하십시오.",
|
||
board: "보드",
|
||
newBoard: "+ 새 보드",
|
||
newBoardTitle: "새 보드",
|
||
newBoardDescription:
|
||
"보드를 사용하면 관련 없는 작업 흐름을 분리할 수 있습니다 — 프로젝트, 저장소, 도메인마다 하나씩. 한 보드의 워커는 다른 보드의 작업을 절대 보지 않습니다.",
|
||
slug: "슬러그",
|
||
slugHint: "— 소문자, 하이픈, 예: atm10-server",
|
||
displayName: "표시 이름",
|
||
displayNameHint: "(선택)",
|
||
description: "설명",
|
||
descriptionHint: "(선택)",
|
||
icon: "아이콘",
|
||
iconHint: "(한 글자 또는 이모지)",
|
||
switchAfterCreate: "생성 후 이 보드로 전환",
|
||
cancel: "취소",
|
||
creating: "생성 중…",
|
||
createBoard: "보드 생성",
|
||
search: "검색",
|
||
filterCards: "카드 필터링…",
|
||
tenant: "테넌트",
|
||
allTenants: "모든 테넌트",
|
||
assignee: "담당자",
|
||
allProfiles: "모든 프로필",
|
||
showArchived: "보관된 항목 표시",
|
||
lanesByProfile: "프로필별 레인",
|
||
nudgeDispatcher: "디스패처 깨우기",
|
||
refresh: "새로 고침",
|
||
selected: "선택됨",
|
||
complete: "완료",
|
||
archive: "보관",
|
||
apply: "적용",
|
||
clear: "지우기",
|
||
createTask: "이 열에 작업 만들기",
|
||
noTasks: "— 작업 없음 —",
|
||
unassigned: "미지정",
|
||
untitled: "(제목 없음)",
|
||
loadingDetail: "불러오는 중…",
|
||
addComment: "댓글 추가… (Enter로 전송)",
|
||
comment: "댓글",
|
||
status: "상태",
|
||
workspace: "작업 공간",
|
||
skills: "스킬",
|
||
createdBy: "작성자",
|
||
result: "결과",
|
||
comments: "댓글",
|
||
events: "이벤트",
|
||
runHistory: "실행 기록",
|
||
workerLog: "워커 로그",
|
||
loadingLog: "로그를 불러오는 중…",
|
||
noWorkerLog:
|
||
"— 아직 워커 로그가 없습니다 (작업이 시작되지 않았거나 로그가 순환되었습니다) —",
|
||
noDescription: "— 설명 없음 —",
|
||
noComments: "— 댓글 없음 —",
|
||
edit: "편집",
|
||
save: "저장",
|
||
dependencies: "종속성",
|
||
parents: "상위 작업:",
|
||
children: "하위 작업:",
|
||
none: "없음",
|
||
addParent: "— 상위 작업 추가 —",
|
||
addChild: "— 하위 작업 추가 —",
|
||
removeDependency: "종속성 제거",
|
||
block: "차단",
|
||
unblock: "차단 해제",
|
||
notifyHomeChannels: "홈 채널에 알림",
|
||
diagnostics: "진단",
|
||
hide: "숨기기",
|
||
show: "표시",
|
||
attention: "주의",
|
||
tasksNeedAttention: "개의 작업이 주의를 필요로 합니다",
|
||
taskNeedsAttention: "작업 1개가 주의를 필요로 합니다",
|
||
diagnostic: "진단",
|
||
open: "열기",
|
||
close: "닫기 (Esc)",
|
||
reassignTo: "다음으로 재지정:",
|
||
copied: "복사됨",
|
||
copyCommand: "명령을 클립보드로 복사",
|
||
reclaim: "회수",
|
||
reassign: "재지정",
|
||
renderingError: "Kanban 탭에서 렌더링 오류가 발생했습니다",
|
||
reloadView: "뷰 다시 불러오기",
|
||
wsAuthFailed:
|
||
"WebSocket 인증 실패 — 페이지를 다시 불러와 세션 토큰을 갱신하십시오.",
|
||
markDone: "{n}개의 작업을 완료로 표시하시겠습니까?",
|
||
markArchived: "{n}개의 작업을 보관하시겠습니까?",
|
||
warning: "경고",
|
||
phantomIds: "팬텀 ID:",
|
||
active: "활성",
|
||
ended: "종료됨",
|
||
noProfile: "(프로필 없음)",
|
||
showAllAttempts: "모든 시도 표시",
|
||
sendingUpdates: "업데이트 전송 대상: ",
|
||
sendNotifications: "완료 / 차단됨 / 포기 알림 전송 대상",
|
||
archiveBoardConfirm:
|
||
"보드 '{name}'을(를) 보관하시겠습니까? 보드는 boards/_archived/로 이동되어 나중에 복구할 수 있습니다. 이 보드의 작업은 더 이상 UI 어디에도 나타나지 않습니다.",
|
||
archiveBoardTitle: "이 보드 보관",
|
||
boardSwitcherHint: "보드를 사용하면 관련 없는 작업 흐름을 분리할 수 있습니다",
|
||
taskCreatedWarning: "작업이 생성되었지만: ",
|
||
moveFailed: "이동 실패: ",
|
||
bulkFailed: "일괄 처리: ",
|
||
completionBlockedHallucination: "⚠ 완료가 차단됨 — 팬텀 카드 ID",
|
||
suspectedHallucinatedReferences: "⚠ 본문이 팬텀 카드 ID를 참조함",
|
||
pickProfileFirst: "먼저 프로필을 선택하십시오.",
|
||
unblockedMessage: "{id}의 차단을 해제했습니다. 작업이 다음 틱을 위해 준비되었습니다.",
|
||
unblockFailed: "차단 해제 실패: ",
|
||
reclaimedMessage: "{id}을(를) 회수했습니다. 작업이 ready 상태로 돌아갔습니다.",
|
||
reclaimFailed: "회수 실패: ",
|
||
reassignedMessage: "{id}을(를) {profile}(으)로 재지정했습니다.",
|
||
reassignFailed: "재지정 실패: ",
|
||
selectForBulk: "일괄 작업을 위해 선택",
|
||
clickToEdit: "클릭하여 편집",
|
||
clickToEditAssignee: "클릭하여 담당자 편집",
|
||
emptyAssignee: "(비우면 = 지정 해제)",
|
||
columnLabels: {
|
||
triage: "분류",
|
||
todo: "할 일",
|
||
ready: "준비됨",
|
||
running: "진행 중",
|
||
blocked: "차단됨",
|
||
done: "완료",
|
||
archived: "보관됨",
|
||
},
|
||
columnHelp: {
|
||
triage: "원시 아이디어 — 스페시파이어가 사양을 구체화합니다",
|
||
todo: "종속성 대기 중 또는 미지정",
|
||
ready: "지정되었으며 디스패처 틱 대기 중",
|
||
running: "워커가 점유 중 — 실행 중",
|
||
blocked: "워커가 사람의 입력을 요청함",
|
||
done: "완료됨",
|
||
archived: "보관됨",
|
||
},
|
||
confirmDone:
|
||
"이 작업을 완료로 표시하시겠습니까? 워커의 점유가 해제되고 종속된 하위 작업이 ready 상태가 됩니다.",
|
||
confirmArchive:
|
||
"이 작업을 보관하시겠습니까? 기본 보드 보기에서 사라집니다.",
|
||
confirmBlocked:
|
||
"이 작업을 차단됨으로 표시하시겠습니까? 워커의 점유가 해제됩니다.",
|
||
completionSummary:
|
||
"{label}의 완료 요약입니다. 이는 작업 결과로 저장됩니다.",
|
||
completionSummaryRequired:
|
||
"작업을 완료로 표시하기 전에 완료 요약이 필요합니다.",
|
||
triagePlaceholder: "대략적인 아이디어 — AI가 사양을 작성합니다…",
|
||
taskTitlePlaceholder: "새 작업 제목…",
|
||
specifier: "스페시파이어",
|
||
assigneePlaceholder: "담당자",
|
||
priority: "우선순위",
|
||
skillsPlaceholder:
|
||
"스킬 (선택, 쉼표로 구분): translation, github-code-review",
|
||
noParent: "— 상위 작업 없음 —",
|
||
workspacePathDir: "작업 공간 경로 (필수, 예: ~/projects/my-app)",
|
||
workspacePathOptional:
|
||
"작업 공간 경로 (선택, 비어 있으면 담당자에서 파생됨)",
|
||
logTruncated: "(마지막 100 KB 표시 중 — 전체 로그 위치: ",
|
||
logAt: ")",
|
||
},
|
||
};
|